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

Commit776f287

Browse files
authored
feat: allow admins to create workspaces for other users in UI (#4247)
* added permission for creating a workspace on behalf of a user* committing stashed files* hooked up autocomplete for users* added label* added translations* wrote test* added inputMargin prop* fixed permissions* added inputSTyle prop* ran prettier* fix lint
1 parent70d7dd9 commit776f287

File tree

9 files changed

+169
-74
lines changed

9 files changed

+169
-74
lines changed

‎site/src/components/UserAutocomplete/UserAutocomplete.tsx

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,20 @@ import { ChangeEvent, useEffect, useState } from "react"
1010
import{searchUserMachine}from"xServices/users/searchUserXService"
1111

1212
exporttypeUserAutocompleteProps={
13-
value?:User
13+
value:User|null
1414
onChange:(user:User|null)=>void
15+
label?:string
16+
inputMargin?:"none"|"dense"|"normal"
17+
inputStyles?:string
1518
}
1619

17-
exportconstUserAutocomplete:React.FC<UserAutocompleteProps>=({ value, onChange})=>{
20+
exportconstUserAutocomplete:React.FC<UserAutocompleteProps>=({
21+
value,
22+
onChange,
23+
label,
24+
inputMargin,
25+
inputStyles,
26+
})=>{
1827
conststyles=useStyles()
1928
const[isAutocompleteOpen,setIsAutocompleteOpen]=useState(false)
2029
const[searchState,sendSearch]=useMachine(searchUserMachine)
@@ -77,9 +86,11 @@ export const UserAutocomplete: React.FC<UserAutocompleteProps> = ({ value, onCha
7786
renderInput={(params)=>(
7887
<TextField
7988
{...params}
80-
margin="none"
8189
variant="outlined"
90+
margin={inputMargin??"normal"}
91+
label={label??undefined}
8292
placeholder="User email or username"
93+
className={inputStyles}
8394
InputProps={{
8495
...params.InputProps,
8596
onChange:handleFilterChange,
@@ -111,7 +122,7 @@ export const useStyles = makeStyles((theme) => {
111122
},
112123

113124
"& input":{
114-
fontSize:14,
125+
fontSize:16,
115126
padding:`${theme.spacing(0,0.5,0,0.5)} !important`,
116127
},
117128
},
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"templateLabel":"Template",
3+
"nameLabel":"Name",
4+
"ownerLabel":"Workspace Owner"
5+
}

‎site/src/i18n/en/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
importauditLogfrom"./auditLog.json"
22
importcommonfrom"./common.json"
3+
importcreateWorkspacePagefrom"./createWorkspacePage.json"
34
importtemplatePagefrom"./templatePage.json"
45
importtemplatesPagefrom"./templatesPage.json"
56
importworkspacePagefrom"./workspacePage.json"
@@ -10,4 +11,5 @@ export const en = {
1011
auditLog,
1112
templatePage,
1213
templatesPage,
14+
createWorkspacePage,
1315
}
Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
/* eslint-disable @typescript-eslint/no-floating-promises */
2-
import{screen}from"@testing-library/react"
2+
import{fireEvent,screen,waitFor}from"@testing-library/react"
33
importuserEventfrom"@testing-library/user-event"
44
import*asAPIfrom"api/api"
5-
import{LanguageasFooterLanguage}from"../../components/FormFooter/FormFooter"
6-
import{MockTemplate,MockWorkspace}from"../../testHelpers/entities"
7-
import{renderWithAuth}from"../../testHelpers/renderHelpers"
5+
import{LanguageasFooterLanguage}from"components/FormFooter/FormFooter"
6+
importi18nextfrom"i18next"
7+
import{MockTemplate,MockUser,MockWorkspace,MockWorkspaceRequest}from"testHelpers/entities"
8+
import{renderWithAuth}from"testHelpers/renderHelpers"
89
importCreateWorkspacePagefrom"./CreateWorkspacePage"
9-
import{Language}from"./CreateWorkspacePageView"
10+
11+
const{ t}=i18next
12+
13+
constnameLabelText=t("nameLabel",{ns:"createWorkspacePage"})
1014

1115
constrenderCreateWorkspacePage=()=>{
1216
returnrenderWithAuth(<CreateWorkspacePage/>,{
@@ -22,14 +26,26 @@ describe("CreateWorkspacePage", () => {
2226
expect(element).toBeDefined()
2327
})
2428

25-
it("succeeds",async()=>{
29+
it("succeeds with default owner",async()=>{
30+
jest.spyOn(API,"getUsers").mockResolvedValueOnce([MockUser])
2631
jest.spyOn(API,"createWorkspace").mockResolvedValueOnce(MockWorkspace)
2732

2833
renderCreateWorkspacePage()
2934

30-
constnameField=awaitscreen.findByLabelText(Language.nameLabel)
31-
userEvent.type(nameField,"test")
35+
constnameField=awaitscreen.findByLabelText(nameLabelText)
36+
37+
// have to use fireEvent b/c userEvent isn't cleaning up properly between tests
38+
fireEvent.change(nameField,{
39+
target:{value:"test"},
40+
})
41+
3242
constsubmitButton=screen.getByText(FooterLanguage.defaultSubmitLabel)
3343
userEvent.click(submitButton)
44+
45+
awaitwaitFor(()=>
46+
expect(API.createWorkspace).toBeCalledWith(MockUser.organization_ids[0],MockUser.id,{
47+
...MockWorkspaceRequest,
48+
}),
49+
)
3450
})
3551
})

‎site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import{useMachine}from"@xstate/react"
2-
import{FC}from"react"
1+
import{useActor,useMachine}from"@xstate/react"
2+
import{User}from"api/typesGenerated"
3+
import{useOrganizationId}from"hooks/useOrganizationId"
4+
import{FC,useContext,useState}from"react"
35
import{Helmet}from"react-helmet-async"
46
import{useNavigate,useParams}from"react-router-dom"
5-
import{useOrganizationId}from"../../hooks/useOrganizationId"
6-
import{pageTitle}from"../../util/page"
7-
import{createWorkspaceMachine}from"../../xServices/createWorkspace/createWorkspaceXService"
7+
import{pageTitle}from"util/page"
8+
import{createWorkspaceMachine}from"xServices/createWorkspace/createWorkspaceXService"
9+
import{XServiceContext}from"xServices/StateContext"
810
import{CreateWorkspaceErrors,CreateWorkspacePageView}from"./CreateWorkspacePageView"
911

1012
constCreateWorkspacePage:FC=()=>{
@@ -28,8 +30,15 @@ const CreateWorkspacePage: FC = () => {
2830
getTemplateSchemaError,
2931
getTemplatesError,
3032
createWorkspaceError,
33+
permissions,
3134
}=createWorkspaceState.context
3235

36+
constxServices=useContext(XServiceContext)
37+
const[authState]=useActor(xServices.authXService)
38+
const{ me}=authState.context
39+
40+
const[owner,setOwner]=useState<User|null>(me??null)
41+
3342
return(
3443
<>
3544
<Helmet>
@@ -49,13 +58,17 @@ const CreateWorkspacePage: FC = () => {
4958
[CreateWorkspaceErrors.GET_TEMPLATE_SCHEMA_ERROR]:getTemplateSchemaError,
5059
[CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR]:createWorkspaceError,
5160
}}
61+
canCreateForUser={permissions?.createWorkspaceForUser}
62+
defaultWorkspaceOwner={me??null}
63+
setOwner={setOwner}
5264
onCancel={()=>{
5365
navigate("/templates")
5466
}}
5567
onSubmit={(request)=>{
5668
send({
5769
type:"CREATE_WORKSPACE",
5870
request,
71+
owner,
5972
})
6073
}}
6174
/>

‎site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx

Lines changed: 30 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
1-
import{makeStyles}from"@material-ui/core/styles"
21
importTextFieldfrom"@material-ui/core/TextField"
2+
import*asTypesGenfrom"api/typesGenerated"
33
import{ErrorSummary}from"components/ErrorSummary/ErrorSummary"
4+
import{FormFooter}from"components/FormFooter/FormFooter"
5+
import{FullPageForm}from"components/FullPageForm/FullPageForm"
6+
import{Loader}from"components/Loader/Loader"
7+
import{ParameterInput}from"components/ParameterInput/ParameterInput"
8+
import{Stack}from"components/Stack/Stack"
9+
import{UserAutocomplete}from"components/UserAutocomplete/UserAutocomplete"
410
import{FormikContextType,FormikTouched,useFormik}from"formik"
11+
import{i18n}from"i18n"
512
import{FC,useState}from"react"
13+
import{useTranslation}from"react-i18next"
14+
import{getFormHelpers,nameValidator,onChangeTrimmed}from"util/formUtils"
615
import*asYupfrom"yup"
7-
import*asTypesGenfrom"../../api/typesGenerated"
8-
import{FormFooter}from"../../components/FormFooter/FormFooter"
9-
import{FullPageForm}from"../../components/FullPageForm/FullPageForm"
10-
import{Loader}from"../../components/Loader/Loader"
11-
import{ParameterInput}from"../../components/ParameterInput/ParameterInput"
12-
import{Stack}from"../../components/Stack/Stack"
13-
import{getFormHelpers,nameValidator,onChangeTrimmed}from"../../util/formUtils"
14-
15-
exportconstLanguage={
16-
templateLabel:"Template",
17-
nameLabel:"Name",
18-
}
1916

2017
exportenumCreateWorkspaceErrors{
2118
GET_TEMPLATES_ERROR="getTemplatesError",
@@ -33,21 +30,27 @@ export interface CreateWorkspacePageViewProps {
3330
selectedTemplate?:TypesGen.Template
3431
templateSchema?:TypesGen.ParameterSchema[]
3532
createWorkspaceErrors:Partial<Record<CreateWorkspaceErrors,Error|unknown>>
33+
canCreateForUser?:boolean
34+
defaultWorkspaceOwner:TypesGen.User|null
35+
setOwner:(arg0:TypesGen.User|null)=>void
3636
onCancel:()=>void
3737
onSubmit:(req:TypesGen.CreateWorkspaceRequest)=>void
3838
// initialTouched is only used for testing the error state of the form.
3939
initialTouched?:FormikTouched<TypesGen.CreateWorkspaceRequest>
4040
}
4141

42+
const{ t}=i18n
43+
4244
exportconstvalidationSchema=Yup.object({
43-
name:nameValidator(Language.nameLabel),
45+
name:nameValidator(t("nameLabel",{ns:"createWorkspacePage"})),
4446
})
4547

4648
exportconstCreateWorkspacePageView:FC<React.PropsWithChildren<CreateWorkspacePageViewProps>>=(
4749
props,
4850
)=>{
51+
const{ t}=useTranslation("createWorkspacePage")
52+
4953
const[parameterValues,setParameterValues]=useState<Record<string,string>>({})
50-
useStyles()
5154

5255
constform:FormikContextType<TypesGen.CreateWorkspaceRequest>=
5356
useFormik<TypesGen.CreateWorkspaceRequest>({
@@ -114,17 +117,15 @@ export const CreateWorkspacePageView: FC<React.PropsWithChildren<CreateWorkspace
114117
<FullPageFormtitle="Create workspace"onCancel={props.onCancel}>
115118
<formonSubmit={form.handleSubmit}>
116119
<Stack>
117-
{props.createWorkspaceErrors[CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR] ?(
120+
{Boolean(props.createWorkspaceErrors[CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR])&&(
118121
<ErrorSummary
119122
error={props.createWorkspaceErrors[CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR]}
120123
/>
121-
) :(
122-
<></>
123124
)}
124125
<TextField
125126
disabled
126127
fullWidth
127-
label={Language.templateLabel}
128+
label={t("templateLabel")}
128129
value={props.selectedTemplate?.name||props.templateName}
129130
variant="outlined"
130131
/>
@@ -138,10 +139,19 @@ export const CreateWorkspacePageView: FC<React.PropsWithChildren<CreateWorkspace
138139
onChange={onChangeTrimmed(form)}
139140
autoFocus
140141
fullWidth
141-
label={Language.nameLabel}
142+
label={t("nameLabel")}
142143
variant="outlined"
143144
/>
144145

146+
{props.canCreateForUser&&(
147+
<UserAutocomplete
148+
value={props.defaultWorkspaceOwner}
149+
onChange={(user)=>props.setOwner(user)}
150+
label={t("ownerLabel")}
151+
inputMargin="dense"
152+
/>
153+
)}
154+
145155
{props.templateSchema.length>0&&(
146156
<Stack>
147157
{props.templateSchema.map((schema)=>(
@@ -168,33 +178,3 @@ export const CreateWorkspacePageView: FC<React.PropsWithChildren<CreateWorkspace
168178
</FullPageForm>
169179
)
170180
}
171-
172-
constuseStyles=makeStyles((theme)=>({
173-
readMoreLink:{
174-
display:"flex",
175-
alignItems:"center",
176-
177-
"& svg":{
178-
width:12,
179-
height:12,
180-
marginLeft:theme.spacing(0.5),
181-
},
182-
},
183-
emptyState:{
184-
padding:0,
185-
fontFamily:"inherit",
186-
textAlign:"left",
187-
minHeight:"auto",
188-
alignItems:"flex-start",
189-
},
190-
emptyStateDescription:{
191-
lineHeight:"160%",
192-
},
193-
code:{
194-
background:theme.palette.background.paper,
195-
width:"100%",
196-
},
197-
codeButton:{
198-
background:theme.palette.background.paper,
199-
},
200-
}))

‎site/src/testHelpers/entities.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,13 @@ export const MockQueuedWorkspace: TypesGen.Workspace = {
324324
},
325325
}
326326

327+
// requests the MockWorkspace
328+
exportconstMockWorkspaceRequest:TypesGen.CreateWorkspaceRequest={
329+
name:"test",
330+
parameter_values:[],
331+
template_id:"test-template",
332+
}
333+
327334
exportconstMockWorkspaceApp:TypesGen.WorkspaceApp={
328335
id:"test-app",
329336
name:"test-app",

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp