From 4dddf764bdbe18a389322716c120f80370c2b9b6 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Wed, 10 Jul 2024 18:14:34 +0700 Subject: [PATCH 1/2] Finish identity page project provisioning/deprovisioning --- .../routes/v2/identity-project-router.ts | 7 +- .../identity-project/identity-project-dal.ts | 10 +- frontend/src/hooks/api/identities/queries.tsx | 8 +- frontend/src/hooks/api/identities/types.ts | 2 + frontend/src/hooks/api/workspace/queries.tsx | 12 +- .../IdentityClientSecrets.tsx | 32 ++-- .../components/IdentityDetailsSection.tsx | 32 ++-- .../components/IdentityProjectsSection.tsx | 57 ------ .../IdentityAddToProjectModal.tsx | 175 ++++++++++++++++++ .../IdentityProjectRow.tsx | 67 +++++++ .../IdentityProjectsSection.tsx | 92 +++++++++ .../IdentityProjectsTable.tsx | 58 ++++++ .../IdentityProjectsSection/index.tsx | 1 + .../components/IdentityTokenModal.tsx | 1 - .../Org/IdentityPage/components/index.tsx | 2 +- 15 files changed, 457 insertions(+), 99 deletions(-) delete mode 100644 frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection.tsx create mode 100644 frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityAddToProjectModal.tsx create mode 100644 frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectRow.tsx create mode 100644 frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectsSection.tsx create mode 100644 frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectsTable.tsx create mode 100644 frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/index.tsx diff --git a/backend/src/server/routes/v2/identity-project-router.ts b/backend/src/server/routes/v2/identity-project-router.ts index d259a46fd..806a54d99 100644 --- a/backend/src/server/routes/v2/identity-project-router.ts +++ b/backend/src/server/routes/v2/identity-project-router.ts @@ -5,6 +5,7 @@ import { IdentitiesSchema, IdentityProjectMembershipsSchema, ProjectMembershipRole, + ProjectsSchema, ProjectUserMembershipRolesSchema } from "@app/db/schemas"; import { PROJECT_IDENTITIES } from "@app/lib/api-docs"; @@ -234,7 +235,8 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider) temporaryAccessEndTime: z.date().nullable().optional() }) ), - identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true }) + identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true }), + project: ProjectsSchema.pick({ name: true, id: true }) }) .array() }) @@ -291,7 +293,8 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider) temporaryAccessEndTime: z.date().nullable().optional() }) ), - identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true }) + identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true }), + project: ProjectsSchema.pick({ name: true, id: true }) }) }) } diff --git a/backend/src/services/identity-project/identity-project-dal.ts b/backend/src/services/identity-project/identity-project-dal.ts index adfc9cf77..76618889b 100644 --- a/backend/src/services/identity-project/identity-project-dal.ts +++ b/backend/src/services/identity-project/identity-project-dal.ts @@ -111,6 +111,7 @@ export const identityProjectDALFactory = (db: TDbClient) => { try { const docs = await (tx || db.replicaNode())(TableName.IdentityProjectMembership) .where(`${TableName.IdentityProjectMembership}.projectId`, projectId) + .join(TableName.Project, `${TableName.IdentityProjectMembership}.projectId`, `${TableName.Project}.id`) .join(TableName.Identity, `${TableName.IdentityProjectMembership}.identityId`, `${TableName.Identity}.id`) .where((qb) => { if (filter.identityId) { @@ -149,12 +150,13 @@ export const identityProjectDALFactory = (db: TDbClient) => { db.ref("isTemporary").withSchema(TableName.IdentityProjectMembershipRole), db.ref("temporaryRange").withSchema(TableName.IdentityProjectMembershipRole), db.ref("temporaryAccessStartTime").withSchema(TableName.IdentityProjectMembershipRole), - db.ref("temporaryAccessEndTime").withSchema(TableName.IdentityProjectMembershipRole) + db.ref("temporaryAccessEndTime").withSchema(TableName.IdentityProjectMembershipRole), + db.ref("name").as("projectName").withSchema(TableName.Project) ); const members = sqlNestRelationships({ data: docs, - parentMapper: ({ identityId, identityName, identityAuthMethod, id, createdAt, updatedAt }) => ({ + parentMapper: ({ identityId, identityName, identityAuthMethod, id, createdAt, updatedAt, projectName }) => ({ id, identityId, createdAt, @@ -163,6 +165,10 @@ export const identityProjectDALFactory = (db: TDbClient) => { id: identityId, name: identityName, authMethod: identityAuthMethod + }, + project: { + id: projectId, + name: projectName } }), key: "id", diff --git a/frontend/src/hooks/api/identities/queries.tsx b/frontend/src/hooks/api/identities/queries.tsx index 5a72ecb78..a9f2378bc 100644 --- a/frontend/src/hooks/api/identities/queries.tsx +++ b/frontend/src/hooks/api/identities/queries.tsx @@ -9,9 +9,11 @@ import { IdentityAzureAuth, IdentityGcpAuth, IdentityKubernetesAuth, + IdentityMembership, IdentityMembershipOrg, IdentityTokenAuth, - IdentityUniversalAuth} from "./types"; + IdentityUniversalAuth +} from "./types"; export const identitiesKeys = { getIdentityById: (identityId: string) => [{ identityId }, "identity"] as const, @@ -53,7 +55,9 @@ export const useGetIdentityProjectMemberships = (identityId: string) => { queryFn: async () => { const { data: { identityMemberships } - } = await apiRequest.get(`/api/v1/identities/${identityId}/identity-memberships`); + } = await apiRequest.get<{ identityMemberships: IdentityMembership[] }>( + `/api/v1/identities/${identityId}/identity-memberships` + ); return identityMemberships; } }); diff --git a/frontend/src/hooks/api/identities/types.ts b/frontend/src/hooks/api/identities/types.ts index 96dc70547..7ad32423f 100644 --- a/frontend/src/hooks/api/identities/types.ts +++ b/frontend/src/hooks/api/identities/types.ts @@ -1,4 +1,5 @@ import { TOrgRole } from "../roles/types"; +import { Workspace } from "../workspace/types"; import { IdentityAuthMethod } from "./enums"; export type IdentityTrustedIp = { @@ -45,6 +46,7 @@ export type IdentityMembershipOrg = { export type IdentityMembership = { id: string; identity: Identity; + project: Pick; roles: Array< { id: string; diff --git a/frontend/src/hooks/api/workspace/queries.tsx b/frontend/src/hooks/api/workspace/queries.tsx index 202480de8..1b818fd83 100644 --- a/frontend/src/hooks/api/workspace/queries.tsx +++ b/frontend/src/hooks/api/workspace/queries.tsx @@ -6,6 +6,7 @@ import { CaStatus } from "../ca/enums"; import { TCertificateAuthority } from "../ca/types"; import { TCertificate } from "../certificates/types"; import { TGroupMembership } from "../groups/types"; +import { identitiesKeys } from "../identities/queries"; import { IdentityMembership } from "../identities/types"; import { IntegrationAuth } from "../integrationAuth/types"; import { TIntegration } from "../integrations/types"; @@ -152,7 +153,7 @@ export const useGetWorkspaceById = (workspaceId: string) => { return useQuery({ queryKey: workspaceKeys.getWorkspaceById(workspaceId), queryFn: () => fetchWorkspaceById(workspaceId), - enabled: true + enabled: Boolean(workspaceId) }); }; @@ -441,8 +442,9 @@ export const useAddIdentityToWorkspace = () => { return identityMembership; }, - onSuccess: (_, { workspaceId }) => { + onSuccess: (_, { identityId, workspaceId }) => { queryClient.invalidateQueries(workspaceKeys.getWorkspaceIdentityMemberships(workspaceId)); + queryClient.invalidateQueries(identitiesKeys.getIdentityProjectMemberships(identityId)); } }); }; @@ -462,8 +464,9 @@ export const useUpdateIdentityWorkspaceRole = () => { return identityMembership; }, - onSuccess: (_, { workspaceId }) => { + onSuccess: (_, { identityId, workspaceId }) => { queryClient.invalidateQueries(workspaceKeys.getWorkspaceIdentityMemberships(workspaceId)); + queryClient.invalidateQueries(identitiesKeys.getIdentityProjectMemberships(identityId)); } }); }; @@ -485,8 +488,9 @@ export const useDeleteIdentityFromWorkspace = () => { ); return identityMembership; }, - onSuccess: (_, { workspaceId }) => { + onSuccess: (_, { identityId, workspaceId }) => { queryClient.invalidateQueries(workspaceKeys.getWorkspaceIdentityMemberships(workspaceId)); + queryClient.invalidateQueries(identitiesKeys.getIdentityProjectMemberships(identityId)); } }); }; diff --git a/frontend/src/views/Org/IdentityPage/components/IdentityAuthenticationSection/IdentityClientSecrets.tsx b/frontend/src/views/Org/IdentityPage/components/IdentityAuthenticationSection/IdentityClientSecrets.tsx index 21402fb6d..19810c3d0 100644 --- a/frontend/src/views/Org/IdentityPage/components/IdentityAuthenticationSection/IdentityClientSecrets.tsx +++ b/frontend/src/views/Org/IdentityPage/components/IdentityAuthenticationSection/IdentityClientSecrets.tsx @@ -1,4 +1,4 @@ -import { faCheck, faCopy,faKey, faTrash } from "@fortawesome/free-solid-svg-icons"; +import { faCheck, faCopy, faKey, faTrash } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { format } from "date-fns"; @@ -37,21 +37,23 @@ export const IdentityClientSecrets = ({ identityId, handlePopUpOpen }: Props) =>

Client ID

-
+

{identityUniversalAuth?.clientId ?? ""}

- - { - navigator.clipboard.writeText(identityUniversalAuth?.clientId ?? ""); - setCopyTextClientId("Copied"); - }} - > - - - +
+ + { + navigator.clipboard.writeText(identityUniversalAuth?.clientId ?? ""); + setCopyTextClientId("Copied"); + }} + > + + + +
{clientSecrets?.length ? ( diff --git a/frontend/src/views/Org/IdentityPage/components/IdentityDetailsSection.tsx b/frontend/src/views/Org/IdentityPage/components/IdentityDetailsSection.tsx index b2adb451d..aa7536bb8 100644 --- a/frontend/src/views/Org/IdentityPage/components/IdentityDetailsSection.tsx +++ b/frontend/src/views/Org/IdentityPage/components/IdentityDetailsSection.tsx @@ -1,4 +1,4 @@ -import { faCheck,faCopy, faPencil } from "@fortawesome/free-solid-svg-icons"; +import { faCheck, faCopy, faPencil } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { OrgPermissionCan } from "@app/components/permissions"; @@ -54,21 +54,23 @@ export const IdentityDetailsSection = ({ identityId, handlePopUpOpen }: Props) =

Identity ID

-
+

{data.identity.id}

- - { - navigator.clipboard.writeText(data.identity.id); - setCopyTextId("Copied"); - }} - > - - - +
+ + { + navigator.clipboard.writeText(data.identity.id); + setCopyTextId("Copied"); + }} + > + + + +
diff --git a/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection.tsx b/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection.tsx deleted file mode 100644 index abf8f950b..000000000 --- a/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { faKey } from "@fortawesome/free-solid-svg-icons"; - -import { - EmptyState, - Table, - TableContainer, - TableSkeleton, - TBody, - Td, - Th, - THead, - Tr -} from "@app/components/v2"; -import { useGetIdentityProjectMemberships } from "@app/hooks/api"; - -type Props = { - identityId: string; -}; - -export const IdentityProjectsSection = ({ identityId }: Props) => { - const { data: projectMemberships, isLoading } = useGetIdentityProjectMemberships(identityId); - return ( -
-
-

Projects

-
-
- - - - - - - - - - {isLoading && } - {!isLoading && - projectMemberships?.map((membership: any) => { - // TODO: fix any - return ( - - - - - ); - })} - -
NameRole
{membership.project.name}{membership.roles[0].role}
- {!isLoading && !projectMemberships?.length && ( - - )} -
-
-
- ); -}; diff --git a/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityAddToProjectModal.tsx b/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityAddToProjectModal.tsx new file mode 100644 index 000000000..e3f48c5b3 --- /dev/null +++ b/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityAddToProjectModal.tsx @@ -0,0 +1,175 @@ +import { useMemo } from "react"; +import { Controller, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { createNotification } from "@app/components/notifications"; +import { Button, FormControl, Modal, ModalContent,Select, SelectItem } from "@app/components/v2"; +import { useWorkspace } from "@app/context"; +import { + useAddIdentityToWorkspace, + useGetIdentityProjectMemberships, + useGetProjectRoles, + useGetWorkspaceById +} from "@app/hooks/api"; +import { UsePopUpState } from "@app/hooks/usePopUp"; + +const schema = z + .object({ + projectId: z.string(), + role: z.string() + }) + .required(); + +type FormData = z.infer; + +type Props = { + identityId: string; + popUp: UsePopUpState<["addIdentityToProject"]>; + handlePopUpToggle: ( + popUpName: keyof UsePopUpState<["addIdentityToProject"]>, + state?: boolean + ) => void; +}; + +export const IdentityAddToProjectModal = ({ identityId, popUp, handlePopUpToggle }: Props) => { + const { workspaces } = useWorkspace(); + const { mutateAsync: addIdentityToWorkspace } = useAddIdentityToWorkspace(); + + const { + control, + handleSubmit, + reset, + formState: { isSubmitting }, + watch + } = useForm({ + resolver: zodResolver(schema) + }); + + const projectId = watch("projectId"); + const { data: projectMemberships } = useGetIdentityProjectMemberships(identityId); + const { data: project } = useGetWorkspaceById(projectId); + const { data: roles } = useGetProjectRoles(project?.slug ?? ""); + + const filteredWorkspaces = useMemo(() => { + const wsWorkspaceIds = new Map(); + + projectMemberships?.forEach((projectMembership: any) => { + wsWorkspaceIds.set(projectMembership.project.id, true); + }); + + return (workspaces || []).filter(({ id }) => !wsWorkspaceIds.has(id)); + }, [workspaces, projectMemberships]); + + const onFormSubmit = async ({ projectId: workspaceId, role }: FormData) => { + try { + await addIdentityToWorkspace({ + workspaceId, + identityId, + role: role || undefined + }); + + createNotification({ + text: "Successfully added identity to project", + type: "success" + }); + + reset(); + handlePopUpToggle("addIdentityToProject", false); + } catch (err) { + console.error(err); + const error = err as any; + const text = error?.response?.data?.message ?? "Failed to add identity to project"; + + createNotification({ + text, + type: "error" + }); + } + }; + + return ( + { + handlePopUpToggle("addIdentityToProject", isOpen); + reset(); + }} + > + +
+ ( + + + + )} + /> + ( + + + + )} + /> +
+ + +
+ +
+
+ ); +}; diff --git a/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectRow.tsx b/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectRow.tsx new file mode 100644 index 000000000..9ada7a3f6 --- /dev/null +++ b/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectRow.tsx @@ -0,0 +1,67 @@ +import { useRouter } from "next/router"; +import { faTrash } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { format } from "date-fns"; + +import { IconButton, Td,Tooltip, Tr } from "@app/components/v2"; +import { IdentityMembership } from "@app/hooks/api/identities/types"; +import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; +import { UsePopUpState } from "@app/hooks/usePopUp"; + +type Props = { + membership: IdentityMembership; + handlePopUpOpen: ( + popUpName: keyof UsePopUpState<["removeIdentityFromProject"]>, + data?: {} + ) => void; +}; + +const formatRoleName = (role: string, customRoleName?: string) => { + if (role === ProjectMembershipRole.Custom) return customRoleName; + if (role === ProjectMembershipRole.Member) return "Developer"; + if (role === ProjectMembershipRole.Viewer) return "Viewer"; + if (role === ProjectMembershipRole.NoAccess) return "No access"; + return role; +}; + +export const IdentityProjectRow = ({ + membership: { id, createdAt, identity, project, roles }, + handlePopUpOpen +}: Props) => { + const router = useRouter(); + return ( + router.push(`/project/${project.id}/members`)} + > + {project.name} + {`${formatRoleName(roles[0].role, roles[0].customRoleName)}${ + roles.length > 1 ? ` (+${roles.length - 1})` : "" + }`} + {format(new Date(createdAt), "yyyy-MM-dd")} + +
+ + { + e.stopPropagation(); + handlePopUpOpen("removeIdentityFromProject", { + identityId: identity.id, + identityName: identity.name, + projectId: project.id, + projectName: project.name + }); + }} + > + + + +
+ + + ); +}; diff --git a/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectsSection.tsx b/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectsSection.tsx new file mode 100644 index 000000000..b0c13009d --- /dev/null +++ b/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectsSection.tsx @@ -0,0 +1,92 @@ +import { faPlus } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { createNotification } from "@app/components/notifications"; +import { DeleteActionModal, IconButton } from "@app/components/v2"; +import { useDeleteIdentityFromWorkspace } from "@app/hooks/api"; +import { usePopUp } from "@app/hooks/usePopUp"; + +import { IdentityAddToProjectModal } from "./IdentityAddToProjectModal"; +import { IdentityProjectsTable } from "./IdentityProjectsTable"; + +type Props = { + identityId: string; +}; + +export const IdentityProjectsSection = ({ identityId }: Props) => { + const { mutateAsync: deleteMutateAsync } = useDeleteIdentityFromWorkspace(); + + const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ + "addIdentityToProject", + "removeIdentityFromProject" + ] as const); + + const onRemoveIdentitySubmit = async (id: string, projectId: string) => { + try { + await deleteMutateAsync({ + identityId: id, + workspaceId: projectId + }); + + createNotification({ + text: "Successfully removed identity from project", + type: "success" + }); + + handlePopUpClose("removeIdentityFromProject"); + } catch (err) { + console.error(err); + const error = err as any; + const text = error?.response?.data?.message ?? "Failed to remove identity from project"; + + createNotification({ + text, + type: "error" + }); + } + }; + + return ( +
+
+

Projects

+ { + handlePopUpOpen("addIdentityToProject"); + }} + > + + +
+
+ +
+ handlePopUpToggle("removeIdentityFromProject", isOpen)} + deleteKey="confirm" + onDeleteApproved={() => { + const popupData = popUp?.removeIdentityFromProject?.data as { + identityId: string; + projectId: string; + }; + + return onRemoveIdentitySubmit(popupData.identityId, popupData.projectId); + }} + /> + +
+ ); +}; diff --git a/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectsTable.tsx b/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectsTable.tsx new file mode 100644 index 000000000..504c2405e --- /dev/null +++ b/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectsTable.tsx @@ -0,0 +1,58 @@ +import { faKey } from "@fortawesome/free-solid-svg-icons"; + +import { + EmptyState, + Table, + TableContainer, + TableSkeleton, + TBody, + Th, + THead, + Tr +} from "@app/components/v2"; +import { useGetIdentityProjectMemberships } from "@app/hooks/api"; +import { UsePopUpState } from "@app/hooks/usePopUp"; + +import { IdentityProjectRow } from "./IdentityProjectRow"; + +type Props = { + identityId: string; + handlePopUpOpen: ( + popUpName: keyof UsePopUpState<["removeIdentityFromProject"]>, + data?: {} + ) => void; +}; + +export const IdentityProjectsTable = ({ identityId, handlePopUpOpen }: Props) => { + const { data: projectMemberships, isLoading } = useGetIdentityProjectMemberships(identityId); + return ( + + + + + + + + + + + {isLoading && } + {!isLoading && + projectMemberships?.map((membership) => { + return ( + + ); + })} + +
NameRoleAdded On +
+ {!isLoading && !projectMemberships?.length && ( + + )} +
+ ); +}; diff --git a/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/index.tsx b/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/index.tsx new file mode 100644 index 000000000..4eb7d40a9 --- /dev/null +++ b/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/index.tsx @@ -0,0 +1 @@ +export { IdentityProjectsSection } from "./IdentityProjectsSection"; diff --git a/frontend/src/views/Org/IdentityPage/components/IdentityTokenModal.tsx b/frontend/src/views/Org/IdentityPage/components/IdentityTokenModal.tsx index dfc26592e..e4f04752a 100644 --- a/frontend/src/views/Org/IdentityPage/components/IdentityTokenModal.tsx +++ b/frontend/src/views/Org/IdentityPage/components/IdentityTokenModal.tsx @@ -92,7 +92,6 @@ export const IdentityTokenModal = ({ popUp, handlePopUpToggle }: Props) => { }); setToken(newTokenData.accessToken); - // note: may be helpful to tell user ttl etc. } createNotification({ diff --git a/frontend/src/views/Org/IdentityPage/components/index.tsx b/frontend/src/views/Org/IdentityPage/components/index.tsx index f9a810b7d..8fcbcfc21 100644 --- a/frontend/src/views/Org/IdentityPage/components/index.tsx +++ b/frontend/src/views/Org/IdentityPage/components/index.tsx @@ -1,6 +1,6 @@ export { IdentityAuthenticationSection } from "./IdentityAuthenticationSection/IdentityAuthenticationSection"; export { IdentityClientSecretModal } from "./IdentityClientSecretModal"; export { IdentityDetailsSection } from "./IdentityDetailsSection"; -export { IdentityProjectsSection } from "./IdentityProjectsSection"; +export { IdentityProjectsSection } from "./IdentityProjectsSection/IdentityProjectsSection"; export { IdentityTokenListModal } from "./IdentityTokenListModal"; export { IdentityTokenModal } from "./IdentityTokenModal"; From 1d9966af76f38fb12f2668ac045a4683891bd2d7 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Wed, 10 Jul 2024 18:15:39 +0700 Subject: [PATCH 2/2] Add admin to display identity table --- .../components/IdentityProjectsSection/IdentityProjectRow.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectRow.tsx b/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectRow.tsx index 9ada7a3f6..2c4c1148f 100644 --- a/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectRow.tsx +++ b/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectRow.tsx @@ -3,7 +3,7 @@ import { faTrash } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { format } from "date-fns"; -import { IconButton, Td,Tooltip, Tr } from "@app/components/v2"; +import { IconButton, Td, Tooltip, Tr } from "@app/components/v2"; import { IdentityMembership } from "@app/hooks/api/identities/types"; import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -18,6 +18,7 @@ type Props = { const formatRoleName = (role: string, customRoleName?: string) => { if (role === ProjectMembershipRole.Custom) return customRoleName; + if (role === ProjectMembershipRole.Admin) return "Admin"; if (role === ProjectMembershipRole.Member) return "Developer"; if (role === ProjectMembershipRole.Viewer) return "Viewer"; if (role === ProjectMembershipRole.NoAccess) return "No access";