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

Commit15d74a1

Browse files
authored
feat: improve custom roles create/edit page (#14456)
* fix: improve show/hide checkbox text* feat: add parent checkbox for grouped resource permissions* fix: align action list item to a grid* chore: add additional tests* fix: format
1 parentf3ea740 commit15d74a1

File tree

3 files changed

+177
-41
lines changed

3 files changed

+177
-41
lines changed

‎site/src/pages/ManagementSettingsPage/CustomRolesPage/CreateEditRolePageView.stories.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
importtype{Meta,StoryObj}from"@storybook/react";
2+
import{expect,userEvent,within}from"@storybook/test";
23
import{
4+
MockRole2WithOrgPermissions,
35
MockRoleWithOrgPermissions,
46
assignableRole,
57
mockApiError,
@@ -25,6 +27,16 @@ export const Default: Story = {
2527
},
2628
};
2729

30+
exportconstCheckboxIndeterminate:Story={
31+
args:{
32+
role:assignableRole(MockRole2WithOrgPermissions,true),
33+
onSubmit:()=>null,
34+
isLoading:false,
35+
organizationName:"my-org",
36+
canAssignOrgRole:true,
37+
},
38+
};
39+
2840
exportconstWithError:Story={
2941
args:{
3042
role:assignableRole(MockRoleWithOrgPermissions,true),
@@ -61,3 +73,17 @@ export const ShowAllResources: Story = {
6173
allResources:true,
6274
},
6375
};
76+
77+
exportconstToggleParentCheckbox:Story={
78+
play:async({ canvasElement})=>{
79+
constuser=userEvent.setup();
80+
constcanvas=within(canvasElement);
81+
constcheckbox=awaitcanvas
82+
.getByTestId("audit_log")
83+
.getElementsByTagName("input")[0];
84+
awaituser.click(checkbox);
85+
awaitexpect(checkbox).toBeChecked();
86+
awaituser.click(checkbox);
87+
awaitexpect(checkbox).not.toBeChecked();
88+
},
89+
};

‎site/src/pages/ManagementSettingsPage/CustomRolesPage/CreateEditRolePageView.tsx

Lines changed: 136 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -146,12 +146,6 @@ export const CreateEditRolePageView: FC<CreateEditRolePageViewProps> = ({
146146
);
147147
};
148148

149-
interfaceActionCheckboxesProps{
150-
permissions:readonlyPermission[]|undefined;
151-
form:ReturnType<typeofuseFormik<Role>>&{values:Role};
152-
allResources:boolean;
153-
}
154-
155149
constResourceActionComparator=(
156150
p:Permission,
157151
resource:string,
@@ -160,6 +154,7 @@ const ResourceActionComparator = (
160154
p.resource_type===resource&&
161155
(p.action.toString()==="*"||p.action===action);
162156

157+
// the subset of resources that are useful for most users
163158
constDEFAULT_RESOURCES=[
164159
"audit_log",
165160
"group",
@@ -177,6 +172,12 @@ const filteredRBACResourceActions = Object.fromEntries(
177172
),
178173
);
179174

175+
interfaceActionCheckboxesProps{
176+
permissions:readonlyPermission[];
177+
form:ReturnType<typeofuseFormik<Role>>&{values:Role};
178+
allResources:boolean;
179+
}
180+
180181
constActionCheckboxes:FC<ActionCheckboxesProps>=({
181182
permissions,
182183
form,
@@ -185,6 +186,10 @@ const ActionCheckboxes: FC<ActionCheckboxesProps> = ({
185186
const[checkedActions,setCheckActions]=useState(permissions);
186187
const[showAllResources,setShowAllResources]=useState(allResources);
187188

189+
constresourceActions=showAllResources
190+
?RBACResourceActions
191+
:filteredRBACResourceActions;
192+
188193
consthandleActionCheckChange=async(
189194
e:ChangeEvent<HTMLInputElement>,
190195
form:ReturnType<typeofuseFormik<Role>>&{values:Role},
@@ -194,7 +199,7 @@ const ActionCheckboxes: FC<ActionCheckboxesProps> = ({
194199

195200
constnewPermissions=checked
196201
?[
197-
...(checkedActions??[]),
202+
...checkedActions,
198203
{
199204
negate:false,
200205
resource_type:resource_typeasRBACResource,
@@ -209,9 +214,36 @@ const ActionCheckboxes: FC<ActionCheckboxesProps> = ({
209214
awaitform.setFieldValue("organization_permissions",newPermissions);
210215
};
211216

212-
constresourceActions=showAllResources
213-
?RBACResourceActions
214-
:filteredRBACResourceActions;
217+
consthandleResourceCheckChange=async(
218+
e:ChangeEvent<HTMLInputElement>,
219+
form:ReturnType<typeofuseFormik<Role>>&{values:Role},
220+
indeterminate:boolean,
221+
)=>{
222+
const{ name, checked}=e.currentTarget;
223+
constresource=nameasRBACResource;
224+
225+
constresourceActionsForResource=resourceActions[resource]||{};
226+
227+
constnewCheckedActions=
228+
!checked||indeterminate
229+
?checkedActions?.filter((p)=>p.resource_type!==resource)
230+
:checkedActions;
231+
232+
constnewPermissions=
233+
checked||indeterminate
234+
?[
235+
...newCheckedActions,
236+
...Object.keys(resourceActionsForResource).map((resourceKey)=>({
237+
negate:false,
238+
resource_type:resourceasRBACResource,
239+
action:resourceKeyasRBACAction,
240+
})),
241+
]
242+
:[...newCheckedActions];
243+
244+
setCheckActions(newPermissions);
245+
awaitform.setFieldValue("organization_permissions",newPermissions);
246+
};
215247

216248
return(
217249
<TableContainer>
@@ -233,36 +265,17 @@ const ActionCheckboxes: FC<ActionCheckboxesProps> = ({
233265
<TableBody>
234266
{Object.entries(resourceActions).map(([resourceKey,value])=>{
235267
return(
236-
<TableRowkey={resourceKey}>
237-
<TableCellsx={{paddingLeft:2}}colSpan={2}>
238-
<likey={resourceKey}css={styles.checkBoxes}>
239-
{resourceKey}
240-
<ulcss={styles.checkBoxes}>
241-
{Object.entries(value).map(([actionKey,value])=>(
242-
<likey={actionKey}>
243-
<spancss={styles.actionText}>
244-
<Checkbox
245-
size="small"
246-
name={`${resourceKey}:${actionKey}`}
247-
checked={checkedActions?.some((p)=>
248-
ResourceActionComparator(
249-
p,
250-
resourceKey,
251-
actionKey,
252-
),
253-
)}
254-
onChange={(e)=>handleActionCheckChange(e,form)}
255-
/>
256-
{actionKey}
257-
</span>{" "}
258-
&ndash;{" "}
259-
<spancss={styles.actionDescription}>{value}</span>
260-
</li>
261-
))}
262-
</ul>
263-
</li>
264-
</TableCell>
265-
</TableRow>
268+
<PermissionCheckboxGroup
269+
key={resourceKey}
270+
checkedActions={checkedActions?.filter(
271+
(a)=>a.resource_type===resourceKey,
272+
)}
273+
resourceKey={resourceKey}
274+
value={value}
275+
form={form}
276+
handleActionCheckChange={handleActionCheckChange}
277+
handleResourceCheckChange={handleResourceCheckChange}
278+
/>
266279
);
267280
})}
268281
</TableBody>
@@ -285,6 +298,77 @@ const ActionCheckboxes: FC<ActionCheckboxesProps> = ({
285298
);
286299
};
287300

301+
interfacePermissionCheckboxGroupProps{
302+
checkedActions:readonlyPermission[];
303+
resourceKey:string;
304+
value:Partial<Record<RBACAction,string>>;
305+
form:ReturnType<typeofuseFormik<Role>>&{values:Role};
306+
handleActionCheckChange:(
307+
e:ChangeEvent<HTMLInputElement>,
308+
form:ReturnType<typeofuseFormik<Role>>&{values:Role},
309+
)=>Promise<void>;
310+
handleResourceCheckChange:(
311+
e:ChangeEvent<HTMLInputElement>,
312+
form:ReturnType<typeofuseFormik<Role>>&{values:Role},
313+
indeterminate:boolean,
314+
)=>Promise<void>;
315+
}
316+
317+
constPermissionCheckboxGroup:FC<PermissionCheckboxGroupProps>=({
318+
checkedActions,
319+
resourceKey,
320+
value,
321+
form,
322+
handleActionCheckChange,
323+
handleResourceCheckChange,
324+
})=>{
325+
return(
326+
<TableRowkey={resourceKey}>
327+
<TableCellsx={{paddingLeft:2}}colSpan={2}>
328+
<likey={resourceKey}css={styles.checkBoxes}>
329+
<Checkbox
330+
size="small"
331+
name={`${resourceKey}`}
332+
checked={checkedActions.length===Object.keys(value).length}
333+
indeterminate={
334+
checkedActions.length>0&&
335+
checkedActions.length<Object.keys(value).length
336+
}
337+
data-testid={`${resourceKey}`}
338+
onChange={(e)=>
339+
handleResourceCheckChange(
340+
e,
341+
form,
342+
checkedActions.length>0&&
343+
checkedActions.length<Object.keys(value).length,
344+
)
345+
}
346+
/>
347+
{resourceKey}
348+
<ulcss={styles.checkBoxes}>
349+
{Object.entries(value).map(([actionKey,value])=>(
350+
<likey={actionKey}css={styles.actionItem}>
351+
<spancss={styles.actionText}>
352+
<Checkbox
353+
size="small"
354+
name={`${resourceKey}:${actionKey}`}
355+
checked={checkedActions.some((p)=>
356+
ResourceActionComparator(p,resourceKey,actionKey),
357+
)}
358+
onChange={(e)=>handleActionCheckChange(e,form)}
359+
/>
360+
{actionKey}
361+
</span>
362+
<spancss={styles.actionDescription}>{value}</span>
363+
</li>
364+
))}
365+
</ul>
366+
</li>
367+
</TableCell>
368+
</TableRow>
369+
);
370+
};
371+
288372
interfaceShowAllResourcesCheckboxProps{
289373
showAllResources:boolean;
290374
setShowAllResources:React.Dispatch<React.SetStateAction<boolean>>;
@@ -308,7 +392,13 @@ const ShowAllResourcesCheckbox: FC<ShowAllResourcesCheckboxProps> = ({
308392
icon={<VisibilityOffOutlinedIcon/>}
309393
/>
310394
}
311-
label={<spanstyle={{fontSize:12}}>Show all permissions</span>}
395+
label={
396+
<spanstyle={{fontSize:12}}>
397+
{showAllResources
398+
?"Hide advanced permissions"
399+
:"Show advanced permissions"}
400+
</span>
401+
}
312402
/>
313403
);
314404
};
@@ -323,7 +413,12 @@ const styles = {
323413
}),
324414
actionDescription:(theme)=>({
325415
color:theme.palette.text.secondary,
416+
paddingTop:6,
326417
}),
418+
actionItem:{
419+
display:"grid",
420+
gridTemplateColumns:"270px 1fr",
421+
},
327422
}satisfiesRecord<string,Interpolation<Theme>>;
328423

329424
exportdefaultCreateEditRolePageView;

‎site/src/testHelpers/entities.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,21 @@ export const MockRoleWithOrgPermissions: TypesGen.Role = {
392392
user_permissions:[],
393393
};
394394

395+
exportconstMockRole2WithOrgPermissions:TypesGen.Role={
396+
name:"my-role-1",
397+
display_name:"My Role 1",
398+
organization_id:MockOrganization.id,
399+
site_permissions:[],
400+
organization_permissions:[
401+
{
402+
negate:false,
403+
resource_type:"audit_log",
404+
action:"create",
405+
},
406+
],
407+
user_permissions:[],
408+
};
409+
395410
// assignableRole takes a role and a boolean. The boolean implies if the
396411
// actor can assign (add/remove) the role from other users.
397412
exportfunctionassignableRole(

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp