mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 16:27:40 +00:00
improvement: standardize and update server side pagination for change requests
This commit is contained in:
@@ -30,6 +30,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim(),
|
||||||
environment: z.string().trim().optional(),
|
environment: z.string().trim().optional(),
|
||||||
committer: z.string().trim().optional(),
|
committer: z.string().trim().optional(),
|
||||||
|
search: z.string().trim().optional(),
|
||||||
status: z.nativeEnum(RequestState).optional(),
|
status: z.nativeEnum(RequestState).optional(),
|
||||||
limit: z.coerce.number().default(20),
|
limit: z.coerce.number().default(20),
|
||||||
offset: z.coerce.number().default(0)
|
offset: z.coerce.number().default(0)
|
||||||
@@ -60,20 +61,20 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
committerUser: approvalRequestUser,
|
committerUser: approvalRequestUser,
|
||||||
commits: z.object({ op: z.string(), secretId: z.string().nullable().optional() }).array(),
|
commits: z.object({ op: z.string(), secretId: z.string().nullable().optional() }).array(),
|
||||||
environment: z.string(),
|
environment: z.string(),
|
||||||
secretPath: z.string(),
|
|
||||||
reviewers: z.object({ userId: z.string(), status: z.string() }).array(),
|
reviewers: z.object({ userId: z.string(), status: z.string() }).array(),
|
||||||
approvers: z
|
approvers: z
|
||||||
.object({
|
.object({
|
||||||
userId: z.string().nullable().optional()
|
userId: z.string().nullable().optional()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
}).array()
|
}).array(),
|
||||||
|
totalCount: z.number()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const approvals = await server.services.secretApprovalRequest.getSecretApprovals({
|
const { approvals, totalCount } = await server.services.secretApprovalRequest.getSecretApprovals({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
@@ -81,7 +82,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
...req.query,
|
...req.query,
|
||||||
projectId: req.query.workspaceId
|
projectId: req.query.workspaceId
|
||||||
});
|
});
|
||||||
return { approvals };
|
return { approvals, totalCount };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ type TFindQueryFilter = {
|
|||||||
committer?: string;
|
committer?: string;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
|
search?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
||||||
@@ -340,7 +341,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const findByProjectId = async (
|
const findByProjectId = async (
|
||||||
{ status, limit = 20, offset = 0, projectId, committer, environment, userId }: TFindQueryFilter,
|
{ status, limit = 20, offset = 0, projectId, committer, environment, userId, search }: TFindQueryFilter,
|
||||||
tx?: Knex
|
tx?: Knex
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
@@ -433,16 +434,36 @@ 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");
|
.orderBy("createdAt", "desc");
|
||||||
|
|
||||||
|
if (search) {
|
||||||
|
void query
|
||||||
|
.whereRaw(`CONCAT_WS(' ', ??, ??) ilike ?`, [
|
||||||
|
db.ref("firstName").withSchema("committerUser"),
|
||||||
|
db.ref("lastName").withSchema("committerUser"),
|
||||||
|
`%${search}%`
|
||||||
|
])
|
||||||
|
.orWhereRaw(`?? ilike ?`, [db.ref("username").withSchema("committerUser"), `%${search}%`])
|
||||||
|
.orWhereRaw(`?? ilike ?`, [db.ref("email").withSchema("committerUser"), `%${search}%`])
|
||||||
|
.orWhereILike(`${TableName.Environment}.name`, `%${search}%`)
|
||||||
|
.orWhereILike(`${TableName.Environment}.slug`, `%${search}%`)
|
||||||
|
.orWhereILike(`${TableName.SecretApprovalPolicy}.secretPath`, `%${search}%`);
|
||||||
|
}
|
||||||
|
|
||||||
const docs = await (tx || db)
|
const docs = await (tx || db)
|
||||||
.with("w", query)
|
.with("w", query)
|
||||||
.select("*")
|
.select("*")
|
||||||
.from<Awaited<typeof query>[number]>("w")
|
.from<Awaited<typeof query>[number]>("w")
|
||||||
.where("w.rank", ">=", offset)
|
.where("w.rank", ">=", offset)
|
||||||
.andWhere("w.rank", "<", offset + limit);
|
.andWhere("w.rank", "<", offset + limit);
|
||||||
|
|
||||||
|
// @ts-expect-error knex does not infer
|
||||||
|
const totalCount = Number(docs[0]?.total_count || 0);
|
||||||
|
|
||||||
const formattedDoc = sqlNestRelationships({
|
const formattedDoc = sqlNestRelationships({
|
||||||
data: docs,
|
data: docs,
|
||||||
key: "id",
|
key: "id",
|
||||||
@@ -504,17 +525,20 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
return formattedDoc.map((el) => ({
|
return {
|
||||||
...el,
|
approvals: formattedDoc.map((el) => ({
|
||||||
policy: { ...el.policy, approvers: el.approvers, bypassers: el.bypassers }
|
...el,
|
||||||
}));
|
policy: { ...el.policy, approvers: el.approvers, bypassers: el.bypassers }
|
||||||
|
})),
|
||||||
|
totalCount
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "FindSAR" });
|
throw new DatabaseError({ error, name: "FindSAR" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const findByProjectIdBridgeSecretV2 = async (
|
const findByProjectIdBridgeSecretV2 = async (
|
||||||
{ status, limit = 20, offset = 0, projectId, committer, environment, userId }: TFindQueryFilter,
|
{ status, limit = 20, offset = 0, projectId, committer, environment, userId, search }: TFindQueryFilter,
|
||||||
tx?: Knex
|
tx?: Knex
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
@@ -607,16 +631,37 @@ 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");
|
.orderBy("createdAt", "desc");
|
||||||
|
|
||||||
|
if (search) {
|
||||||
|
void query
|
||||||
|
.whereRaw(`CONCAT_WS(' ', ??, ??) ilike ?`, [
|
||||||
|
db.ref("firstName").withSchema("committerUser"),
|
||||||
|
db.ref("lastName").withSchema("committerUser"),
|
||||||
|
`%${search}%`
|
||||||
|
])
|
||||||
|
.orWhereRaw(`?? ilike ?`, [db.ref("username").withSchema("committerUser"), `%${search}%`])
|
||||||
|
.orWhereRaw(`?? ilike ?`, [db.ref("email").withSchema("committerUser"), `%${search}%`])
|
||||||
|
.orWhereILike(`${TableName.Environment}.name`, `%${search}%`)
|
||||||
|
.orWhereILike(`${TableName.Environment}.slug`, `%${search}%`)
|
||||||
|
.orWhereILike(`${TableName.SecretApprovalPolicy}.secretPath`, `%${search}%`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rankOffset = offset + 1;
|
||||||
const docs = await (tx || db)
|
const docs = await (tx || db)
|
||||||
.with("w", query)
|
.with("w", query)
|
||||||
.select("*")
|
.select("*")
|
||||||
.from<Awaited<typeof query>[number]>("w")
|
.from<Awaited<typeof query>[number]>("w")
|
||||||
.where("w.rank", ">=", offset)
|
.where("w.rank", ">=", rankOffset)
|
||||||
.andWhere("w.rank", "<", offset + limit);
|
.andWhere("w.rank", "<", rankOffset + limit);
|
||||||
|
|
||||||
|
// @ts-expect-error knex does not infer
|
||||||
|
const totalCount = Number(docs[0]?.total_count || 0);
|
||||||
|
|
||||||
const formattedDoc = sqlNestRelationships({
|
const formattedDoc = sqlNestRelationships({
|
||||||
data: docs,
|
data: docs,
|
||||||
key: "id",
|
key: "id",
|
||||||
@@ -682,10 +727,13 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
return formattedDoc.map((el) => ({
|
return {
|
||||||
...el,
|
approvals: formattedDoc.map((el) => ({
|
||||||
policy: { ...el.policy, approvers: el.approvers, bypassers: el.bypassers }
|
...el,
|
||||||
}));
|
policy: { ...el.policy, approvers: el.approvers, bypassers: el.bypassers }
|
||||||
|
})),
|
||||||
|
totalCount
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "FindSAR" });
|
throw new DatabaseError({ error, name: "FindSAR" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,7 +194,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
environment,
|
environment,
|
||||||
committer,
|
committer,
|
||||||
limit,
|
limit,
|
||||||
offset
|
offset,
|
||||||
|
search
|
||||||
}: TListApprovalsDTO) => {
|
}: TListApprovalsDTO) => {
|
||||||
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
||||||
|
|
||||||
@@ -209,47 +210,29 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
|
|
||||||
const { shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
const { shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
||||||
|
|
||||||
const getSecretMapPath = async (folderIds: string[]) => {
|
|
||||||
const secretPaths = await folderDAL.findSecretPathByFolderIds(projectId, folderIds);
|
|
||||||
|
|
||||||
const secretPathMap: Record<string, string> = {};
|
|
||||||
|
|
||||||
secretPaths.forEach((folder) => {
|
|
||||||
if (folder) secretPathMap[folder.id] = folder.path;
|
|
||||||
});
|
|
||||||
|
|
||||||
return secretPathMap;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (shouldUseSecretV2Bridge) {
|
if (shouldUseSecretV2Bridge) {
|
||||||
const approvalsV2 = await secretApprovalRequestDAL.findByProjectIdBridgeSecretV2({
|
return secretApprovalRequestDAL.findByProjectIdBridgeSecretV2({
|
||||||
projectId,
|
projectId,
|
||||||
committer,
|
committer,
|
||||||
environment,
|
environment,
|
||||||
status,
|
status,
|
||||||
userId: actorId,
|
userId: actorId,
|
||||||
limit,
|
limit,
|
||||||
offset
|
offset,
|
||||||
|
search
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretPathMap = await getSecretMapPath([...new Set(approvalsV2.map((approval) => approval.folderId))]);
|
|
||||||
|
|
||||||
return approvalsV2.map((approval) => ({ ...approval, secretPath: secretPathMap[approval.folderId] }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const approvals = await secretApprovalRequestDAL.findByProjectId({
|
return secretApprovalRequestDAL.findByProjectId({
|
||||||
projectId,
|
projectId,
|
||||||
committer,
|
committer,
|
||||||
environment,
|
environment,
|
||||||
status,
|
status,
|
||||||
userId: actorId,
|
userId: actorId,
|
||||||
limit,
|
limit,
|
||||||
offset
|
offset,
|
||||||
|
search
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretPathMap = await getSecretMapPath([...new Set(approvals.map((approval) => approval.folderId))]);
|
|
||||||
|
|
||||||
return approvals.map((approval) => ({ ...approval, secretPath: secretPathMap[approval.folderId] }));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSecretApprovalDetails = async ({
|
const getSecretApprovalDetails = async ({
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ export type TListApprovalsDTO = {
|
|||||||
committer?: string;
|
committer?: string;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
|
search?: string;
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type TSecretApprovalDetailsDTO = {
|
export type TSecretApprovalDetailsDTO = {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable no-param-reassign */
|
/* eslint-disable no-param-reassign */
|
||||||
import { useInfiniteQuery, useQuery, UseQueryOptions } from "@tanstack/react-query";
|
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
decryptAssymmetric,
|
decryptAssymmetric,
|
||||||
@@ -25,10 +25,11 @@ export const secretApprovalRequestKeys = {
|
|||||||
status,
|
status,
|
||||||
committer,
|
committer,
|
||||||
offset,
|
offset,
|
||||||
limit
|
limit,
|
||||||
|
search
|
||||||
}: TGetSecretApprovalRequestList) =>
|
}: TGetSecretApprovalRequestList) =>
|
||||||
[
|
[
|
||||||
{ workspaceId, environment, status, committer, offset, limit },
|
{ workspaceId, environment, status, committer, offset, limit, search },
|
||||||
"secret-approval-requests"
|
"secret-approval-requests"
|
||||||
] as const,
|
] as const,
|
||||||
detail: ({ id }: Omit<TGetSecretApprovalRequestDetails, "decryptKey">) =>
|
detail: ({ id }: Omit<TGetSecretApprovalRequestDetails, "decryptKey">) =>
|
||||||
@@ -118,23 +119,25 @@ const fetchSecretApprovalRequestList = async ({
|
|||||||
committer,
|
committer,
|
||||||
status = "open",
|
status = "open",
|
||||||
limit = 20,
|
limit = 20,
|
||||||
offset
|
offset = 0,
|
||||||
|
search = ""
|
||||||
}: TGetSecretApprovalRequestList) => {
|
}: TGetSecretApprovalRequestList) => {
|
||||||
const { data } = await apiRequest.get<{ approvals: TSecretApprovalRequest[] }>(
|
const { data } = await apiRequest.get<{
|
||||||
"/api/v1/secret-approval-requests",
|
approvals: TSecretApprovalRequest[];
|
||||||
{
|
totalCount: number;
|
||||||
params: {
|
}>("/api/v1/secret-approval-requests", {
|
||||||
workspaceId,
|
params: {
|
||||||
environment,
|
workspaceId,
|
||||||
committer,
|
environment,
|
||||||
status,
|
committer,
|
||||||
limit,
|
status,
|
||||||
offset
|
limit,
|
||||||
}
|
offset,
|
||||||
|
search
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
return data.approvals;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetSecretApprovalRequests = ({
|
export const useGetSecretApprovalRequests = ({
|
||||||
@@ -143,31 +146,32 @@ export const useGetSecretApprovalRequests = ({
|
|||||||
options = {},
|
options = {},
|
||||||
status,
|
status,
|
||||||
limit = 20,
|
limit = 20,
|
||||||
|
offset = 0,
|
||||||
|
search,
|
||||||
committer
|
committer
|
||||||
}: TGetSecretApprovalRequestList & TReactQueryOptions) =>
|
}: TGetSecretApprovalRequestList & TReactQueryOptions) =>
|
||||||
useInfiniteQuery({
|
useQuery({
|
||||||
initialPageParam: 0,
|
|
||||||
queryKey: secretApprovalRequestKeys.list({
|
queryKey: secretApprovalRequestKeys.list({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
committer,
|
committer,
|
||||||
status
|
status,
|
||||||
|
limit,
|
||||||
|
search,
|
||||||
|
offset
|
||||||
}),
|
}),
|
||||||
queryFn: ({ pageParam }) =>
|
queryFn: () =>
|
||||||
fetchSecretApprovalRequestList({
|
fetchSecretApprovalRequestList({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
status,
|
status,
|
||||||
committer,
|
committer,
|
||||||
limit,
|
limit,
|
||||||
offset: pageParam
|
offset,
|
||||||
|
search
|
||||||
}),
|
}),
|
||||||
enabled: Boolean(workspaceId) && (options?.enabled ?? true),
|
enabled: Boolean(workspaceId) && (options?.enabled ?? true),
|
||||||
getNextPageParam: (lastPage, pages) => {
|
placeholderData: (previousData) => previousData
|
||||||
if (lastPage.length && lastPage.length < limit) return undefined;
|
|
||||||
|
|
||||||
return lastPage?.length !== 0 ? pages.length * limit : undefined;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchSecretApprovalRequestDetails = async ({
|
const fetchSecretApprovalRequestDetails = async ({
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ export type TGetSecretApprovalRequestList = {
|
|||||||
committer?: string;
|
committer?: string;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
|
search?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TGetSecretApprovalRequestCount = {
|
export type TGetSecretApprovalRequestCount = {
|
||||||
|
|||||||
+57
-48
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
faArrowUpRightFromSquare,
|
faArrowUpRightFromSquare,
|
||||||
faBookOpen,
|
faBookOpen,
|
||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
EmptyState,
|
EmptyState,
|
||||||
Input,
|
Input,
|
||||||
|
Pagination,
|
||||||
Skeleton
|
Skeleton
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { ROUTE_PATHS } from "@app/const/routes";
|
import { ROUTE_PATHS } from "@app/const/routes";
|
||||||
@@ -34,6 +35,12 @@ import {
|
|||||||
useUser,
|
useUser,
|
||||||
useWorkspace
|
useWorkspace
|
||||||
} from "@app/context";
|
} from "@app/context";
|
||||||
|
import {
|
||||||
|
getUserTablePreference,
|
||||||
|
PreferenceKey,
|
||||||
|
setUserTablePreference
|
||||||
|
} from "@app/helpers/userTablePreferences";
|
||||||
|
import { usePagination } from "@app/hooks";
|
||||||
import {
|
import {
|
||||||
useGetSecretApprovalRequestCount,
|
useGetSecretApprovalRequestCount,
|
||||||
useGetSecretApprovalRequests,
|
useGetSecretApprovalRequests,
|
||||||
@@ -58,18 +65,41 @@ export const SecretApprovalRequest = () => {
|
|||||||
const [usingUrlRequestId, setUsingUrlRequestId] = useState(false);
|
const [usingUrlRequestId, setUsingUrlRequestId] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: secretApprovalRequests,
|
debouncedSearch: debouncedSearchFilter,
|
||||||
isFetchingNextPage: isFetchingNextApprovalRequest,
|
search: searchFilter,
|
||||||
fetchNextPage: fetchNextApprovalRequest,
|
setSearch: setSearchFilter,
|
||||||
hasNextPage: hasNextApprovalPage,
|
setPage,
|
||||||
|
page,
|
||||||
|
perPage,
|
||||||
|
setPerPage,
|
||||||
|
offset,
|
||||||
|
limit
|
||||||
|
} = usePagination("", {
|
||||||
|
initPerPage: getUserTablePreference("changeRequestsTable", PreferenceKey.PerPage, 20)
|
||||||
|
});
|
||||||
|
|
||||||
|
const handlePerPageChange = (newPerPage: number) => {
|
||||||
|
setPerPage(newPerPage);
|
||||||
|
setUserTablePreference("changeRequestsTable", PreferenceKey.PerPage, newPerPage);
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
isPending: isApprovalRequestLoading,
|
isPending: isApprovalRequestLoading,
|
||||||
refetch
|
refetch
|
||||||
} = useGetSecretApprovalRequests({
|
} = useGetSecretApprovalRequests({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
status: statusFilter,
|
status: statusFilter,
|
||||||
environment: envFilter,
|
environment: envFilter,
|
||||||
committer: committerFilter
|
committer: committerFilter,
|
||||||
|
search: debouncedSearchFilter,
|
||||||
|
limit,
|
||||||
|
offset
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const totalApprovalCount = data?.totalCount ?? 0;
|
||||||
|
const secretApprovalRequests = data?.approvals ?? [];
|
||||||
|
|
||||||
const { data: secretApprovalRequestCount, isSuccess: isSecretApprovalReqCountSuccess } =
|
const { data: secretApprovalRequestCount, isSuccess: isSecretApprovalReqCountSuccess } =
|
||||||
useGetSecretApprovalRequestCount({ workspaceId });
|
useGetSecretApprovalRequestCount({ workspaceId });
|
||||||
const { user: userSession } = useUser();
|
const { user: userSession } = useUser();
|
||||||
@@ -94,30 +124,7 @@ export const SecretApprovalRequest = () => {
|
|||||||
refetch();
|
refetch();
|
||||||
};
|
};
|
||||||
|
|
||||||
const isRequestListEmpty =
|
const isRequestListEmpty = !isApprovalRequestLoading && secretApprovalRequests?.length === 0;
|
||||||
!isApprovalRequestLoading && secretApprovalRequests?.pages[0]?.length === 0;
|
|
||||||
|
|
||||||
const [searchFilter, setSearchFilter] = useState("");
|
|
||||||
|
|
||||||
const filteredRequests = useMemo(
|
|
||||||
() =>
|
|
||||||
secretApprovalRequests?.pages.flatMap((requests) =>
|
|
||||||
requests.filter((request) => {
|
|
||||||
const { environment, committerUser, secretPath } = request;
|
|
||||||
|
|
||||||
const searchValue = searchFilter.trim().toLowerCase();
|
|
||||||
|
|
||||||
return (
|
|
||||||
environment?.toLowerCase().includes(searchValue) ||
|
|
||||||
`${committerUser?.email ?? ""} ${committerUser?.firstName ?? ""} ${committerUser?.lastName ?? ""}`
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchValue) ||
|
|
||||||
secretPath?.toLowerCase().includes(searchValue)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
) ?? [],
|
|
||||||
[secretApprovalRequests?.pages, searchFilter]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
@@ -171,7 +178,7 @@ export const SecretApprovalRequest = () => {
|
|||||||
value={searchFilter}
|
value={searchFilter}
|
||||||
onChange={(e) => setSearchFilter(e.target.value)}
|
onChange={(e) => setSearchFilter(e.target.value)}
|
||||||
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||||
placeholder="Search change requests by author, environment slug or secret path..."
|
placeholder="Search change requests by author, environment or policy path..."
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
containerClassName="mb-4"
|
containerClassName="mb-4"
|
||||||
/>
|
/>
|
||||||
@@ -285,14 +292,14 @@ 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 && (
|
{isRequestListEmpty && !searchFilter && (
|
||||||
<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`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{filteredRequests.map((secretApproval) => {
|
{secretApprovalRequests.map((secretApproval) => {
|
||||||
const {
|
const {
|
||||||
id: reqId,
|
id: reqId,
|
||||||
commits,
|
commits,
|
||||||
@@ -337,9 +344,23 @@ export const SecretApprovalRequest = () => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{Boolean(
|
{Boolean(
|
||||||
!filteredRequests.length && !isRequestListEmpty && !isApprovalRequestLoading
|
!secretApprovalRequests.length && searchFilter && !isApprovalRequestLoading
|
||||||
) && <EmptyState title="No Requests Match Search" icon={faSearch} />}
|
) && (
|
||||||
{(isFetchingNextApprovalRequest || isApprovalRequestLoading) && (
|
<div className="py-12">
|
||||||
|
<EmptyState title="No Requests Match Search" icon={faSearch} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{Boolean(totalApprovalCount) && (
|
||||||
|
<Pagination
|
||||||
|
className="border-none"
|
||||||
|
count={totalApprovalCount}
|
||||||
|
page={page}
|
||||||
|
perPage={perPage}
|
||||||
|
onChangePage={setPage}
|
||||||
|
onChangePerPage={handlePerPageChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isApprovalRequestLoading && (
|
||||||
<div>
|
<div>
|
||||||
{Array.apply(0, Array(3)).map((_x, index) => (
|
{Array.apply(0, Array(3)).map((_x, index) => (
|
||||||
<div
|
<div
|
||||||
@@ -356,18 +377,6 @@ export const SecretApprovalRequest = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{hasNextApprovalPage && (
|
|
||||||
<Button
|
|
||||||
className="mt-4 text-sm"
|
|
||||||
isFullWidth
|
|
||||||
colorSchema="secondary"
|
|
||||||
isLoading={isFetchingNextApprovalRequest}
|
|
||||||
isDisabled={isFetchingNextApprovalRequest || !hasNextApprovalPage}
|
|
||||||
onClick={() => fetchNextApprovalRequest()}
|
|
||||||
>
|
|
||||||
{hasNextApprovalPage ? "Load More" : "End of History"}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user