mirror of
https://github.com/Infisical/infisical.git
synced 2025-08-22 10:12:15 +00:00
Compare commits
10 Commits
misc/optim
...
daniel/sci
Author | SHA1 | Date | |
---|---|---|---|
|
41a3ac6bd4 | ||
|
2fb5cc1712 | ||
|
b352428032 | ||
|
914bb3d389 | ||
|
be70bfa33f | ||
|
7758e5dbfa | ||
|
22fca374f2 | ||
|
94039ca509 | ||
|
c8f124e4c5 | ||
|
2501c57030 |
@@ -116,6 +116,7 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
|
||||
approvals: z.number(),
|
||||
approvers: z
|
||||
.object({
|
||||
isOrgMembershipActive: z.boolean().nullable().optional(),
|
||||
userId: z.string().nullable().optional(),
|
||||
sequence: z.number().nullable().optional(),
|
||||
approvalsRequired: z.number().nullable().optional(),
|
||||
@@ -133,6 +134,7 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
|
||||
}),
|
||||
reviewers: z
|
||||
.object({
|
||||
isOrgMembershipActive: z.boolean().nullable().optional(),
|
||||
userId: z.string(),
|
||||
status: z.string()
|
||||
})
|
||||
|
@@ -294,12 +294,13 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
200: z.object({
|
||||
approval: SecretApprovalRequestsSchema.merge(
|
||||
z.object({
|
||||
// secretPath: z.string(),
|
||||
policy: z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
approvals: z.number(),
|
||||
approvers: approvalRequestUser.array(),
|
||||
approvers: approvalRequestUser
|
||||
.extend({ isOrgMembershipActive: z.boolean().nullable().optional() })
|
||||
.array(),
|
||||
bypassers: approvalRequestUser.array(),
|
||||
secretPath: z.string().optional().nullable(),
|
||||
enforcementLevel: z.string(),
|
||||
@@ -309,7 +310,13 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
environment: z.string(),
|
||||
statusChangedByUser: approvalRequestUser.optional(),
|
||||
committerUser: approvalRequestUser.nullish(),
|
||||
reviewers: approvalRequestUser.extend({ status: z.string(), comment: z.string().optional() }).array(),
|
||||
reviewers: approvalRequestUser
|
||||
.extend({
|
||||
status: z.string(),
|
||||
comment: z.string().optional(),
|
||||
isOrgMembershipActive: z.boolean().nullable().optional()
|
||||
})
|
||||
.array(),
|
||||
secretPath: z.string(),
|
||||
commits: secretRawSchema
|
||||
.omit({ _id: true, environment: true, workspace: true, type: true, version: true, secretValue: true })
|
||||
|
@@ -5,6 +5,7 @@ import {
|
||||
AccessApprovalRequestsSchema,
|
||||
TableName,
|
||||
TAccessApprovalRequests,
|
||||
TOrgMemberships,
|
||||
TUserGroupMembership,
|
||||
TUsers
|
||||
} from "@app/db/schemas";
|
||||
@@ -144,6 +145,7 @@ export interface TAccessApprovalRequestDALFactory extends Omit<TOrmify<TableName
|
||||
approvalsRequired: number | null | undefined;
|
||||
email: string | null | undefined;
|
||||
username: string;
|
||||
isOrgMembershipActive: boolean;
|
||||
}
|
||||
| {
|
||||
userId: string;
|
||||
@@ -151,6 +153,7 @@ export interface TAccessApprovalRequestDALFactory extends Omit<TOrmify<TableName
|
||||
approvalsRequired: number | null | undefined;
|
||||
email: string | null | undefined;
|
||||
username: string;
|
||||
isOrgMembershipActive: boolean;
|
||||
}
|
||||
)[];
|
||||
bypassers: string[];
|
||||
@@ -202,6 +205,7 @@ export interface TAccessApprovalRequestDALFactory extends Omit<TOrmify<TableName
|
||||
reviewers: {
|
||||
userId: string;
|
||||
status: string;
|
||||
isOrgMembershipActive: boolean;
|
||||
}[];
|
||||
approvers: (
|
||||
| {
|
||||
@@ -210,6 +214,7 @@ export interface TAccessApprovalRequestDALFactory extends Omit<TOrmify<TableName
|
||||
approvalsRequired: number | null | undefined;
|
||||
email: string | null | undefined;
|
||||
username: string;
|
||||
isOrgMembershipActive: boolean;
|
||||
}
|
||||
| {
|
||||
userId: string;
|
||||
@@ -217,6 +222,7 @@ export interface TAccessApprovalRequestDALFactory extends Omit<TOrmify<TableName
|
||||
approvalsRequired: number | null | undefined;
|
||||
email: string | null | undefined;
|
||||
username: string;
|
||||
isOrgMembershipActive: boolean;
|
||||
}
|
||||
)[];
|
||||
bypassers: string[];
|
||||
@@ -288,6 +294,24 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
|
||||
`requestedByUser.id`
|
||||
)
|
||||
|
||||
.leftJoin<TOrgMemberships>(
|
||||
db(TableName.OrgMembership).as("approverOrgMembership"),
|
||||
`${TableName.AccessApprovalPolicyApprover}.approverUserId`,
|
||||
`approverOrgMembership.userId`
|
||||
)
|
||||
|
||||
.leftJoin<TOrgMemberships>(
|
||||
db(TableName.OrgMembership).as("approverGroupOrgMembership"),
|
||||
`${TableName.Users}.id`,
|
||||
`approverGroupOrgMembership.userId`
|
||||
)
|
||||
|
||||
.leftJoin<TOrgMemberships>(
|
||||
db(TableName.OrgMembership).as("reviewerOrgMembership"),
|
||||
`${TableName.AccessApprovalRequestReviewer}.reviewerUserId`,
|
||||
`reviewerOrgMembership.userId`
|
||||
)
|
||||
|
||||
.leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
|
||||
|
||||
.select(selectAllTableCols(TableName.AccessApprovalRequest))
|
||||
@@ -300,6 +324,10 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
|
||||
db.ref("allowedSelfApprovals").withSchema(TableName.AccessApprovalPolicy).as("policyAllowedSelfApprovals"),
|
||||
db.ref("envId").withSchema(TableName.AccessApprovalPolicy).as("policyEnvId"),
|
||||
db.ref("deletedAt").withSchema(TableName.AccessApprovalPolicy).as("policyDeletedAt"),
|
||||
|
||||
db.ref("isActive").withSchema("approverOrgMembership").as("approverIsOrgMembershipActive"),
|
||||
db.ref("isActive").withSchema("approverGroupOrgMembership").as("approverGroupIsOrgMembershipActive"),
|
||||
db.ref("isActive").withSchema("reviewerOrgMembership").as("reviewerIsOrgMembershipActive"),
|
||||
db.ref("maxTimePeriod").withSchema(TableName.AccessApprovalPolicy).as("policyMaxTimePeriod")
|
||||
)
|
||||
.select(db.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
|
||||
@@ -396,17 +424,26 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
|
||||
{
|
||||
key: "reviewerUserId",
|
||||
label: "reviewers" as const,
|
||||
mapper: ({ reviewerUserId: userId, reviewerStatus: status }) => (userId ? { userId, status } : undefined)
|
||||
mapper: ({ reviewerUserId: userId, reviewerStatus: status, reviewerIsOrgMembershipActive }) =>
|
||||
userId ? { userId, status, isOrgMembershipActive: reviewerIsOrgMembershipActive } : undefined
|
||||
},
|
||||
{
|
||||
key: "approverUserId",
|
||||
label: "approvers" as const,
|
||||
mapper: ({ approverUserId, approverSequence, approvalsRequired, approverUsername, approverEmail }) => ({
|
||||
mapper: ({
|
||||
approverUserId,
|
||||
approverSequence,
|
||||
approvalsRequired,
|
||||
approverUsername,
|
||||
approverEmail,
|
||||
approverIsOrgMembershipActive
|
||||
}) => ({
|
||||
userId: approverUserId,
|
||||
sequence: approverSequence,
|
||||
approvalsRequired,
|
||||
email: approverEmail,
|
||||
username: approverUsername
|
||||
username: approverUsername,
|
||||
isOrgMembershipActive: approverIsOrgMembershipActive
|
||||
})
|
||||
},
|
||||
{
|
||||
@@ -417,13 +454,15 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
|
||||
approverSequence,
|
||||
approvalsRequired,
|
||||
approverGroupEmail,
|
||||
approverGroupUsername
|
||||
approverGroupUsername,
|
||||
approverGroupIsOrgMembershipActive
|
||||
}) => ({
|
||||
userId: approverGroupUserId,
|
||||
sequence: approverSequence,
|
||||
approvalsRequired,
|
||||
email: approverGroupEmail,
|
||||
username: approverGroupUsername
|
||||
username: approverGroupUsername,
|
||||
isOrgMembershipActive: approverGroupIsOrgMembershipActive
|
||||
})
|
||||
},
|
||||
{ key: "bypasserUserId", label: "bypassers" as const, mapper: ({ bypasserUserId }) => bypasserUserId },
|
||||
|
@@ -64,6 +64,7 @@ export interface TAccessApprovalRequestServiceFactory {
|
||||
approvalsRequired: number | null | undefined;
|
||||
email: string | null | undefined;
|
||||
username: string;
|
||||
isOrgMembershipActive: boolean;
|
||||
}
|
||||
| {
|
||||
userId: string;
|
||||
@@ -71,6 +72,7 @@ export interface TAccessApprovalRequestServiceFactory {
|
||||
approvalsRequired: number | null | undefined;
|
||||
email: string | null | undefined;
|
||||
username: string;
|
||||
isOrgMembershipActive: boolean;
|
||||
}
|
||||
)[];
|
||||
bypassers: string[];
|
||||
@@ -122,6 +124,7 @@ export interface TAccessApprovalRequestServiceFactory {
|
||||
reviewers: {
|
||||
userId: string;
|
||||
status: string;
|
||||
isOrgMembershipActive: boolean;
|
||||
}[];
|
||||
approvers: (
|
||||
| {
|
||||
@@ -130,6 +133,7 @@ export interface TAccessApprovalRequestServiceFactory {
|
||||
approvalsRequired: number | null | undefined;
|
||||
email: string | null | undefined;
|
||||
username: string;
|
||||
isOrgMembershipActive: boolean;
|
||||
}
|
||||
| {
|
||||
userId: string;
|
||||
@@ -137,6 +141,7 @@ export interface TAccessApprovalRequestServiceFactory {
|
||||
approvalsRequired: number | null | undefined;
|
||||
email: string | null | undefined;
|
||||
username: string;
|
||||
isOrgMembershipActive: boolean;
|
||||
}
|
||||
)[];
|
||||
bypassers: string[];
|
||||
|
@@ -4,6 +4,7 @@ import { TDbClient } from "@app/db";
|
||||
import {
|
||||
SecretApprovalRequestsSchema,
|
||||
TableName,
|
||||
TOrgMemberships,
|
||||
TSecretApprovalRequests,
|
||||
TSecretApprovalRequestsSecrets,
|
||||
TUserGroupMembership,
|
||||
@@ -107,11 +108,32 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
`${TableName.SecretApprovalRequestReviewer}.reviewerUserId`,
|
||||
`secretApprovalReviewerUser.id`
|
||||
)
|
||||
|
||||
.leftJoin<TOrgMemberships>(
|
||||
db(TableName.OrgMembership).as("approverOrgMembership"),
|
||||
`${TableName.SecretApprovalPolicyApprover}.approverUserId`,
|
||||
`approverOrgMembership.userId`
|
||||
)
|
||||
|
||||
.leftJoin<TOrgMemberships>(
|
||||
db(TableName.OrgMembership).as("approverGroupOrgMembership"),
|
||||
`secretApprovalPolicyGroupApproverUser.id`,
|
||||
`approverGroupOrgMembership.userId`
|
||||
)
|
||||
|
||||
.leftJoin<TOrgMemberships>(
|
||||
db(TableName.OrgMembership).as("reviewerOrgMembership"),
|
||||
`${TableName.SecretApprovalRequestReviewer}.reviewerUserId`,
|
||||
`reviewerOrgMembership.userId`
|
||||
)
|
||||
|
||||
.select(selectAllTableCols(TableName.SecretApprovalRequest))
|
||||
.select(
|
||||
tx.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover),
|
||||
tx.ref("userId").withSchema("approverUserGroupMembership").as("approverGroupUserId"),
|
||||
tx.ref("email").withSchema("secretApprovalPolicyApproverUser").as("approverEmail"),
|
||||
tx.ref("isActive").withSchema("approverOrgMembership").as("approverIsOrgMembershipActive"),
|
||||
tx.ref("isActive").withSchema("approverGroupOrgMembership").as("approverGroupIsOrgMembershipActive"),
|
||||
tx.ref("email").withSchema("secretApprovalPolicyGroupApproverUser").as("approverGroupEmail"),
|
||||
tx.ref("username").withSchema("secretApprovalPolicyApproverUser").as("approverUsername"),
|
||||
tx.ref("username").withSchema("secretApprovalPolicyGroupApproverUser").as("approverGroupUsername"),
|
||||
@@ -148,6 +170,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
tx.ref("username").withSchema("secretApprovalReviewerUser").as("reviewerUsername"),
|
||||
tx.ref("firstName").withSchema("secretApprovalReviewerUser").as("reviewerFirstName"),
|
||||
tx.ref("lastName").withSchema("secretApprovalReviewerUser").as("reviewerLastName"),
|
||||
tx.ref("isActive").withSchema("reviewerOrgMembership").as("reviewerIsOrgMembershipActive"),
|
||||
tx.ref("id").withSchema(TableName.SecretApprovalPolicy).as("policyId"),
|
||||
tx.ref("name").withSchema(TableName.SecretApprovalPolicy).as("policyName"),
|
||||
tx.ref("projectId").withSchema(TableName.Environment),
|
||||
@@ -211,9 +234,21 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
reviewerLastName: lastName,
|
||||
reviewerUsername: username,
|
||||
reviewerFirstName: firstName,
|
||||
reviewerComment: comment
|
||||
reviewerComment: comment,
|
||||
reviewerIsOrgMembershipActive: isOrgMembershipActive
|
||||
}) =>
|
||||
userId ? { userId, status, email, firstName, lastName, username, comment: comment ?? "" } : undefined
|
||||
userId
|
||||
? {
|
||||
userId,
|
||||
status,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
username,
|
||||
comment: comment ?? "",
|
||||
isOrgMembershipActive
|
||||
}
|
||||
: undefined
|
||||
},
|
||||
{
|
||||
key: "approverUserId",
|
||||
@@ -223,13 +258,15 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
approverEmail: email,
|
||||
approverUsername: username,
|
||||
approverLastName: lastName,
|
||||
approverFirstName: firstName
|
||||
approverFirstName: firstName,
|
||||
approverIsOrgMembershipActive: isOrgMembershipActive
|
||||
}) => ({
|
||||
userId,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
username
|
||||
username,
|
||||
isOrgMembershipActive
|
||||
})
|
||||
},
|
||||
{
|
||||
@@ -240,13 +277,15 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
approverGroupEmail: email,
|
||||
approverGroupUsername: username,
|
||||
approverGroupLastName: lastName,
|
||||
approverGroupFirstName: firstName
|
||||
approverGroupFirstName: firstName,
|
||||
approverGroupIsOrgMembershipActive: isOrgMembershipActive
|
||||
}) => ({
|
||||
userId,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
username
|
||||
username,
|
||||
isOrgMembershipActive
|
||||
})
|
||||
},
|
||||
{
|
||||
|
@@ -258,6 +258,7 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
||||
|
||||
const secretApprovalRequest = await secretApprovalRequestDAL.findById(id);
|
||||
|
||||
if (!secretApprovalRequest)
|
||||
throw new NotFoundError({ message: `Secret approval request with ID '${id}' not found` });
|
||||
|
||||
|
@@ -108,7 +108,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
id: true
|
||||
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true })),
|
||||
})
|
||||
.merge(UserEncryptionKeysSchema.pick({ publicKey: true }))
|
||||
.extend({
|
||||
isOrgMembershipActive: z.boolean()
|
||||
}),
|
||||
project: SanitizedProjectSchema.pick({ name: true, id: true }),
|
||||
roles: z.array(
|
||||
z.object({
|
||||
|
@@ -156,6 +156,7 @@ export const groupProjectDALFactory = (db: TDbClient) => {
|
||||
`${TableName.GroupProjectMembershipRole}.customRoleId`,
|
||||
`${TableName.ProjectRoles}.id`
|
||||
)
|
||||
.join(TableName.OrgMembership, `${TableName.Users}.id`, `${TableName.OrgMembership}.userId`)
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.UserGroupMembership),
|
||||
db.ref("createdAt").withSchema(TableName.UserGroupMembership),
|
||||
@@ -176,7 +177,8 @@ export const groupProjectDALFactory = (db: TDbClient) => {
|
||||
db.ref("temporaryRange").withSchema(TableName.GroupProjectMembershipRole),
|
||||
db.ref("temporaryAccessStartTime").withSchema(TableName.GroupProjectMembershipRole),
|
||||
db.ref("temporaryAccessEndTime").withSchema(TableName.GroupProjectMembershipRole),
|
||||
db.ref("name").as("projectName").withSchema(TableName.Project)
|
||||
db.ref("name").as("projectName").withSchema(TableName.Project),
|
||||
db.ref("isActive").withSchema(TableName.OrgMembership)
|
||||
)
|
||||
.where({ isGhost: false });
|
||||
|
||||
@@ -192,7 +194,8 @@ export const groupProjectDALFactory = (db: TDbClient) => {
|
||||
id,
|
||||
userId,
|
||||
projectName,
|
||||
createdAt
|
||||
createdAt,
|
||||
isActive
|
||||
}) => ({
|
||||
isGroupMember: true,
|
||||
id,
|
||||
@@ -202,7 +205,7 @@ export const groupProjectDALFactory = (db: TDbClient) => {
|
||||
id: projectId,
|
||||
name: projectName
|
||||
},
|
||||
user: { email, username, firstName, lastName, id: userId, publicKey, isGhost },
|
||||
user: { email, username, firstName, lastName, id: userId, publicKey, isGhost, isOrgMembershipActive: isActive },
|
||||
createdAt
|
||||
}),
|
||||
key: "id",
|
||||
|
@@ -21,6 +21,14 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
|
||||
.where({ [`${TableName.ProjectMembership}.projectId` as "projectId"]: projectId })
|
||||
.join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`)
|
||||
.join(TableName.Users, `${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`)
|
||||
.join(TableName.OrgMembership, (qb) => {
|
||||
qb.on(`${TableName.Users}.id`, "=", `${TableName.OrgMembership}.userId`).andOn(
|
||||
`${TableName.OrgMembership}.orgId`,
|
||||
"=",
|
||||
`${TableName.Project}.orgId`
|
||||
);
|
||||
})
|
||||
|
||||
.where((qb) => {
|
||||
if (filter.usernames) {
|
||||
void qb.whereIn("username", filter.usernames);
|
||||
@@ -90,7 +98,8 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
|
||||
db.ref("temporaryRange").withSchema(TableName.ProjectUserMembershipRole),
|
||||
db.ref("temporaryAccessStartTime").withSchema(TableName.ProjectUserMembershipRole),
|
||||
db.ref("temporaryAccessEndTime").withSchema(TableName.ProjectUserMembershipRole),
|
||||
db.ref("name").as("projectName").withSchema(TableName.Project)
|
||||
db.ref("name").as("projectName").withSchema(TableName.Project),
|
||||
db.ref("isActive").withSchema(TableName.OrgMembership)
|
||||
)
|
||||
.where({ isGhost: false })
|
||||
.orderBy(`${TableName.Users}.username` as "username");
|
||||
@@ -107,12 +116,22 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
|
||||
id,
|
||||
userId,
|
||||
projectName,
|
||||
createdAt
|
||||
createdAt,
|
||||
isActive
|
||||
}) => ({
|
||||
id,
|
||||
userId,
|
||||
projectId,
|
||||
user: { email, username, firstName, lastName, id: userId, publicKey, isGhost },
|
||||
user: {
|
||||
email,
|
||||
username,
|
||||
firstName,
|
||||
lastName,
|
||||
id: userId,
|
||||
publicKey,
|
||||
isGhost,
|
||||
isOrgMembershipActive: isActive
|
||||
},
|
||||
project: {
|
||||
id: projectId,
|
||||
name: projectName
|
||||
|
@@ -97,7 +97,6 @@ export const projectMembershipServiceFactory = ({
|
||||
|
||||
const projectMembers = await projectMembershipDAL.findAllProjectMembers(projectId, { roles });
|
||||
|
||||
// projectMembers[0].project
|
||||
if (includeGroupMembers) {
|
||||
const groupMembers = await groupProjectDAL.findAllProjectGroupMembers(projectId);
|
||||
const allMembers = [
|
||||
|
@@ -36,6 +36,7 @@ export type Approver = {
|
||||
type: ApproverType;
|
||||
sequence?: number;
|
||||
approvalsRequired?: number;
|
||||
isOrgMembershipActive: boolean;
|
||||
};
|
||||
|
||||
export type Bypasser = {
|
||||
@@ -82,6 +83,7 @@ export type TAccessApprovalRequest = {
|
||||
name: string;
|
||||
approvals: number;
|
||||
approvers: {
|
||||
isOrgMembershipActive: boolean;
|
||||
userId: string;
|
||||
sequence?: number;
|
||||
approvalsRequired?: number;
|
||||
@@ -98,6 +100,7 @@ export type TAccessApprovalRequest = {
|
||||
};
|
||||
|
||||
reviewers: {
|
||||
isOrgMembershipActive: boolean;
|
||||
userId: string;
|
||||
status: string;
|
||||
}[];
|
||||
@@ -168,7 +171,7 @@ export type TCreateAccessPolicyDTO = {
|
||||
projectSlug: string;
|
||||
name?: string;
|
||||
environments: string[];
|
||||
approvers?: Approver[];
|
||||
approvers?: Omit<Approver, "isOrgMembershipActive">[];
|
||||
bypassers?: Bypasser[];
|
||||
approvals?: number;
|
||||
secretPath: string;
|
||||
@@ -181,7 +184,7 @@ export type TCreateAccessPolicyDTO = {
|
||||
export type TUpdateAccessPolicyDTO = {
|
||||
id: string;
|
||||
name?: string;
|
||||
approvers?: Approver[];
|
||||
approvers?: Omit<Approver, "isOrgMembershipActive">[];
|
||||
bypassers?: Bypasser[];
|
||||
secretPath?: string;
|
||||
environments?: string[];
|
||||
|
@@ -20,6 +20,7 @@ export enum ApproverType {
|
||||
}
|
||||
|
||||
export type Approver = {
|
||||
isOrgMembershipActive: boolean;
|
||||
id: string;
|
||||
type: ApproverType;
|
||||
};
|
||||
@@ -49,7 +50,7 @@ export type TCreateSecretPolicyDTO = {
|
||||
name?: string;
|
||||
environments: string[];
|
||||
secretPath: string;
|
||||
approvers?: Approver[];
|
||||
approvers?: Omit<Approver, "isOrgMembershipActive">[];
|
||||
bypassers?: Bypasser[];
|
||||
approvals?: number;
|
||||
enforcementLevel: EnforcementLevel;
|
||||
@@ -59,7 +60,7 @@ export type TCreateSecretPolicyDTO = {
|
||||
export type TUpdateSecretPolicyDTO = {
|
||||
id: string;
|
||||
name?: string;
|
||||
approvers?: Approver[];
|
||||
approvers?: Omit<Approver, "isOrgMembershipActive">[];
|
||||
bypassers?: Bypasser[];
|
||||
secretPath?: string;
|
||||
approvals?: number;
|
||||
|
@@ -53,6 +53,7 @@ export type TSecretApprovalRequest = {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
username: string;
|
||||
isOrgMembershipActive: boolean;
|
||||
}[];
|
||||
workspace: string;
|
||||
environment: string;
|
||||
@@ -62,6 +63,7 @@ export type TSecretApprovalRequest = {
|
||||
status: "open" | "close";
|
||||
policy: Omit<TSecretApprovalPolicy, "approvers" | "bypassers"> & {
|
||||
approvers: {
|
||||
isOrgMembershipActive: boolean;
|
||||
userId: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
|
@@ -83,6 +83,7 @@ export type TProjectMembership = {
|
||||
export type TWorkspaceUser = {
|
||||
id: string;
|
||||
user: {
|
||||
isOrgMembershipActive: boolean;
|
||||
email: string;
|
||||
username: string;
|
||||
firstName: string;
|
||||
|
@@ -3,7 +3,10 @@ import {
|
||||
faBan,
|
||||
faCheck,
|
||||
faHourglass,
|
||||
faTriangleExclamation
|
||||
faQuestionCircle,
|
||||
faTriangleExclamation,
|
||||
faUser,
|
||||
faUserSlash
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import ms from "ms";
|
||||
@@ -33,7 +36,7 @@ import { EnforcementLevel } from "@app/hooks/api/policies/enums";
|
||||
import { ApprovalStatus, TWorkspaceUser } from "@app/hooks/api/types";
|
||||
import { groupBy } from "@app/lib/fn/array";
|
||||
|
||||
const getReviewedStatusSymbol = (status?: ApprovalStatus) => {
|
||||
const getReviewedStatusSymbol = (status?: ApprovalStatus, isOrgMembershipActive?: boolean) => {
|
||||
if (status === ApprovalStatus.APPROVED)
|
||||
return (
|
||||
<Badge variant="success" className="flex h-4 items-center justify-center">
|
||||
@@ -46,6 +49,16 @@ const getReviewedStatusSymbol = (status?: ApprovalStatus) => {
|
||||
<FontAwesomeIcon icon={faBan} size="xs" />
|
||||
</Badge>
|
||||
);
|
||||
|
||||
if (!isOrgMembershipActive) {
|
||||
return (
|
||||
// Can't do a tooltip here because nested tooltips doesn't work properly as of yet.
|
||||
// TODO(daniel): Fix nested tooltips in the future.
|
||||
<Badge variant="danger" className="flex h-4 items-center justify-center">
|
||||
<FontAwesomeIcon icon={faUserSlash} size="xs" />
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Badge variant="primary" className="flex h-4 items-center justify-center">
|
||||
<FontAwesomeIcon icon={faHourglass} size="xs" />
|
||||
@@ -81,6 +94,7 @@ export const ReviewAccessRequestModal = ({
|
||||
}) => {
|
||||
const [isLoading, setIsLoading] = useState<"approved" | "rejected" | null>(null);
|
||||
const [bypassApproval, setBypassApproval] = useState(false);
|
||||
|
||||
const [bypassReason, setBypassReason] = useState("");
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { data: groupMemberships = [] } = useListWorkspaceGroups(currentWorkspace?.id || "");
|
||||
@@ -215,7 +229,10 @@ export const ReviewAccessRequestModal = ({
|
||||
const approvers = approversBySequence?.map((approverChain) => {
|
||||
const reviewers = request.policy.approvers
|
||||
.filter((el) => (el.sequence || 1) === approverChain.sequence)
|
||||
.map((el) => ({ ...el, status: reviewesGroupById?.[el.userId]?.[0]?.status }));
|
||||
.map((el) => ({
|
||||
...el,
|
||||
status: reviewesGroupById?.[el.userId]?.[0]?.status
|
||||
}));
|
||||
const hasApproved =
|
||||
reviewers.filter((el) => el.status === "approved").length >=
|
||||
(approverChain?.approvals || 1);
|
||||
@@ -383,12 +400,31 @@ export const ReviewAccessRequestModal = ({
|
||||
</div>
|
||||
)}
|
||||
<div className="grid flex-1 grid-cols-5 border-b border-mineshaft-600 p-4">
|
||||
<GenericFieldLabel className="col-span-2" label="Users">
|
||||
{approver?.user
|
||||
?.map(
|
||||
(el) => approverSequence?.membersGroupById?.[el.id]?.[0]?.user?.username
|
||||
)
|
||||
.join(", ")}
|
||||
<GenericFieldLabel className="col-span-2" icon={faUser} label="Users">
|
||||
{Boolean(approver.user.length) && (
|
||||
<div className="flex flex-row flex-wrap gap-2">
|
||||
{approver?.user?.map((el) => {
|
||||
const member = approverSequence?.membersGroupById?.[el.id]?.[0];
|
||||
if (!member) return null;
|
||||
|
||||
return member.user.isOrgMembershipActive ? (
|
||||
<span key={el.id}>{member.user.username}</span>
|
||||
) : (
|
||||
<span className="opacity-40" key={el.id}>
|
||||
{member.user.username}{" "}
|
||||
<span className="text-xs">
|
||||
<Tooltip content="This user has been deactivated and no longer has an active organization membership.">
|
||||
<div>
|
||||
(Inactive){" "}
|
||||
<FontAwesomeIcon size="xs" icon={faQuestionCircle} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</GenericFieldLabel>
|
||||
<GenericFieldLabel className="col-span-2" label="Groups">
|
||||
{approver?.group
|
||||
@@ -413,8 +449,18 @@ export const ReviewAccessRequestModal = ({
|
||||
key={`reviewer-${idx + 1}`}
|
||||
className="flex items-center gap-2 px-2 py-2 text-sm"
|
||||
>
|
||||
<div className="flex-1">{el.username}</div>
|
||||
{getReviewedStatusSymbol(el?.status as ApprovalStatus)}
|
||||
<div
|
||||
className={twMerge(
|
||||
"flex-1",
|
||||
!el.isOrgMembershipActive && "opacity-40"
|
||||
)}
|
||||
>
|
||||
{el.username}
|
||||
</div>
|
||||
{getReviewedStatusSymbol(
|
||||
el?.status as ApprovalStatus,
|
||||
el.isOrgMembershipActive
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
@@ -43,6 +43,9 @@ import {
|
||||
import { EnforcementLevel, PolicyType } from "@app/hooks/api/policies/enums";
|
||||
import { TWorkspaceUser } from "@app/hooks/api/users/types";
|
||||
|
||||
import { PolicyMemberOption } from "./PolicyMemberOption";
|
||||
import { PolicyBypasserMemberOption } from "./PolicyBypasserMemberOption";
|
||||
|
||||
type Props = {
|
||||
isOpen?: boolean;
|
||||
onToggle: (isOpen: boolean) => void;
|
||||
@@ -59,7 +62,11 @@ const formSchema = z
|
||||
secretPath: z.string().trim().min(1),
|
||||
approvals: z.number().min(1).default(1),
|
||||
userApprovers: z
|
||||
.object({ type: z.literal(ApproverType.User), id: z.string() })
|
||||
.object({
|
||||
type: z.literal(ApproverType.User),
|
||||
id: z.string(),
|
||||
isOrgMembershipActive: z.boolean().optional()
|
||||
})
|
||||
.array()
|
||||
.default([]),
|
||||
groupApprovers: z
|
||||
@@ -67,7 +74,11 @@ const formSchema = z
|
||||
.array()
|
||||
.default([]),
|
||||
userBypassers: z
|
||||
.object({ type: z.literal(BypasserType.User), id: z.string() })
|
||||
.object({
|
||||
type: z.literal(BypasserType.User),
|
||||
id: z.string(),
|
||||
isOrgMembershipActive: z.boolean().optional()
|
||||
})
|
||||
.array()
|
||||
.default([]),
|
||||
groupBypassers: z
|
||||
@@ -80,7 +91,11 @@ const formSchema = z
|
||||
sequenceApprovers: z
|
||||
.object({
|
||||
user: z
|
||||
.object({ type: z.literal(ApproverType.User), id: z.string() })
|
||||
.object({
|
||||
type: z.literal(ApproverType.User),
|
||||
id: z.string(),
|
||||
isOrgMembershipActive: z.boolean().optional()
|
||||
})
|
||||
.array()
|
||||
.default([]),
|
||||
group: z
|
||||
@@ -129,7 +144,7 @@ const Form = ({
|
||||
handleSubmit,
|
||||
watch,
|
||||
resetField,
|
||||
formState: { isSubmitting }
|
||||
formState: { isSubmitting, errors }
|
||||
} = useForm<TFormSchema>({
|
||||
resolver: zodResolver(formSchema),
|
||||
values: editValues
|
||||
@@ -139,7 +154,11 @@ const Form = ({
|
||||
userApprovers:
|
||||
editValues?.approvers
|
||||
?.filter((approver) => approver.type === ApproverType.User)
|
||||
.map(({ id, type }) => ({ id, type: type as ApproverType.User })) || [],
|
||||
.map(({ id, type, isOrgMembershipActive }) => ({
|
||||
id,
|
||||
type: type as ApproverType.User,
|
||||
isOrgMembershipActive
|
||||
})) || [],
|
||||
groupApprovers:
|
||||
editValues?.approvers
|
||||
?.filter((approver) => approver.type === ApproverType.Group)
|
||||
@@ -235,7 +254,9 @@ const Form = ({
|
||||
...data,
|
||||
approvers: sequenceApprovers?.flatMap((approvers, index) =>
|
||||
approvers.user
|
||||
.map((el) => ({ ...el, sequence: index + 1 }) as Approver)
|
||||
.map(
|
||||
(el) => ({ ...el, sequence: index + 1 }) as Omit<Approver, "isOrgMembershipActive">
|
||||
)
|
||||
.concat(approvers.group.map((el) => ({ ...el, sequence: index + 1 })))
|
||||
),
|
||||
approvalsRequired: sequenceApprovers?.map((el, index) => ({
|
||||
@@ -291,7 +312,9 @@ const Form = ({
|
||||
...data,
|
||||
approvers: sequenceApprovers?.flatMap((approvers, index) =>
|
||||
approvers.user
|
||||
.map((el) => ({ ...el, sequence: index + 1 }) as Approver)
|
||||
.map(
|
||||
(el) => ({ ...el, sequence: index + 1 }) as Omit<Approver, "isOrgMembershipActive">
|
||||
)
|
||||
.concat(approvers.group.map((el) => ({ ...el, sequence: index + 1 })))
|
||||
),
|
||||
approvalsRequired: sequenceApprovers?.map((el, index) => ({
|
||||
@@ -329,7 +352,8 @@ const Form = ({
|
||||
() =>
|
||||
members.map((member) => ({
|
||||
id: member.user.id,
|
||||
type: ApproverType.User
|
||||
type: ApproverType.User,
|
||||
isOrgMembershipActive: member.user.isOrgMembershipActive
|
||||
})),
|
||||
[members]
|
||||
);
|
||||
@@ -347,7 +371,8 @@ const Form = ({
|
||||
() =>
|
||||
members.map((member) => ({
|
||||
id: member.user.id,
|
||||
type: BypasserType.User
|
||||
type: BypasserType.User,
|
||||
isOrgMembershipActive: member.user.isOrgMembershipActive
|
||||
})),
|
||||
[members]
|
||||
);
|
||||
@@ -390,6 +415,8 @@ const Form = ({
|
||||
setDragOverItem(null);
|
||||
};
|
||||
|
||||
console.log("error", errors);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col space-y-3">
|
||||
<form onSubmit={handleSubmit(handleFormSubmit)}>
|
||||
@@ -608,6 +635,7 @@ const Form = ({
|
||||
isMulti
|
||||
placeholder="Select members..."
|
||||
options={memberOptions}
|
||||
components={{ Option: PolicyMemberOption }}
|
||||
getOptionValue={(option) => option.id}
|
||||
getOptionLabel={(option) => {
|
||||
const member = members?.find((m) => m.user.id === option.id);
|
||||
@@ -685,6 +713,7 @@ const Form = ({
|
||||
menuPlacement="top"
|
||||
isMulti
|
||||
placeholder="Select members..."
|
||||
components={{ Option: PolicyMemberOption }}
|
||||
options={memberOptions}
|
||||
getOptionValue={(option) => option.id}
|
||||
getOptionLabel={(option) => {
|
||||
@@ -783,6 +812,7 @@ const Form = ({
|
||||
menuPlacement="top"
|
||||
isMulti
|
||||
placeholder="Select members..."
|
||||
components={{ Option: PolicyBypasserMemberOption }}
|
||||
options={bypasserMemberOptions}
|
||||
getOptionValue={(option) => option.id}
|
||||
getOptionLabel={(option) => {
|
||||
|
@@ -3,6 +3,7 @@ import {
|
||||
faClipboardCheck,
|
||||
faEdit,
|
||||
faEllipsisV,
|
||||
faQuestionCircle,
|
||||
faTrash,
|
||||
faUser,
|
||||
faUserGroup
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
GenericFieldLabel,
|
||||
IconButton,
|
||||
Td,
|
||||
Tooltip,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { Badge } from "@app/components/v2/Badge";
|
||||
@@ -86,10 +88,9 @@ export const ApprovalPolicyRow = ({
|
||||
return entityInSameSequence?.map((el) => {
|
||||
return {
|
||||
sequence: el.sequence || policy.approvals,
|
||||
userLabels: members
|
||||
?.filter((member) => el.user.find((i) => i.id === member.user.id))
|
||||
.map((member) => getMemberLabel(member))
|
||||
.join(", "),
|
||||
|
||||
users: members.filter((member) => el.user.find((i) => i.id === member.user.id)),
|
||||
|
||||
groupLabels: groups
|
||||
?.filter(({ group }) => el.group.find((i) => i.id === group.id))
|
||||
.map(({ group }) => group.name)
|
||||
@@ -212,7 +213,27 @@ export const ApprovalPolicyRow = ({
|
||||
)}
|
||||
<div className="grid flex-1 grid-cols-5 border-b border-mineshaft-600 p-4">
|
||||
<GenericFieldLabel className="col-span-2" icon={faUser} label="Users">
|
||||
{el.userLabels}
|
||||
{Boolean(el.users.length) && (
|
||||
<div className="flex flex-row flex-wrap gap-2">
|
||||
{el.users.map((u) => {
|
||||
return u.user.isOrgMembershipActive ? (
|
||||
<span key={u.id}>{getMemberLabel(u)}</span>
|
||||
) : (
|
||||
<span className="opacity-40" key={u.id}>
|
||||
{getMemberLabel(u)}{" "}
|
||||
<span className="text-xs">
|
||||
<Tooltip content="This user has been deactivated and no longer has an active organization membership.">
|
||||
<div>
|
||||
(Inactive){" "}
|
||||
<FontAwesomeIcon size="xs" icon={faQuestionCircle} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</GenericFieldLabel>
|
||||
<GenericFieldLabel className="col-span-2" icon={faUserGroup} label="Groups">
|
||||
{el.groupLabels}
|
||||
|
@@ -0,0 +1,39 @@
|
||||
import { components, OptionProps } from "react-select";
|
||||
import { faCheckCircle } from "@fortawesome/free-regular-svg-icons";
|
||||
import { faBan } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { Badge } from "@app/components/v2";
|
||||
import { BypasserType } from "@app/hooks/api/accessApproval/types";
|
||||
|
||||
export const PolicyBypasserMemberOption = ({
|
||||
isSelected,
|
||||
children,
|
||||
...props
|
||||
}: OptionProps<{
|
||||
id: string;
|
||||
type: BypasserType;
|
||||
isOrgMembershipActive?: boolean;
|
||||
}>) => {
|
||||
return (
|
||||
<components.Option isSelected={isSelected} {...props}>
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<p
|
||||
className={twMerge("truncate", !props.data.isOrgMembershipActive && "text-mineshaft-400")}
|
||||
>
|
||||
{children}
|
||||
</p>
|
||||
{!props.data.isOrgMembershipActive && (
|
||||
<Badge className="pointer-events-none ml-1 mr-auto flex h-5 w-min items-center gap-1.5 whitespace-nowrap bg-mineshaft-400/50 text-bunker-300">
|
||||
<FontAwesomeIcon icon={faBan} />
|
||||
Inactive
|
||||
</Badge>
|
||||
)}
|
||||
{isSelected && (
|
||||
<FontAwesomeIcon className="ml-2 text-primary" icon={faCheckCircle} size="sm" />
|
||||
)}
|
||||
</div>
|
||||
</components.Option>
|
||||
);
|
||||
};
|
@@ -0,0 +1,39 @@
|
||||
import { components, OptionProps } from "react-select";
|
||||
import { faCheckCircle } from "@fortawesome/free-regular-svg-icons";
|
||||
import { faBan } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { Badge } from "@app/components/v2";
|
||||
import { ApproverType } from "@app/hooks/api/accessApproval/types";
|
||||
|
||||
export const PolicyMemberOption = ({
|
||||
isSelected,
|
||||
children,
|
||||
...props
|
||||
}: OptionProps<{
|
||||
id: string;
|
||||
isOrgMembershipActive?: boolean;
|
||||
type: ApproverType;
|
||||
}>) => {
|
||||
return (
|
||||
<components.Option isSelected={isSelected} {...props}>
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<p
|
||||
className={twMerge("truncate", !props.data.isOrgMembershipActive && "text-mineshaft-400")}
|
||||
>
|
||||
{children}
|
||||
</p>
|
||||
{!props.data.isOrgMembershipActive && (
|
||||
<Badge className="pointer-events-none ml-1 mr-auto flex h-5 w-min items-center gap-1.5 whitespace-nowrap bg-mineshaft-400/50 text-bunker-300">
|
||||
<FontAwesomeIcon icon={faBan} />
|
||||
Inactive
|
||||
</Badge>
|
||||
)}
|
||||
{isSelected && (
|
||||
<FontAwesomeIcon className="ml-2 text-primary" icon={faCheckCircle} size="sm" />
|
||||
)}
|
||||
</div>
|
||||
</components.Option>
|
||||
);
|
||||
};
|
@@ -8,7 +8,8 @@ import {
|
||||
faCodeBranch,
|
||||
faComment,
|
||||
faFolder,
|
||||
faHourglass
|
||||
faHourglass,
|
||||
faUserSlash
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
@@ -85,6 +86,7 @@ const getReviewedStatusSymbol = (status?: ApprovalStatus) => {
|
||||
return <FontAwesomeIcon icon={faCheck} size="xs" className="text-green" />;
|
||||
if (status === ApprovalStatus.REJECTED)
|
||||
return <FontAwesomeIcon icon={faBan} size="xs" className="text-red" />;
|
||||
|
||||
return <FontAwesomeIcon icon={faHourglass} size="xs" className="text-yellow" />;
|
||||
};
|
||||
|
||||
@@ -162,11 +164,15 @@ export const SecretApprovalRequestChanges = ({
|
||||
secretApprovalRequestDetails.policy.bypassers.some(({ userId }) => userId === userSession.id);
|
||||
|
||||
const reviewedUsers = secretApprovalRequestDetails?.reviewers?.reduce<
|
||||
Record<string, { status: ApprovalStatus; comment: string }>
|
||||
Record<string, { status: ApprovalStatus; comment: string; isOrgMembershipActive: boolean }>
|
||||
>(
|
||||
(prev, curr) => ({
|
||||
...prev,
|
||||
[curr.userId]: { status: curr.status, comment: curr.comment }
|
||||
[curr.userId]: {
|
||||
status: curr.status,
|
||||
comment: curr.comment,
|
||||
isOrgMembershipActive: curr.isOrgMembershipActive
|
||||
}
|
||||
}),
|
||||
{}
|
||||
);
|
||||
@@ -533,26 +539,48 @@ export const SecretApprovalRequestChanges = ({
|
||||
)
|
||||
.map((requiredApprover) => {
|
||||
const reviewer = reviewedUsers?.[requiredApprover.userId];
|
||||
const { isOrgMembershipActive } = requiredApprover;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-nowrap items-center justify-between space-x-2 rounded border border-mineshaft-600 bg-mineshaft-800 px-2 py-1"
|
||||
key={`required-approver-${requiredApprover.userId}`}
|
||||
>
|
||||
<Tooltip
|
||||
content={
|
||||
requiredApprover.firstName
|
||||
? `${requiredApprover.firstName || ""} ${requiredApprover.lastName || ""}`
|
||||
: undefined
|
||||
}
|
||||
position="left"
|
||||
sideOffset={10}
|
||||
<div
|
||||
className={twMerge(
|
||||
"flex items-center gap-1 text-sm",
|
||||
!isOrgMembershipActive && "opacity-40"
|
||||
)}
|
||||
>
|
||||
<div className="flex text-sm">
|
||||
<div>{requiredApprover?.email}</div>
|
||||
<span className="text-red">*</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div>
|
||||
<Tooltip
|
||||
content={
|
||||
requiredApprover.firstName
|
||||
? `${requiredApprover.firstName || ""} ${requiredApprover.lastName || ""}`
|
||||
: undefined
|
||||
}
|
||||
position="left"
|
||||
sideOffset={10}
|
||||
>
|
||||
<div className="flex">
|
||||
<div>{requiredApprover?.email}</div>
|
||||
<span className="text-red">*</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
{!isOrgMembershipActive && (
|
||||
<Tooltip
|
||||
className="relative !z-[500]"
|
||||
content="This user has been deactivated and no longer has an active organization membership."
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faUserSlash}
|
||||
size="xs"
|
||||
className="text-mineshaft-300"
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
{reviewer?.comment && (
|
||||
<Tooltip className="max-w-lg break-words" content={reviewer.comment}>
|
||||
<FontAwesomeIcon
|
||||
@@ -562,9 +590,21 @@ export const SecretApprovalRequestChanges = ({
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip content={`Status: ${reviewer?.status || ApprovalStatus.PENDING}`}>
|
||||
{getReviewedStatusSymbol(reviewer?.status)}
|
||||
</Tooltip>
|
||||
<div className="flex gap-2">
|
||||
<Tooltip
|
||||
className="relative !z-[500]"
|
||||
content={
|
||||
<span className="text-sm">
|
||||
Status:{" "}
|
||||
<span className="capitalize">
|
||||
{reviewer?.status || ApprovalStatus.PENDING}
|
||||
</span>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
{getReviewedStatusSymbol(reviewer?.status)}
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -578,20 +618,43 @@ export const SecretApprovalRequestChanges = ({
|
||||
)
|
||||
.map((reviewer) => {
|
||||
const status = reviewedUsers?.[reviewer.userId].status;
|
||||
const { isOrgMembershipActive } = reviewer;
|
||||
return (
|
||||
<div
|
||||
className="flex flex-nowrap items-center space-x-2 rounded bg-mineshaft-800 px-2 py-1"
|
||||
className="flex flex-nowrap items-center justify-between space-x-2 rounded bg-mineshaft-800 px-2 py-1"
|
||||
key={`required-approver-${reviewer.userId}`}
|
||||
>
|
||||
<div className="flex-grow text-sm">
|
||||
<Tooltip content={`${reviewer.firstName || ""} ${reviewer.lastName || ""}`}>
|
||||
<span>{reviewer?.email} </span>
|
||||
<div
|
||||
className={twMerge(
|
||||
"flex items-center gap-1 text-sm",
|
||||
!isOrgMembershipActive && "opacity-40"
|
||||
)}
|
||||
>
|
||||
<Tooltip
|
||||
className="relative !z-[500]"
|
||||
content={`${reviewer.firstName || ""} ${reviewer.lastName || ""}`}
|
||||
>
|
||||
<div className="flex">
|
||||
<span>{reviewer?.email} </span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<span className="text-red">*</span>
|
||||
{!isOrgMembershipActive && (
|
||||
<Tooltip
|
||||
className="relative !z-[500]"
|
||||
content="This user has been deactivated and no longer has an active organization membership."
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faUserSlash}
|
||||
size="xs"
|
||||
className="text-mineshaft-300"
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{reviewer.comment && (
|
||||
<Tooltip content={reviewer.comment}>
|
||||
<Tooltip className="relative !z-[500]" content={reviewer.comment}>
|
||||
<FontAwesomeIcon
|
||||
icon={faComment}
|
||||
size="xs"
|
||||
@@ -599,7 +662,15 @@ export const SecretApprovalRequestChanges = ({
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip content={status || ApprovalStatus.PENDING}>
|
||||
<Tooltip
|
||||
className="relative !z-[500]"
|
||||
content={
|
||||
<span className="text-sm">
|
||||
Status:{" "}
|
||||
<span className="capitalize">{status || ApprovalStatus.PENDING}</span>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
{getReviewedStatusSymbol(status)}
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user