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

Commit82275a8

Browse files
feat(site): Read users into basic UsersTable (#981)
* Start users* Set up fake response* Update handler* Update types* Set up page* Start adding table* Add header* Add Header* Remove roles* Add UsersPageView* Add test* Lint* Storybook error summary* Strip Pager to just what's currently needed* Clean up ErrorSummary while I'm here* Storybook tweaks* Extract language* Lint* Add missing $Co-authored-by: G r e y <grey@coder.com>* Lint* Lint* Fix syntax error* LintCo-authored-by: G r e y <grey@coder.com>
1 parentf803e37 commit82275a8

File tree

17 files changed

+287
-14
lines changed

17 files changed

+287
-14
lines changed

‎site/src/AppRouter.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { SettingsPage } from "./pages/settings"
1616
import{TemplatesPage}from"./pages/templates"
1717
import{TemplatePage}from"./pages/templates/[organization]/[template]"
1818
import{CreateWorkspacePage}from"./pages/templates/[organization]/[template]/create"
19-
import{UsersPage}from"./pages/users"
19+
import{UsersPage}from"./pages/UsersPage/UsersPage"
2020
import{WorkspacePage}from"./pages/workspaces/[workspace]"
2121

2222
exportconstAppRouter:React.FC=()=>(

‎site/src/api/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
importaxios,{AxiosRequestHeaders}from"axios"
22
import{mutate}from"swr"
3+
import{MockPager,MockUser,MockUser2}from"../test_helpers"
34
import*asTypesfrom"./types"
45

56
constCONTENT_TYPE_JSON:AxiosRequestHeaders={
@@ -69,6 +70,15 @@ export const getApiKey = async (): Promise<Types.APIKeyResponse> => {
6970
returnresponse.data
7071
}
7172

73+
exportconstgetUsers=async():Promise<Types.PagedUsers>=>{
74+
// const response = await axios.get<Types.UserResponse[]>("/api/v2/users")
75+
// return response.data
76+
returnPromise.resolve({
77+
page:[MockUser,MockUser2],
78+
pager:MockPager,
79+
})
80+
}
81+
7282
exportconstgetBuildInfo=async():Promise<Types.BuildInfoResponse>=>{
7383
constresponse=awaitaxios.get("/api/v2/buildinfo")
7484
returnresponse.data

‎site/src/api/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,15 @@ export interface UserAgent {
7979
readonlyos:string
8080
}
8181

82+
exportinterfacePager{
83+
total:number
84+
}
85+
86+
exportinterfacePagedUsers{
87+
page:UserResponse[]
88+
pager:Pager
89+
}
90+
8291
exportinterfaceWorkspaceAutostartRequest{
8392
schedule:string
8493
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import{ComponentMeta,Story}from"@storybook/react"
2+
importReactfrom"react"
3+
import{ErrorSummary,ErrorSummaryProps}from"."
4+
5+
exportdefault{
6+
title:"components/ErrorSummary",
7+
component:ErrorSummary,
8+
}asComponentMeta<typeofErrorSummary>
9+
10+
constTemplate:Story<ErrorSummaryProps>=(args)=><ErrorSummary{...args}/>
11+
12+
exportconstWithError=Template.bind({})
13+
WithError.args={
14+
error:newError("Something went wrong!"),
15+
}
16+
17+
exportconstWithUndefined=Template.bind({})
Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
importReactfrom"react"
22

3+
constLanguage={
4+
unknownErrorMessage:"Unknown error",
5+
}
6+
37
exportinterfaceErrorSummaryProps{
4-
error:Error|undefined
8+
error:Error|unknown
59
}
610

711
exportconstErrorSummary:React.FC<ErrorSummaryProps>=({ error})=>{
812
// TODO: More interesting error page
913

10-
if(typeoferror==="undefined"){
11-
return<div>{"Unknown error"}</div>
14+
if(!(errorinstanceofError)){
15+
return<div>{Language.unknownErrorMessage}</div>
16+
}else{
17+
return<div>{error.toString()}</div>
1218
}
13-
14-
return<div>{error.toString()}</div>
1519
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import{ComponentMeta,Story}from"@storybook/react"
2+
importReactfrom"react"
3+
import{MockUser,MockUser2}from"../../test_helpers"
4+
import{UsersTable,UsersTableProps}from"./UsersTable"
5+
6+
exportdefault{
7+
title:"Components/UsersTable",
8+
component:UsersTable,
9+
}asComponentMeta<typeofUsersTable>
10+
11+
constTemplate:Story<UsersTableProps>=(args)=><UsersTable{...args}/>
12+
13+
exportconstExample=Template.bind({})
14+
Example.args={
15+
users:[MockUser,MockUser2],
16+
}
17+
18+
exportconstEmpty=Template.bind({})
19+
Empty.args={
20+
users:[],
21+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
importReactfrom"react"
2+
import{UserResponse}from"../../api/types"
3+
import{Column,Table}from"../../components/Table"
4+
import{EmptyState}from"../EmptyState"
5+
import{UserCell}from"../Table/Cells/UserCell"
6+
7+
constLanguage={
8+
pageTitle:"Users",
9+
usersTitle:"All users",
10+
emptyMessage:"No users found",
11+
usernameLabel:"User",
12+
}
13+
14+
constemptyState=<EmptyStatemessage={Language.emptyMessage}/>
15+
16+
constcolumns:Column<UserResponse>[]=[
17+
{
18+
key:"username",
19+
name:Language.usernameLabel,
20+
renderer:(field,data)=>{
21+
return<UserCellAvatar={{username:data.username}}primaryText={data.username}caption={data.email}/>
22+
},
23+
},
24+
]
25+
26+
exportinterfaceUsersTableProps{
27+
users:UserResponse[]
28+
}
29+
30+
exportconstUsersTable:React.FC<UsersTableProps>=({ users})=>{
31+
return<Tablecolumns={columns}data={users}title={Language.usersTitle}emptyState={emptyState}/>
32+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import{screen}from"@testing-library/react"
2+
importReactfrom"react"
3+
import{MockPager,render}from"../../test_helpers"
4+
import{UsersPage}from"./UsersPage"
5+
import{Language}from"./UsersPageView"
6+
7+
describe("Users Page",()=>{
8+
it("has a header with the total number of users",async()=>{
9+
render(<UsersPage/>)
10+
consttotal=awaitscreen.findByText(/\d+total/)
11+
expect(total.innerHTML).toEqual(Language.pageSubtitle(MockPager))
12+
})
13+
it("shows users",async()=>{
14+
render(<UsersPage/>)
15+
constusers=awaitscreen.findAllByText(/.*@coder.com/)
16+
expect(users.length).toEqual(2)
17+
})
18+
})
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import{useActor}from"@xstate/react"
2+
importReact,{useContext}from"react"
3+
import{ErrorSummary}from"../../components/ErrorSummary"
4+
import{XServiceContext}from"../../xServices/StateContext"
5+
import{UsersPageView}from"./UsersPageView"
6+
7+
exportconstUsersPage:React.FC=()=>{
8+
constxServices=useContext(XServiceContext)
9+
const[usersState]=useActor(xServices.usersXService)
10+
const{ users, pager, getUsersError}=usersState.context
11+
12+
if(usersState.matches("error")){
13+
return<ErrorSummaryerror={getUsersError}/>
14+
}else{
15+
return<UsersPageViewusers={users}pager={pager}/>
16+
}
17+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import{ComponentMeta,Story}from"@storybook/react"
2+
importReactfrom"react"
3+
import{MockPager,MockUser,MockUser2}from"../../test_helpers"
4+
import{UsersPageView,UsersPageViewProps}from"./UsersPageView"
5+
6+
exportdefault{
7+
title:"pages/UsersPageView",
8+
component:UsersPageView,
9+
}asComponentMeta<typeofUsersPageView>
10+
11+
constTemplate:Story<UsersPageViewProps>=(args)=><UsersPageView{...args}/>
12+
13+
exportconstReady=Template.bind({})
14+
Ready.args={
15+
users:[MockUser,MockUser2],
16+
pager:MockPager,
17+
}
18+
exportconstEmpty=Template.bind({})
19+
Empty.args={
20+
users:[],
21+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import{makeStyles}from"@material-ui/core/styles"
2+
importReactfrom"react"
3+
import{Pager,UserResponse}from"../../api/types"
4+
import{Header}from"../../components/Header"
5+
import{UsersTable}from"../../components/UsersTable/UsersTable"
6+
7+
exportconstLanguage={
8+
pageTitle:"Users",
9+
pageSubtitle:(pager:Pager|undefined):string=>(pager ?`${pager.total} total` :""),
10+
}
11+
12+
exportinterfaceUsersPageViewProps{
13+
users:UserResponse[]
14+
pager?:Pager
15+
}
16+
17+
exportconstUsersPageView:React.FC<UsersPageViewProps>=({ users, pager})=>{
18+
conststyles=useStyles()
19+
return(
20+
<divclassName={styles.flexColumn}>
21+
<Headertitle={Language.pageTitle}subTitle={Language.pageSubtitle(pager)}/>
22+
<UsersTableusers={users}/>
23+
</div>
24+
)
25+
}
26+
27+
constuseStyles=makeStyles(()=>({
28+
flexColumn:{
29+
display:"flex",
30+
flexDirection:"column",
31+
},
32+
}))

‎site/src/pages/login.test.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ describe("SignInPage", () => {
4141
constpassword=screen.getByLabelText(Language.passwordLabel)
4242
awaituserEvent.type(email,"test@coder.com")
4343
awaituserEvent.type(password,"password")
44-
4544
// Click sign-in
4645
constsignInButton=awaitscreen.findByText(Language.signIn)
4746
act(()=>signInButton.click())

‎site/src/pages/users.tsx

Lines changed: 0 additions & 5 deletions
This file was deleted.

‎site/src/test_helpers/entities.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import{
22
BuildInfoResponse,
33
Organization,
4+
Pager,
45
Provisioner,
56
Template,
67
UserAgent,
@@ -25,6 +26,17 @@ export const MockUser: UserResponse = {
2526
created_at:"",
2627
}
2728

29+
exportconstMockUser2:UserResponse={
30+
id:"test-user-2",
31+
username:"TestUser2",
32+
email:"test2@coder.com",
33+
created_at:"",
34+
}
35+
36+
exportconstMockPager:Pager={
37+
total:2,
38+
}
39+
2840
exportconstMockOrganization:Organization={
2941
id:"test-org",
3042
name:"Test Organization",

‎site/src/test_helpers/handlers.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ export const handlers = [
2121
}),
2222

2323
// users
24+
rest.get("/api/v2/users",async(req,res,ctx)=>{
25+
returnres(ctx.status(200),ctx.json({page:[M.MockUser,M.MockUser2],pager:M.MockPager}))
26+
}),
2427
rest.post("/api/v2/users/me/workspaces",async(req,res,ctx)=>{
2528
returnres(ctx.status(200),ctx.json(M.MockWorkspace))
2629
}),

‎site/src/xServices/StateContext.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ import React, { createContext } from "react"
33
import{ActorRefFrom}from"xstate"
44
import{authMachine}from"./auth/authXService"
55
import{buildInfoMachine}from"./buildInfo/buildInfoXService"
6+
import{usersMachine}from"./users/usersXService"
67

78
interfaceXServiceContextType{
8-
buildInfoXService:ActorRefFrom<typeofbuildInfoMachine>
99
authXService:ActorRefFrom<typeofauthMachine>
10+
buildInfoXService:ActorRefFrom<typeofbuildInfoMachine>
11+
usersXService:ActorRefFrom<typeofusersMachine>
1012
}
1113

1214
/**
@@ -23,8 +25,9 @@ export const XServiceProvider: React.FC = ({ children }) => {
2325
return(
2426
<XServiceContext.Provider
2527
value={{
26-
buildInfoXService:useInterpret(buildInfoMachine),
2728
authXService:useInterpret(authMachine),
29+
buildInfoXService:useInterpret(buildInfoMachine),
30+
usersXService:useInterpret(usersMachine),
2831
}}
2932
>
3033
{children}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import{assign,createMachine}from"xstate"
2+
import*asAPIfrom"../../api"
3+
import*asTypesfrom"../../api/types"
4+
5+
exportinterfaceUsersContext{
6+
users:Types.UserResponse[]
7+
pager?:Types.Pager
8+
getUsersError?:Error|unknown
9+
}
10+
11+
exporttypeUsersEvent={type:"GET_USERS"}
12+
13+
exportconstusersMachine=createMachine(
14+
{
15+
tsTypes:{}asimport("./usersXService.typegen").Typegen0,
16+
schema:{
17+
context:{}asUsersContext,
18+
events:{}asUsersEvent,
19+
services:{}as{
20+
getUsers:{
21+
data:Types.PagedUsers
22+
}
23+
},
24+
},
25+
id:"usersState",
26+
context:{
27+
users:[],
28+
},
29+
initial:"gettingUsers",
30+
states:{
31+
gettingUsers:{
32+
invoke:{
33+
src:"getUsers",
34+
id:"getUsers",
35+
onDone:[
36+
{
37+
target:"#usersState.ready",
38+
actions:["assignUsers","clearGetUsersError"],
39+
},
40+
],
41+
onError:[
42+
{
43+
actions:"assignGetUsersError",
44+
target:"#usersState.error",
45+
},
46+
],
47+
},
48+
tags:"loading",
49+
},
50+
ready:{
51+
on:{
52+
GET_USERS:"gettingUsers",
53+
},
54+
},
55+
error:{
56+
on:{
57+
GET_USERS:"gettingUsers",
58+
},
59+
},
60+
},
61+
},
62+
{
63+
services:{
64+
getUsers:API.getUsers,
65+
},
66+
actions:{
67+
assignUsers:assign({
68+
users:(_,event)=>event.data.page,
69+
pager:(_,event)=>event.data.pager,
70+
}),
71+
assignGetUsersError:assign({
72+
getUsersError:(_,event)=>event.data,
73+
}),
74+
clearGetUsersError:assign((context:UsersContext)=>({
75+
...context,
76+
getUsersError:undefined,
77+
})),
78+
},
79+
},
80+
)

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp