feat(folders): implemented ui for folders in dashboard

This commit is contained in:
akhilmhdh
2023-06-04 13:18:05 +05:30
parent deaa85cbe7
commit 579516bd38
19 changed files with 708 additions and 128 deletions
+1
View File
@@ -2,6 +2,7 @@ export * from './auth';
export * from './incidentContacts';
export * from './keys';
export * from './organization';
export * from './secretFolders';
export * from './secrets';
export * from './secretSnapshots';
export * from './serviceAccounts';
@@ -0,0 +1 @@
export { useCreateFolder, useDeleteFolder, useGetProjectFolders, useUpdateFolder } from './queries';
@@ -0,0 +1,129 @@
import { useCallback } from 'react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { apiRequest } from '@app/config/request';
import { secretSnapshotKeys } from '../secretSnapshots/queries';
import {
CreateFolderDTO,
DeleteFolderDTO,
GetProjectFoldersDTO,
TSecretFolder,
UpdateFolderDTO
} from './types';
const queryKeys = {
getSecretFolders: (workspaceId: string, environment: string, parentFolderId?: string) =>
['secret-folders', { workspaceId, environment, parentFolderId }] as const
};
export const useGetProjectFolders = ({
workspaceId,
parentFolderId,
environment,
isPaused,
sortDir
}: GetProjectFoldersDTO) =>
useQuery({
queryKey: queryKeys.getSecretFolders(workspaceId, environment, parentFolderId),
enabled: Boolean(workspaceId) && Boolean(environment) && !isPaused,
queryFn: async () => {
const { data } = await apiRequest.get<{ folders: TSecretFolder[]; dir: TSecretFolder[] }>(
'/api/v1/folders',
{
params: {
workspaceId,
environment,
parentFolderId
}
}
);
return data;
},
select: useCallback(
({ folders, dir }: { folders: TSecretFolder[]; dir: TSecretFolder[] }) => ({
dir,
folders: folders.sort((a, b) =>
sortDir === 'asc'
? a?.name?.localeCompare(b?.name || '')
: b?.name?.localeCompare(a?.name || '')
)
}),
[sortDir]
)
});
export const useCreateFolder = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, CreateFolderDTO>({
mutationFn: async (dto) => {
const { data } = await apiRequest.post('/api/v1/folders', dto);
return data;
},
onSuccess: (_, { workspaceId, environment, parentFolderId }) => {
queryClient.invalidateQueries(
queryKeys.getSecretFolders(workspaceId, environment, parentFolderId)
);
queryClient.invalidateQueries(
secretSnapshotKeys.count(workspaceId, environment, parentFolderId)
);
queryClient.invalidateQueries(
secretSnapshotKeys.list(workspaceId, environment, parentFolderId)
);
}
});
};
export const useUpdateFolder = (parentFolderId: string) => {
const queryClient = useQueryClient();
return useMutation<{}, {}, UpdateFolderDTO>({
mutationFn: async ({ folderId, name, environment, workspaceId }) => {
const { data } = await apiRequest.patch(`/api/v1/folders/${folderId}`, {
name,
environment,
workspaceId
});
return data;
},
onSuccess: (_, { workspaceId, environment }) => {
queryClient.invalidateQueries(
queryKeys.getSecretFolders(workspaceId, environment, parentFolderId)
);
queryClient.invalidateQueries(
secretSnapshotKeys.count(workspaceId, environment, parentFolderId)
);
queryClient.invalidateQueries(
secretSnapshotKeys.list(workspaceId, environment, parentFolderId)
);
}
});
};
export const useDeleteFolder = (parentFolderId: string) => {
const queryClient = useQueryClient();
return useMutation<{}, {}, DeleteFolderDTO>({
mutationFn: async ({ folderId, environment, workspaceId }) => {
const { data } = await apiRequest.delete(`/api/v1/folders/${folderId}`, {
data: {
environment,
workspaceId
}
});
return data;
},
onSuccess: (_, { workspaceId, environment }) => {
queryClient.invalidateQueries(
queryKeys.getSecretFolders(workspaceId, environment, parentFolderId)
);
queryClient.invalidateQueries(
secretSnapshotKeys.count(workspaceId, environment, parentFolderId)
);
queryClient.invalidateQueries(
secretSnapshotKeys.list(workspaceId, environment, parentFolderId)
);
}
});
};
@@ -0,0 +1,32 @@
export type TSecretFolder = {
id: string;
name: string;
};
export type GetProjectFoldersDTO = {
workspaceId: string;
environment: string;
parentFolderId?: string;
isPaused?: boolean;
sortDir?: 'asc' | 'desc';
};
export type CreateFolderDTO = {
workspaceId: string;
environment: string;
folderName: string;
parentFolderId?: string;
};
export type UpdateFolderDTO = {
workspaceId: string;
environment: string;
name: string;
folderId: string;
};
export type DeleteFolderDTO = {
workspaceId: string;
environment: string;
folderId: string;
};
@@ -17,18 +17,31 @@ import {
} from './types';
export const secretSnapshotKeys = {
list: (workspaceId: string) => [{ workspaceId }, 'secret-snapshot'] as const,
list: (workspaceId: string, env: string, folderId?: string) =>
[{ workspaceId, env, folderId }, 'secret-snapshot'] as const,
snapshotSecrets: (snapshotId: string) => [{ snapshotId }, 'secret-snapshot'] as const,
count: (workspaceId: string) => [{ workspaceId }, 'count', 'secret-snapshot']
count: (workspaceId: string, env: string, folderId?: string) => [
{ workspaceId, env, folderId },
'count',
'secret-snapshot'
]
};
const fetchWorkspaceSecretSnaphots = async (workspaceId: string, limit = 10, offset = 0) => {
const fetchWorkspaceSecretSnaphots = async (
workspaceId: string,
environment: string,
folderId?: string,
limit = 10,
offset = 0
) => {
const res = await apiRequest.get<{ secretSnapshots: TWorkspaceSecretSnapshot[] }>(
`/api/v1/workspace/${workspaceId}/secret-snapshots`,
{
params: {
limit,
offset
offset,
environment,
folderId
}
}
);
@@ -38,9 +51,16 @@ const fetchWorkspaceSecretSnaphots = async (workspaceId: string, limit = 10, off
export const useGetWorkspaceSecretSnapshots = (dto: GetWorkspaceSecretSnapshotsDTO) =>
useInfiniteQuery({
enabled: Boolean(dto.workspaceId),
queryKey: secretSnapshotKeys.list(dto.workspaceId),
queryFn: ({ pageParam }) => fetchWorkspaceSecretSnaphots(dto.workspaceId, dto.limit, pageParam),
enabled: Boolean(dto.workspaceId && dto.environment),
queryKey: secretSnapshotKeys.list(dto.workspaceId, dto.environment, dto?.folder),
queryFn: ({ pageParam }) =>
fetchWorkspaceSecretSnaphots(
dto.workspaceId,
dto.environment,
dto?.folder,
dto.limit,
pageParam
),
getNextPageParam: (lastPage, pages) =>
lastPage.length !== 0 ? pages.length * dto.limit : undefined
});
@@ -115,40 +135,53 @@ export const useGetSnapshotSecrets = ({ decryptFileKey, env, snapshotId }: TSnap
}
});
return { version: data.version, secrets: sharedSecrets, createdAt: data.createdAt };
return {
version: data.version,
secrets: sharedSecrets,
createdAt: data.createdAt,
folders: data.folderVersion
};
}
});
const fetchWorkspaceSecretSnaphotCount = async (workspaceId: string) => {
const fetchWorkspaceSecretSnaphotCount = async (
workspaceId: string,
environment: string,
folderId?: string
) => {
const res = await apiRequest.get<{ count: number }>(
`/api/v1/workspace/${workspaceId}/secret-snapshots/count`
`/api/v1/workspace/${workspaceId}/secret-snapshots/count`,
{
params: {
environment,
folderId
}
}
);
return res.data.count;
};
export const useGetWsSnapshotCount = (workspaceId: string) =>
export const useGetWsSnapshotCount = (workspaceId: string, env: string, folderId?: string) =>
useQuery({
enabled: Boolean(workspaceId),
queryKey: secretSnapshotKeys.count(workspaceId),
queryFn: () => fetchWorkspaceSecretSnaphotCount(workspaceId)
enabled: Boolean(workspaceId && env),
queryKey: secretSnapshotKeys.count(workspaceId, env, folderId),
queryFn: () => fetchWorkspaceSecretSnaphotCount(workspaceId, env, folderId)
});
export const usePerformSecretRollback = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, TSecretRollbackDTO>({
mutationFn: async (dto) => {
mutationFn: async ({ workspaceId, ...dto }) => {
const { data } = await apiRequest.post(
`/api/v1/workspace/${dto.workspaceId}/secret-snapshots/rollback`,
{
version: dto.version
}
`/api/v1/workspace/${workspaceId}/secret-snapshots/rollback`,
dto
);
return data;
},
onSuccess: (_, dto) => {
queryClient.invalidateQueries(secretSnapshotKeys.list(dto.workspaceId));
queryClient.invalidateQueries(secretSnapshotKeys.count(dto.workspaceId));
onSuccess: (_, { workspaceId, environment, folderId }) => {
queryClient.invalidateQueries(secretSnapshotKeys.list(workspaceId, environment, folderId));
queryClient.invalidateQueries(secretSnapshotKeys.count(workspaceId, environment, folderId));
}
});
};
@@ -13,6 +13,7 @@ export type TWorkspaceSecretSnapshot = {
export type TSnapshotSecret = Omit<TWorkspaceSecretSnapshot, 'secretVersions'> & {
secretVersions: EncryptedSecretVersion[];
folderVersion: Array<{ name: string; id: string }>;
};
export type TSnapshotSecretProps = {
@@ -24,9 +25,13 @@ export type TSnapshotSecretProps = {
export type GetWorkspaceSecretSnapshotsDTO = {
workspaceId: string;
limit: number;
environment: string;
folder?: string;
};
export type TSecretRollbackDTO = {
workspaceId: string;
version: number;
environment: string;
folderId?: string;
};
+24 -11
View File
@@ -19,19 +19,24 @@ import {
export const secretKeys = {
// this is also used in secretSnapshot part
getProjectSecret: (workspaceId: string, env: string | string[]) => [
{ workspaceId, env },
getProjectSecret: (workspaceId: string, env: string | string[], folderId?: string) => [
{ workspaceId, env, folderId },
'secrets'
],
getSecretVersion: (secretId: string) => [{ secretId }, 'secret-versions']
};
const fetchProjectEncryptedSecrets = async (workspaceId: string, env: string | string[]) => {
const fetchProjectEncryptedSecrets = async (
workspaceId: string,
env: string | string[],
folderId?: string
) => {
if (typeof env === 'string') {
const { data } = await apiRequest.get<{ secrets: EncryptedSecret[] }>('/api/v2/secrets', {
params: {
environment: env,
workspaceId
workspaceId,
folderId: folderId || undefined
}
});
return data.secrets;
@@ -46,7 +51,8 @@ const fetchProjectEncryptedSecrets = async (workspaceId: string, env: string | s
const { data } = await apiRequest.get<{ secrets: EncryptedSecret[] }>('/api/v2/secrets', {
params: {
environment: envPoint,
workspaceId
workspaceId,
folderId
}
});
allEnvData = allEnvData.concat(data.secrets);
@@ -63,13 +69,14 @@ export const useGetProjectSecrets = ({
workspaceId,
env,
decryptFileKey,
isPaused
isPaused,
folderId
}: GetProjectSecretsDTO) =>
useQuery({
// wait for all values to be available
enabled: Boolean(decryptFileKey && workspaceId && env) && !isPaused,
queryKey: secretKeys.getProjectSecret(workspaceId, env),
queryFn: () => fetchProjectEncryptedSecrets(workspaceId, env),
queryKey: secretKeys.getProjectSecret(workspaceId, env, folderId),
queryFn: () => fetchProjectEncryptedSecrets(workspaceId, env, folderId),
select: (data) => {
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY') as string;
const latestKey = decryptFileKey;
@@ -283,9 +290,15 @@ export const useBatchSecretsOp = () => {
return data;
},
onSuccess: (_, dto) => {
queryClient.invalidateQueries(secretKeys.getProjectSecret(dto.workspaceId, dto.environment));
queryClient.invalidateQueries(secretSnapshotKeys.list(dto.workspaceId));
queryClient.invalidateQueries(secretSnapshotKeys.count(dto.workspaceId));
queryClient.invalidateQueries(
secretKeys.getProjectSecret(dto.workspaceId, dto.environment, dto.folderId)
);
queryClient.invalidateQueries(
secretSnapshotKeys.list(dto.workspaceId, dto.environment, dto?.folderId)
);
queryClient.invalidateQueries(
secretSnapshotKeys.count(dto.workspaceId, dto.environment, dto?.folderId)
);
}
});
};
+3
View File
@@ -61,6 +61,7 @@ type SecretTagArg = { _id: string; name: string; slug: string };
export type UpdateSecretArg = {
_id: string;
folderId?: string;
type: 'shared' | 'personal';
secretName: string;
secretKeyCiphertext: string;
@@ -81,6 +82,7 @@ export type DeleteSecretArg = { _id: string };
export type BatchSecretDTO = {
workspaceId: string;
folderId: string;
environment: string;
requests: Array<
| { method: 'POST'; secret: CreateSecretArg }
@@ -93,6 +95,7 @@ export type GetProjectSecretsDTO = {
workspaceId: string;
env: string | string[];
decryptFileKey: UserWsKeyPair;
folderId?: string;
isPaused?: boolean;
onSuccess?: (data: DecryptedSecret[]) => void;
};