Skip to content

Commit af7e5dd

Browse files
committed
feat: add roleAssignProjects mutation and implement project assignment in RoleService and RoleSettingsPage
1 parent abc21bb commit af7e5dd

File tree

3 files changed

+141
-18
lines changed

3 files changed

+141
-18
lines changed

src/packages/ce/src/dashboard/role/RoleSettingsPage.tsx

Lines changed: 97 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
"use client"
22

33
import React from "react";
4-
import type {Namespace, NamespaceRole, NamespaceRoleAbility} from "@code0-tech/sagittarius-graphql-types";
4+
import type {Namespace, NamespaceRole, NamespaceRoleAbility, Scalars} from "@code0-tech/sagittarius-graphql-types";
55
import {useParams} from "next/navigation";
66
import {
7+
Alert,
78
AuroraBackground,
89
Button,
910
Card,
1011
CheckboxInput,
1112
Col,
1213
DLayout,
14+
DNamespaceProjectView,
1315
Flex,
1416
Row,
1517
Spacing,
@@ -24,6 +26,9 @@ import {ProjectService} from "@edition/project/Project.service";
2426
import {Tab, TabContent, TabList, TabTrigger} from "@code0-tech/pictor/dist/components/tab/Tab";
2527
import CardSection from "@code0-tech/pictor/dist/components/card/CardSection";
2628
import {DNamespaceRolePermissions} from "@code0-tech/pictor/dist/components/d-role/DNamespaceRolePermissions";
29+
import DNamespaceProjectMenu from "@code0-tech/pictor/dist/components/d-project/DNamespaceProjectMenu";
30+
import {DNamespaceProjectContent} from "@code0-tech/pictor/dist/components/d-project/DNamespaceProjectContent";
31+
import {IconTrash} from "@tabler/icons-react";
2732

2833
type Permission = {
2934
label: string
@@ -199,8 +204,14 @@ export const RoleSettingsPage: React.FC = () => {
199204
}
200205
}, [role])
201206

207+
const roleAssignedProjects = React.useMemo(() => role?.assignedProjects?.nodes?.map(p => p?.id!!) ?? [], [role])
208+
const [assignedProjectIds, setAssignedProjectIds] = React.useState<Scalars['NamespaceProjectID']['output'][]>(roleAssignedProjects)
202209
const [initialValues, setInitialValues] = React.useState(roleAbilities)
203210

211+
React.useEffect(() => {
212+
setAssignedProjectIds(roleAssignedProjects)
213+
}, [role])
214+
204215
React.useEffect(() => {
205216
setInitialValues(roleAbilities)
206217
}, [role])
@@ -229,6 +240,39 @@ export const RoleSettingsPage: React.FC = () => {
229240
}
230241
})
231242

243+
const assignProjects = () => {
244+
startTransition(() => {
245+
roleService.roleAssignProject({
246+
roleId: roleId,
247+
projectIds: assignedProjectIds as Scalars['NamespaceProjectID']['output'][]
248+
}).then(payload => {
249+
if ((payload?.errors?.length ?? 0) <= 0) {
250+
toast({
251+
title: "All projects were successfully assigned to the role.",
252+
color: "success",
253+
dismissible: true,
254+
})
255+
}
256+
})
257+
})
258+
}
259+
260+
const addAssignedProject = (projectId: Scalars['NamespaceProjectID']['output']) => {
261+
setAssignedProjectIds(prevState => {
262+
return [...prevState, projectId]
263+
})
264+
}
265+
266+
const removeAssignedProject = (projectId: Scalars['NamespaceProjectID']['output']) => {
267+
setAssignedProjectIds(prevState => {
268+
return prevState.filter(id => id !== projectId)
269+
})
270+
}
271+
272+
const filterProjects = React.useCallback((project: DNamespaceProjectView) => {
273+
return !assignedProjectIds.find(projectId => projectId == project.id!!)
274+
}, [assignedProjectIds])
275+
232276
return <>
233277
<Spacing spacing={"xl"}/>
234278
<Flex style={{gap: "0.7rem"}} align={"center"}>
@@ -247,23 +291,21 @@ export const RoleSettingsPage: React.FC = () => {
247291
</TabTrigger>
248292
<TabTrigger value={"project"} asChild>
249293
<Button paddingSize={"xxs"} variant={"none"}>
250-
<Text size={"md"} hierarchy={"primary"}>Assigned projects</Text>
294+
<Text size={"md"} hierarchy={"primary"}>Limit to projects</Text>
251295
</Button>
252296
</TabTrigger>
253297
</TabList>
254298
}>
255299
<>
256300
<TabContent value={"permission"} style={{overflow: "hidden"}}>
257-
<Text size={"xl"} hierarchy={"primary"} style={{fontWeight: 600}}>Select from role
258-
templates</Text>
301+
<Text size={"xl"} hierarchy={"primary"} style={{fontWeight: 600}}>
302+
Select from role templates
303+
</Text>
259304
<Spacing spacing={"xl"}/>
260305
{React.useMemo(() => {
261306
return <Row>
262307
{permissionTemplates.map(permissionTemplate => {
263308

264-
//schreibe mir hier eine variable isSelected die true ist wenn die permissionTemplate.abilities genau den gleichen inhalten entsprechen wie die aktuellen rule abilities hat
265-
266-
267309
const templateAbilities = Object.entries(permissionTemplate.abilities)
268310
.filter(([_, enabled]) => enabled)
269311
.map(([ability]) => ability as NamespaceRoleAbility);
@@ -298,10 +340,17 @@ export const RoleSettingsPage: React.FC = () => {
298340
</Row>
299341
}, [initialValues, role])}
300342
<Spacing spacing={"xl"}/>
301-
<Text size={"xl"} hierarchy={"primary"} style={{fontWeight: 600}}>Current stored
302-
permissions</Text>
303-
<Spacing spacing={"xs"}/>
304-
<DNamespaceRolePermissions abilities={role?.abilities!!}/>
343+
<Flex align={"center"} justify={"space-between"}>
344+
<div>
345+
<Text size={"xl"} hierarchy={"primary"} style={{fontWeight: 600}}>Current stored
346+
permissions</Text>
347+
<Spacing spacing={"xs"}/>
348+
<DNamespaceRolePermissions abilities={role?.abilities!!}/>
349+
</div>
350+
<Button color={"success"} onClick={validate}>
351+
Update role permissions
352+
</Button>
353+
</Flex>
305354
<Spacing spacing={"xl"}/>
306355
<Card p={1.3}>
307356
{permissions.map(permissionGroup => {
@@ -322,12 +371,44 @@ export const RoleSettingsPage: React.FC = () => {
322371
</CardSection>
323372
})}
324373
</Card>
325-
<Spacing spacing={"xl"}/>
326-
<Flex justify={"end"}>
327-
<Button color={"success"} onClick={validate}>
328-
Update role permissions
329-
</Button>
374+
</TabContent>
375+
<TabContent value={"project"} style={{overflow: "hidden"}}>
376+
<Flex align={"center"} justify={"space-between"}>
377+
<Text size={"xl"} hierarchy={"primary"} style={{fontWeight: 600}}>Projects that members can
378+
access</Text>
379+
<Flex align={"center"} style={{gap: ".7rem"}}>
380+
<Button color={"success"} onClick={assignProjects}>Update assigned projects</Button>
381+
<DNamespaceProjectMenu namespaceId={namespaceId}
382+
key={String(assignedProjectIds)}
383+
filter={filterProjects}
384+
onProjectSelect={(project) => addAssignedProject(project.id!!)}>
385+
<Button>Add project</Button>
386+
</DNamespaceProjectMenu>
387+
</Flex>
330388
</Flex>
389+
390+
<Spacing spacing={"xl"}/>
391+
{(assignedProjectIds.length ?? 0) <= 0 ? (
392+
<Alert color={"info"}>
393+
<Text style={{textAlign: "center"}} size={"md"} hierarchy={"secondary"}>
394+
This role has no project assignments. Members with this role will have access to all
395+
projects in the organization namespace.
396+
</Text>
397+
</Alert>
398+
) : (
399+
<Card>
400+
{assignedProjectIds.map(projectId => {
401+
return <CardSection key={projectId} border>
402+
<Flex align={"center"} style={{gap: "1.3rem"}} justify={"space-between"}>
403+
<DNamespaceProjectContent minimized projectId={projectId}/>
404+
<Button color={"error"} variant={"filled"} onClick={() => removeAssignedProject(projectId)}>
405+
<IconTrash size={16}/>
406+
</Button>
407+
</Flex>
408+
</CardSection>
409+
})}
410+
</Card>
411+
)}
331412
</TabContent>
332413
</>
333414
</DLayout>

src/packages/ce/src/role/Role.service.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
import {GraphqlClient} from "@core/util/graphql-client";
2121
import rolesQuery from "@edition/role/queries/Roles.query.graphql";
2222
import roleAssignAbilitiesMutation from "@edition/role/mutations/Role.assignAbilities.mutation.graphql";
23+
import roleAssignProjectsMutation from "@edition/role/mutations/Role.assignProjects.mutation.graphql";
2324

2425
export class RoleService extends DNamespaceRoleReactiveService {
2526

@@ -94,8 +95,33 @@ export class RoleService extends DNamespaceRoleReactiveService {
9495
return result.data?.namespacesRolesAssignAbilities ?? undefined
9596
}
9697

97-
roleAssignProject(payload: NamespacesRolesAssignProjectsInput): Promise<NamespacesRolesAssignProjectsPayload | undefined> {
98-
throw new Error("Method not implemented.")
98+
async roleAssignProject(payload: NamespacesRolesAssignProjectsInput): Promise<NamespacesRolesAssignProjectsPayload | undefined> {
99+
const result = await this.client.mutate<Mutation, NamespacesRolesAssignProjectsInput>({
100+
mutation: roleAssignProjectsMutation,
101+
variables: {
102+
...payload
103+
}
104+
})
105+
106+
//TODO: should be done by a new query
107+
if (result.data && result.data.namespacesRolesAssignProjects) {
108+
const currentRole = this.getById(payload.roleId)
109+
const index = super.values().findIndex(m => m.id === payload.roleId)
110+
111+
const newRole = new DNamespaceRoleView({
112+
...currentRole?.json(),
113+
assignedProjects: {
114+
count: payload.projectIds.length,
115+
nodes: payload.projectIds.map(id => ({
116+
id: id,
117+
})),
118+
}
119+
})
120+
121+
this.set(index, newRole)
122+
}
123+
124+
return result.data?.namespacesRolesAssignProjects ?? undefined
99125
}
100126

101127
roleCreate(payload: NamespacesRolesCreateInput): Promise<NamespacesRolesCreatePayload | undefined> {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
mutation roleAssignProjects($roleId: NamespaceRoleID!, $projectIds: [NamespaceProjectID!]!) {
2+
namespacesRolesAssignProjects(input: {
3+
roleId: $roleId,
4+
projectIds: $projectIds
5+
}) {
6+
errors {
7+
...on Error {
8+
errorCode,
9+
details {
10+
__typename
11+
}
12+
}
13+
}
14+
15+
}
16+
}

0 commit comments

Comments
 (0)