- Notifications
You must be signed in to change notification settings - Fork928
feat(site): Read users into basic UsersTable#981
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
fa600a5
aa75ca0
665e217
934e060
a2a1a04
201b34a
b45ddaf
0c341ed
aac1aee
40c61db
ca729e9
3fce4f0
96d3a72
6757380
b5fd9c8
30ced2f
272633b
1dda810
be60aa8
8d22584
91ac0d5
a50ea8a
bf06e92
34eb66b
8c03bbc
1cbc885
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
import axios, { AxiosRequestHeaders } from "axios" | ||
import { mutate } from "swr" | ||
import { MockPager, MockUser, MockUser2 } from "../test_helpers" | ||
import * as Types from "./types" | ||
const CONTENT_TYPE_JSON: AxiosRequestHeaders = { | ||
@@ -69,6 +70,15 @@ export const getApiKey = async (): Promise<Types.APIKeyResponse> => { | ||
return response.data | ||
} | ||
export const getUsers = async (): Promise<Types.PagedUsers> => { | ||
// const response = await axios.get<Types.UserResponse[]>("/api/v2/users") | ||
// return response.data | ||
return Promise.resolve({ | ||
page: [MockUser, MockUser2], | ||
pager: MockPager, | ||
}) | ||
} | ||
presleyp marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
export const getBuildInfo = async (): Promise<Types.BuildInfoResponse> => { | ||
const response = await axios.get("/api/v2/buildinfo") | ||
return response.data | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import{ComponentMeta,Story}from"@storybook/react" | ||
importReactfrom"react" | ||
import{ErrorSummary,ErrorSummaryProps}from"." | ||
exportdefault{ | ||
title:"components/ErrorSummary", | ||
component:ErrorSummary, | ||
}asComponentMeta<typeofErrorSummary> | ||
constTemplate:Story<ErrorSummaryProps>=(args)=><ErrorSummary{...args}/> | ||
exportconstWithError=Template.bind({}) | ||
WithError.args={ | ||
error:newError("Something went wrong!"), | ||
} | ||
exportconstWithUndefined=Template.bind({}) | ||
greyscaled marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,19 @@ | ||
import React from "react" | ||
const Language = { | ||
unknownErrorMessage: "Unknown error", | ||
} | ||
export interface ErrorSummaryProps { | ||
error: Error |unknown | ||
} | ||
export const ErrorSummary: React.FC<ErrorSummaryProps> = ({ error }) => { | ||
// TODO: More interesting error page | ||
if (!(error instanceof Error)) { | ||
presleyp marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
return <div>{Language.unknownErrorMessage}</div> | ||
} else { | ||
return <div>{error.toString()}</div> | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { ComponentMeta, Story } from "@storybook/react" | ||
import React from "react" | ||
import { MockUser, MockUser2 } from "../../test_helpers" | ||
import { UsersTable, UsersTableProps } from "./UsersTable" | ||
export default { | ||
title: "Components/UsersTable", | ||
component: UsersTable, | ||
} as ComponentMeta<typeof UsersTable> | ||
const Template: Story<UsersTableProps> = (args) => <UsersTable {...args} /> | ||
export const Example = Template.bind({}) | ||
Example.args = { | ||
users: [MockUser, MockUser2], | ||
} | ||
presleyp marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
export const Empty = Template.bind({}) | ||
Empty.args = { | ||
users: [], | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import React from "react" | ||
import { UserResponse } from "../../api/types" | ||
import { Column, Table } from "../../components/Table" | ||
import { EmptyState } from "../EmptyState" | ||
import { UserCell } from "../Table/Cells/UserCell" | ||
const Language = { | ||
pageTitle: "Users", | ||
usersTitle: "All users", | ||
emptyMessage: "No users found", | ||
usernameLabel: "User", | ||
} | ||
const emptyState = <EmptyState message={Language.emptyMessage} /> | ||
const columns: Column<UserResponse>[] = [ | ||
{ | ||
key: "username", | ||
name: Language.usernameLabel, | ||
renderer: (field, data) => { | ||
return <UserCell Avatar={{ username: data.username }} primaryText={data.username} caption={data.email} /> | ||
}, | ||
}, | ||
] | ||
export interface UsersTableProps { | ||
users: UserResponse[] | ||
} | ||
export const UsersTable: React.FC<UsersTableProps> = ({ users }) => { | ||
return <Table columns={columns} data={users} title={Language.usersTitle} emptyState={emptyState} /> | ||
} | ||
presleyp marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { screen } from "@testing-library/react" | ||
import React from "react" | ||
import { MockPager, render } from "../../test_helpers" | ||
import { UsersPage } from "./UsersPage" | ||
import { Language } from "./UsersPageView" | ||
describe("Users Page", () => { | ||
it("has a header with the total number of users", async () => { | ||
render(<UsersPage />) | ||
const total = await screen.findByText(/\d+ total/) | ||
expect(total.innerHTML).toEqual(Language.pageSubtitle(MockPager)) | ||
}) | ||
it("shows users", async () => { | ||
render(<UsersPage />) | ||
const users = await screen.findAllByText(/.*@coder.com/) | ||
expect(users.length).toEqual(2) | ||
presleyp marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { useActor } from "@xstate/react" | ||
import React, { useContext } from "react" | ||
import { ErrorSummary } from "../../components/ErrorSummary" | ||
import { XServiceContext } from "../../xServices/StateContext" | ||
import { UsersPageView } from "./UsersPageView" | ||
export const UsersPage: React.FC = () => { | ||
presleyp marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
const xServices = useContext(XServiceContext) | ||
const [usersState] = useActor(xServices.usersXService) | ||
const { users, pager, getUsersError } = usersState.context | ||
if (usersState.matches("error")) { | ||
return <ErrorSummary error={getUsersError} /> | ||
} else { | ||
return <UsersPageView users={users} pager={pager} /> | ||
} | ||
} | ||
presleyp marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { ComponentMeta, Story } from "@storybook/react" | ||
import React from "react" | ||
import { MockPager, MockUser, MockUser2 } from "../../test_helpers" | ||
import { UsersPageView, UsersPageViewProps } from "./UsersPageView" | ||
export default { | ||
title: "pages/UsersPageView", | ||
component: UsersPageView, | ||
} as ComponentMeta<typeof UsersPageView> | ||
const Template: Story<UsersPageViewProps> = (args) => <UsersPageView {...args} /> | ||
export const Ready = Template.bind({}) | ||
Ready.args = { | ||
users: [MockUser, MockUser2], | ||
pager: MockPager, | ||
} | ||
export const Empty = Template.bind({}) | ||
Empty.args = { | ||
users: [], | ||
} | ||
presleyp marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { makeStyles } from "@material-ui/core/styles" | ||
import React from "react" | ||
import { Pager, UserResponse } from "../../api/types" | ||
import { Header } from "../../components/Header" | ||
import { UsersTable } from "../../components/UsersTable/UsersTable" | ||
export const Language = { | ||
pageTitle: "Users", | ||
pageSubtitle: (pager: Pager | undefined): string => (pager ? `${pager.total} total` : ""), | ||
} | ||
export interface UsersPageViewProps { | ||
users: UserResponse[] | ||
pager?: Pager | ||
} | ||
export const UsersPageView: React.FC<UsersPageViewProps> = ({ users, pager }) => { | ||
const styles = useStyles() | ||
return ( | ||
<div className={styles.flexColumn}> | ||
<Header title={Language.pageTitle} subTitle={Language.pageSubtitle(pager)} /> | ||
<UsersTable users={users} /> | ||
</div> | ||
) | ||
} | ||
const useStyles = makeStyles(() => ({ | ||
flexColumn: { | ||
display: "flex", | ||
flexDirection: "column", | ||
}, | ||
})) |
This file was deleted.
Uh oh!
There was an error while loading.Please reload this page.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -3,10 +3,12 @@ import React, { createContext } from "react" | ||
import { ActorRefFrom } from "xstate" | ||
import { authMachine } from "./auth/authXService" | ||
import { buildInfoMachine } from "./buildInfo/buildInfoXService" | ||
import { usersMachine } from "./users/usersXService" | ||
interface XServiceContextType { | ||
authXService: ActorRefFrom<typeof authMachine> | ||
buildInfoXService: ActorRefFrom<typeof buildInfoMachine> | ||
usersXService: ActorRefFrom<typeof usersMachine> | ||
presleyp marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
} | ||
/** | ||
@@ -23,8 +25,9 @@ export const XServiceProvider: React.FC = ({ children }) => { | ||
return ( | ||
<XServiceContext.Provider | ||
value={{ | ||
authXService: useInterpret(authMachine), | ||
buildInfoXService: useInterpret(buildInfoMachine), | ||
usersXService: useInterpret(usersMachine), | ||
}} | ||
> | ||
{children} | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import { assign, createMachine } from "xstate" | ||
import * as API from "../../api" | ||
import * as Types from "../../api/types" | ||
export interface UsersContext { | ||
users: Types.UserResponse[] | ||
pager?: Types.Pager | ||
getUsersError?: Error | unknown | ||
} | ||
export type UsersEvent = { type: "GET_USERS" } | ||
export const usersMachine = createMachine( | ||
{ | ||
tsTypes: {} as import("./usersXService.typegen").Typegen0, | ||
schema: { | ||
context: {} as UsersContext, | ||
events: {} as UsersEvent, | ||
services: {} as { | ||
getUsers: { | ||
data: Types.PagedUsers | ||
} | ||
}, | ||
}, | ||
id: "usersState", | ||
context: { | ||
users: [], | ||
}, | ||
initial: "gettingUsers", | ||
states: { | ||
gettingUsers: { | ||
invoke: { | ||
src: "getUsers", | ||
id: "getUsers", | ||
onDone: [ | ||
{ | ||
target: "#usersState.ready", | ||
actions: ["assignUsers", "clearGetUsersError"], | ||
}, | ||
], | ||
onError: [ | ||
{ | ||
actions: "assignGetUsersError", | ||
target: "#usersState.error", | ||
}, | ||
], | ||
}, | ||
tags: "loading", | ||
}, | ||
ready: { | ||
on: { | ||
GET_USERS: "gettingUsers", | ||
}, | ||
}, | ||
error: { | ||
on: { | ||
GET_USERS: "gettingUsers", | ||
}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
services: { | ||
getUsers: API.getUsers, | ||
}, | ||
actions: { | ||
assignUsers: assign({ | ||
users: (_, event) => event.data.page, | ||
pager: (_, event) => event.data.pager, | ||
}), | ||
assignGetUsersError: assign({ | ||
getUsersError: (_, event) => event.data, | ||
}), | ||
clearGetUsersError: assign((context: UsersContext) => ({ | ||
...context, | ||
getUsersError: undefined, | ||
})), | ||
}, | ||
}, | ||
) |