Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit40c0fc2

Browse files
refactor: Remove users redirect to active filter (#4056)
1 parentb78ab9e commit40c0fc2

File tree

10 files changed

+205
-178
lines changed

10 files changed

+205
-178
lines changed

‎site/src/components/NavbarView/NavbarView.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@ const NavItems: React.FC<
4848
</NavLink>
4949
</ListItem>
5050
<ListItembuttonclassName={styles.item}>
51-
<NavLinkclassName={styles.link}to="/users">
51+
<NavLink
52+
className={styles.link}
53+
to={`/users?filter=${encodeURIComponent("status:active")}`}
54+
>
5255
{Language.users}
5356
</NavLink>
5457
</ListItem>

‎site/src/components/SearchBarWithFilter/SearchBarWithFilter.test.tsx

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import{fireEvent,screen}from"@testing-library/react"
1+
import{screen}from"@testing-library/react"
22
importuserEventfrom"@testing-library/user-event"
33
import{render}from"../../testHelpers/renderHelpers"
44
import{SearchBarWithFilter}from"./SearchBarWithFilter"
@@ -21,18 +21,6 @@ describe("SearchBarWithFilter", () => {
2121
awaituserEvent.type(searchInput,"workspace")// 9 characters
2222

2323
// Then
24-
expect(onFilter).toBeCalledTimes(10)// 9 characters + 1 on component mount
25-
})
26-
27-
it("calls the onFilter handler on submit",async()=>{
28-
// When
29-
constonFilter=jest.fn()
30-
render(<SearchBarWithFilteronFilter={onFilter}/>)
31-
32-
constsearchInput=screen.getByRole("textbox")
33-
awaitfireEvent.keyDown(searchInput,{key:"Enter",code:"Enter",charCode:13})
34-
35-
// Then
36-
expect(onFilter).toBeCalledTimes(1)
24+
expect(onFilter).toBeCalledTimes(9)// 9 characters
3725
})
3826
})

‎site/src/components/SearchBarWithFilter/SearchBarWithFilter.tsx

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@ import OutlinedInput from "@material-ui/core/OutlinedInput"
77
import{makeStyles}from"@material-ui/core/styles"
88
import{Theme}from"@material-ui/core/styles/createMuiTheme"
99
importSearchIconfrom"@material-ui/icons/Search"
10-
import{FormikErrors,useFormik}from"formik"
1110
importdebouncefrom"just-debounce-it"
12-
import{useCallback,useEffect,useState}from"react"
11+
import{useCallback,useRef,useState}from"react"
1312
import{getValidationErrorMessage}from"../../api/errors"
1413
import{CloseDropdown,OpenDropdown}from"../DropdownArrows/DropdownArrows"
1514
import{Stack}from"../Stack/Stack"
@@ -30,29 +29,14 @@ export interface PresetFilter {
3029
query:string
3130
}
3231

33-
interfaceFilterFormValues{
34-
query:string
35-
}
36-
37-
exporttypeFilterFormErrors=FormikErrors<FilterFormValues>
38-
3932
exportconstSearchBarWithFilter:React.FC<React.PropsWithChildren<SearchBarWithFilterProps>>=({
4033
filter,
4134
onFilter,
4235
presetFilters,
4336
error,
4437
})=>{
4538
conststyles=useStyles({error:Boolean(error)})
46-
47-
constform=useFormik<FilterFormValues>({
48-
enableReinitialize:true,
49-
initialValues:{
50-
query:filter??"",
51-
},
52-
onSubmit:({ query})=>{
53-
onFilter(query)
54-
},
55-
})
39+
constsearchInputRef=useRef<HTMLInputElement>(null)
5640

5741
// debounce query string entry by user
5842
// we want the dependency array empty here
@@ -65,12 +49,6 @@ export const SearchBarWithFilter: React.FC<React.PropsWithChildren<SearchBarWith
6549
[],
6650
)
6751

68-
// update the query params while typing
69-
useEffect(()=>{
70-
debouncedOnFilter(form.values.query)
71-
return()=>debouncedOnFilter.cancel()
72-
},[debouncedOnFilter,form.values.query])
73-
7452
const[anchorEl,setAnchorEl]=useState<null|HTMLElement>(null)
7553

7654
consthandleClick=(event:React.MouseEvent<HTMLButtonElement>)=>{
@@ -82,8 +60,15 @@ export const SearchBarWithFilter: React.FC<React.PropsWithChildren<SearchBarWith
8260
}
8361

8462
constsetPresetFilter=(query:string)=>()=>{
85-
voidform.setFieldValue("query",query)
86-
voidform.submitForm()
63+
if(!searchInputRef.current){
64+
thrownewError("Search input not found.")
65+
}
66+
67+
onFilter(query)
68+
// Update this to the input directly instead of create a new state and
69+
// re-render the component since the onFilter is already calling the
70+
// filtering process
71+
searchInputRef.current.value=query
8772
handleClose()
8873
}
8974

@@ -103,21 +88,24 @@ export const SearchBarWithFilter: React.FC<React.PropsWithChildren<SearchBarWith
10388
</Button>
10489
)}
10590

106-
<formonSubmit={form.handleSubmit}className={styles.filterForm}>
91+
<divrole="form"className={styles.filterForm}>
10792
<OutlinedInput
10893
id="query"
10994
name="query"
110-
value={form.values.query}
95+
defaultValue={filter}
11196
error={Boolean(error)}
11297
className={styles.inputStyles}
113-
onChange={form.handleChange}
98+
onChange={(event)=>{
99+
debouncedOnFilter(event.currentTarget.value)
100+
}}
101+
inputRef={searchInputRef}
114102
startAdornment={
115103
<InputAdornmentposition="start"className={styles.searchIcon}>
116104
<SearchIconfontSize="small"/>
117105
</InputAdornment>
118106
}
119107
/>
120-
</form>
108+
</div>
121109

122110
{presetFilters&&presetFilters.length&&(
123111
<Menu

‎site/src/pages/UsersPage/CreateUserPage/CreateUserPage.test.tsx

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,20 @@ import { rest } from "msw"
44
import*asAPIfrom"../../../api/api"
55
import{LanguageasFormLanguage}from"../../../components/CreateUserForm/CreateUserForm"
66
import{LanguageasFooterLanguage}from"../../../components/FormFooter/FormFooter"
7-
import{history,render}from"../../../testHelpers/renderHelpers"
7+
import{
8+
history,
9+
renderWithAuth,
10+
waitForLoaderToBeRemoved,
11+
}from"../../../testHelpers/renderHelpers"
812
import{server}from"../../../testHelpers/server"
9-
import{LanguageasUserLanguage}from"../../../xServices/users/usersXService"
13+
import{LanguageasCreateUserLanguage}from"../../../xServices/users/createUserXService"
1014
import{CreateUserPage}from"./CreateUserPage"
1115

16+
constrenderCreateUserPage=async()=>{
17+
renderWithAuth(<CreateUserPage/>)
18+
awaitwaitForLoaderToBeRemoved()
19+
}
20+
1221
constfillForm=async({
1322
username="someuser",
1423
email="someone@coder.com",
@@ -34,7 +43,7 @@ describe("Create User Page", () => {
3443
})
3544

3645
it("shows validation error message",async()=>{
37-
render(<CreateUserPage/>)
46+
awaitrenderCreateUserPage()
3847
awaitfillForm({email:"test"})
3948
consterrorMessage=awaitscreen.findByText(FormLanguage.emailInvalid)
4049
expect(errorMessage).toBeDefined()
@@ -44,9 +53,9 @@ describe("Create User Page", () => {
4453
jest.spyOn(API,"createUser").mockRejectedValueOnce({
4554
data:"unknown error",
4655
})
47-
render(<CreateUserPage/>)
56+
awaitrenderCreateUserPage()
4857
awaitfillForm({})
49-
consterrorMessage=awaitscreen.findByText(UserLanguage.createUserError)
58+
consterrorMessage=awaitscreen.findByText(CreateUserLanguage.createUserError)
5059
expect(errorMessage).toBeDefined()
5160
})
5261

@@ -68,30 +77,16 @@ describe("Create User Page", () => {
6877
)
6978
}),
7079
)
71-
render(<CreateUserPage/>)
80+
awaitrenderCreateUserPage()
7281
awaitfillForm({})
7382
consterrorMessage=awaitscreen.findByText(fieldErrorMessage)
7483
expect(errorMessage).toBeDefined()
7584
})
7685

7786
it("shows success notification and redirects to users page",async()=>{
78-
render(<CreateUserPage/>)
87+
awaitrenderCreateUserPage()
7988
awaitfillForm({})
80-
constsuccessMessage=screen.findByText(UserLanguage.createUserSuccess)
89+
constsuccessMessage=screen.findByText(CreateUserLanguage.createUserSuccess)
8190
expect(successMessage).toBeDefined()
8291
})
83-
84-
it("redirects to users page on cancel",async()=>{
85-
render(<CreateUserPage/>)
86-
constcancelButton=awaitscreen.findByText(FooterLanguage.cancelLabel)
87-
cancelButton.click()
88-
expect(history.location.pathname).toEqual("/users")
89-
})
90-
91-
it("redirects to users page on close",async()=>{
92-
render(<CreateUserPage/>)
93-
constcloseButton=awaitscreen.findByText("ESC")
94-
closeButton.click()
95-
expect(history.location.pathname).toEqual("/users")
96-
})
9792
})

