From d6122094759628eb6c428ab1a6b9472c042db0d9 Mon Sep 17 00:00:00 2001 From: Presley Date: Mon, 21 Mar 2022 17:18:44 +0000 Subject: [PATCH 01/10] Extract and unit test redirect functions --- site/src/components/Page/RequireAuth.tsx | 4 +++- site/src/pages/login.tsx | 10 ++-------- site/src/util/redirect.test.ts | 16 ++++++++++++++++ site/src/util/redirect.ts | 22 ++++++++++++++++++++++ 4 files changed, 43 insertions(+), 9 deletions(-) create mode 100644 site/src/util/redirect.test.ts create mode 100644 site/src/util/redirect.ts diff --git a/site/src/components/Page/RequireAuth.tsx b/site/src/components/Page/RequireAuth.tsx index 099713cc1d286..16fc5c835f4f6 100644 --- a/site/src/components/Page/RequireAuth.tsx +++ b/site/src/components/Page/RequireAuth.tsx @@ -1,6 +1,7 @@ import { useActor } from "@xstate/react" import React, { useContext } from "react" import { Navigate, useLocation } from "react-router" +import { embedRedirect } from "../../util/redirect" import { XServiceContext } from "../../xServices/StateContext" import { FullScreenLoader } from "../Loader/FullScreenLoader" @@ -12,9 +13,10 @@ export const RequireAuth: React.FC = ({ children }) => { const xServices = useContext(XServiceContext) const [userState] = useActor(xServices.userXService) const location = useLocation() + const redirectTo = embedRedirect(location.pathname) if (userState.matches("signedOut") || !userState.context.me) { - return + return } else if (userState.hasTag("loading")) { return } else { diff --git a/site/src/pages/login.tsx b/site/src/pages/login.tsx index f6f2c08f67917..f47524796a6ba 100644 --- a/site/src/pages/login.tsx +++ b/site/src/pages/login.tsx @@ -3,8 +3,8 @@ import { useActor } from "@xstate/react" import React, { useContext } from "react" import { SignInForm } from "./../components/SignIn" import { Navigate, useLocation } from "react-router-dom" -import { Location } from "history" import { XServiceContext } from "../xServices/StateContext" +import { retrieveRedirect } from "../util/redirect" export const useStyles = makeStyles((theme) => ({ root: { @@ -20,13 +20,7 @@ export const useStyles = makeStyles((theme) => ({ }, })) -const getRedirectFromLocation = (location: Location) => { - const defaultRedirect = "/" - const searchParams = new URLSearchParams(location.search) - const redirect = searchParams.get("redirect") - return redirect ? redirect : defaultRedirect -} export const SignInPage: React.FC = () => { const styles = useStyles() @@ -34,7 +28,7 @@ export const SignInPage: React.FC = () => { const xServices = useContext(XServiceContext) const [userState, userSend] = useActor(xServices.userXService) const isLoading = userState.hasTag("loading") - const redirectTo = getRedirectFromLocation(location) + const redirectTo = retrieveRedirect(location.search) const authErrorMessage = userState.context.authError ? (userState.context.authError as Error).message : undefined const onSubmit = async ({ email, password }: { email: string; password: string }) => { diff --git a/site/src/util/redirect.test.ts b/site/src/util/redirect.test.ts new file mode 100644 index 0000000000000..5756674841975 --- /dev/null +++ b/site/src/util/redirect.test.ts @@ -0,0 +1,16 @@ +import { embedRedirect, retrieveRedirect } from "./redirect" + +describe('redirect helper functions', () => { + it("embeds the page to return to in the URL", () => { + const result = embedRedirect("/workspaces", "/page") + expect(result).toEqual("/page?redirect=%2Fworkspaces") + }) + it("defaults to navigating to the login page", () => { + const result = embedRedirect("/workspaces") + expect(result).toEqual("/login?redirect=%2Fworkspaces") + }) + it("retrieves the page to return to from the URL", () => { + const result = retrieveRedirect("?redirect=%2Fworkspaces") + expect(result).toEqual("/workspaces") + }) +}) \ No newline at end of file diff --git a/site/src/util/redirect.ts b/site/src/util/redirect.ts new file mode 100644 index 0000000000000..1fd8f26662620 --- /dev/null +++ b/site/src/util/redirect.ts @@ -0,0 +1,22 @@ +/** + * Creates a url containing a page to navigate to now, and embedding another + * URL in the query string so you can return to it later. + * @param navigateTo page to navigate to now (by default, /login) + * @param returnTo page to redirect to later (for instance, after logging in) + * @returns URL containing a redirect query parameter + */ +export const embedRedirect = (returnTo: string, navigateTo: string = "/login") => ( + `${navigateTo}?redirect=${encodeURIComponent(returnTo)}` +) + +/** + * Retrieves a url from the query string of the current URL + * @param search the query string in the current URL + * @returns the URL to redirect to + */ +export const retrieveRedirect = (search: string) => { + const defaultRedirect = "/" + const searchParams = new URLSearchParams(search) + const redirect = searchParams.get("redirect") + return redirect ? redirect : defaultRedirect +} \ No newline at end of file From 194c787a7924589fb2324050452a569bc1011fb7 Mon Sep 17 00:00:00 2001 From: Presley Date: Mon, 21 Mar 2022 18:31:46 +0000 Subject: [PATCH 02/10] Move router to make app more testable --- site/src/Main.tsx | 3 ++- site/src/app.tsx | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/site/src/Main.tsx b/site/src/Main.tsx index 157f3c56f7670..c6f1c41c8f23d 100644 --- a/site/src/Main.tsx +++ b/site/src/Main.tsx @@ -1,5 +1,6 @@ import React from "react" import ReactDOM from "react-dom" +import { BrowserRouter as Router } from "react-router-dom" import { App } from "./app" @@ -8,7 +9,7 @@ import { App } from "./app" // like: https://github.com/coder/m/blob/50898bd4803df7639bd181e484c74ac5d84da474/product/coder/site/pages/_app.tsx#L32 const main = () => { const element = document.getElementById("root") - ReactDOM.render(, element) + ReactDOM.render(, element) } main() diff --git a/site/src/app.tsx b/site/src/app.tsx index e375f60d5810f..13180086654af 100644 --- a/site/src/app.tsx +++ b/site/src/app.tsx @@ -3,7 +3,7 @@ import CssBaseline from "@material-ui/core/CssBaseline" import ThemeProvider from "@material-ui/styles/ThemeProvider" import { SWRConfig } from "swr" import { light } from "./theme" -import { BrowserRouter as Router, Route, Routes } from "react-router-dom" +import { Route, Routes } from "react-router-dom" import { CliAuthenticationPage } from "./pages/cli-auth" import { NotFoundPage } from "./pages/404" @@ -19,7 +19,6 @@ import { XServiceProvider } from "./xServices/StateContext" export const App: React.FC = () => { return ( - { - ) } From 5198d396252e5ed879324fb2fe221afc5afde2c1 Mon Sep 17 00:00:00 2001 From: Presley Date: Mon, 21 Mar 2022 18:31:56 +0000 Subject: [PATCH 03/10] Make mock entities more consistent --- site/src/test_helpers/entities.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/site/src/test_helpers/entities.ts b/site/src/test_helpers/entities.ts index 7633161b4a23a..89c2af2f20c6f 100644 --- a/site/src/test_helpers/entities.ts +++ b/site/src/test_helpers/entities.ts @@ -5,14 +5,14 @@ export const MockSessionToken = { session_token: "my-session-token" } export const MockAPIKey = { key: "my-api-key" } export const MockUser: UserResponse = { - id: "test-user-id", + id: "test-user", username: "TestUser", email: "test@coder.com", created_at: "", } export const MockProject: Project = { - id: "project-id", + id: "test-project", created_at: "", updated_at: "", organization_id: "test-org", @@ -38,6 +38,6 @@ export const MockWorkspace: Workspace = { name: "Test-Workspace", created_at: "", updated_at: "", - project_id: "project-id", - owner_id: "test-user-id", + project_id: "test-project", + owner_id: "test-user", } From e79e3ded2f6820352ccb96a78a39a3d71a4b6a6e Mon Sep 17 00:00:00 2001 From: Presley Date: Mon, 21 Mar 2022 21:27:21 +0000 Subject: [PATCH 04/10] Use labels instead of placeholders --- site/src/components/SignIn/SignInForm.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/src/components/SignIn/SignInForm.tsx b/site/src/components/SignIn/SignInForm.tsx index 3c52f3d936fd8..880ddb093a202 100644 --- a/site/src/components/SignIn/SignInForm.tsx +++ b/site/src/components/SignIn/SignInForm.tsx @@ -61,6 +61,7 @@ export const SignInForm: React.FC = ({ isLoading, authErrorMess
= ({ isLoading, authErrorMess inputProps={{ id: "signin-form-inpt-email", }} - placeholder="Email" variant="outlined" /> = ({ isLoading, authErrorMess id: "signin-form-inpt-password", }} isPassword - placeholder="Password" variant="outlined" /> {authErrorMessage && ( From 2c74904dbeb1f386e92bd0efa9339087eaabaf67 Mon Sep 17 00:00:00 2001 From: Presley Date: Mon, 21 Mar 2022 21:27:31 +0000 Subject: [PATCH 05/10] Fill out handlers --- site/src/test_helpers/handlers.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/site/src/test_helpers/handlers.ts b/site/src/test_helpers/handlers.ts index c7ae6df9481d9..5c25831b5d3a2 100644 --- a/site/src/test_helpers/handlers.ts +++ b/site/src/test_helpers/handlers.ts @@ -5,6 +5,24 @@ export const handlers = [ rest.post("/api/v2/users/me/workspaces", async (req, res, ctx) => { return res(ctx.status(200), ctx.json(M.MockWorkspace)) }), + rest.get("/api/v2/projects/:organizationId", async (req, res, ctx) => { + return res(ctx.status(200), ctx.json(M.MockProject)) + }), + rest.get("/api/v2/users/me/organizations/:organizationId", async (req, res, ctx) => { + return res(ctx.status(200), ctx.json(M.MockProject)) + }), + rest.get("/api/v2/workspaces/:workspaceId", async (req, res, ctx) => { + return res(ctx.status(200), ctx.json(M.MockWorkspace)) + }), + rest.get("/api/v2/projects/:projectId", async (req, res, ctx) => { + return res(ctx.status(200), ctx.json(M.MockProject)) + }), + rest.get("/api/v2/organizations/:organizationId", async (req, res, ctx) => { + return res(ctx.status(200), ctx.json(M.MockOrganization)) + }), + rest.get("/api/v2/organizations/:organizationId/projects/:projectId", async (req, res, ctx) => { + return res(ctx.status(200), ctx.json(M.MockOrganization)) + }), rest.post("/api/v2/users/login", async (req, res, ctx) => { return res(ctx.status(200), ctx.json(M.MockSessionToken)) }), From 3e6b156f017a0b52d69642978f4f109fcc7e6503 Mon Sep 17 00:00:00 2001 From: Presley Date: Mon, 21 Mar 2022 23:36:34 +0000 Subject: [PATCH 06/10] Lint --- site/src/Main.tsx | 7 +- site/src/app.tsx | 128 ++++++++++++++++----------------- site/src/pages/login.tsx | 2 - site/src/util/redirect.test.ts | 4 +- site/src/util/redirect.ts | 7 +- 5 files changed, 75 insertions(+), 73 deletions(-) diff --git a/site/src/Main.tsx b/site/src/Main.tsx index c6f1c41c8f23d..d522a9c394951 100644 --- a/site/src/Main.tsx +++ b/site/src/Main.tsx @@ -9,7 +9,12 @@ import { App } from "./app" // like: https://github.com/coder/m/blob/50898bd4803df7639bd181e484c74ac5d84da474/product/coder/site/pages/_app.tsx#L32 const main = () => { const element = document.getElementById("root") - ReactDOM.render(, element) + ReactDOM.render( + + + , + element, + ) } main() diff --git a/site/src/app.tsx b/site/src/app.tsx index 13180086654af..17400c8dde4e4 100644 --- a/site/src/app.tsx +++ b/site/src/app.tsx @@ -19,91 +19,91 @@ import { XServiceProvider } from "./xServices/StateContext" export const App: React.FC = () => { return ( - { - const res = await fetch(url) + { + const res = await fetch(url) - // By default, `fetch` won't treat 4xx or 5xx response as errors. - // However, we want SWR to treat these as errors - so if `res.ok` is false, - // we want to throw an error to bubble that up to SWR. - if (!res.ok) { - const err = new Error((await res.json()).error?.message || res.statusText) - throw err - } - return res.json() - }, - }} - > - - - + // By default, `fetch` won't treat 4xx or 5xx response as errors. + // However, we want SWR to treat these as errors - so if `res.ok` is false, + // we want to throw an error to bubble that up to SWR. + if (!res.ok) { + const err = new Error((await res.json()).error?.message || res.statusText) + throw err + } + return res.json() + }, + }} + > + + + - - + + + + + + } + /> + + } /> + } /> + } /> + + - - + + + } /> - - } /> - } /> - } /> - - + - + } /> - - - - - } - /> - - - - } - /> - - - - - - + + + } /> + + + + + + + } + /> + - {/* Using path="*"" means "match anything", so this route + {/* Using path="*"" means "match anything", so this route acts like a catch-all for URLs that we don't have explicit routes for. */} - } /> - - - - - + } /> + + + + + ) } diff --git a/site/src/pages/login.tsx b/site/src/pages/login.tsx index f47524796a6ba..f072bb51d8789 100644 --- a/site/src/pages/login.tsx +++ b/site/src/pages/login.tsx @@ -20,8 +20,6 @@ export const useStyles = makeStyles((theme) => ({ }, })) - - export const SignInPage: React.FC = () => { const styles = useStyles() const location = useLocation() diff --git a/site/src/util/redirect.test.ts b/site/src/util/redirect.test.ts index 5756674841975..a0cab46f46e75 100644 --- a/site/src/util/redirect.test.ts +++ b/site/src/util/redirect.test.ts @@ -1,6 +1,6 @@ import { embedRedirect, retrieveRedirect } from "./redirect" -describe('redirect helper functions', () => { +describe("redirect helper functions", () => { it("embeds the page to return to in the URL", () => { const result = embedRedirect("/workspaces", "/page") expect(result).toEqual("/page?redirect=%2Fworkspaces") @@ -13,4 +13,4 @@ describe('redirect helper functions', () => { const result = retrieveRedirect("?redirect=%2Fworkspaces") expect(result).toEqual("/workspaces") }) -}) \ No newline at end of file +}) diff --git a/site/src/util/redirect.ts b/site/src/util/redirect.ts index 1fd8f26662620..fa0616684396e 100644 --- a/site/src/util/redirect.ts +++ b/site/src/util/redirect.ts @@ -5,18 +5,17 @@ * @param returnTo page to redirect to later (for instance, after logging in) * @returns URL containing a redirect query parameter */ -export const embedRedirect = (returnTo: string, navigateTo: string = "/login") => ( +export const embedRedirect = (returnTo: string, navigateTo = "/login"): string => `${navigateTo}?redirect=${encodeURIComponent(returnTo)}` -) /** * Retrieves a url from the query string of the current URL * @param search the query string in the current URL * @returns the URL to redirect to */ -export const retrieveRedirect = (search: string) => { +export const retrieveRedirect = (search: string): string => { const defaultRedirect = "/" const searchParams = new URLSearchParams(search) const redirect = searchParams.get("redirect") return redirect ? redirect : defaultRedirect -} \ No newline at end of file +} From fd0407f5c76c122fed19c7ec921c0daf684f6462 Mon Sep 17 00:00:00 2001 From: Presley Date: Tue, 22 Mar 2022 18:22:39 +0000 Subject: [PATCH 07/10] Reorganize App --- site/src/AppRouter.tsx | 76 +++++++++++++++++++++++++ site/src/Main.tsx | 8 +-- site/src/app.tsx | 126 ++++++++++------------------------------- 3 files changed, 106 insertions(+), 104 deletions(-) create mode 100644 site/src/AppRouter.tsx diff --git a/site/src/AppRouter.tsx b/site/src/AppRouter.tsx new file mode 100644 index 0000000000000..f67d887e2890e --- /dev/null +++ b/site/src/AppRouter.tsx @@ -0,0 +1,76 @@ +import React from "react" +import { Routes, Route } from "react-router-dom" +import { RequireAuth, AuthAndNav } from "./components" +import { IndexPage } from "./pages" +import { NotFoundPage } from "./pages/404" +import { CliAuthenticationPage } from "./pages/cli-auth" +import { HealthzPage } from "./pages/healthz" +import { SignInPage } from "./pages/login" +import { ProjectsPage } from "./pages/projects" +import { ProjectPage } from "./pages/projects/[organization]/[project]" +import { CreateWorkspacePage } from "./pages/projects/[organization]/[project]/create" +import { WorkspacePage } from "./pages/workspaces/[workspace]" + +export const AppRouter: React.FC = () => ( + + + + + + } + /> + + } /> + } /> + } /> + + + + + + } + /> + + + + + } + /> + + + + } + /> + + + + + + + + } + /> + + + {/* Using path="*"" means "match anything", so this route + acts like a catch-all for URLs that we don't have explicit + routes for. */} + } /> + + +) diff --git a/site/src/Main.tsx b/site/src/Main.tsx index d522a9c394951..157f3c56f7670 100644 --- a/site/src/Main.tsx +++ b/site/src/Main.tsx @@ -1,6 +1,5 @@ import React from "react" import ReactDOM from "react-dom" -import { BrowserRouter as Router } from "react-router-dom" import { App } from "./app" @@ -9,12 +8,7 @@ import { App } from "./app" // like: https://github.com/coder/m/blob/50898bd4803df7639bd181e484c74ac5d84da474/product/coder/site/pages/_app.tsx#L32 const main = () => { const element = document.getElementById("root") - ReactDOM.render( - - - , - element, - ) + ReactDOM.render(, element) } main() diff --git a/site/src/app.tsx b/site/src/app.tsx index 17400c8dde4e4..e8a48e66676cb 100644 --- a/site/src/app.tsx +++ b/site/src/app.tsx @@ -3,107 +3,39 @@ import CssBaseline from "@material-ui/core/CssBaseline" import ThemeProvider from "@material-ui/styles/ThemeProvider" import { SWRConfig } from "swr" import { light } from "./theme" -import { Route, Routes } from "react-router-dom" +import { BrowserRouter as Router } from "react-router-dom" -import { CliAuthenticationPage } from "./pages/cli-auth" -import { NotFoundPage } from "./pages/404" -import { IndexPage } from "./pages/index" -import { SignInPage } from "./pages/login" -import { ProjectsPage } from "./pages/projects" -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" +import { AppRouter } from "./AppRouter" export const App: React.FC = () => { return ( - { - const res = await fetch(url) - - // By default, `fetch` won't treat 4xx or 5xx response as errors. - // However, we want SWR to treat these as errors - so if `res.ok` is false, - // we want to throw an error to bubble that up to SWR. - if (!res.ok) { - const err = new Error((await res.json()).error?.message || res.statusText) - throw err - } - return res.json() - }, - }} - > - - - - - - - - - - } - /> - - } /> - } /> - } /> - - - - - - } - /> - - - - - } - /> - - - - } - /> - - - - - - - - } - /> - - - {/* Using path="*"" means "match anything", so this route - acts like a catch-all for URLs that we don't have explicit - routes for. */} - } /> - - - - - + + { + const res = await fetch(url) + + // By default, `fetch` won't treat 4xx or 5xx response as errors. + // However, we want SWR to treat these as errors - so if `res.ok` is false, + // we want to throw an error to bubble that up to SWR. + if (!res.ok) { + const err = new Error((await res.json()).error?.message || res.statusText) + throw err + } + return res.json() + }, + }} + > + + + + + + + + ) } From 3633dfccb652eec6f46d0e689e8fe2782bbdc955 Mon Sep 17 00:00:00 2001 From: Presley Date: Tue, 22 Mar 2022 18:25:01 +0000 Subject: [PATCH 08/10] Make mock entities reference each other --- site/src/test_helpers/entities.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/site/src/test_helpers/entities.ts b/site/src/test_helpers/entities.ts index 89c2af2f20c6f..f0d4f0b60d1fa 100644 --- a/site/src/test_helpers/entities.ts +++ b/site/src/test_helpers/entities.ts @@ -11,14 +11,11 @@ export const MockUser: UserResponse = { created_at: "", } -export const MockProject: Project = { - id: "test-project", +export const MockOrganization: Organization = { + id: "test-org", + name: "Test Organization", created_at: "", updated_at: "", - organization_id: "test-org", - name: "Test Project", - provisioner: "test-provisioner", - active_version_id: "", } export const MockProvisioner: Provisioner = { @@ -26,11 +23,14 @@ export const MockProvisioner: Provisioner = { name: "Test Provisioner", } -export const MockOrganization: Organization = { - id: "test-org", - name: "Test Organization", +export const MockProject: Project = { + id: "test-project", created_at: "", updated_at: "", + organization_id: MockOrganization.id, + name: "Test Project", + provisioner: MockProvisioner.id, + active_version_id: "", } export const MockWorkspace: Workspace = { @@ -38,6 +38,6 @@ export const MockWorkspace: Workspace = { name: "Test-Workspace", created_at: "", updated_at: "", - project_id: "test-project", - owner_id: "test-user", + project_id: MockProject.id, + owner_id: MockUser.id } From 507b54aa2d07f2f2fb02cf41dacd267c924cd8b2 Mon Sep 17 00:00:00 2001 From: Presley Date: Tue, 22 Mar 2022 21:51:49 +0000 Subject: [PATCH 09/10] Add describes in tests --- site/src/util/redirect.test.ts | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/site/src/util/redirect.test.ts b/site/src/util/redirect.test.ts index a0cab46f46e75..c4be13828680f 100644 --- a/site/src/util/redirect.test.ts +++ b/site/src/util/redirect.test.ts @@ -1,16 +1,20 @@ import { embedRedirect, retrieveRedirect } from "./redirect" describe("redirect helper functions", () => { - it("embeds the page to return to in the URL", () => { - const result = embedRedirect("/workspaces", "/page") - expect(result).toEqual("/page?redirect=%2Fworkspaces") + describe("embedRedirect", () => { + it("embeds the page to return to in the URL", () => { + const result = embedRedirect("/workspaces", "/page") + expect(result).toEqual("/page?redirect=%2Fworkspaces") + }) + it("defaults to navigating to the login page", () => { + const result = embedRedirect("/workspaces") + expect(result).toEqual("/login?redirect=%2Fworkspaces") + }) }) - it("defaults to navigating to the login page", () => { - const result = embedRedirect("/workspaces") - expect(result).toEqual("/login?redirect=%2Fworkspaces") - }) - it("retrieves the page to return to from the URL", () => { - const result = retrieveRedirect("?redirect=%2Fworkspaces") - expect(result).toEqual("/workspaces") + describe("retrieveRedirect", () => { + it("retrieves the page to return to from the URL", () => { + const result = retrieveRedirect("?redirect=%2Fworkspaces") + expect(result).toEqual("/workspaces") + }) }) }) From 0e2315c851d80494d688fb7e717fd1bf6b44f35b Mon Sep 17 00:00:00 2001 From: Presley Date: Tue, 22 Mar 2022 21:52:09 +0000 Subject: [PATCH 10/10] Clean up api and mocks --- site/src/api/index.ts | 20 -------------------- site/src/test_helpers/entities.ts | 2 +- site/src/test_helpers/handlers.ts | 28 ++++++++++++++++------------ 3 files changed, 17 insertions(+), 33 deletions(-) diff --git a/site/src/api/index.ts b/site/src/api/index.ts index 30b63da9cec81..0d4ff993fb0ad 100644 --- a/site/src/api/index.ts +++ b/site/src/api/index.ts @@ -17,26 +17,6 @@ export const provisioners: Types.Provisioner[] = [ }, ] -export namespace Project { - export const create = async (request: Types.CreateProjectRequest): Promise => { - const response = await fetch(`/api/v2/projects/${request.organizationId}/`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(request), - }) - - const body = await response.json() - await mutate("/api/v2/projects") - if (!response.ok) { - throw new Error(body.message) - } - - return body - } -} - export namespace Workspace { export const create = async (request: Types.CreateWorkspaceRequest): Promise => { const response = await fetch(`/api/v2/users/me/workspaces`, { diff --git a/site/src/test_helpers/entities.ts b/site/src/test_helpers/entities.ts index f0d4f0b60d1fa..fdd0b756dabc4 100644 --- a/site/src/test_helpers/entities.ts +++ b/site/src/test_helpers/entities.ts @@ -39,5 +39,5 @@ export const MockWorkspace: Workspace = { created_at: "", updated_at: "", project_id: MockProject.id, - owner_id: MockUser.id + owner_id: MockUser.id, } diff --git a/site/src/test_helpers/handlers.ts b/site/src/test_helpers/handlers.ts index 5c25831b5d3a2..5e558a4818e34 100644 --- a/site/src/test_helpers/handlers.ts +++ b/site/src/test_helpers/handlers.ts @@ -2,25 +2,24 @@ import { rest } from "msw" import * as M from "./entities" export const handlers = [ - rest.post("/api/v2/users/me/workspaces", async (req, res, ctx) => { - return res(ctx.status(200), ctx.json(M.MockWorkspace)) - }), - rest.get("/api/v2/projects/:organizationId", async (req, res, ctx) => { - return res(ctx.status(200), ctx.json(M.MockProject)) + // organizations + rest.get("/api/v2/organizations/:organizationId", async (req, res, ctx) => { + return res(ctx.status(200), ctx.json(M.MockOrganization)) }), - rest.get("/api/v2/users/me/organizations/:organizationId", async (req, res, ctx) => { + rest.get("/api/v2/organizations/:organizationId/projects/:projectId", async (req, res, ctx) => { return res(ctx.status(200), ctx.json(M.MockProject)) }), - rest.get("/api/v2/workspaces/:workspaceId", async (req, res, ctx) => { - return res(ctx.status(200), ctx.json(M.MockWorkspace)) - }), + + // projects rest.get("/api/v2/projects/:projectId", async (req, res, ctx) => { return res(ctx.status(200), ctx.json(M.MockProject)) }), - rest.get("/api/v2/organizations/:organizationId", async (req, res, ctx) => { - return res(ctx.status(200), ctx.json(M.MockOrganization)) + + // users + rest.post("/api/v2/users/me/workspaces", async (req, res, ctx) => { + return res(ctx.status(200), ctx.json(M.MockWorkspace)) }), - rest.get("/api/v2/organizations/:organizationId/projects/:projectId", async (req, res, ctx) => { + rest.get("/api/v2/users/me/organizations/:organizationId", async (req, res, ctx) => { return res(ctx.status(200), ctx.json(M.MockOrganization)) }), rest.post("/api/v2/users/login", async (req, res, ctx) => { @@ -35,4 +34,9 @@ export const handlers = [ rest.get("/api/v2/users/me/keys", async (req, res, ctx) => { return res(ctx.status(200), ctx.json(M.MockAPIKey)) }), + + // workspaces + rest.get("/api/v2/workspaces/:workspaceId", async (req, res, ctx) => { + return res(ctx.status(200), ctx.json(M.MockWorkspace)) + }), ]