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

Commit0a71c34

Browse files
authored
feat: create and modify organization groups (#13887)
1 parentdd99457 commit0a71c34

24 files changed

+1205
-89
lines changed

‎enterprise/coderd/groups.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package coderd
22

33
import (
44
"database/sql"
5+
"errors"
56
"fmt"
67
"net/http"
78

@@ -170,9 +171,9 @@ func (api *API) patchGroup(rw http.ResponseWriter, r *http.Request) {
170171
OrganizationID:group.OrganizationID,
171172
UserID:uuid.MustParse(id),
172173
}))
173-
ifxerrors.Is(err,sql.ErrNoRows) {
174+
iferrors.Is(err,sql.ErrNoRows) {
174175
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
175-
Message:fmt.Sprintf("User%qmust be a member of organization %q",id,group.ID),
176+
Message:fmt.Sprintf("User must be a member of organization %q",group.Name),
176177
})
177178
return
178179
}
@@ -364,7 +365,7 @@ func (api *API) group(rw http.ResponseWriter, r *http.Request) {
364365
)
365366

366367
users,err:=api.Database.GetGroupMembersByGroupID(ctx,group.ID)
367-
iferr!=nil&&!xerrors.Is(err,sql.ErrNoRows) {
368+
iferr!=nil&&!errors.Is(err,sql.ErrNoRows) {
368369
httpapi.InternalServerError(rw,err)
369370
return
370371
}
@@ -391,7 +392,7 @@ func (api *API) groups(rw http.ResponseWriter, r *http.Request) {
391392
)
392393

393394
groups,err:=api.Database.GetGroupsByOrganizationID(ctx,org.ID)
394-
iferr!=nil&&!xerrors.Is(err,sql.ErrNoRows) {
395+
iferr!=nil&&!errors.Is(err,sql.ErrNoRows) {
395396
httpapi.InternalServerError(rw,err)
396397
return
397398
}

‎site/src/api/api.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -515,19 +515,19 @@ class ApiMethods {
515515
};
516516

517517
updateOrganization=async(
518-
orgId:string,
518+
organizationId:string,
519519
params:TypesGen.UpdateOrganizationRequest,
520520
)=>{
521521
constresponse=awaitthis.axios.patch<TypesGen.Organization>(
522-
`/api/v2/organizations/${orgId}`,
522+
`/api/v2/organizations/${organizationId}`,
523523
params,
524524
);
525525
returnresponse.data;
526526
};
527527

528-
deleteOrganization=async(orgId:string)=>{
528+
deleteOrganization=async(organizationId:string)=>{
529529
awaitthis.axios.delete<TypesGen.Organization>(
530-
`/api/v2/organizations/${orgId}`,
530+
`/api/v2/organizations/${organizationId}`,
531531
);
532532
};
533533

@@ -1485,9 +1485,12 @@ class ApiMethods {
14851485
returnresponse.data;
14861486
};
14871487

1488-
getGroup=async(groupName:string):Promise<TypesGen.Group>=>{
1488+
getGroup=async(
1489+
organizationId:string,
1490+
groupName:string,
1491+
):Promise<TypesGen.Group>=>{
14891492
constresponse=awaitthis.axios.get(
1490-
`/api/v2/organizations/default/groups/${groupName}`,
1493+
`/api/v2/organizations/${organizationId}/groups/${groupName}`,
14911494
);
14921495
returnresponse.data;
14931496
};

‎site/src/api/queries/groups.ts

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ import type {
99
constGROUPS_QUERY_KEY=["groups"];
1010
typeGroupSortOrder="asc"|"desc";
1111

12-
constgetGroupQueryKey=(groupName:string)=>["group",groupName];
12+
constgetGroupQueryKey=(organizationId:string,groupName:string)=>[
13+
organizationId,
14+
"group",
15+
groupName,
16+
];
1317

1418
exportconstgroups=(organizationId:string)=>{
1519
return{
@@ -18,10 +22,10 @@ export const groups = (organizationId: string) => {
1822
}satisfiesUseQueryOptions<Group[]>;
1923
};
2024

21-
exportconstgroup=(groupName:string)=>{
25+
exportconstgroup=(organizationId:string,groupName:string)=>{
2226
return{
23-
queryKey:getGroupQueryKey(groupName),
24-
queryFn:()=>API.getGroup(groupName),
27+
queryKey:getGroupQueryKey(organizationId,groupName),
28+
queryFn:()=>API.getGroup(organizationId,groupName),
2529
};
2630
};
2731

@@ -69,7 +73,7 @@ export function groupsForUser(organizationId: string, userId: string) {
6973

7074
exportconstgroupPermissions=(groupId:string)=>{
7175
return{
72-
queryKey:[...getGroupQueryKey(groupId),"permissions"],
76+
queryKey:["group",groupId,"permissions"],
7377
queryFn:()=>
7478
API.checkAuthorization({
7579
checks:{
@@ -85,12 +89,12 @@ export const groupPermissions = (groupId: string) => {
8589
};
8690
};
8791

88-
exportconstcreateGroup=(queryClient:QueryClient)=>{
92+
exportconstcreateGroup=(
93+
queryClient:QueryClient,
94+
organizationId:string,
95+
)=>{
8996
return{
90-
mutationFn:({
91-
organizationId,
92-
...request
93-
}:CreateGroupRequest&{organizationId:string})=>
97+
mutationFn:(request:CreateGroupRequest)=>
9498
API.createGroup(organizationId,request),
9599
onSuccess:async()=>{
96100
awaitqueryClient.invalidateQueries(GROUPS_QUERY_KEY);
@@ -106,15 +110,15 @@ export const patchGroup = (queryClient: QueryClient) => {
106110
}:PatchGroupRequest&{groupId:string})=>
107111
API.patchGroup(groupId,request),
108112
onSuccess:async(updatedGroup:Group)=>
109-
invalidateGroup(queryClient,updatedGroup.id),
113+
invalidateGroup(queryClient,"default",updatedGroup.id),
110114
};
111115
};
112116

113117
exportconstdeleteGroup=(queryClient:QueryClient)=>{
114118
return{
115119
mutationFn:API.deleteGroup,
116120
onSuccess:async(_:void,groupId:string)=>
117-
invalidateGroup(queryClient,groupId),
121+
invalidateGroup(queryClient,"default",groupId),
118122
};
119123
};
120124

@@ -123,7 +127,7 @@ export const addMember = (queryClient: QueryClient) => {
123127
mutationFn:({ groupId, userId}:{groupId:string;userId:string})=>
124128
API.addMember(groupId,userId),
125129
onSuccess:async(updatedGroup:Group)=>
126-
invalidateGroup(queryClient,updatedGroup.id),
130+
invalidateGroup(queryClient,"default",updatedGroup.id),
127131
};
128132
};
129133

@@ -132,14 +136,18 @@ export const removeMember = (queryClient: QueryClient) => {
132136
mutationFn:({ groupId, userId}:{groupId:string;userId:string})=>
133137
API.removeMember(groupId,userId),
134138
onSuccess:async(updatedGroup:Group)=>
135-
invalidateGroup(queryClient,updatedGroup.id),
139+
invalidateGroup(queryClient,"default",updatedGroup.id),
136140
};
137141
};
138142

139-
exportconstinvalidateGroup=(queryClient:QueryClient,groupId:string)=>
143+
exportconstinvalidateGroup=(
144+
queryClient:QueryClient,
145+
organizationId:string,
146+
groupId:string,
147+
)=>
140148
Promise.all([
141149
queryClient.invalidateQueries(GROUPS_QUERY_KEY),
142-
queryClient.invalidateQueries(getGroupQueryKey(groupId)),
150+
queryClient.invalidateQueries(getGroupQueryKey(organizationId,groupId)),
143151
]);
144152

145153
exportfunctionsortGroupsByName(

‎site/src/api/queries/organizations.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ export const createOrganization = (queryClient: QueryClient) => {
1919
};
2020

2121
interfaceUpdateOrganizationVariables{
22-
orgId:string;
22+
organizationId:string;
2323
req:UpdateOrganizationRequest;
2424
}
2525

2626
exportconstupdateOrganization=(queryClient:QueryClient)=>{
2727
return{
2828
mutationFn:(variables:UpdateOrganizationVariables)=>
29-
API.updateOrganization(variables.orgId,variables.req),
29+
API.updateOrganization(variables.organizationId,variables.req),
3030

3131
onSuccess:async()=>{
3232
awaitqueryClient.invalidateQueries(organizationsKey);
@@ -36,7 +36,8 @@ export const updateOrganization = (queryClient: QueryClient) => {
3636

3737
exportconstdeleteOrganization=(queryClient:QueryClient)=>{
3838
return{
39-
mutationFn:(orgId:string)=>API.deleteOrganization(orgId),
39+
mutationFn:(organizationId:string)=>
40+
API.deleteOrganization(organizationId),
4041

4142
onSuccess:async()=>{
4243
awaitqueryClient.invalidateQueries(meKey);
@@ -79,7 +80,7 @@ export const removeOrganizationMember = (
7980
};
8081
};
8182

82-
exportconstorganizationsKey=["organizations","me"]asconst;
83+
exportconstorganizationsKey=["organizations"]asconst;
8384

8485
exportconstorganizations=()=>{
8586
return{

‎site/src/components/PageHeader/PageHeader.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,23 @@ export const PageHeaderCaption: FC<PropsWithChildren> = ({ children }) => {
107107
</span>
108108
);
109109
};
110+
111+
interfaceResourcePageHeaderPropsextendsOmit<PageHeaderProps,"children">{
112+
displayName?:string;
113+
name:string;
114+
}
115+
116+
exportconstResourcePageHeader:FC<ResourcePageHeaderProps>=({
117+
displayName,
118+
name,
119+
...props
120+
})=>{
121+
consttitle=displayName||name;
122+
123+
return(
124+
<PageHeader{...props}>
125+
<PageHeaderTitle>{title}</PageHeaderTitle>
126+
{name!==title&&<PageHeaderSubtitle>{name}</PageHeaderSubtitle>}
127+
</PageHeader>
128+
);
129+
};

‎site/src/pages/GroupsPage/CreateGroupPage.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,13 @@ import { Helmet } from "react-helmet-async";
33
import{useMutation,useQueryClient}from"react-query";
44
import{useNavigate}from"react-router-dom";
55
import{createGroup}from"api/queries/groups";
6-
import{useDashboard}from"modules/dashboard/useDashboard";
76
import{pageTitle}from"utils/page";
87
importCreateGroupPageViewfrom"./CreateGroupPageView";
98

109
exportconstCreateGroupPage:FC=()=>{
1110
constqueryClient=useQueryClient();
1211
constnavigate=useNavigate();
13-
const{ organizationId}=useDashboard();
14-
constcreateGroupMutation=useMutation(createGroup(queryClient));
12+
constcreateGroupMutation=useMutation(createGroup(queryClient,"default"));
1513

1614
return(
1715
<>
@@ -20,10 +18,7 @@ export const CreateGroupPage: FC = () => {
2018
</Helmet>
2119
<CreateGroupPageView
2220
onSubmit={async(data)=>{
23-
constnewGroup=awaitcreateGroupMutation.mutateAsync({
24-
organizationId,
25-
...data,
26-
});
21+
constnewGroup=awaitcreateGroupMutation.mutateAsync(data);
2722
navigate(`/groups/${newGroup.name}`);
2823
}}
2924
error={createGroupMutation.error}

‎site/src/pages/GroupsPage/GroupPage.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,13 @@ import { isEveryoneGroup } from "utils/groups";
5454
import{pageTitle}from"utils/page";
5555

5656
exportconstGroupPage:FC=()=>{
57-
const{ groupName}=useParams()as{groupName:string};
57+
const{ groupName, organization}=useParams()as{
58+
organization:string;
59+
groupName:string;
60+
};
5861
constqueryClient=useQueryClient();
5962
constnavigate=useNavigate();
60-
constgroupQuery=useQuery(group(groupName));
63+
constgroupQuery=useQuery(group(organization,groupName));
6164
constgroupData=groupQuery.data;
6265
const{data:permissions}=useQuery(
6366
groupData!==undefined

‎site/src/pages/GroupsPage/SettingsGroupPage.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ import SettingsGroupPageView from "./SettingsGroupPageView";
1313
exportconstSettingsGroupPage:FC=()=>{
1414
const{ groupName}=useParams()as{groupName:string};
1515
constqueryClient=useQueryClient();
16-
constgroupQuery=useQuery(group(groupName));
17-
const{data:groupData, isLoading, error}=useQuery(group(groupName));
16+
constgroupQuery=useQuery(group("default",groupName));
1817
constpatchGroupMutation=useMutation(patchGroup(queryClient));
1918
constnavigate=useNavigate();
2019

@@ -28,19 +27,20 @@ export const SettingsGroupPage: FC = () => {
2827
</Helmet>
2928
);
3029

31-
if(error){
32-
return<ErrorAlerterror={error}/>;
30+
if(groupQuery.error){
31+
return<ErrorAlerterror={groupQuery.error}/>;
3332
}
3433

35-
if(isLoading||!groupData){
34+
if(groupQuery.isLoading||!groupQuery.data){
3635
return(
3736
<>
3837
{helmet}
3938
<Loader/>
4039
</>
4140
);
4241
}
43-
constgroupId=groupData.id;
42+
43+
constgroupId=groupQuery.data.id;
4444

4545
return(
4646
<>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
importtype{FC}from"react";
2+
import{Helmet}from"react-helmet-async";
3+
import{useMutation,useQueryClient}from"react-query";
4+
import{useNavigate,useParams}from"react-router-dom";
5+
import{createGroup}from"api/queries/groups";
6+
import{pageTitle}from"utils/page";
7+
importCreateGroupPageViewfrom"./CreateGroupPageView";
8+
9+
exportconstCreateGroupPage:FC=()=>{
10+
constqueryClient=useQueryClient();
11+
constnavigate=useNavigate();
12+
const{ organization}=useParams()as{organization:string};
13+
constcreateGroupMutation=useMutation(
14+
createGroup(queryClient,organization),
15+
);
16+
17+
return(
18+
<>
19+
<Helmet>
20+
<title>{pageTitle("Create Group")}</title>
21+
</Helmet>
22+
<CreateGroupPageView
23+
onSubmit={async(data)=>{
24+
constnewGroup=awaitcreateGroupMutation.mutateAsync(data);
25+
navigate(`/organizations/${organization}/groups/${newGroup.name}`);
26+
}}
27+
error={createGroupMutation.error}
28+
isLoading={createGroupMutation.isLoading}
29+
/>
30+
</>
31+
);
32+
};
33+
exportdefaultCreateGroupPage;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
importtype{Meta,StoryObj}from"@storybook/react";
2+
import{userEvent,within}from"@storybook/test";
3+
import{mockApiError}from"testHelpers/entities";
4+
import{CreateGroupPageView}from"./CreateGroupPageView";
5+
6+
constmeta:Meta<typeofCreateGroupPageView>={
7+
title:"pages/OrganizationGroupsPage/CreateGroupPageView",
8+
component:CreateGroupPageView,
9+
};
10+
11+
exportdefaultmeta;
12+
typeStory=StoryObj<typeofCreateGroupPageView>;
13+
14+
exportconstExample:Story={};
15+
16+
exportconstWithError:Story={
17+
args:{
18+
error:mockApiError({
19+
message:"A group named new-group already exists.",
20+
validations:[{field:"name",detail:"Group names must be unique"}],
21+
}),
22+
},
23+
play:async({ canvasElement, step})=>{
24+
constcanvas=within(canvasElement);
25+
26+
awaitstep("Enter name",async()=>{
27+
constinput=canvas.getByLabelText("Name");
28+
awaituserEvent.type(input,"new-group");
29+
input.blur();
30+
});
31+
},
32+
};

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp