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

Commita2be7c0

Browse files
authored
fix: create and read workspace page (#1294)
* Change name of existing workspace call* Add new api call (has handler already)* WorkspacesPage -> WorkspacePage* starting to replace swr* Add other api calls* Fix api call* Replace swr with xstate* Format* Test - wip* Fix route in template page* Fix endpoint in create workspace* Fix tests* Lint
1 parent3dbcddc commita2be7c0

File tree

14 files changed

+261
-66
lines changed

14 files changed

+261
-66
lines changed

‎site/src/AppRouter.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { TemplatePage } from "./pages/TemplatesPages/OrganizationPage/TemplatePa
1919
import{TemplatesPage}from"./pages/TemplatesPages/TemplatesPage"
2020
import{CreateUserPage}from"./pages/UsersPage/CreateUserPage/CreateUserPage"
2121
import{UsersPage}from"./pages/UsersPage/UsersPage"
22-
import{WorkspacePage}from"./pages/WorkspacesPage/WorkspacesPage"
22+
import{WorkspacePage}from"./pages/WorkspacePage/WorkspacePage"
2323

2424
constTerminalPage=React.lazy(()=>import("./pages/TerminalPage/TerminalPage"))
2525

‎site/src/api/index.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const provisioners: Types.Provisioner[] = [
2020

2121
exportnamespaceWorkspace{
2222
exportconstcreate=async(request:Types.CreateWorkspaceRequest):Promise<Types.Workspace>=>{
23-
constresponse=awaitfetch(`/api/v2/users/me/workspaces`,{
23+
constresponse=awaitfetch(`/api/v2/organizations/${request.organization_id}/workspaces`,{
2424
method:"POST",
2525
headers:{
2626
"Content-Type":"application/json",
@@ -80,12 +80,27 @@ export const getUsers = async (): Promise<TypesGen.User[]> => {
8080
returnresponse.data
8181
}
8282

83+
exportconstgetOrganization=async(organizationId:string):Promise<Types.Organization>=>{
84+
constresponse=awaitaxios.get<Types.Organization>(`/api/v2/organizations/${organizationId}`)
85+
returnresponse.data
86+
}
87+
8388
exportconstgetOrganizations=async():Promise<Types.Organization[]>=>{
8489
constresponse=awaitaxios.get<Types.Organization[]>("/api/v2/users/me/organizations")
8590
returnresponse.data
8691
}
8792

88-
exportconstgetWorkspace=async(
93+
exportconstgetTemplate=async(templateId:string):Promise<Types.Template>=>{
94+
constresponse=awaitaxios.get<Types.Template>(`/api/v2/templates/${templateId}`)
95+
returnresponse.data
96+
}
97+
98+
exportconstgetWorkspace=async(workspaceId:string):Promise<Types.Workspace>=>{
99+
constresponse=awaitaxios.get<Types.Workspace>(`/api/v2/workspaces/${workspaceId}`)
100+
returnresponse.data
101+
}
102+
103+
exportconstgetWorkspaceByOwnerAndName=async(
89104
organizationID:string,
90105
username="me",
91106
workspaceName:string,

‎site/src/api/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export interface CreateTemplateRequest {
6161
exportinterfaceCreateWorkspaceRequest{
6262
name:string
6363
template_id:string
64+
organization_id:string
6465
}
6566

6667
exportinterfaceWorkspaceBuild{

‎site/src/forms/CreateWorkspaceForm.test.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import{render,screen}from"@testing-library/react"
22
importReactfrom"react"
3-
import{MockTemplate,MockWorkspace}from"../testHelpers"
3+
import{MockOrganization,MockTemplate,MockWorkspace}from"../testHelpers"
44
import{CreateWorkspaceForm}from"./CreateWorkspaceForm"
55

66
describe("CreateWorkspaceForm",()=>{
@@ -10,7 +10,14 @@ describe("CreateWorkspaceForm", () => {
1010
constonCancel=()=>Promise.resolve()
1111

1212
// When
13-
render(<CreateWorkspaceFormtemplate={MockTemplate}onSubmit={onSubmit}onCancel={onCancel}/>)
13+
render(
14+
<CreateWorkspaceForm
15+
template={MockTemplate}
16+
onSubmit={onSubmit}
17+
onCancel={onCancel}
18+
organization_id={MockOrganization.id}
19+
/>,
20+
)
1421

1522
// Then
1623
// Simple smoke test to verify form renders

‎site/src/forms/CreateWorkspaceForm.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,19 @@ export interface CreateWorkspaceForm {
1515
template:Template
1616
onSubmit:(request:CreateWorkspaceRequest)=>Promise<Workspace>
1717
onCancel:()=>void
18+
organization_id:string
1819
}
1920

2021
constvalidationSchema=Yup.object({
2122
name:Yup.string().required("Name is required"),
2223
})
2324

24-
exportconstCreateWorkspaceForm:React.FC<CreateWorkspaceForm>=({ template, onSubmit, onCancel})=>{
25+
exportconstCreateWorkspaceForm:React.FC<CreateWorkspaceForm>=({
26+
template,
27+
onSubmit,
28+
onCancel,
29+
organization_id,
30+
})=>{
2531
conststyles=useStyles()
2632

2733
constform:FormikContextType<{name:string}>=useFormik<{name:string}>({
@@ -34,6 +40,7 @@ export const CreateWorkspaceForm: React.FC<CreateWorkspaceForm> = ({ template, o
3440
returnonSubmit({
3541
template_id:template.id,
3642
name:name,
43+
organization_id,
3744
})
3845
},
3946
})

‎site/src/pages/TemplatesPages/OrganizationPage/TemplatePage/CreateWorkspacePage.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import{makeStyles}from"@material-ui/core/styles"
2-
importReact,{useCallback}from"react"
2+
import{useSelector}from"@xstate/react"
3+
importReact,{useCallback,useContext}from"react"
34
import{useNavigate,useParams}from"react-router-dom"
45
importuseSWRfrom"swr"
56
import*asAPIfrom"../../../../api"
@@ -8,12 +9,17 @@ import { ErrorSummary } from "../../../../components/ErrorSummary/ErrorSummary"
89
import{FullScreenLoader}from"../../../../components/Loader/FullScreenLoader"
910
import{CreateWorkspaceForm}from"../../../../forms/CreateWorkspaceForm"
1011
import{unsafeSWRArgument}from"../../../../util"
12+
import{selectOrgId}from"../../../../xServices/auth/authSelectors"
13+
import{XServiceContext}from"../../../../xServices/StateContext"
1114

1215
exportconstCreateWorkspacePage:React.FC=()=>{
1316
const{organization:organizationName,template:templateName}=useParams()
1417
constnavigate=useNavigate()
1518
conststyles=useStyles()
1619

20+
constxServices=useContext(XServiceContext)
21+
constmyOrgId=useSelector(xServices.authXService,selectOrgId)
22+
1723
const{data:organizationInfo,error:organizationError}=useSWR<Types.Organization,Error>(
1824
()=>`/api/v2/users/me/organizations/${organizationName}`,
1925
)
@@ -44,9 +50,13 @@ export const CreateWorkspacePage: React.FC = () => {
4450
return<FullScreenLoader/>
4551
}
4652

53+
if(!myOrgId){
54+
return<ErrorSummaryerror={Error("no organization id")}/>
55+
}
56+
4757
return(
4858
<divclassName={styles.root}>
49-
<CreateWorkspaceFormonCancel={onCancel}onSubmit={onSubmit}template={template}/>
59+
<CreateWorkspaceFormonCancel={onCancel}onSubmit={onSubmit}template={template}organization_id={myOrgId}/>
5060
</div>
5161
)
5262
}

‎site/src/pages/TemplatesPages/OrganizationPage/TemplatePage/TemplatePage.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ export const TemplatePage: React.FC = () => {
2626

2727
// This just grabs all workspaces... and then later filters them to match the
2828
// current template.
29-
const{data:workspaces,error:workspacesError}=useSWR<Workspace[],Error>(()=>`/api/v2/users/me/workspaces`)
29+
30+
const{data:workspaces,error:workspacesError}=useSWR<Workspace[],Error>(
31+
()=>`/api/v2/organizations/${unsafeSWRArgument(organizationInfo).id}/workspaces`,
32+
)
3033

3134
if(organizationError){
3235
return<ErrorSummaryerror={organizationError}/>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import{screen}from"@testing-library/react"
2+
importReactfrom"react"
3+
import{MockTemplate,MockWorkspace,renderWithAuth}from"../../testHelpers"
4+
import{WorkspacePage}from"./WorkspacePage"
5+
6+
describe("Workspace Page",()=>{
7+
it("shows a workspace",async()=>{
8+
renderWithAuth(<WorkspacePage/>,{route:`/workspaces/${MockWorkspace.id}`,path:"/workspaces/:workspace"})
9+
constworkspaceName=awaitscreen.findByText(MockWorkspace.name)
10+
consttemplateName=awaitscreen.findByText(MockTemplate.name)
11+
expect(workspaceName).toBeDefined()
12+
expect(templateName).toBeDefined()
13+
})
14+
})
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import{useActor}from"@xstate/react"
2+
importReact,{useContext,useEffect}from"react"
3+
import{useParams}from"react-router-dom"
4+
import{ErrorSummary}from"../../components/ErrorSummary/ErrorSummary"
5+
import{FullScreenLoader}from"../../components/Loader/FullScreenLoader"
6+
import{Margins}from"../../components/Margins/Margins"
7+
import{Stack}from"../../components/Stack/Stack"
8+
import{Workspace}from"../../components/Workspace/Workspace"
9+
import{firstOrItem}from"../../util/array"
10+
import{XServiceContext}from"../../xServices/StateContext"
11+
12+
exportconstWorkspacePage:React.FC=()=>{
13+
const{workspace:workspaceQueryParam}=useParams()
14+
constworkspaceId=firstOrItem(workspaceQueryParam,null)
15+
16+
constxServices=useContext(XServiceContext)
17+
const[workspaceState,workspaceSend]=useActor(xServices.workspaceXService)
18+
const{ workspace, template, organization, getWorkspaceError, getTemplateError, getOrganizationError}=
19+
workspaceState.context
20+
21+
/**
22+
* Get workspace, template, and organization on mount and whenever workspaceId changes.
23+
* workspaceSend should not change.
24+
*/
25+
useEffect(()=>{
26+
workspaceId&&workspaceSend({type:"GET_WORKSPACE", workspaceId})
27+
},[workspaceId,workspaceSend])
28+
29+
if(workspaceState.matches("error")){
30+
return<ErrorSummaryerror={getWorkspaceError||getTemplateError||getOrganizationError}/>
31+
}elseif(!workspace||!template||!organization){
32+
return<FullScreenLoader/>
33+
}else{
34+
return(
35+
<Margins>
36+
<Stackspacing={4}>
37+
<Workspaceorganization={organization}template={template}workspace={workspace}/>
38+
</Stack>
39+
</Margins>
40+
)
41+
}
42+
}

‎site/src/pages/WorkspacesPage/WorkspacesPage.tsx

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

‎site/src/testHelpers/index.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,22 @@ export const render = (component: React.ReactElement): RenderResult => {
2626

2727
typeRenderWithAuthResult=RenderResult&{user:typeofMockUser}
2828

29-
exportfunctionrenderWithAuth(ui:JSX.Element,{ route="/"}:{route?:string}={}):RenderWithAuthResult{
29+
/**
30+
*
31+
*@param ui The component to render and test
32+
*@param options Can contain `route`, the URL to use, such as /users/user1, and `path`,
33+
* such as /users/:userid. When there are no parameters, they are the same and you can just supply `route`.
34+
*/
35+
exportfunctionrenderWithAuth(
36+
ui:JSX.Element,
37+
{ route="/", path}:{route?:string;path?:string}={},
38+
):RenderWithAuthResult{
3039
constrenderResult=wrappedRender(
3140
<MemoryRouterinitialEntries={[route]}>
3241
<XServiceProvider>
3342
<ThemeProvidertheme={dark}>
3443
<Routes>
35-
<Routepath={route}element={<RequireAuth>{ui}</RequireAuth>}/>
44+
<Routepath={path??route}element={<RequireAuth>{ui}</RequireAuth>}/>
3645
</Routes>
3746
</ThemeProvider>
3847
</XServiceProvider>

‎site/src/xServices/StateContext.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ import { ActorRefFrom } from "xstate"
55
import{authMachine}from"./auth/authXService"
66
import{buildInfoMachine}from"./buildInfo/buildInfoXService"
77
import{usersMachine}from"./users/usersXService"
8+
import{workspaceMachine}from"./workspace/workspaceXService"
89

910
interfaceXServiceContextType{
1011
authXService:ActorRefFrom<typeofauthMachine>
1112
buildInfoXService:ActorRefFrom<typeofbuildInfoMachine>
1213
usersXService:ActorRefFrom<typeofusersMachine>
14+
workspaceXService:ActorRefFrom<typeofworkspaceMachine>
1315
}
1416

1517
/**
@@ -34,6 +36,7 @@ export const XServiceProvider: React.FC = ({ children }) => {
3436
authXService:useInterpret(authMachine),
3537
buildInfoXService:useInterpret(buildInfoMachine),
3638
usersXService:useInterpret(()=>usersMachine.withConfig({actions:{ redirectToUsersPage}})),
39+
workspaceXService:useInterpret(workspaceMachine),
3740
}}
3841
>
3942
{children}

‎site/src/xServices/terminal/terminalXService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ export const terminalMachine =
154154
if(!context.organizations||!context.workspaceName){
155155
thrownewError("organizations or workspace not set")
156156
}
157-
returnAPI.getWorkspace(context.organizations[0].id,context.username,context.workspaceName)
157+
returnAPI.getWorkspaceByOwnerAndName(context.organizations[0].id,context.username,context.workspaceName)
158158
},
159159
getWorkspaceAgent:async(context)=>{
160160
if(!context.workspace||!context.workspaceName){

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp