mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-04 19:26:19 +00:00
improvements: address feedback and fix bugs
This commit is contained in:
@@ -89,7 +89,7 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
projectSlug: z.string().trim(),
|
projectSlug: z.string().trim(),
|
||||||
authorProjectMembershipId: z.string().trim().optional(),
|
authorUserId: z.string().trim().optional(),
|
||||||
envSlug: z.string().trim().optional()
|
envSlug: z.string().trim().optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
@@ -143,7 +143,7 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { requests } = await server.services.accessApprovalRequest.listApprovalRequests({
|
const { requests } = await server.services.accessApprovalRequest.listApprovalRequests({
|
||||||
projectSlug: req.query.projectSlug,
|
projectSlug: req.query.projectSlug,
|
||||||
authorProjectMembershipId: req.query.authorProjectMembershipId,
|
authorUserId: req.query.authorUserId,
|
||||||
envSlug: req.query.envSlug,
|
envSlug: req.query.envSlug,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
|
|||||||
@@ -725,16 +725,17 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
|
|||||||
)
|
)
|
||||||
|
|
||||||
.where(`${TableName.Environment}.projectId`, projectId)
|
.where(`${TableName.Environment}.projectId`, projectId)
|
||||||
.where(`${TableName.AccessApprovalPolicy}.deletedAt`, null)
|
|
||||||
.select(selectAllTableCols(TableName.AccessApprovalRequest))
|
.select(selectAllTableCols(TableName.AccessApprovalRequest))
|
||||||
.select(db.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus"))
|
.select(db.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus"))
|
||||||
.select(db.ref("reviewerUserId").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerUserId"));
|
.select(db.ref("reviewerUserId").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerUserId"))
|
||||||
|
.select(db.ref("deletedAt").withSchema(TableName.AccessApprovalPolicy).as("policyDeletedAt"));
|
||||||
|
|
||||||
const formattedRequests = sqlNestRelationships({
|
const formattedRequests = sqlNestRelationships({
|
||||||
data: accessRequests,
|
data: accessRequests,
|
||||||
key: "id",
|
key: "id",
|
||||||
parentMapper: (doc) => ({
|
parentMapper: (doc) => ({
|
||||||
...AccessApprovalRequestsSchema.parse(doc)
|
...AccessApprovalRequestsSchema.parse(doc),
|
||||||
|
isPolicyDeleted: Boolean(doc.policyDeletedAt)
|
||||||
}),
|
}),
|
||||||
childrenMapper: [
|
childrenMapper: [
|
||||||
{
|
{
|
||||||
@@ -751,7 +752,8 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
|
|||||||
(req) =>
|
(req) =>
|
||||||
!req.privilegeId &&
|
!req.privilegeId &&
|
||||||
!req.reviewers.some((r) => r.status === ApprovalStatus.REJECTED) &&
|
!req.reviewers.some((r) => r.status === ApprovalStatus.REJECTED) &&
|
||||||
req.status === ApprovalStatus.PENDING
|
req.status === ApprovalStatus.PENDING &&
|
||||||
|
!req.isPolicyDeleted
|
||||||
);
|
);
|
||||||
|
|
||||||
// an approval is finalized if there are any rejections, a privilege ID is set or the number of approvals is equal to the number of approvals required.
|
// an approval is finalized if there are any rejections, a privilege ID is set or the number of approvals is equal to the number of approvals required.
|
||||||
@@ -759,7 +761,8 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
|
|||||||
(req) =>
|
(req) =>
|
||||||
req.privilegeId ||
|
req.privilegeId ||
|
||||||
req.reviewers.some((r) => r.status === ApprovalStatus.REJECTED) ||
|
req.reviewers.some((r) => r.status === ApprovalStatus.REJECTED) ||
|
||||||
req.status !== ApprovalStatus.PENDING
|
req.status !== ApprovalStatus.PENDING ||
|
||||||
|
req.isPolicyDeleted
|
||||||
);
|
);
|
||||||
|
|
||||||
return { pendingCount: pendingApprovals.length, finalizedCount: finalizedApprovals.length };
|
return { pendingCount: pendingApprovals.length, finalizedCount: finalizedApprovals.length };
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
|
|
||||||
const listApprovalRequests: TAccessApprovalRequestServiceFactory["listApprovalRequests"] = async ({
|
const listApprovalRequests: TAccessApprovalRequestServiceFactory["listApprovalRequests"] = async ({
|
||||||
projectSlug,
|
projectSlug,
|
||||||
authorProjectMembershipId,
|
authorUserId,
|
||||||
envSlug,
|
envSlug,
|
||||||
actor,
|
actor,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
@@ -300,8 +300,8 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
const policies = await accessApprovalPolicyDAL.find({ projectId: project.id });
|
const policies = await accessApprovalPolicyDAL.find({ projectId: project.id });
|
||||||
let requests = await accessApprovalRequestDAL.findRequestsWithPrivilegeByPolicyIds(policies.map((p) => p.id));
|
let requests = await accessApprovalRequestDAL.findRequestsWithPrivilegeByPolicyIds(policies.map((p) => p.id));
|
||||||
|
|
||||||
if (authorProjectMembershipId) {
|
if (authorUserId) {
|
||||||
requests = requests.filter((request) => request.requestedByUserId === actorId);
|
requests = requests.filter((request) => request.requestedByUserId === authorUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (envSlug) {
|
if (envSlug) {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export type TCreateAccessApprovalRequestDTO = {
|
|||||||
|
|
||||||
export type TListApprovalRequestsDTO = {
|
export type TListApprovalRequestsDTO = {
|
||||||
projectSlug: string;
|
projectSlug: string;
|
||||||
authorProjectMembershipId?: string;
|
authorUserId?: string;
|
||||||
envSlug?: string;
|
envSlug?: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
status: null,
|
status: null,
|
||||||
trial_end: null,
|
trial_end: null,
|
||||||
has_used_trial: true,
|
has_used_trial: true,
|
||||||
secretApproval: false,
|
secretApproval: true,
|
||||||
secretRotation: false,
|
secretRotation: false,
|
||||||
caCrl: false,
|
caCrl: false,
|
||||||
instanceUserManagement: false,
|
instanceUserManagement: false,
|
||||||
|
|||||||
@@ -315,7 +315,6 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
.where(`${TableName.SecretApprovalPolicyApprover}.approverUserId`, userId)
|
.where(`${TableName.SecretApprovalPolicyApprover}.approverUserId`, userId)
|
||||||
.orWhere(`${TableName.SecretApprovalRequest}.committerUserId`, userId)
|
.orWhere(`${TableName.SecretApprovalRequest}.committerUserId`, userId)
|
||||||
)
|
)
|
||||||
.andWhere((bd) => void bd.where(`${TableName.SecretApprovalPolicy}.deletedAt`, null))
|
|
||||||
.select("status", `${TableName.SecretApprovalRequest}.id`)
|
.select("status", `${TableName.SecretApprovalRequest}.id`)
|
||||||
.groupBy(`${TableName.SecretApprovalRequest}.id`, "status")
|
.groupBy(`${TableName.SecretApprovalRequest}.id`, "status")
|
||||||
.count("status")
|
.count("status")
|
||||||
@@ -347,7 +346,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
try {
|
try {
|
||||||
// akhilmhdh: If ever u wanted a 1 to so many relationship connected with pagination
|
// akhilmhdh: If ever u wanted a 1 to so many relationship connected with pagination
|
||||||
// this is the place u wanna look at.
|
// this is the place u wanna look at.
|
||||||
const query = (tx || db.replicaNode())(TableName.SecretApprovalRequest)
|
const innerQuery = (tx || db.replicaNode())(TableName.SecretApprovalRequest)
|
||||||
.join(TableName.SecretFolder, `${TableName.SecretApprovalRequest}.folderId`, `${TableName.SecretFolder}.id`)
|
.join(TableName.SecretFolder, `${TableName.SecretApprovalRequest}.folderId`, `${TableName.SecretFolder}.id`)
|
||||||
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
|
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
|
||||||
.join(
|
.join(
|
||||||
@@ -434,24 +433,31 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("email").withSchema("committerUser").as("committerUserEmail"),
|
db.ref("email").withSchema("committerUser").as("committerUserEmail"),
|
||||||
db.ref("username").withSchema("committerUser").as("committerUserUsername"),
|
db.ref("username").withSchema("committerUser").as("committerUserUsername"),
|
||||||
db.ref("firstName").withSchema("committerUser").as("committerUserFirstName"),
|
db.ref("firstName").withSchema("committerUser").as("committerUserFirstName"),
|
||||||
db.ref("lastName").withSchema("committerUser").as("committerUserLastName"),
|
db.ref("lastName").withSchema("committerUser").as("committerUserLastName")
|
||||||
|
|
||||||
db.raw(`count(*) OVER() as total_count`)
|
|
||||||
)
|
)
|
||||||
.orderBy("createdAt", "desc");
|
.distinctOn(`${TableName.SecretApprovalRequest}.id`)
|
||||||
|
.as("inner");
|
||||||
|
|
||||||
|
const query = (tx || db)
|
||||||
|
.select("*")
|
||||||
|
.select(db.raw("count(*) OVER() as total_count"))
|
||||||
|
.from(innerQuery)
|
||||||
|
.orderBy("createdAt", "desc") as typeof innerQuery;
|
||||||
|
|
||||||
if (search) {
|
if (search) {
|
||||||
void query
|
void query.where((qb) => {
|
||||||
.whereRaw(`CONCAT_WS(' ', ??, ??) ilike ?`, [
|
void qb
|
||||||
db.ref("firstName").withSchema("committerUser"),
|
.whereRaw(`CONCAT_WS(' ', ??, ??) ilike ?`, [
|
||||||
db.ref("lastName").withSchema("committerUser"),
|
db.ref("firstName").withSchema("committerUser"),
|
||||||
`%${search}%`
|
db.ref("lastName").withSchema("committerUser"),
|
||||||
])
|
`%${search}%`
|
||||||
.orWhereRaw(`?? ilike ?`, [db.ref("username").withSchema("committerUser"), `%${search}%`])
|
])
|
||||||
.orWhereRaw(`?? ilike ?`, [db.ref("email").withSchema("committerUser"), `%${search}%`])
|
.orWhereRaw(`?? ilike ?`, [db.ref("username").withSchema("committerUser"), `%${search}%`])
|
||||||
.orWhereILike(`${TableName.Environment}.name`, `%${search}%`)
|
.orWhereRaw(`?? ilike ?`, [db.ref("email").withSchema("committerUser"), `%${search}%`])
|
||||||
.orWhereILike(`${TableName.Environment}.slug`, `%${search}%`)
|
.orWhereILike(`${TableName.Environment}.name`, `%${search}%`)
|
||||||
.orWhereILike(`${TableName.SecretApprovalPolicy}.secretPath`, `%${search}%`);
|
.orWhereILike(`${TableName.Environment}.slug`, `%${search}%`)
|
||||||
|
.orWhereILike(`${TableName.SecretApprovalPolicy}.secretPath`, `%${search}%`);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const docs = await (tx || db)
|
const docs = await (tx || db)
|
||||||
@@ -544,7 +550,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
try {
|
try {
|
||||||
// akhilmhdh: If ever u wanted a 1 to so many relationship connected with pagination
|
// akhilmhdh: If ever u wanted a 1 to so many relationship connected with pagination
|
||||||
// this is the place u wanna look at.
|
// this is the place u wanna look at.
|
||||||
const query = (tx || db.replicaNode())(TableName.SecretApprovalRequest)
|
const innerQuery = (tx || db.replicaNode())(TableName.SecretApprovalRequest)
|
||||||
.join(TableName.SecretFolder, `${TableName.SecretApprovalRequest}.folderId`, `${TableName.SecretFolder}.id`)
|
.join(TableName.SecretFolder, `${TableName.SecretApprovalRequest}.folderId`, `${TableName.SecretFolder}.id`)
|
||||||
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
|
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
|
||||||
.join(
|
.join(
|
||||||
@@ -631,24 +637,31 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("email").withSchema("committerUser").as("committerUserEmail"),
|
db.ref("email").withSchema("committerUser").as("committerUserEmail"),
|
||||||
db.ref("username").withSchema("committerUser").as("committerUserUsername"),
|
db.ref("username").withSchema("committerUser").as("committerUserUsername"),
|
||||||
db.ref("firstName").withSchema("committerUser").as("committerUserFirstName"),
|
db.ref("firstName").withSchema("committerUser").as("committerUserFirstName"),
|
||||||
db.ref("lastName").withSchema("committerUser").as("committerUserLastName"),
|
db.ref("lastName").withSchema("committerUser").as("committerUserLastName")
|
||||||
|
|
||||||
db.raw(`count(*) OVER() as total_count`)
|
|
||||||
)
|
)
|
||||||
.orderBy("createdAt", "desc");
|
.distinctOn(`${TableName.SecretApprovalRequest}.id`)
|
||||||
|
.as("inner");
|
||||||
|
|
||||||
|
const query = (tx || db)
|
||||||
|
.select("*")
|
||||||
|
.select(db.raw("count(*) OVER() as total_count"))
|
||||||
|
.from(innerQuery)
|
||||||
|
.orderBy("createdAt", "desc") as typeof innerQuery;
|
||||||
|
|
||||||
if (search) {
|
if (search) {
|
||||||
void query
|
void query.where((qb) => {
|
||||||
.whereRaw(`CONCAT_WS(' ', ??, ??) ilike ?`, [
|
void qb
|
||||||
db.ref("firstName").withSchema("committerUser"),
|
.whereRaw(`CONCAT_WS(' ', ??, ??) ilike ?`, [
|
||||||
db.ref("lastName").withSchema("committerUser"),
|
db.ref("firstName").withSchema("committerUser"),
|
||||||
`%${search}%`
|
db.ref("lastName").withSchema("committerUser"),
|
||||||
])
|
`%${search}%`
|
||||||
.orWhereRaw(`?? ilike ?`, [db.ref("username").withSchema("committerUser"), `%${search}%`])
|
])
|
||||||
.orWhereRaw(`?? ilike ?`, [db.ref("email").withSchema("committerUser"), `%${search}%`])
|
.orWhereRaw(`?? ilike ?`, [db.ref("username").withSchema("committerUser"), `%${search}%`])
|
||||||
.orWhereILike(`${TableName.Environment}.name`, `%${search}%`)
|
.orWhereRaw(`?? ilike ?`, [db.ref("email").withSchema("committerUser"), `%${search}%`])
|
||||||
.orWhereILike(`${TableName.Environment}.slug`, `%${search}%`)
|
.orWhereILike(`${TableName.Environment}.name`, `%${search}%`)
|
||||||
.orWhereILike(`${TableName.SecretApprovalPolicy}.secretPath`, `%${search}%`);
|
.orWhereILike(`${TableName.Environment}.slug`, `%${search}%`)
|
||||||
|
.orWhereILike(`${TableName.SecretApprovalPolicy}.secretPath`, `%${search}%`);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const rankOffset = offset + 1;
|
const rankOffset = offset + 1;
|
||||||
|
|||||||
@@ -65,11 +65,11 @@ const fetchApprovalPolicies = async ({ projectSlug }: TGetAccessApprovalRequests
|
|||||||
const fetchApprovalRequests = async ({
|
const fetchApprovalRequests = async ({
|
||||||
projectSlug,
|
projectSlug,
|
||||||
envSlug,
|
envSlug,
|
||||||
authorProjectMembershipId
|
authorUserId
|
||||||
}: TGetAccessApprovalRequestsDTO) => {
|
}: TGetAccessApprovalRequestsDTO) => {
|
||||||
const { data } = await apiRequest.get<{ requests: TAccessApprovalRequest[] }>(
|
const { data } = await apiRequest.get<{ requests: TAccessApprovalRequest[] }>(
|
||||||
"/api/v1/access-approvals/requests",
|
"/api/v1/access-approvals/requests",
|
||||||
{ params: { projectSlug, envSlug, authorProjectMembershipId } }
|
{ params: { projectSlug, envSlug, authorUserId } }
|
||||||
);
|
);
|
||||||
|
|
||||||
return data.requests.map((request) => ({
|
return data.requests.map((request) => ({
|
||||||
@@ -109,12 +109,12 @@ export const useGetAccessRequestsCount = ({
|
|||||||
export const useGetAccessApprovalPolicies = ({
|
export const useGetAccessApprovalPolicies = ({
|
||||||
projectSlug,
|
projectSlug,
|
||||||
envSlug,
|
envSlug,
|
||||||
authorProjectMembershipId,
|
authorUserId,
|
||||||
options = {}
|
options = {}
|
||||||
}: TGetAccessApprovalRequestsDTO & TReactQueryOptions) =>
|
}: TGetAccessApprovalRequestsDTO & TReactQueryOptions) =>
|
||||||
useQuery({
|
useQuery({
|
||||||
queryKey: accessApprovalKeys.getAccessApprovalPolicies(projectSlug),
|
queryKey: accessApprovalKeys.getAccessApprovalPolicies(projectSlug),
|
||||||
queryFn: () => fetchApprovalPolicies({ projectSlug, envSlug, authorProjectMembershipId }),
|
queryFn: () => fetchApprovalPolicies({ projectSlug, envSlug, authorUserId }),
|
||||||
...options,
|
...options,
|
||||||
enabled: Boolean(projectSlug) && (options?.enabled ?? true)
|
enabled: Boolean(projectSlug) && (options?.enabled ?? true)
|
||||||
});
|
});
|
||||||
@@ -122,16 +122,13 @@ export const useGetAccessApprovalPolicies = ({
|
|||||||
export const useGetAccessApprovalRequests = ({
|
export const useGetAccessApprovalRequests = ({
|
||||||
projectSlug,
|
projectSlug,
|
||||||
envSlug,
|
envSlug,
|
||||||
authorProjectMembershipId,
|
authorUserId,
|
||||||
options = {}
|
options = {}
|
||||||
}: TGetAccessApprovalRequestsDTO & TReactQueryOptions) =>
|
}: TGetAccessApprovalRequestsDTO & TReactQueryOptions) =>
|
||||||
useQuery({
|
useQuery({
|
||||||
queryKey: accessApprovalKeys.getAccessApprovalRequests(
|
queryKey: accessApprovalKeys.getAccessApprovalRequests(projectSlug, envSlug, authorUserId),
|
||||||
projectSlug,
|
queryFn: () => fetchApprovalRequests({ projectSlug, envSlug, authorUserId }),
|
||||||
envSlug,
|
|
||||||
authorProjectMembershipId
|
|
||||||
),
|
|
||||||
queryFn: () => fetchApprovalRequests({ projectSlug, envSlug, authorProjectMembershipId }),
|
|
||||||
...options,
|
...options,
|
||||||
enabled: Boolean(projectSlug) && (options?.enabled ?? true)
|
enabled: Boolean(projectSlug) && (options?.enabled ?? true),
|
||||||
|
placeholderData: (previousData) => previousData
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ export type TCreateAccessRequestDTO = {
|
|||||||
export type TGetAccessApprovalRequestsDTO = {
|
export type TGetAccessApprovalRequestsDTO = {
|
||||||
projectSlug: string;
|
projectSlug: string;
|
||||||
envSlug?: string;
|
envSlug?: string;
|
||||||
authorProjectMembershipId?: string;
|
authorUserId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TGetAccessPolicyApprovalCountDTO = {
|
export type TGetAccessPolicyApprovalCountDTO = {
|
||||||
|
|||||||
+70
-25
@@ -3,6 +3,7 @@
|
|||||||
import { useCallback, useMemo, useState } from "react";
|
import { useCallback, useMemo, useState } from "react";
|
||||||
import {
|
import {
|
||||||
faArrowUpRightFromSquare,
|
faArrowUpRightFromSquare,
|
||||||
|
faBan,
|
||||||
faBookOpen,
|
faBookOpen,
|
||||||
faCheck,
|
faCheck,
|
||||||
faCheckCircle,
|
faCheckCircle,
|
||||||
@@ -12,10 +13,12 @@ import {
|
|||||||
faMagnifyingGlass,
|
faMagnifyingGlass,
|
||||||
faPlus,
|
faPlus,
|
||||||
faSearch,
|
faSearch,
|
||||||
faUser
|
faStopwatch,
|
||||||
|
faUser,
|
||||||
|
IconDefinition
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { formatDistance } from "date-fns";
|
import { format, formatDistance } from "date-fns";
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
@@ -133,7 +136,7 @@ export const AccessApprovalRequest = ({
|
|||||||
isPending: areRequestsPending
|
isPending: areRequestsPending
|
||||||
} = useGetAccessApprovalRequests({
|
} = useGetAccessApprovalRequests({
|
||||||
projectSlug,
|
projectSlug,
|
||||||
authorProjectMembershipId: requestedByFilter,
|
authorUserId: requestedByFilter,
|
||||||
envSlug: envFilter
|
envSlug: envFilter
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -203,9 +206,15 @@ export const AccessApprovalRequest = ({
|
|||||||
const canBypass =
|
const canBypass =
|
||||||
!request.policy.bypassers.length || request.policy.bypassers.includes(user.id);
|
!request.policy.bypassers.length || request.policy.bypassers.includes(user.id);
|
||||||
|
|
||||||
let displayData: { label: string; type: "primary" | "danger" | "success" } = {
|
let displayData: {
|
||||||
|
label: string;
|
||||||
|
type: "primary" | "danger" | "success";
|
||||||
|
tooltipContent?: string;
|
||||||
|
icon: IconDefinition | null;
|
||||||
|
} = {
|
||||||
label: "",
|
label: "",
|
||||||
type: "primary"
|
type: "primary",
|
||||||
|
icon: null
|
||||||
};
|
};
|
||||||
|
|
||||||
const isExpired =
|
const isExpired =
|
||||||
@@ -213,20 +222,42 @@ export const AccessApprovalRequest = ({
|
|||||||
request.isApproved &&
|
request.isApproved &&
|
||||||
new Date() > new Date(request.privilege.temporaryAccessEndTime || ("" as string));
|
new Date() > new Date(request.privilege.temporaryAccessEndTime || ("" as string));
|
||||||
|
|
||||||
if (isExpired) displayData = { label: "Access Expired", type: "danger" };
|
if (isExpired)
|
||||||
else if (isAccepted) displayData = { label: "Access Granted", type: "success" };
|
displayData = {
|
||||||
else if (isRejectedByAnyone) displayData = { label: "Rejected", type: "danger" };
|
label: "Access Expired",
|
||||||
|
type: "danger",
|
||||||
|
icon: faStopwatch,
|
||||||
|
tooltipContent: request.privilege?.temporaryAccessEndTime
|
||||||
|
? `Expired ${format(request.privilege.temporaryAccessEndTime, "M/d/yyyy h:mm aa")}`
|
||||||
|
: undefined
|
||||||
|
};
|
||||||
|
else if (isAccepted)
|
||||||
|
displayData = {
|
||||||
|
label: "Access Granted",
|
||||||
|
type: "success",
|
||||||
|
icon: faCheck,
|
||||||
|
tooltipContent: `Granted ${format(request.updatedAt, "M/d/yyyy h:mm aa")}`
|
||||||
|
};
|
||||||
|
else if (isRejectedByAnyone)
|
||||||
|
displayData = {
|
||||||
|
label: "Rejected",
|
||||||
|
type: "danger",
|
||||||
|
icon: faBan,
|
||||||
|
tooltipContent: `Rejected ${format(request.updatedAt, "M/d/yyyy h:mm aa")}`
|
||||||
|
};
|
||||||
else if (userReviewStatus === ApprovalStatus.APPROVED) {
|
else if (userReviewStatus === ApprovalStatus.APPROVED) {
|
||||||
displayData = {
|
displayData = {
|
||||||
label: `Pending ${request.policy.approvals - request.reviewers.length} review${
|
label: `Pending ${request.policy.approvals - request.reviewers.length} review${
|
||||||
request.policy.approvals - request.reviewers.length > 1 ? "s" : ""
|
request.policy.approvals - request.reviewers.length > 1 ? "s" : ""
|
||||||
}`,
|
}`,
|
||||||
type: "primary"
|
type: "primary",
|
||||||
|
icon: faClipboardCheck
|
||||||
};
|
};
|
||||||
} else if (!isReviewedByUser)
|
} else if (!isReviewedByUser)
|
||||||
displayData = {
|
displayData = {
|
||||||
label: "Review Required",
|
label: "Review Required",
|
||||||
type: "primary"
|
type: "primary",
|
||||||
|
icon: faClipboardCheck
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -266,6 +297,8 @@ export const AccessApprovalRequest = ({
|
|||||||
[generateRequestDetails, membersGroupById, user, setSelectedRequest, handlePopUpOpen]
|
[generateRequestDetails, membersGroupById, user, setSelectedRequest, handlePopUpOpen]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isFiltered = Boolean(search || envFilter || requestedByFilter);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -357,7 +390,7 @@ export const AccessApprovalRequest = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faCheck} className="mr-2" />
|
<FontAwesomeIcon icon={faCheck} className="mr-2" />
|
||||||
{!!requestCount && requestCount.finalizedCount} Completed
|
{!!requestCount && requestCount.finalizedCount} Closed
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-grow justify-end space-x-8">
|
<div className="flex flex-grow justify-end space-x-8">
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
@@ -365,7 +398,7 @@ export const AccessApprovalRequest = ({
|
|||||||
<Button
|
<Button
|
||||||
variant="plain"
|
variant="plain"
|
||||||
colorSchema="secondary"
|
colorSchema="secondary"
|
||||||
className="text-bunker-300"
|
className={envFilter ? "text-white" : "text-bunker-300"}
|
||||||
rightIcon={<FontAwesomeIcon icon={faChevronDown} size="sm" className="ml-2" />}
|
rightIcon={<FontAwesomeIcon icon={faChevronDown} size="sm" className="ml-2" />}
|
||||||
>
|
>
|
||||||
Environments
|
Environments
|
||||||
@@ -419,10 +452,16 @@ export const AccessApprovalRequest = ({
|
|||||||
{members?.map(({ user: membershipUser, id }) => (
|
{members?.map(({ user: membershipUser, id }) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setRequestedByFilter((state) => (state === id ? undefined : id))
|
setRequestedByFilter((state) =>
|
||||||
|
state === membershipUser.id ? undefined : membershipUser.id
|
||||||
|
)
|
||||||
}
|
}
|
||||||
key={`request-filter-member-${id}`}
|
key={`request-filter-member-${id}`}
|
||||||
icon={requestedByFilter === id && <FontAwesomeIcon icon={faCheckCircle} />}
|
icon={
|
||||||
|
requestedByFilter === membershipUser.id && (
|
||||||
|
<FontAwesomeIcon icon={faCheckCircle} />
|
||||||
|
)
|
||||||
|
}
|
||||||
iconPos="right"
|
iconPos="right"
|
||||||
>
|
>
|
||||||
{membershipUser.username}
|
{membershipUser.username}
|
||||||
@@ -434,16 +473,16 @@ export const AccessApprovalRequest = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col rounded-b-md border-x border-b border-t border-mineshaft-600 bg-mineshaft-800">
|
<div className="flex flex-col rounded-b-md border-x border-b border-t border-mineshaft-600 bg-mineshaft-800">
|
||||||
{filteredRequests?.length === 0 && !search && (
|
{filteredRequests?.length === 0 && !isFiltered && (
|
||||||
<div className="py-12">
|
<div className="py-12">
|
||||||
<EmptyState
|
<EmptyState
|
||||||
title={`No ${statusFilter === "open" ? "Pending" : "Completed"} Access Requests`}
|
title={`No ${statusFilter === "open" ? "Pending" : "Closed"} Access Requests`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{Boolean(!filteredRequests?.length && search && !areRequestsPending) && (
|
{Boolean(!filteredRequests?.length && isFiltered && !areRequestsPending) && (
|
||||||
<div className="py-12">
|
<div className="py-12">
|
||||||
<EmptyState title="No Requests Match Search" icon={faSearch} />
|
<EmptyState title="No Requests Match Filters" icon={faSearch} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!!filteredRequests?.length &&
|
{!!filteredRequests?.length &&
|
||||||
@@ -494,13 +533,19 @@ export const AccessApprovalRequest = ({
|
|||||||
<span>Requested By You</span>
|
<span>Requested By You</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Badge
|
<Tooltip content={details.displayData.tooltipContent}>
|
||||||
className="flex items-center gap-1.5 whitespace-nowrap"
|
<div>
|
||||||
variant={details.displayData.type}
|
<Badge
|
||||||
>
|
className="flex items-center gap-1.5 whitespace-nowrap"
|
||||||
<FontAwesomeIcon icon={faClipboardCheck} />
|
variant={details.displayData.type}
|
||||||
<span>{details.displayData.label}</span>
|
>
|
||||||
</Badge>
|
{details.displayData.icon && (
|
||||||
|
<FontAwesomeIcon icon={details.displayData.icon} />
|
||||||
|
)}
|
||||||
|
<span>{details.displayData.label}</span>
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+5
-3
@@ -126,6 +126,8 @@ export const SecretApprovalRequest = () => {
|
|||||||
|
|
||||||
const isRequestListEmpty = !isApprovalRequestLoading && secretApprovalRequests?.length === 0;
|
const isRequestListEmpty = !isApprovalRequestLoading && secretApprovalRequests?.length === 0;
|
||||||
|
|
||||||
|
const isFiltered = Boolean(searchFilter || envFilter || committerFilter);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
{isSecretApprovalScreen ? (
|
{isSecretApprovalScreen ? (
|
||||||
@@ -292,7 +294,7 @@ export const SecretApprovalRequest = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col rounded-b-md border-x border-b border-t border-mineshaft-600 bg-mineshaft-800">
|
<div className="flex flex-col rounded-b-md border-x border-b border-t border-mineshaft-600 bg-mineshaft-800">
|
||||||
{isRequestListEmpty && !searchFilter && (
|
{isRequestListEmpty && !isFiltered && (
|
||||||
<div className="py-12">
|
<div className="py-12">
|
||||||
<EmptyState
|
<EmptyState
|
||||||
title={`No ${statusFilter === "open" ? "Open" : "Closed"} Change Requests`}
|
title={`No ${statusFilter === "open" ? "Open" : "Closed"} Change Requests`}
|
||||||
@@ -344,10 +346,10 @@ export const SecretApprovalRequest = () => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{Boolean(
|
{Boolean(
|
||||||
!secretApprovalRequests.length && searchFilter && !isApprovalRequestLoading
|
!secretApprovalRequests.length && isFiltered && !isApprovalRequestLoading
|
||||||
) && (
|
) && (
|
||||||
<div className="py-12">
|
<div className="py-12">
|
||||||
<EmptyState title="No Requests Match Search" icon={faSearch} />
|
<EmptyState title="No Requests Match Filters" icon={faSearch} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{Boolean(totalApprovalCount) && (
|
{Boolean(totalApprovalCount) && (
|
||||||
|
|||||||
Reference in New Issue
Block a user