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

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

Merged
presleyp merged 26 commits intomainfromusers-table/presleyp/592
Apr 14, 2022
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
26 commits
Select commitHold shift + click to select a range
fa600a5
Start users
presleypApr 11, 2022
aa75ca0
Merge branch 'main' into users-table/presleyp/592
presleypApr 11, 2022
665e217
Set up fake response
presleypApr 11, 2022
934e060
Update handler
presleypApr 11, 2022
a2a1a04
Update types
presleypApr 11, 2022
201b34a
Set up page
presleypApr 11, 2022
b45ddaf
Start adding table
presleypApr 12, 2022
0c341ed
Add header
presleypApr 12, 2022
aac1aee
Add Header
presleypApr 12, 2022
40c61db
Remove roles
presleypApr 12, 2022
ca729e9
Add UsersPageView
presleypApr 12, 2022
3fce4f0
Add test
presleypApr 12, 2022
96d3a72
Lint
presleypApr 12, 2022
6757380
Storybook error summary
presleypApr 12, 2022
b5fd9c8
Strip Pager to just what's currently needed
presleypApr 13, 2022
30ced2f
Clean up ErrorSummary while I'm here
presleypApr 13, 2022
272633b
Storybook tweaks
presleypApr 13, 2022
1dda810
Extract language
presleypApr 13, 2022
be60aa8
Lint
presleypApr 13, 2022
8d22584
Add missing $
presleypApr 14, 2022
91ac0d5
Merge branch 'main' into users-table/presleyp/592
presleypApr 14, 2022
a50ea8a
Lint
presleypApr 14, 2022
bf06e92
Merge branch 'users-table/presleyp/592' of github.com:coder/coder int…
presleypApr 14, 2022
34eb66b
Lint
presleypApr 14, 2022
8c03bbc
Fix syntax error
presleypApr 14, 2022
1cbc885
Lint
presleypApr 14, 2022
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
2 changes: 1 addition & 1 deletionsite/src/AppRouter.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,7 +16,7 @@ import { SettingsPage } from "./pages/settings"
import { TemplatesPage } from "./pages/templates"
import { TemplatePage } from "./pages/templates/[organization]/[template]"
import { CreateWorkspacePage } from "./pages/templates/[organization]/[template]/create"
import { UsersPage } from "./pages/users"
import { UsersPage } from "./pages/UsersPage/UsersPage"
import { WorkspacePage } from "./pages/workspaces/[workspace]"

export const AppRouter: React.FC = () => (
Expand Down
10 changes: 10 additions & 0 deletionssite/src/api/index.ts
View file
Open in desktop
Original file line numberDiff line numberDiff 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 = {
Expand DownExpand Up@@ -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,
})
}

export const getBuildInfo = async (): Promise<Types.BuildInfoResponse> => {
const response = await axios.get("/api/v2/buildinfo")
return response.data
Expand Down
9 changes: 9 additions & 0 deletionssite/src/api/types.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -79,6 +79,15 @@ export interface UserAgent {
readonly os: string
}

export interface Pager {
total: number
}

export interface PagedUsers {
page: UserResponse[]
pager: Pager
}

export interface WorkspaceAutostartRequest {
schedule: string
}
Expand Down
17 changes: 17 additions & 0 deletionssite/src/components/ErrorSummary/ErrorSummary.stories.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff 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({})
14 changes: 9 additions & 5 deletionssite/src/components/ErrorSummary/index.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
import React from "react"

const Language = {
unknownErrorMessage: "Unknown error",
}

export interface ErrorSummaryProps {
error: Error |undefined
error: Error |unknown
}

export const ErrorSummary: React.FC<ErrorSummaryProps> = ({ error }) => {
// TODO: More interesting error page

if (typeof error === "undefined") {
return <div>{"Unknown error"}</div>
if (!(error instanceof Error)) {
return <div>{Language.unknownErrorMessage}</div>
} else {
return <div>{error.toString()}</div>
}

return <div>{error.toString()}</div>
}
21 changes: 21 additions & 0 deletionssite/src/components/UsersTable/UsersTable.stories.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff 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],
}

export const Empty = Template.bind({})
Empty.args = {
users: [],
}
32 changes: 32 additions & 0 deletionssite/src/components/UsersTable/UsersTable.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff 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} />
}
18 changes: 18 additions & 0 deletionssite/src/pages/UsersPage/UsersPage.test.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff 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)
})
})
17 changes: 17 additions & 0 deletionssite/src/pages/UsersPage/UsersPage.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff 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 = () => {
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} />
}
}
21 changes: 21 additions & 0 deletionssite/src/pages/UsersPage/UsersPageView.stories.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff 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: [],
}
32 changes: 32 additions & 0 deletionssite/src/pages/UsersPage/UsersPageView.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff 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",
},
}))
1 change: 0 additions & 1 deletionsite/src/pages/login.test.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,7 +41,6 @@ describe("SignInPage", () => {
const password = screen.getByLabelText(Language.passwordLabel)
await userEvent.type(email, "test@coder.com")
await userEvent.type(password, "password")

// Click sign-in
const signInButton = await screen.findByText(Language.signIn)
act(() => signInButton.click())
Expand Down
5 changes: 0 additions & 5 deletionssite/src/pages/users.tsx
View file
Open in desktop

This file was deleted.

12 changes: 12 additions & 0 deletionssite/src/test_helpers/entities.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import {
BuildInfoResponse,
Organization,
Pager,
Provisioner,
Template,
UserAgent,
Expand All@@ -25,6 +26,17 @@ export const MockUser: UserResponse = {
created_at: "",
}

export const MockUser2: UserResponse = {
id: "test-user-2",
username: "TestUser2",
email: "test2@coder.com",
created_at: "",
}

export const MockPager: Pager = {
total: 2,
}

export const MockOrganization: Organization = {
id: "test-org",
name: "Test Organization",
Expand Down
3 changes: 3 additions & 0 deletionssite/src/test_helpers/handlers.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,6 +21,9 @@ export const handlers = [
}),

// users
rest.get("/api/v2/users", async (req, res, ctx) => {
return res(ctx.status(200), ctx.json({ page: [M.MockUser, M.MockUser2], pager: M.MockPager }))
}),
rest.post("/api/v2/users/me/workspaces", async (req, res, ctx) => {
return res(ctx.status(200), ctx.json(M.MockWorkspace))
}),
Expand Down
7 changes: 5 additions & 2 deletionssite/src/xServices/StateContext.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 {
buildInfoXService: ActorRefFrom<typeof buildInfoMachine>
authXService: ActorRefFrom<typeof authMachine>
buildInfoXService: ActorRefFrom<typeof buildInfoMachine>
usersXService: ActorRefFrom<typeof usersMachine>
}

/**
Expand All@@ -23,8 +25,9 @@ export const XServiceProvider: React.FC = ({ children }) => {
return (
<XServiceContext.Provider
value={{
buildInfoXService: useInterpret(buildInfoMachine),
authXService: useInterpret(authMachine),
buildInfoXService: useInterpret(buildInfoMachine),
usersXService: useInterpret(usersMachine),
}}
>
{children}
Expand Down
80 changes: 80 additions & 0 deletionssite/src/xServices/users/usersXService.ts
View file
Open in desktop
Original file line numberDiff line numberDiff 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,
})),
},
},
)

[8]ページ先頭

©2009-2025 Movatter.jp