Thanks to visit codestin.com
Credit goes to github.com

Skip to content

refactor(site): replace UserContext with userXService #465

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

Merged
merged 48 commits into from
Mar 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
e620598
Install and configure XState
presleyp Mar 15, 2022
c3a143b
userXService - typegen not working yet
presleyp Mar 15, 2022
561cd6b
Lint, fix error transitions
presleyp Mar 15, 2022
1548fb8
Lint
presleyp Mar 15, 2022
80e10c8
Change initial state to handle loss of state
presleyp Mar 15, 2022
3daf0a9
Fix gitignore
presleyp Mar 15, 2022
9f65327
Fix types by hook or by crook
presleyp Mar 15, 2022
d6bd5f6
Use xservice in all pages
presleyp Mar 15, 2022
242cb61
Merge branch 'main' into 435/presleyp/user-state
presleyp Mar 16, 2022
1e07da7
Glue/visual component separation
presleyp Mar 16, 2022
16692da
Fix dependency merge
presleyp Mar 16, 2022
d1017f0
Lint
presleyp Mar 16, 2022
85779b9
Remove UserContext
presleyp Mar 16, 2022
f82b099
Remove inspector
presleyp Mar 16, 2022
d4d79ea
Add typegen command to site/out
bryphe-coder Mar 16, 2022
ca2fc53
Merge branch '435/presleyp/user-state' of github.com:coder/coder into…
bryphe-coder Mar 16, 2022
17db4a9
Fix index page redirects
presleyp Mar 16, 2022
aa5441d
Merge branch '435/presleyp/user-state' of github.com:coder/coder into…
presleyp Mar 16, 2022
8d3f051
DRY up nav and redirects
presleyp Mar 16, 2022
d98c8c7
Merge branch 'main' into 435/presleyp/user-state
presleyp Mar 16, 2022
e5fd635
Moves based on merge
presleyp Mar 16, 2022
9ef9dca
Moving Page helpers into Page dir
presleyp Mar 16, 2022
97d94de
Move xservice into src, update script
presleyp Mar 16, 2022
9c21d4f
Move and storybook navbarview
presleyp Mar 16, 2022
d1b1bab
Update docs
presleyp Mar 16, 2022
e6a7895
Install MSW
presleyp Mar 16, 2022
6ac0530
Reorganization, with apologies
presleyp Mar 16, 2022
1c54f80
Missed spots
presleyp Mar 16, 2022
ee8ff32
Add mock handlers
presleyp Mar 16, 2022
a88e260
Configure jest for msw
presleyp Mar 17, 2022
e0e36a0
Fix typos
presleyp Mar 17, 2022
cf7f934
Shift unit test to NavbarView
presleyp Mar 17, 2022
ca3e7fc
Fix test types
presleyp Mar 17, 2022
6700453
Rename NavbarView test
presleyp Mar 17, 2022
48ac5b7
Attempt at test, wip
presleyp Mar 17, 2022
5c142db
Fix config
presleyp Mar 17, 2022
595859a
Be logged out, only warn
presleyp Mar 17, 2022
1824877
Conditionally show text to help test
presleyp Mar 17, 2022
f3902a1
Use a Context for MSW's sake
presleyp Mar 18, 2022
219bc44
mocks -> test_helpers
presleyp Mar 18, 2022
9d638ee
Enable dev tools
presleyp Mar 18, 2022
b4bb44e
Format
presleyp Mar 18, 2022
9c40fc5
Fix import
presleyp Mar 18, 2022
46e3888
Merge branch 'main' into 435/presleyp/user-state
presleyp Mar 18, 2022
e0f4d7a
Fixes
presleyp Mar 18, 2022
5a0f9ac
Merge branch 'main' into 435/presleyp/user-state
presleyp Mar 18, 2022
42f0554
Lint
presleyp Mar 18, 2022
2be726b
run typegen postinstall
presleyp Mar 18, 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ site/storybook-static/
site/test-results/
site/yarn-error.log
coverage/
site/**/*.typegen.ts

# Build
dist/
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ provisionersdk/proto: provisionersdk/proto/provisioner.proto

site/out:
./scripts/yarn_install.sh
cd site && yarn typegen
cd site && yarn build
# Restores GITKEEP files!
git checkout HEAD site/out
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ To manually run the server and go through first-time set up, run the following c

You'll now be able to login and access the server.

To create a project, run:
- `dist/coder_linux_amd64/coder projects create -d /path/to/project`

### Development
Expand All @@ -63,6 +62,10 @@ The `develop.sh` script does three things:

This is the recommend flow for working on the front-end, as hot-reload is set up as part of the webpack config.

Note that `./develop.sh` creates a user and allows you to log into the UI, but does not log you into the CLI, which is required for creating a project. Use the `login` command above before the `projects create` command.

While we're working on automating XState typegen, you may need to run `yarn typegen` from `site`.

## Front-End Plan

For the front-end team, we're planning on 2 phases to the 'v2' work:
Expand Down
1 change: 1 addition & 0 deletions site/.eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ coverage
.next
storybook-static
test-results
**/*.typegen.ts
16 changes: 16 additions & 0 deletions site/jest.setup.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
import { server } from "./src/test_helpers/server"

// Establish API mocking before all tests through MSW.
beforeAll(() =>
server.listen({
onUnhandledRequest: "warn",
}),
)

// Reset any request handlers that we may add during the tests,
// so they don't affect other tests.
afterEach(() => server.resetHandlers())

// Clean up after the tests are finished.
afterAll(() => server.close())

// Helper utility to fail jest tests if a console.error is logged
// Pulled from this blog post:
// https://www.benmvp.com/blog/catch-warnings-jest-tests/
Expand Down
10 changes: 8 additions & 2 deletions site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"repository": "https://github.com/coder/coder",
"private": true,
"scripts": {
"postinstall": "yarn typegen",
"build": "NODE_ENV=production webpack build --config=webpack.prod.ts",
"build:analyze": "NODE_ENV=production webpack --profile --progress --json --config=webpack.prod.ts > out/stats.json && webpack-bundle-analyzer out/stats.json out",
"dev": "webpack-dev-server --config=webpack.dev.ts",
Expand All @@ -18,19 +19,22 @@
"storybook:build": "build-storybook",
"test": "jest --selectProjects test",
"test:coverage": "jest --selectProjects test --collectCoverage",
"test:watch": "jest --selectProjects test --watch"
"test:watch": "jest --selectProjects test --watch",
"typegen": "xstate typegen 'src/**/*.ts'"
},
"dependencies": {
"@material-ui/core": "4.9.4",
"@material-ui/icons": "4.5.1",
"@material-ui/lab": "4.0.0-alpha.42",
"@xstate/react": "^2.0.1",
"axios": "0.26.1",
"formik": "2.2.9",
"history": "5.3.0",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-router-dom": "6.2.2",
"swr": "1.2.2",
"xstate": "^4.30.6",
"yup": "0.32.11"
},
"devDependencies": {
Expand All @@ -43,13 +47,14 @@
"@storybook/react": "6.4.19",
"@testing-library/react": "12.1.4",
"@types/express": "4.17.13",
"@types/jest": "27.4.1",
"@types/jest": "^27.4.1",
"@types/node": "14.18.12",
"@types/react": "17.0.40",
"@types/react-dom": "17.0.13",
"@types/superagent": "4.1.15",
"@typescript-eslint/eslint-plugin": "5.15.0",
"@typescript-eslint/parser": "5.15.0",
"@xstate/cli": "^0.1.4",
"copy-webpack-plugin": "10.2.4",
"eslint": "8.11.0",
"eslint-config-prettier": "8.5.0",
Expand All @@ -66,6 +71,7 @@
"jest": "27.5.1",
"jest-junit": "13.0.0",
"jest-runner-eslint": "1.0.0",
"msw": "^0.39.2",
"prettier": "2.6.0",
"react-hot-loader": "4.13.0",
"sql-formatter": "4.0.2",
Expand Down
3 changes: 2 additions & 1 deletion site/src/api.test.ts → site/src/api/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import axios from "axios"
import { APIKeyResponse, getApiKey, login, LoginResponse, logout } from "./api"
import { getApiKey, login, logout } from "."
import { LoginResponse, APIKeyResponse } from "./types"

// Mock the axios module so that no real network requests are made, but rather
// we swap in a resolved or rejected value
Expand Down
71 changes: 11 additions & 60 deletions site/src/api.ts → site/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,12 @@
import axios, { AxiosRequestHeaders } from "axios"
import { mutate } from "swr"
import * as Types from "./types"

const CONTENT_TYPE_JSON: AxiosRequestHeaders = {
"Content-Type": "application/json",
}

/**
* `Organization` must be kept in sync with the go struct in organizations.go
*/
export interface Organization {
id: string
name: string
created_at: string
updated_at: string
}

export interface Provisioner {
id: string
name: string
}

export const provisioners: Provisioner[] = [
export const provisioners: Types.Provisioner[] = [
{
id: "terraform",
name: "Terraform",
Expand All @@ -31,25 +17,8 @@ export const provisioners: Provisioner[] = [
},
]

// This must be kept in sync with the `Project` struct in the back-end
export interface Project {
id: string
created_at: string
updated_at: string
organization_id: string
name: string
provisioner: string
active_version_id: string
}

export interface CreateProjectRequest {
name: string
organizationId: string
provisioner: string
}

export namespace Project {
export const create = async (request: CreateProjectRequest): Promise<Project> => {
export const create = async (request: Types.CreateProjectRequest): Promise<Types.Project> => {
const response = await fetch(`/api/v2/projects/${request.organizationId}/`, {
method: "POST",
headers: {
Expand All @@ -68,23 +37,8 @@ export namespace Project {
}
}

export interface CreateWorkspaceRequest {
name: string
project_id: string
}

// Must be kept in sync with backend Workspace struct
export interface Workspace {
id: string
created_at: string
updated_at: string
owner_id: string
project_id: string
name: string
}

export namespace Workspace {
export const create = async (request: CreateWorkspaceRequest): Promise<Workspace> => {
export const create = async (request: Types.CreateWorkspaceRequest): Promise<Types.Workspace> => {
const response = await fetch(`/api/v2/users/me/workspaces`, {
method: "POST",
headers: {
Expand All @@ -108,17 +62,13 @@ export namespace Workspace {
}
}

export interface LoginResponse {
session_token: string
}

export const login = async (email: string, password: string): Promise<LoginResponse> => {
export const login = async (email: string, password: string): Promise<Types.LoginResponse> => {
const payload = JSON.stringify({
email,
password,
})

const response = await axios.post<LoginResponse>("/api/v2/users/login", payload, {
const response = await axios.post<Types.LoginResponse>("/api/v2/users/login", payload, {
headers: { ...CONTENT_TYPE_JSON },
})

Expand All @@ -129,11 +79,12 @@ export const logout = async (): Promise<void> => {
await axios.post("/api/v2/users/logout")
}

export interface APIKeyResponse {
key: string
export const getUser = async (): Promise<Types.UserResponse> => {
const response = await axios.get<Types.UserResponse>("/api/v2/users/me")
return response.data
}

export const getApiKey = async (): Promise<APIKeyResponse> => {
const response = await axios.post<APIKeyResponse>("/api/v2/users/me/keys")
export const getApiKey = async (): Promise<Types.APIKeyResponse> => {
const response = await axios.post<Types.APIKeyResponse>("/api/v2/users/me/keys")
return response.data
}
61 changes: 61 additions & 0 deletions site/src/api/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
export interface LoginResponse {
session_token: string
}

export interface UserResponse {
readonly id: string
readonly username: string
readonly email: string
readonly created_at: string
}

/**
* `Organization` must be kept in sync with the go struct in organizations.go
*/
export interface Organization {
id: string
name: string
created_at: string
updated_at: string
}

export interface Provisioner {
id: string
name: string
}

// This must be kept in sync with the `Project` struct in the back-end
export interface Project {
id: string
created_at: string
updated_at: string
organization_id: string
name: string
provisioner: string
active_version_id: string
}

export interface CreateProjectRequest {
name: string
organizationId: string
provisioner: string
}

export interface CreateWorkspaceRequest {
name: string
project_id: string
}

// Must be kept in sync with backend Workspace struct
export interface Workspace {
id: string
created_at: string
updated_at: string
owner_id: string
project_id: string
name: string
}

export interface APIKeyResponse {
key: string
}
52 changes: 44 additions & 8 deletions site/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React from "react"
import CssBaseline from "@material-ui/core/CssBaseline"
import ThemeProvider from "@material-ui/styles/ThemeProvider"
import { SWRConfig } from "swr"
import { UserProvider } from "./contexts/UserContext"
import { light } from "./theme"
import { BrowserRouter as Router, Route, Routes } from "react-router-dom"

Expand All @@ -15,6 +14,8 @@ import { ProjectPage } from "./pages/projects/[organization]/[project]"
import { CreateWorkspacePage } from "./pages/projects/[organization]/[project]/create"
import { WorkspacePage } from "./pages/workspaces/[workspace]"
import { HealthzPage } from "./pages/healthz"
import { AuthAndNav, RequireAuth } from "./components/Page"
import { XServiceProvider } from "./xServices/StateContext"

export const App: React.FC = () => {
return (
Expand All @@ -37,28 +38,63 @@ export const App: React.FC = () => {
},
}}
>
<UserProvider>
<XServiceProvider>
<ThemeProvider theme={light}>
<CssBaseline />

<Routes>
<Route path="/">
<Route index element={<IndexPage />} />
<Route
index
element={
<RequireAuth>
<IndexPage />
</RequireAuth>
}
/>

<Route path="login" element={<SignInPage />} />
<Route path="healthz" element={<HealthzPage />} />
<Route path="cli-auth" element={<CliAuthenticationPage />} />

<Route path="projects">
<Route index element={<ProjectsPage />} />
<Route
index
element={
<AuthAndNav>
<ProjectsPage />
</AuthAndNav>
}
/>
<Route path=":organization/:project">
<Route index element={<ProjectPage />} />
<Route path="create" element={<CreateWorkspacePage />} />
<Route
index
element={
<AuthAndNav>
<ProjectPage />
</AuthAndNav>
}
/>
<Route
path="create"
element={
<RequireAuth>
<CreateWorkspacePage />
</RequireAuth>
}
/>
</Route>
</Route>

<Route path="workspaces">
<Route path=":workspace" element={<WorkspacePage />} />
<Route
path=":workspace"
element={
<AuthAndNav>
<WorkspacePage />
</AuthAndNav>
}
/>
</Route>

{/* Using path="*"" means "match anything", so this route
Expand All @@ -68,7 +104,7 @@ export const App: React.FC = () => {
</Route>
</Routes>
</ThemeProvider>
</UserProvider>
</XServiceProvider>
</SWRConfig>
</Router>
)
Expand Down
Loading