‎site/src/pages/UsersPage/CreateUserPage/CreateUserPage.tsx

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,29 @@
1-
import{useActor,useSelector}from"@xstate/react"
2-
importReact,{useContext}from"react"
1+
import{useMachine}from"@xstate/react"
2+
import{useOrganizationId}from"hooks/useOrganizationId"
3+
importReactfrom"react"
34
import{Helmet}from"react-helmet-async"
45
import{useNavigate}from"react-router"
6+
import{createUserMachine}from"xServices/users/createUserXService"
57
import*asTypesGenfrom"../../../api/typesGenerated"
68
import{CreateUserForm}from"../../../components/CreateUserForm/CreateUserForm"
79
import{Margins}from"../../../components/Margins/Margins"
810
import{pageTitle}from"../../../util/page"
9-
import{selectOrgId}from"../../../xServices/auth/authSelectors"
10-
import{XServiceContext}from"../../../xServices/StateContext"
1111

1212
exportconstLanguage={
1313
unknownError:"Oops, an unknown error occurred.",
1414
}
1515

1616
exportconstCreateUserPage:React.FC=()=>{
17-
constxServices=useContext(XServiceContext)
18-
constmyOrgId=useSelector(xServices.authXService,selectOrgId)
19-
const[usersState,usersSend]=useActor(xServices.usersXService)
20-
const{ createUserErrorMessage, createUserFormErrors}=usersState.context
17+
constmyOrgId=useOrganizationId()
2118
constnavigate=useNavigate()
19+
const[createUserState,createUserSend]=useMachine(createUserMachine,{
20+
actions:{
21+
redirectToUsersPage:()=>{
22+
navigate("/users")
23+
},
24+
},
25+
})
26+
const{ createUserErrorMessage, createUserFormErrors}=createUserState.context
2227
// There is no field for organization id in Community Edition, so handle its field error like a generic error
2328
constgenericError=
2429
createUserErrorMessage||
@@ -32,14 +37,14 @@ export const CreateUserPage: React.FC = () => {
3237
</Helmet>
3338
<CreateUserForm
3439
formErrors={createUserFormErrors}
35-
onSubmit={(user:TypesGen.CreateUserRequest)=>usersSend({type:"CREATE", user})}
40+
onSubmit={(user:TypesGen.CreateUserRequest)=>createUserSend({type:"CREATE", user})}
3641
onCancel={()=>{
37-
usersSend("CANCEL_CREATE_USER")
42+
createUserSend("CANCEL_CREATE_USER")
3843
navigate("/users")
3944
}}
40-
isLoading={usersState.hasTag("loading")}
45+
isLoading={createUserState.hasTag("loading")}
4146
error={genericError}
42-
myOrgId={myOrgId??""}
47+
myOrgId={myOrgId}
4348
/>
4449
</Margins>
4550
)

‎site/src/pages/UsersPage/UsersPage.tsx

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import{useActor}from"@xstate/react"
1+
import{useActor,useMachine}from"@xstate/react"
22
import{FC,ReactNode,useContext,useEffect}from"react"
33
import{Helmet}from"react-helmet-async"
44
import{useNavigate}from"react-router"
55
import{useSearchParams}from"react-router-dom"
6+
import{usersMachine}from"xServices/users/usersXService"
67
import{ConfirmDialog}from"../../components/Dialogs/ConfirmDialog/ConfirmDialog"
78
import{ResetPasswordDialog}from"../../components/Dialogs/ResetPasswordDialog/ResetPasswordDialog"
8-
import{userFilterQuery}from"../../util/filters"
99
import{pageTitle}from"../../util/page"
1010
import{XServiceContext}from"../../xServices/StateContext"
1111
import{UsersPageView}from"./UsersPageView"
@@ -24,7 +24,14 @@ export const Language = {
2424

2525
exportconstUsersPage:FC<{children?:ReactNode}>=()=>{
2626
constxServices=useContext(XServiceContext)
27-
const[usersState,usersSend]=useActor(xServices.usersXService)
27+
constnavigate=useNavigate()
28+
const[searchParams,setSearchParams]=useSearchParams()
29+
constfilter=searchParams.get("filter")??undefined
30+
const[usersState,usersSend]=useMachine(usersMachine,{
31+
context:{
32+
filter,
33+
},
34+
})
2835
const{
2936
users,
3037
getUsersError,
@@ -34,8 +41,7 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => {
3441
userIdToResetPassword,
3542
newUserPassword,
3643
}=usersState.context
37-
constnavigate=useNavigate()
38-
const[searchParams,setSearchParams]=useSearchParams()
44+
3945
constuserToBeSuspended=users?.find((u)=>u.id===userIdToSuspend)
4046
constuserToBeDeleted=users?.find((u)=>u.id===userIdToDelete)
4147
constuserToBeActivated=users?.find((u)=>u.id===userIdToActivate)
@@ -60,13 +66,11 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => {
6066

6167
// Fetch users on component mount
6268
useEffect(()=>{
63-
constfilter=searchParams.get("filter")
64-
constquery=filter??userFilterQuery.active
6569
usersSend({
6670
type:"GET_USERS",
67-
query,
71+
query:filter,
6872
})
69-
},[searchParams,usersSend])
73+
},[filter,usersSend])
7074

7175
// Fetch roles on component mount
7276
useEffect(()=>{

‎site/src/testHelpers/renderHelpers.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
importThemeProviderfrom"@material-ui/styles/ThemeProvider"
2-
import{renderaswrappedRender,RenderResult}from"@testing-library/react"
2+
import{
3+
renderaswrappedRender,
4+
RenderResult,
5+
screen,
6+
waitForElementToBeRemoved,
7+
}from"@testing-library/react"
38
import{createMemoryHistory}from"history"
49
import{i18n}from"i18n"
510
import{FC,ReactElement}from"react"
@@ -68,4 +73,7 @@ export function renderWithAuth(
6873
}
6974
}
7075

76+
exportconstwaitForLoaderToBeRemoved=():Promise<void>=>
77+
waitForElementToBeRemoved(()=>screen.getByRole("progressbar"))
78+
7179
export*from"./entities"

‎site/src/xServices/StateContext.tsx

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,11 @@ import { authMachine } from "./auth/authXService"
66
import{buildInfoMachine}from"./buildInfo/buildInfoXService"
77
import{entitlementsMachine}from"./entitlements/entitlementsXService"
88
import{siteRolesMachine}from"./roles/siteRolesXService"
9-
import{usersMachine}from"./users/usersXService"
109

1110
interfaceXServiceContextType{
1211
authXService:ActorRefFrom<typeofauthMachine>
1312
buildInfoXService:ActorRefFrom<typeofbuildInfoMachine>
1413
entitlementsXService:ActorRefFrom<typeofentitlementsMachine>
15-
usersXService:ActorRefFrom<typeofusersMachine>
1614
siteRolesXService:ActorRefFrom<typeofsiteRolesMachine>
1715
}
1816

@@ -28,9 +26,6 @@ export const XServiceContext = createContext({} as XServiceContextType)
2826

2927
exportconstXServiceProvider:FC<{children:ReactNode}>=({ children})=>{
3028
constnavigate=useNavigate()
31-
constredirectToUsersPage=()=>{
32-
navigate("users")
33-
}
3429
constredirectToSetupPage=()=>{
3530
navigate("setup")
3631
}
@@ -43,9 +38,6 @@ export const XServiceProvider: FC<{ children: ReactNode }> = ({ children }) => {
4338
),
4439
buildInfoXService:useInterpret(buildInfoMachine),
4540
entitlementsXService:useInterpret(entitlementsMachine),
46-
usersXService:useInterpret(()=>
47-
usersMachine.withConfig({actions:{ redirectToUsersPage}}),
48-
),
4941
siteRolesXService:useInterpret(siteRolesMachine),
5042
}}
5143
>

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp