Weave roles into groups

This commit is contained in:
Tuan Dang
2024-03-19 11:34:28 -07:00
parent d8d63ecaec
commit 2a134b9dc2
10 changed files with 132 additions and 50 deletions

View File

@@ -1,11 +1,46 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
import { DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols } from "@app/lib/knex";
export type TGroupDALFactory = ReturnType<typeof groupDALFactory>;
export const groupDALFactory = (db: TDbClient) => {
const groupOrm = ormify(db, TableName.Groups);
return { ...groupOrm };
const findByOrgId = async (orgId: string, tx?: Knex) => {
try {
const docs = await (tx || db)(TableName.Groups)
.where(`${TableName.Groups}.orgId`, orgId)
.leftJoin(TableName.OrgRoles, `${TableName.Groups}.roleId`, `${TableName.OrgRoles}.id`)
.select(selectAllTableCols(TableName.Groups))
// cr stands for custom role
.select(db.ref("id").as("crId").withSchema(TableName.OrgRoles))
.select(db.ref("name").as("crName").withSchema(TableName.OrgRoles))
.select(db.ref("slug").as("crSlug").withSchema(TableName.OrgRoles))
.select(db.ref("description").as("crDescription").withSchema(TableName.OrgRoles))
.select(db.ref("permissions").as("crPermission").withSchema(TableName.OrgRoles));
return docs.map(({ crId, crDescription, crSlug, crPermission, crName, ...el }) => ({
...el,
customRole: el.roleId
? {
id: crId,
name: crName,
slug: crSlug,
permissions: crPermission,
description: crDescription
}
: undefined
}));
} catch (error) {
throw new DatabaseError({ error, name: "FindByOrgId" });
}
};
return {
findByOrgId,
...groupOrm
};
};

View File

@@ -105,7 +105,7 @@ export const groupServiceFactory = ({ groupDAL, permissionService, licenseServic
...(role
? {
role: customRole ? OrgMembershipRole.Custom : role,
roleId: customRole?.id
roleId: customRole?.id ?? null
}
: {})
}

View File

@@ -5,6 +5,7 @@ import {
IncidentContactsSchema,
OrganizationsSchema,
OrgMembershipsSchema,
OrgRolesSchema,
UsersSchema
} from "@app/db/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@@ -212,7 +213,17 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
}),
response: {
200: z.object({
groups: GroupsSchema.array()
groups: GroupsSchema.merge(
z.object({
customRole: OrgRolesSchema.pick({
id: true,
name: true,
slug: true,
permissions: true,
description: true
}).optional()
})
).array()
})
}
},

View File

@@ -46,6 +46,7 @@ export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => {
actorOrgId: req.permission.orgId,
orgId: req.params.orgId
});
return { identityMemberships };
}
});

View File

@@ -119,7 +119,7 @@ export const orgServiceFactory = ({
const getOrgGroups = async ({ actor, actorId, orgId, actorAuthMethod, actorOrgId }: TGetOrgGroupsDTO) => {
await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
const groups = await groupDAL.find({ orgId });
const groups = await groupDAL.findByOrgId(orgId);
return groups;
};

View File

@@ -1,3 +1,9 @@
import { TOrgRole } from "../roles/types";
export type TGroupOrgMembership = TGroup & {
customRole?: TOrgRole;
}
export type TGroup = {
id: string;
name: string;

View File

@@ -2,7 +2,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { TGroup } from "../groups/types";
import { TGroupOrgMembership } from "../groups/types";
import { IdentityMembershipOrg } from "../identities/types";
import {
BillingDetails,
@@ -412,7 +412,7 @@ export const useGetOrganizationGroups = (organizationId: string) => {
queryFn: async () => {
const {
data: { groups }
} = await apiRequest.get<{ groups: TGroup[] }>(`/api/v1/organization/${organizationId}/groups`);
} = await apiRequest.get<{ groups: TGroupOrgMembership[] }>(`/api/v1/organization/${organizationId}/groups`);
return groups;
}

View File

@@ -1,16 +1,15 @@
import { useState } from "react";
// import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { faMagnifyingGlass, faPencil, faUsers, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { OrgPermissionCan } from "@app/components/permissions";
import {
// Button,
EmptyState,
IconButton,
Input,
// Select,
// SelectItem,
Select,
SelectItem,
Table,
TableContainer,
TableSkeleton,
@@ -19,14 +18,16 @@ import {
Th,
THead,
Tooltip,
Tr} from "@app/components/v2";
Tr
} from "@app/components/v2";
import {
OrgPermissionActions,
OrgPermissionSubjects,
useOrganization} from "@app/context";
import {
useGetOrganizationGroups,
// useGetOrgRoles
useGetOrgRoles,
useUpdateGroup
} from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
@@ -39,6 +40,11 @@ type Props = {
groupId?: string;
name?: string;
slug?: string;
role?: string;
customRole?: {
name: string;
slug: string;
}
}
) => void;
};
@@ -46,41 +52,40 @@ type Props = {
export const OrgGroupsTable = ({
handlePopUpOpen
}: Props) => {
// const { createNotification } = useNotificationContext();
const { createNotification } = useNotificationContext();
const [searchGroupsFilter, setSearchGroupsFilter] = useState("");
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { isLoading, data: groups } = useGetOrganizationGroups(orgId);
const { mutateAsync: updateMutateAsync } = useUpdateGroup();
// const { data: roles } = useGetOrgRoles(orgId);
const { data: roles } = useGetOrgRoles(orgId);
console.log("OrgGroupsTable groups: ", groups);
console.log("OrgGroupsTable roles: ", groups);
// const handleChangeRole = ({
// groupId,
// role
// }: {
// groupId: string;
// role: string;
// }) => {
// try {
const handleChangeRole = async ({
currentSlug,
role
}: {
currentSlug: string;
role: string;
}) => {
try {
await updateMutateAsync({
currentSlug,
role
});
// // TODO
// createNotification({
// text: "Successfully updated group role",
// type: "success"
// });
// } catch (err) {
// console.error(err);
// createNotification({
// text: "Failed to update group role",
// type: "error"
// });
// }
// }
createNotification({
text: "Successfully updated group role",
type: "success"
});
} catch (err) {
console.error(err);
createNotification({
text: "Failed to update group role",
type: "error"
});
}
}
return (
<div>
@@ -102,12 +107,40 @@ export const OrgGroupsTable = ({
</THead>
<TBody>
{isLoading && <TableSkeleton columns={4} innerKey="org-groups" />}
{!isLoading && groups?.map(({ id, name, slug }) => {
{!isLoading && groups?.map(({ id, name, slug, role, customRole }) => {
return (
<Tr className="h-10" key={`org-group-${id}`}>
<Td>{name}</Td>
<Td>{slug}</Td>
<Td>N/A</Td>
<Td>
<OrgPermissionCan
I={OrgPermissionActions.Edit}
a={OrgPermissionSubjects.Identity}
>
{(isAllowed) => {
return (
<Select
value={role === "custom" ? (customRole?.slug as string) : role}
isDisabled={!isAllowed}
className="w-40 bg-mineshaft-600"
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
onValueChange={(selectedRole) =>
handleChangeRole({
currentSlug: slug,
role: selectedRole
})
}
>
{(roles || []).map(({ slug: roleSlug, name: roleName }) => (
<SelectItem value={roleSlug} key={`role-option-${roleSlug}`}>
{roleName}
</SelectItem>
))}
</Select>
);
}}
</OrgPermissionCan>
</Td>
<Td>
<div className="flex items-center justify-end">
<OrgPermissionCan
@@ -121,7 +154,9 @@ export const OrgGroupsTable = ({
handlePopUpOpen("group", {
groupId: id,
name,
slug
slug,
role,
customRole
});
}}
size="lg"
@@ -143,7 +178,6 @@ export const OrgGroupsTable = ({
<Tooltip content="Delete group">
<IconButton
onClick={() => {
console.log("Delete group");
handlePopUpOpen("deleteGroup", {
slug,
name

View File

@@ -52,9 +52,6 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
const { data, isLoading } = useGetIdentityMembershipOrgs(orgId);
const { data: roles } = useGetOrgRoles(orgId);
console.log("IdentityTable data: ", data);
console.log("IdentityTable roles: ", roles);
const handleChangeRole = async ({ identityId, role }: { identityId: string; role: string }) => {
try {

View File

@@ -62,8 +62,6 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
const { data: serverDetails } = useFetchServerStatus();
const { data: members, isLoading: isMembersLoading } = useGetOrgUsers(orgId);
console.log("OrgGroupsTable members: ", members);
const { mutateAsync: addUserMutateAsync } = useAddUserToOrg();
const { mutateAsync: updateUserOrgRole } = useUpdateOrgUserRole();