From 1d465a50c3eea0ac8913a503c192726e559db00d Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 20 Mar 2024 12:54:44 +0100 Subject: [PATCH 01/19] Feat: Visualize imported folders in overview page --- .../src/hooks/api/secretFolders/queries.tsx | 36 ++++++++++++++----- .../views/SecretMainPage/SecretMainPage.tsx | 12 ++++--- .../SecretOverviewPage/SecretOverviewPage.tsx | 12 ++++--- .../SecretOverviewFolderRow.tsx | 21 +++++++---- 4 files changed, 56 insertions(+), 25 deletions(-) diff --git a/frontend/src/hooks/api/secretFolders/queries.tsx b/frontend/src/hooks/api/secretFolders/queries.tsx index bcda2b0a4..a0e1db282 100644 --- a/frontend/src/hooks/api/secretFolders/queries.tsx +++ b/frontend/src/hooks/api/secretFolders/queries.tsx @@ -13,9 +13,9 @@ import { secretSnapshotKeys } from "../secretSnapshots/queries"; import { TCreateFolderDTO, TDeleteFolderDTO, + TFetchProjectFoldersResponse, TGetFoldersByEnvDTO, TGetProjectFoldersDTO, - TSecretFolder, TUpdateFolderDTO } from "./types"; @@ -25,14 +25,14 @@ export const folderQueryKeys = { }; const fetchProjectFolders = async (workspaceId: string, environment: string, path = "/") => { - const { data } = await apiRequest.get<{ folders: TSecretFolder[] }>("/api/v1/folders", { + const { data } = await apiRequest.get("/api/v1/folders", { params: { workspaceId, environment, path } }); - return data.folders; + return data; }; export const useGetProjectFolders = ({ @@ -43,9 +43,9 @@ export const useGetProjectFolders = ({ }: TGetProjectFoldersDTO & { options?: Omit< UseQueryOptions< - TSecretFolder[], + TFetchProjectFoldersResponse, unknown, - TSecretFolder[], + TFetchProjectFoldersResponse, ReturnType >, "queryKey" | "queryFn" @@ -74,7 +74,7 @@ export const useGetFoldersByEnv = ({ const folderNames = useMemo(() => { const names = new Set(); folders?.forEach(({ data }) => { - data?.forEach(({ name }) => { + data?.folders?.forEach(({ name }) => { names.add(name); }); }); @@ -85,16 +85,34 @@ export const useGetFoldersByEnv = ({ (name: string, env: string) => { const selectedEnvIndex = environments.indexOf(env); if (selectedEnvIndex !== -1) { - return Boolean( - folders?.[selectedEnvIndex]?.data?.find(({ name: folderName }) => folderName === name) + const isPresent = folders?.[selectedEnvIndex]?.data?.folders.find( + ({ name: folderName }) => folderName === name ); + + return Boolean(isPresent); } return false; }, [(folders || []).map((folder) => folder.data)] ); - return { folders, folderNames, isFolderPresentInEnv }; + const isImportedFolderPresentInEnv = useCallback( + (name: string, env: string) => { + const selectedEnvIndex = environments.indexOf(env); + + if (selectedEnvIndex !== -1) { + const isPresent = folders?.[selectedEnvIndex]?.data?.importedFolders.find( + ({ importPath }) => importPath === `/${name}` + ); + + return Boolean(isPresent); + } + return false; + }, + [(folders || []).map((folder) => folder.data)] + ); + + return { folders, folderNames, isFolderPresentInEnv, isImportedFolderPresentInEnv }; }; export const useCreateFolder = () => { diff --git a/frontend/src/views/SecretMainPage/SecretMainPage.tsx b/frontend/src/views/SecretMainPage/SecretMainPage.tsx index 312b676b7..bd8f0e7fe 100644 --- a/frontend/src/views/SecretMainPage/SecretMainPage.tsx +++ b/frontend/src/views/SecretMainPage/SecretMainPage.tsx @@ -104,7 +104,7 @@ export const SecretMainPage = () => { } }); // fetch folders - const { data: folders, isLoading: isFoldersLoading } = useGetProjectFolders({ + const { data: folderData, isLoading: isFoldersLoading } = useGetProjectFolders({ projectId: workspaceId, environment, path: secretPath @@ -163,7 +163,9 @@ export const SecretMainPage = () => { isPaused: !canDoReadRollback }); - const isNotEmtpy = Boolean(secrets?.length || folders?.length || secretImports?.length); + const isNotEmtpy = Boolean( + secrets?.length || folderData?.folders?.length || secretImports?.length + ); const handleSortToggle = () => setSortDir((state) => (state === SortDir.ASC ? SortDir.DESC : SortDir.ASC)); @@ -292,7 +294,7 @@ export const SecretMainPage = () => { /> )} { isProtectedBranch={isProtectedBranch} /> )} - {!canReadSecret && folders?.length === 0 && } + {!canReadSecret && folderData?.folders?.length === 0 && } { workspaceId={workspaceId} secretPath={secretPath} secrets={secrets} - folders={folders} + folders={folderData?.folders} snapshotCount={snapshotCount} onGoBack={handleResetSnapshot} onClickListSnapshot={() => handlePopUpToggle("snapshots", true)} diff --git a/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx b/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx index ec8b5c2f9..ea70506b5 100644 --- a/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx +++ b/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx @@ -125,11 +125,12 @@ export const SecretOverviewPage = () => { secretPath, decryptFileKey: latestFileKey! }); - const { folders, folderNames, isFolderPresentInEnv } = useGetFoldersByEnv({ - projectId: workspaceId, - path: secretPath, - environments: userAvailableEnvs.map(({ slug }) => slug) - }); + const { folders, folderNames, isFolderPresentInEnv, isImportedFolderPresentInEnv } = + useGetFoldersByEnv({ + projectId: workspaceId, + path: secretPath, + environments: userAvailableEnvs.map(({ slug }) => slug) + }); const { mutateAsync: createSecretV3 } = useCreateSecretV3(); const { mutateAsync: updateSecretV3 } = useUpdateSecretV3(); @@ -639,6 +640,7 @@ export const SecretOverviewPage = () => { boolean; + isImportedFolderPresentInEnv: (name: string, env: string) => boolean; onClick: (path: string) => void; }; @@ -15,6 +16,7 @@ export const SecretOverviewFolderRow = ({ folderName, environments = [], isFolderPresentInEnv, + isImportedFolderPresentInEnv, onClick }: Props) => { return ( @@ -29,17 +31,24 @@ export const SecretOverviewFolderRow = ({ {environments.map(({ slug }, i) => { const isPresent = isFolderPresentInEnv(folderName, slug); + const isImportPresent = isImportedFolderPresentInEnv(folderName, slug); return ( -
- -
+ +
+ +
+
); })} From 78058d691af38064f233623ecaa1ef12b70ee0fb Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 20 Mar 2024 12:55:08 +0100 Subject: [PATCH 02/19] Enhancement: Add disabled prop to Tooltip component --- frontend/src/components/v2/Tooltip/Tooltip.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/src/components/v2/Tooltip/Tooltip.tsx b/frontend/src/components/v2/Tooltip/Tooltip.tsx index 9d1285aa0..df1ef9e28 100644 --- a/frontend/src/components/v2/Tooltip/Tooltip.tsx +++ b/frontend/src/components/v2/Tooltip/Tooltip.tsx @@ -11,6 +11,7 @@ export type TooltipProps = Omit void; defaultOpen?: boolean; position?: "top" | "bottom" | "left" | "right"; + isDisabled?: boolean; }; export const Tooltip = ({ @@ -21,6 +22,7 @@ export const Tooltip = ({ defaultOpen, className, asChild = true, + isDisabled, position = "top", ...props }: TooltipProps) => ( @@ -43,6 +45,7 @@ data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade `, + isDisabled && "!hidden", className )} > From 88e609cb661ae65d0a4b1adad264e0039cd334e0 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 20 Mar 2024 12:55:40 +0100 Subject: [PATCH 03/19] Feat: New types for imported folders --- frontend/src/hooks/api/secretFolders/types.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/frontend/src/hooks/api/secretFolders/types.ts b/frontend/src/hooks/api/secretFolders/types.ts index eac202389..e676fcc8e 100644 --- a/frontend/src/hooks/api/secretFolders/types.ts +++ b/frontend/src/hooks/api/secretFolders/types.ts @@ -3,6 +3,24 @@ export type TSecretFolder = { name: string; }; +export type TImportedSecretFolder = { + id: string; + folderId: string; + importEnv: { + id: string; + name: string; + slug: string; + }; + importPath: string; + + // ... It got more fields, but we won't need them. +}; + +export type TFetchProjectFoldersResponse = { + folders: TSecretFolder[]; + importedFolders: TImportedSecretFolder[]; +}; + export type TGetProjectFoldersDTO = { projectId: string; environment: string; From 348fdf642942474209ab5dc73e6503468b7275eb Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 20 Mar 2024 12:56:11 +0100 Subject: [PATCH 04/19] Feat: Visualize imported folders in overview page (include imported folders in response) --- backend/src/server/routes/index.ts | 4 +++- backend/src/server/routes/v1/secret-folder-router.ts | 7 ++++--- .../services/secret-folder/secret-folder-service.ts | 10 ++++++++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index c452bfbd3..2d3926b76 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -415,6 +415,7 @@ export const registerRoutes = async ( const folderService = secretFolderServiceFactory({ permissionService, folderDAL, + secretImportDAL, folderVersionDAL, projectEnvDAL, snapshotService @@ -471,7 +472,8 @@ export const registerRoutes = async ( snapshotService, secretQueueService, secretImportDAL, - projectBotService + projectBotService, + projectEnvDAL }); const sarService = secretApprovalRequestServiceFactory({ permissionService, diff --git a/backend/src/server/routes/v1/secret-folder-router.ts b/backend/src/server/routes/v1/secret-folder-router.ts index bd202b70b..81b941bb5 100644 --- a/backend/src/server/routes/v1/secret-folder-router.ts +++ b/backend/src/server/routes/v1/secret-folder-router.ts @@ -200,14 +200,15 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => }), response: { 200: z.object({ - folders: SecretFoldersSchema.array() + folders: SecretFoldersSchema.array(), + importedFolders: z.any().array() }) } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const path = req.query.path || req.query.directory; - const folders = await server.services.folder.getFolders({ + const { folders, importedFolders } = await server.services.folder.getFolders({ actorId: req.permission.id, actor: req.permission.type, actorAuthMethod: req.permission.authMethod, @@ -216,7 +217,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => projectId: req.query.workspaceId, path }); - return { folders }; + return { folders, importedFolders }; } }); }; diff --git a/backend/src/services/secret-folder/secret-folder-service.ts b/backend/src/services/secret-folder/secret-folder-service.ts index baa1484b1..1e6402132 100644 --- a/backend/src/services/secret-folder/secret-folder-service.ts +++ b/backend/src/services/secret-folder/secret-folder-service.ts @@ -9,6 +9,7 @@ import { TSecretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/ import { BadRequestError } from "@app/lib/errors"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; +import { TSecretImportDALFactory } from "../secret-import/secret-import-dal"; import { TSecretFolderDALFactory } from "./secret-folder-dal"; import { TCreateFolderDTO, TDeleteFolderDTO, TGetFolderDTO, TUpdateFolderDTO } from "./secret-folder-types"; import { TSecretFolderVersionDALFactory } from "./secret-folder-version-dal"; @@ -17,6 +18,7 @@ type TSecretFolderServiceFactoryDep = { permissionService: Pick; snapshotService: Pick; folderDAL: TSecretFolderDALFactory; + secretImportDAL: TSecretImportDALFactory; projectEnvDAL: Pick; folderVersionDAL: TSecretFolderVersionDALFactory; }; @@ -26,6 +28,7 @@ export type TSecretFolderServiceFactory = ReturnType Date: Wed, 20 Mar 2024 13:13:19 +0100 Subject: [PATCH 05/19] Fix: Removed overlap from other working branch --- backend/src/server/routes/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 2d3926b76..fe4016ed9 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -472,8 +472,7 @@ export const registerRoutes = async ( snapshotService, secretQueueService, secretImportDAL, - projectBotService, - projectEnvDAL + projectBotService }); const sarService = secretApprovalRequestServiceFactory({ permissionService, From e5e494d0ee4b1a8c42d3c309619255155f348d00 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 20 Mar 2024 13:13:50 +0100 Subject: [PATCH 06/19] Fix: Also display imported folder indicator for nested folders --- frontend/src/hooks/api/secretFolders/queries.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/frontend/src/hooks/api/secretFolders/queries.tsx b/frontend/src/hooks/api/secretFolders/queries.tsx index a0e1db282..d2c15d327 100644 --- a/frontend/src/hooks/api/secretFolders/queries.tsx +++ b/frontend/src/hooks/api/secretFolders/queries.tsx @@ -63,6 +63,8 @@ export const useGetFoldersByEnv = ({ projectId, environments }: TGetFoldersByEnvDTO) => { + const queryParams = new URLSearchParams(window.location.search); + const folders = useQueries({ queries: environments.map((environment) => ({ queryKey: folderQueryKeys.getSecretFolders({ projectId, environment, path }), @@ -101,8 +103,16 @@ export const useGetFoldersByEnv = ({ const selectedEnvIndex = environments.indexOf(env); if (selectedEnvIndex !== -1) { + const currentlyBrowsingPath = queryParams.get("secretPath") || ""; + + if (env === "staging") { + console.log("selectedEnvIndex", selectedEnvIndex); + console.log(`name /${name}`); + console.log("importedFolders", folders?.[selectedEnvIndex]?.data?.importedFolders); + } + const isPresent = folders?.[selectedEnvIndex]?.data?.importedFolders.find( - ({ importPath }) => importPath === `/${name}` + ({ importPath }) => importPath === `${currentlyBrowsingPath}/${name}` ); return Boolean(isPresent); From 12c414817f4dbf8cab9bbdc43841c5c30af8e1d4 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 20 Mar 2024 13:14:18 +0100 Subject: [PATCH 07/19] Fix: Remove debugging logs --- frontend/src/hooks/api/secretFolders/queries.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/frontend/src/hooks/api/secretFolders/queries.tsx b/frontend/src/hooks/api/secretFolders/queries.tsx index d2c15d327..6cadd0994 100644 --- a/frontend/src/hooks/api/secretFolders/queries.tsx +++ b/frontend/src/hooks/api/secretFolders/queries.tsx @@ -105,12 +105,6 @@ export const useGetFoldersByEnv = ({ if (selectedEnvIndex !== -1) { const currentlyBrowsingPath = queryParams.get("secretPath") || ""; - if (env === "staging") { - console.log("selectedEnvIndex", selectedEnvIndex); - console.log(`name /${name}`); - console.log("importedFolders", folders?.[selectedEnvIndex]?.data?.importedFolders); - } - const isPresent = folders?.[selectedEnvIndex]?.data?.importedFolders.find( ({ importPath }) => importPath === `${currentlyBrowsingPath}/${name}` ); From d7161a353d3b045893a7acefa5f14fdf98a38821 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 20 Mar 2024 13:15:51 +0100 Subject: [PATCH 08/19] Fix: Better variable naming --- frontend/src/hooks/api/secretFolders/queries.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/hooks/api/secretFolders/queries.tsx b/frontend/src/hooks/api/secretFolders/queries.tsx index 6cadd0994..5c9db551b 100644 --- a/frontend/src/hooks/api/secretFolders/queries.tsx +++ b/frontend/src/hooks/api/secretFolders/queries.tsx @@ -95,7 +95,7 @@ export const useGetFoldersByEnv = ({ } return false; }, - [(folders || []).map((folder) => folder.data)] + [(folders || []).map((response) => response.data)] ); const isImportedFolderPresentInEnv = useCallback( @@ -113,7 +113,7 @@ export const useGetFoldersByEnv = ({ } return false; }, - [(folders || []).map((folder) => folder.data)] + [(folders || []).map((response) => response.data)] ); return { folders, folderNames, isFolderPresentInEnv, isImportedFolderPresentInEnv }; From a750d68363a9c31342d945cfb9d22ef2b900e0c6 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 20 Mar 2024 14:40:24 +0100 Subject: [PATCH 09/19] Fix: Reverted backend changes --- backend/src/server/routes/index.ts | 1 - backend/src/server/routes/v1/secret-folder-router.ts | 7 +++---- .../src/services/secret-folder/secret-folder-service.ts | 9 ++------- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index fe4016ed9..c452bfbd3 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -415,7 +415,6 @@ export const registerRoutes = async ( const folderService = secretFolderServiceFactory({ permissionService, folderDAL, - secretImportDAL, folderVersionDAL, projectEnvDAL, snapshotService diff --git a/backend/src/server/routes/v1/secret-folder-router.ts b/backend/src/server/routes/v1/secret-folder-router.ts index 81b941bb5..bd202b70b 100644 --- a/backend/src/server/routes/v1/secret-folder-router.ts +++ b/backend/src/server/routes/v1/secret-folder-router.ts @@ -200,15 +200,14 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => }), response: { 200: z.object({ - folders: SecretFoldersSchema.array(), - importedFolders: z.any().array() + folders: SecretFoldersSchema.array() }) } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const path = req.query.path || req.query.directory; - const { folders, importedFolders } = await server.services.folder.getFolders({ + const folders = await server.services.folder.getFolders({ actorId: req.permission.id, actor: req.permission.type, actorAuthMethod: req.permission.authMethod, @@ -217,7 +216,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => projectId: req.query.workspaceId, path }); - return { folders, importedFolders }; + return { folders }; } }); }; diff --git a/backend/src/services/secret-folder/secret-folder-service.ts b/backend/src/services/secret-folder/secret-folder-service.ts index 1e6402132..c925d2587 100644 --- a/backend/src/services/secret-folder/secret-folder-service.ts +++ b/backend/src/services/secret-folder/secret-folder-service.ts @@ -9,7 +9,6 @@ import { TSecretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/ import { BadRequestError } from "@app/lib/errors"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; -import { TSecretImportDALFactory } from "../secret-import/secret-import-dal"; import { TSecretFolderDALFactory } from "./secret-folder-dal"; import { TCreateFolderDTO, TDeleteFolderDTO, TGetFolderDTO, TUpdateFolderDTO } from "./secret-folder-types"; import { TSecretFolderVersionDALFactory } from "./secret-folder-version-dal"; @@ -18,7 +17,6 @@ type TSecretFolderServiceFactoryDep = { permissionService: Pick; snapshotService: Pick; folderDAL: TSecretFolderDALFactory; - secretImportDAL: TSecretImportDALFactory; projectEnvDAL: Pick; folderVersionDAL: TSecretFolderVersionDALFactory; }; @@ -28,7 +26,6 @@ export type TSecretFolderServiceFactory = ReturnType Date: Wed, 20 Mar 2024 14:42:34 +0100 Subject: [PATCH 10/19] Fix: Restructured frontend --- .../src/hooks/api/secretFolders/queries.tsx | 36 ++------- frontend/src/hooks/api/secretFolders/types.ts | 18 ----- frontend/src/hooks/api/secretImports/index.ts | 2 +- .../src/hooks/api/secretImports/queries.tsx | 74 ++++++++++++++++++- frontend/src/hooks/api/secretImports/types.ts | 25 +++++++ .../views/SecretMainPage/SecretMainPage.tsx | 12 ++- .../SecretOverviewPage/SecretOverviewPage.tsx | 18 +++-- 7 files changed, 122 insertions(+), 63 deletions(-) diff --git a/frontend/src/hooks/api/secretFolders/queries.tsx b/frontend/src/hooks/api/secretFolders/queries.tsx index 5c9db551b..0eb5223b9 100644 --- a/frontend/src/hooks/api/secretFolders/queries.tsx +++ b/frontend/src/hooks/api/secretFolders/queries.tsx @@ -13,9 +13,9 @@ import { secretSnapshotKeys } from "../secretSnapshots/queries"; import { TCreateFolderDTO, TDeleteFolderDTO, - TFetchProjectFoldersResponse, TGetFoldersByEnvDTO, TGetProjectFoldersDTO, + TSecretFolder, TUpdateFolderDTO } from "./types"; @@ -25,14 +25,14 @@ export const folderQueryKeys = { }; const fetchProjectFolders = async (workspaceId: string, environment: string, path = "/") => { - const { data } = await apiRequest.get("/api/v1/folders", { + const { data } = await apiRequest.get<{ folders: TSecretFolder[] }>("/api/v1/folders", { params: { workspaceId, environment, path } }); - return data; + return data.folders; }; export const useGetProjectFolders = ({ @@ -43,9 +43,9 @@ export const useGetProjectFolders = ({ }: TGetProjectFoldersDTO & { options?: Omit< UseQueryOptions< - TFetchProjectFoldersResponse, + TSecretFolder[], unknown, - TFetchProjectFoldersResponse, + TSecretFolder[], ReturnType >, "queryKey" | "queryFn" @@ -63,8 +63,6 @@ export const useGetFoldersByEnv = ({ projectId, environments }: TGetFoldersByEnvDTO) => { - const queryParams = new URLSearchParams(window.location.search); - const folders = useQueries({ queries: environments.map((environment) => ({ queryKey: folderQueryKeys.getSecretFolders({ projectId, environment, path }), @@ -76,7 +74,7 @@ export const useGetFoldersByEnv = ({ const folderNames = useMemo(() => { const names = new Set(); folders?.forEach(({ data }) => { - data?.folders?.forEach(({ name }) => { + data?.forEach(({ name }) => { names.add(name); }); }); @@ -87,7 +85,7 @@ export const useGetFoldersByEnv = ({ (name: string, env: string) => { const selectedEnvIndex = environments.indexOf(env); if (selectedEnvIndex !== -1) { - const isPresent = folders?.[selectedEnvIndex]?.data?.folders.find( + const isPresent = folders?.[selectedEnvIndex]?.data?.find( ({ name: folderName }) => folderName === name ); @@ -98,25 +96,7 @@ export const useGetFoldersByEnv = ({ [(folders || []).map((response) => response.data)] ); - const isImportedFolderPresentInEnv = useCallback( - (name: string, env: string) => { - const selectedEnvIndex = environments.indexOf(env); - - if (selectedEnvIndex !== -1) { - const currentlyBrowsingPath = queryParams.get("secretPath") || ""; - - const isPresent = folders?.[selectedEnvIndex]?.data?.importedFolders.find( - ({ importPath }) => importPath === `${currentlyBrowsingPath}/${name}` - ); - - return Boolean(isPresent); - } - return false; - }, - [(folders || []).map((response) => response.data)] - ); - - return { folders, folderNames, isFolderPresentInEnv, isImportedFolderPresentInEnv }; + return { folders, folderNames, isFolderPresentInEnv }; }; export const useCreateFolder = () => { diff --git a/frontend/src/hooks/api/secretFolders/types.ts b/frontend/src/hooks/api/secretFolders/types.ts index e676fcc8e..eac202389 100644 --- a/frontend/src/hooks/api/secretFolders/types.ts +++ b/frontend/src/hooks/api/secretFolders/types.ts @@ -3,24 +3,6 @@ export type TSecretFolder = { name: string; }; -export type TImportedSecretFolder = { - id: string; - folderId: string; - importEnv: { - id: string; - name: string; - slug: string; - }; - importPath: string; - - // ... It got more fields, but we won't need them. -}; - -export type TFetchProjectFoldersResponse = { - folders: TSecretFolder[]; - importedFolders: TImportedSecretFolder[]; -}; - export type TGetProjectFoldersDTO = { projectId: string; environment: string; diff --git a/frontend/src/hooks/api/secretImports/index.ts b/frontend/src/hooks/api/secretImports/index.ts index 2e1150cb8..9c76d597e 100644 --- a/frontend/src/hooks/api/secretImports/index.ts +++ b/frontend/src/hooks/api/secretImports/index.ts @@ -1,2 +1,2 @@ export { useCreateSecretImport, useDeleteSecretImport, useUpdateSecretImport } from "./mutation"; -export { useGetImportedSecrets, useGetSecretImports } from "./queries"; +export { useGetImportedFoldersByEnv, useGetImportedSecrets, useGetSecretImports } from "./queries"; diff --git a/frontend/src/hooks/api/secretImports/queries.tsx b/frontend/src/hooks/api/secretImports/queries.tsx index 1af6ca878..1714f45ff 100644 --- a/frontend/src/hooks/api/secretImports/queries.tsx +++ b/frontend/src/hooks/api/secretImports/queries.tsx @@ -1,5 +1,5 @@ import { useCallback } from "react"; -import { useQuery, UseQueryOptions } from "@tanstack/react-query"; +import { useQueries, useQuery, UseQueryOptions } from "@tanstack/react-query"; import { decryptAssymmetric, @@ -7,7 +7,15 @@ import { } from "@app/components/utilities/cryptography/crypto"; import { apiRequest } from "@app/config/request"; -import { TGetImportedSecrets, TGetSecretImports, TImportedSecrets, TSecretImport } from "./types"; +import { + TGetImportedFoldersByEnvDTO, + TGetImportedSecrets, + TGetSecretImports, + TImportedSecretFolder, + TImportedSecrets, + TSecretImport, + TuseGetImportedFoldersByEnv +} from "./types"; export const secretImportKeys = { getProjectSecretImports: ({ environment, projectId, path }: TGetSecretImports) => @@ -17,7 +25,9 @@ export const secretImportKeys = { projectId, path }: Omit) => - [{ environment, path, projectId }, "secrets-import-sec"] as const + [{ environment, path, projectId }, "secrets-import-sec"] as const, + getImportedFoldersByEnv: ({ environment, projectId, path }: TGetImportedFoldersByEnvDTO) => + [{ environment, projectId, path }, "imported-folders"] as const }; const fetchSecretImport = async ({ projectId, environment, path = "/" }: TGetSecretImports) => { @@ -75,6 +85,24 @@ const fetchImportedSecrets = async ( return data.secrets; }; +const fetchImportedFolders = async ({ + projectId, + environment, + path +}: TGetImportedFoldersByEnvDTO) => { + const { data } = await apiRequest.get<{ secretImports: TImportedSecretFolder[] }>( + "/api/v1/secret-imports", + { + params: { + workspaceId: projectId, + environment, + path + } + } + ); + return data.secretImports; +}; + export const useGetImportedSecrets = ({ environment, decryptFileKey, @@ -159,3 +187,43 @@ export const useGetImportedSecrets = ({ [decryptFileKey] ) }); + +export const useGetImportedFoldersByEnv = ({ + projectId, + environments, + path = "/" +}: TuseGetImportedFoldersByEnv) => { + const queryParams = new URLSearchParams(window.location.search); + + const importedFolders = useQueries({ + queries: environments.map((env) => ({ + queryKey: secretImportKeys.getImportedFoldersByEnv({ + projectId, + environment: env, + path + }), + queryFn: async () => fetchImportedFolders({ projectId, environment: env, path }), + enabled: Boolean(projectId) && Boolean(env) + })) + }); + + const isImportedFolderPresentInEnv = useCallback( + (name: string, env: string) => { + const selectedEnvIndex = environments.indexOf(env); + + if (selectedEnvIndex !== -1) { + const currentlyBrowsingPath = queryParams.get("secretPath") || ""; + + const isPresent = importedFolders?.[selectedEnvIndex]?.data?.find( + ({ importPath }) => importPath === `${currentlyBrowsingPath}/${name}` + ); + + return Boolean(isPresent); + } + return false; + }, + [(importedFolders || []).map((response) => response.data)] + ); + + return { importedFolders, isImportedFolderPresentInEnv }; +}; diff --git a/frontend/src/hooks/api/secretImports/types.ts b/frontend/src/hooks/api/secretImports/types.ts index eb63e8a96..7c69a8516 100644 --- a/frontend/src/hooks/api/secretImports/types.ts +++ b/frontend/src/hooks/api/secretImports/types.ts @@ -12,6 +12,25 @@ export type TSecretImport = { updatedAt: string; }; +export type TImportedSecretFolder = { + id: string; + folderId: string; + importEnv: { + id: string; + name: string; + slug: string; + }; + importPath: string; + + // ... It got more fields, but we won't need them. +}; + +export type TGetImportedFoldersByEnvDTO = { + projectId: string; + environment: string; + path?: string; +}; + export type TImportedSecrets = { environment: string; environmentInfo: WorkspaceEnv; @@ -33,6 +52,12 @@ export type TGetImportedSecrets = { decryptFileKey: UserWsKeyPair; }; +export type TuseGetImportedFoldersByEnv = { + environments: string[]; + projectId: string; + path?: string; +}; + export type TCreateSecretImportDTO = { projectId: string; environment: string; diff --git a/frontend/src/views/SecretMainPage/SecretMainPage.tsx b/frontend/src/views/SecretMainPage/SecretMainPage.tsx index bd8f0e7fe..312b676b7 100644 --- a/frontend/src/views/SecretMainPage/SecretMainPage.tsx +++ b/frontend/src/views/SecretMainPage/SecretMainPage.tsx @@ -104,7 +104,7 @@ export const SecretMainPage = () => { } }); // fetch folders - const { data: folderData, isLoading: isFoldersLoading } = useGetProjectFolders({ + const { data: folders, isLoading: isFoldersLoading } = useGetProjectFolders({ projectId: workspaceId, environment, path: secretPath @@ -163,9 +163,7 @@ export const SecretMainPage = () => { isPaused: !canDoReadRollback }); - const isNotEmtpy = Boolean( - secrets?.length || folderData?.folders?.length || secretImports?.length - ); + const isNotEmtpy = Boolean(secrets?.length || folders?.length || secretImports?.length); const handleSortToggle = () => setSortDir((state) => (state === SortDir.ASC ? SortDir.DESC : SortDir.ASC)); @@ -294,7 +292,7 @@ export const SecretMainPage = () => { /> )} { isProtectedBranch={isProtectedBranch} /> )} - {!canReadSecret && folderData?.folders?.length === 0 && } + {!canReadSecret && folders?.length === 0 && } { workspaceId={workspaceId} secretPath={secretPath} secrets={secrets} - folders={folderData?.folders} + folders={folders} snapshotCount={snapshotCount} onGoBack={handleResetSnapshot} onClickListSnapshot={() => handlePopUpToggle("snapshots", true)} diff --git a/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx b/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx index ea70506b5..7eda5c203 100644 --- a/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx +++ b/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx @@ -55,6 +55,7 @@ import { useCreateSecretV3, useDeleteSecretV3, useGetFoldersByEnv, + useGetImportedFoldersByEnv, useGetProjectSecretsAllEnv, useGetUserWsKey, useUpdateSecretV3 @@ -125,12 +126,17 @@ export const SecretOverviewPage = () => { secretPath, decryptFileKey: latestFileKey! }); - const { folders, folderNames, isFolderPresentInEnv, isImportedFolderPresentInEnv } = - useGetFoldersByEnv({ - projectId: workspaceId, - path: secretPath, - environments: userAvailableEnvs.map(({ slug }) => slug) - }); + const { folders, folderNames, isFolderPresentInEnv } = useGetFoldersByEnv({ + projectId: workspaceId, + path: secretPath, + environments: userAvailableEnvs.map(({ slug }) => slug) + }); + + const { isImportedFolderPresentInEnv } = useGetImportedFoldersByEnv({ + projectId: workspaceId, + path: secretPath, + environments: userAvailableEnvs.map(({ slug }) => slug) + }); const { mutateAsync: createSecretV3 } = useCreateSecretV3(); const { mutateAsync: updateSecretV3 } = useUpdateSecretV3(); From 4e1733ba6c13943d916062b027b1d0397f103639 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 20 Mar 2024 14:44:40 +0100 Subject: [PATCH 11/19] Fix: More reverting --- frontend/src/hooks/api/secretFolders/queries.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/frontend/src/hooks/api/secretFolders/queries.tsx b/frontend/src/hooks/api/secretFolders/queries.tsx index 0eb5223b9..bcda2b0a4 100644 --- a/frontend/src/hooks/api/secretFolders/queries.tsx +++ b/frontend/src/hooks/api/secretFolders/queries.tsx @@ -85,15 +85,13 @@ export const useGetFoldersByEnv = ({ (name: string, env: string) => { const selectedEnvIndex = environments.indexOf(env); if (selectedEnvIndex !== -1) { - const isPresent = folders?.[selectedEnvIndex]?.data?.find( - ({ name: folderName }) => folderName === name + return Boolean( + folders?.[selectedEnvIndex]?.data?.find(({ name: folderName }) => folderName === name) ); - - return Boolean(isPresent); } return false; }, - [(folders || []).map((response) => response.data)] + [(folders || []).map((folder) => folder.data)] ); return { folders, folderNames, isFolderPresentInEnv }; From 08b79d65ea8b95d0d9491d7366f355be84a3a0d6 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 20 Mar 2024 14:55:02 +0100 Subject: [PATCH 12/19] Fix: Remove unused lint disable --- .../SecretOverviewFolderRow/SecretOverviewFolderRow.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/views/SecretOverviewPage/components/SecretOverviewFolderRow/SecretOverviewFolderRow.tsx b/frontend/src/views/SecretOverviewPage/components/SecretOverviewFolderRow/SecretOverviewFolderRow.tsx index 44ee940b3..ca506f7cb 100644 --- a/frontend/src/views/SecretOverviewPage/components/SecretOverviewFolderRow/SecretOverviewFolderRow.tsx +++ b/frontend/src/views/SecretOverviewPage/components/SecretOverviewFolderRow/SecretOverviewFolderRow.tsx @@ -37,7 +37,6 @@ export const SecretOverviewFolderRow = ({ key={`sec-overview-${slug}-${i + 1}-folder`} className={twMerge( "border-r border-mineshaft-600 py-3 group-hover:bg-mineshaft-700", - // eslint-disable-next-line no-nested-ternary isPresent || isImportPresent ? "text-green-600" : "text-red-600" )} > From c9cb90c98e644c988078af796add4fc491eddff6 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 20 Mar 2024 20:23:21 +0100 Subject: [PATCH 13/19] Feat: Add center property to tooltip --- frontend/src/components/v2/Tooltip/Tooltip.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/v2/Tooltip/Tooltip.tsx b/frontend/src/components/v2/Tooltip/Tooltip.tsx index df1ef9e28..93df61e61 100644 --- a/frontend/src/components/v2/Tooltip/Tooltip.tsx +++ b/frontend/src/components/v2/Tooltip/Tooltip.tsx @@ -12,6 +12,7 @@ export type TooltipProps = Omit From 671adee4d77298b407639bcebca26cb0ca203543 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 20 Mar 2024 20:24:06 +0100 Subject: [PATCH 14/19] Feat: Indicator for wether or not secrets are imported --- .../SecretOverviewTableRow.tsx | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx b/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx index 8ae96d888..8e9e60a3b 100644 --- a/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx +++ b/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx @@ -4,6 +4,7 @@ import { faCheck, faEye, faEyeSlash, + faFileImport, faKey, faXmark } from "@fortawesome/free-solid-svg-icons"; @@ -26,6 +27,7 @@ type Props = { onSecretCreate: (env: string, key: string, value: string) => Promise; onSecretUpdate: (env: string, key: string, value: string, secretId?: string) => Promise; onSecretDelete: (env: string, key: string, secretId?: string) => Promise; + isImportedSecretPresentInEnv: (name: string, env: string) => boolean; }; export const SecretOverviewTableRow = ({ @@ -36,6 +38,7 @@ export const SecretOverviewTableRow = ({ onSecretUpdate, onSecretCreate, onSecretDelete, + isImportedSecretPresentInEnv, expandableColWidth }: Props) => { const [isFormExpanded, setIsFormExpanded] = useToggle(); @@ -61,6 +64,8 @@ export const SecretOverviewTableRow = ({ {environments.map(({ slug }, i) => { const secret = getSecretByKey(slug, secretKey); + + const isSecretImported = isImportedSecretPresentInEnv(secretPath, slug); const isSecretPresent = Boolean(secret); const isSecretEmpty = secret?.value === ""; return ( @@ -69,16 +74,29 @@ export const SecretOverviewTableRow = ({ className={twMerge( "py-0 px-0 group-hover:bg-mineshaft-700", isFormExpanded && "border-t-2 border-mineshaft-500", - isSecretPresent && !isSecretEmpty ? "text-green-600" : "", - isSecretPresent && isSecretEmpty ? "text-yellow" : "", - !isSecretPresent && !isSecretEmpty ? "text-red-600" : "" + (isSecretPresent && !isSecretEmpty) || isSecretImported ? "text-green-600" : "", + isSecretPresent && isSecretEmpty && !isSecretImported ? "text-yellow" : "", + !isSecretPresent && !isSecretEmpty && !isSecretImported ? "text-red-600" : "" )} >
{!isSecretEmpty && ( - - + + )} {isSecretEmpty && ( From 90be28b87a44942bbb515cf958efe789f48740aa Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 20 Mar 2024 20:25:48 +0100 Subject: [PATCH 15/19] Feat: Import indicator --- .../src/hooks/api/secretImports/queries.tsx | 108 +++++++++++++++++- .../views/SecretMainPage/SecretMainPage.tsx | 4 +- .../SecretOverviewPage/SecretOverviewPage.tsx | 9 +- .../SecretOverviewFolderRow.tsx | 6 +- 4 files changed, 117 insertions(+), 10 deletions(-) diff --git a/frontend/src/hooks/api/secretImports/queries.tsx b/frontend/src/hooks/api/secretImports/queries.tsx index 1714f45ff..94321e9e2 100644 --- a/frontend/src/hooks/api/secretImports/queries.tsx +++ b/frontend/src/hooks/api/secretImports/queries.tsx @@ -11,7 +11,7 @@ import { TGetImportedFoldersByEnvDTO, TGetImportedSecrets, TGetSecretImports, - TImportedSecretFolder, + TGetSecretImportsAllEnvs, TImportedSecrets, TSecretImport, TuseGetImportedFoldersByEnv @@ -27,7 +27,9 @@ export const secretImportKeys = { }: Omit) => [{ environment, path, projectId }, "secrets-import-sec"] as const, getImportedFoldersByEnv: ({ environment, projectId, path }: TGetImportedFoldersByEnvDTO) => - [{ environment, projectId, path }, "imported-folders"] as const + [{ environment, projectId, path }, "imported-folders"] as const, + getImportedFoldersAllEnvs: ({ projectId, path, environment }: TGetImportedFoldersByEnvDTO) => + [{ projectId, path, environment }, "imported-folders-all-envs"] as const }; const fetchSecretImport = async ({ projectId, environment, path = "/" }: TGetSecretImports) => { @@ -90,7 +92,7 @@ const fetchImportedFolders = async ({ environment, path }: TGetImportedFoldersByEnvDTO) => { - const { data } = await apiRequest.get<{ secretImports: TImportedSecretFolder[] }>( + const { data } = await apiRequest.get<{ secretImports: TSecretImport[] }>( "/api/v1/secret-imports", { params: { @@ -103,7 +105,7 @@ const fetchImportedFolders = async ({ return data.secretImports; }; -export const useGetImportedSecrets = ({ +export const useGetImportedSecretsSingleEnv = ({ environment, decryptFileKey, path, @@ -188,6 +190,98 @@ export const useGetImportedSecrets = ({ ) }); +export const useGetImportedSecretsAllEnvs = ({ + projectId, + environments, + path = "/", + decryptFileKey +}: TGetSecretImportsAllEnvs) => { + const secretImports = useQueries({ + queries: environments.map((env) => ({ + queryKey: secretImportKeys.getImportedFoldersAllEnvs({ + environment: env, + projectId, + path + }), + queryFn: () => fetchImportedSecrets(projectId, env, path).catch(() => []), + enabled: Boolean(projectId) && Boolean(env), + // eslint-disable-next-line react-hooks/rules-of-hooks + select: useCallback( + (data: TImportedSecrets[]) => { + const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; + const latestKey = decryptFileKey; + const key = decryptAssymmetric({ + ciphertext: latestKey.encryptedKey, + nonce: latestKey.nonce, + publicKey: latestKey.sender.publicKey, + privateKey: PRIVATE_KEY + }); + + return data.map((el) => ({ + environment: el.environment, + secretPath: el.secretPath, + environmentInfo: el.environmentInfo, + folderId: el.folderId, + secrets: el.secrets.map((encSecret) => { + const secretKey = decryptSymmetric({ + ciphertext: encSecret.secretKeyCiphertext, + iv: encSecret.secretKeyIV, + tag: encSecret.secretKeyTag, + key + }); + + const secretValue = decryptSymmetric({ + ciphertext: encSecret.secretValueCiphertext, + iv: encSecret.secretValueIV, + tag: encSecret.secretValueTag, + key + }); + + const secretComment = decryptSymmetric({ + ciphertext: encSecret.secretCommentCiphertext, + iv: encSecret.secretCommentIV, + tag: encSecret.secretCommentTag, + key + }); + + return { + id: encSecret.id, + env: encSecret.environment, + key: secretKey, + value: secretValue, + tags: encSecret.tags, + comment: secretComment, + createdAt: encSecret.createdAt, + updatedAt: encSecret.updatedAt, + version: encSecret.version + }; + }) + })); + }, + [decryptFileKey] + ) + })) + }); + + const isImportedSecretPresentInEnv = useCallback( + (secPath: string, envSlug: string) => { + const selectedEnvIndex = environments.indexOf(envSlug); + + if (selectedEnvIndex !== -1) { + const isPresent = secretImports?.[selectedEnvIndex]?.data?.find( + ({ secretPath }) => secretPath === secPath + ); + + return Boolean(isPresent); + } + return false; + }, + [(secretImports || []).map((response) => response.data)] + ); + + return { secretImports, isImportedSecretPresentInEnv }; +}; + export const useGetImportedFoldersByEnv = ({ projectId, environments, @@ -195,14 +289,16 @@ export const useGetImportedFoldersByEnv = ({ }: TuseGetImportedFoldersByEnv) => { const queryParams = new URLSearchParams(window.location.search); + const currentPath = path; + const importedFolders = useQueries({ queries: environments.map((env) => ({ queryKey: secretImportKeys.getImportedFoldersByEnv({ projectId, environment: env, - path + path: currentPath }), - queryFn: async () => fetchImportedFolders({ projectId, environment: env, path }), + queryFn: async () => fetchImportedFolders({ projectId, environment: env, path: currentPath }), enabled: Boolean(projectId) && Boolean(env) })) }); diff --git a/frontend/src/views/SecretMainPage/SecretMainPage.tsx b/frontend/src/views/SecretMainPage/SecretMainPage.tsx index 312b676b7..a06ceda9d 100644 --- a/frontend/src/views/SecretMainPage/SecretMainPage.tsx +++ b/frontend/src/views/SecretMainPage/SecretMainPage.tsx @@ -17,7 +17,7 @@ import { } from "@app/context"; import { usePopUp } from "@app/hooks"; import { - useGetImportedSecrets, + useGetImportedSecretsSingleEnv, useGetProjectFolders, useGetProjectSecrets, useGetSecretApprovalPolicyOfABoard, @@ -124,7 +124,7 @@ export const SecretMainPage = () => { }); // fetch imported secrets to show user the overriden ones - const { data: importedSecrets } = useGetImportedSecrets({ + const { data: importedSecrets } = useGetImportedSecretsSingleEnv({ projectId: workspaceId, environment, decryptFileKey: decryptFileKey!, diff --git a/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx b/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx index 7eda5c203..02741cc9f 100644 --- a/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx +++ b/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx @@ -56,6 +56,7 @@ import { useDeleteSecretV3, useGetFoldersByEnv, useGetImportedFoldersByEnv, + useGetImportedSecretsAllEnvs, useGetProjectSecretsAllEnv, useGetUserWsKey, useUpdateSecretV3 @@ -134,7 +135,12 @@ export const SecretOverviewPage = () => { const { isImportedFolderPresentInEnv } = useGetImportedFoldersByEnv({ projectId: workspaceId, - path: secretPath, + environments: userAvailableEnvs.map(({ slug }) => slug) + }); + + const { isImportedSecretPresentInEnv } = useGetImportedSecretsAllEnvs({ + projectId: workspaceId, + decryptFileKey: latestFileKey!, environments: userAvailableEnvs.map(({ slug }) => slug) }); @@ -657,6 +663,7 @@ export const SecretOverviewPage = () => { filteredSecretNames.map((key, index) => ( - +
Date: Wed, 20 Mar 2024 20:26:00 +0100 Subject: [PATCH 16/19] Fix: Remove duplicate type --- frontend/src/hooks/api/secretImports/types.ts | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/frontend/src/hooks/api/secretImports/types.ts b/frontend/src/hooks/api/secretImports/types.ts index 7c69a8516..950fc20c4 100644 --- a/frontend/src/hooks/api/secretImports/types.ts +++ b/frontend/src/hooks/api/secretImports/types.ts @@ -12,19 +12,6 @@ export type TSecretImport = { updatedAt: string; }; -export type TImportedSecretFolder = { - id: string; - folderId: string; - importEnv: { - id: string; - name: string; - slug: string; - }; - importPath: string; - - // ... It got more fields, but we won't need them. -}; - export type TGetImportedFoldersByEnvDTO = { projectId: string; environment: string; @@ -45,6 +32,13 @@ export type TGetSecretImports = { path?: string; }; +export type TGetSecretImportsAllEnvs = { + projectId: string; + decryptFileKey: UserWsKeyPair; + path?: string; + environments: string[]; +}; + export type TGetImportedSecrets = { projectId: string; environment: string; From 6363e7d30a83d6ffe2e9cfbd376f8fe48ba6e3dc Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 20 Mar 2024 20:26:28 +0100 Subject: [PATCH 17/19] Update index.ts --- frontend/src/hooks/api/secretImports/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/src/hooks/api/secretImports/index.ts b/frontend/src/hooks/api/secretImports/index.ts index 9c76d597e..f30506b6b 100644 --- a/frontend/src/hooks/api/secretImports/index.ts +++ b/frontend/src/hooks/api/secretImports/index.ts @@ -1,2 +1,7 @@ export { useCreateSecretImport, useDeleteSecretImport, useUpdateSecretImport } from "./mutation"; -export { useGetImportedFoldersByEnv, useGetImportedSecrets, useGetSecretImports } from "./queries"; +export { + useGetImportedFoldersByEnv, + useGetImportedSecretsAllEnvs, + useGetImportedSecretsSingleEnv, + useGetSecretImports +} from "./queries"; From 36adf6863bd88a8bb7a27e8b1c28ea630061d2c6 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 20 Mar 2024 21:09:54 +0100 Subject: [PATCH 18/19] Fix: UI secret import indicator --- frontend/src/hooks/api/secretImports/queries.tsx | 5 +++-- .../SecretOverviewTableRow/SecretOverviewTableRow.tsx | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/frontend/src/hooks/api/secretImports/queries.tsx b/frontend/src/hooks/api/secretImports/queries.tsx index 94321e9e2..1019c6c61 100644 --- a/frontend/src/hooks/api/secretImports/queries.tsx +++ b/frontend/src/hooks/api/secretImports/queries.tsx @@ -264,12 +264,13 @@ export const useGetImportedSecretsAllEnvs = ({ }); const isImportedSecretPresentInEnv = useCallback( - (secPath: string, envSlug: string) => { + (secPath: string, envSlug: string, secretName: string) => { const selectedEnvIndex = environments.indexOf(envSlug); if (selectedEnvIndex !== -1) { const isPresent = secretImports?.[selectedEnvIndex]?.data?.find( - ({ secretPath }) => secretPath === secPath + ({ secretPath, ...rest }) => + secretPath === secPath && rest.secrets.some((s) => s.key === secretName) ); return Boolean(isPresent); diff --git a/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx b/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx index 8e9e60a3b..74c563b05 100644 --- a/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx +++ b/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx @@ -27,7 +27,7 @@ type Props = { onSecretCreate: (env: string, key: string, value: string) => Promise; onSecretUpdate: (env: string, key: string, value: string, secretId?: string) => Promise; onSecretDelete: (env: string, key: string, secretId?: string) => Promise; - isImportedSecretPresentInEnv: (name: string, env: string) => boolean; + isImportedSecretPresentInEnv: (name: string, env: string, secretName: string) => boolean; }; export const SecretOverviewTableRow = ({ @@ -65,7 +65,8 @@ export const SecretOverviewTableRow = ({ {environments.map(({ slug }, i) => { const secret = getSecretByKey(slug, secretKey); - const isSecretImported = isImportedSecretPresentInEnv(secretPath, slug); + const isSecretImported = isImportedSecretPresentInEnv(secretPath, slug, secretKey); + const isSecretPresent = Boolean(secret); const isSecretEmpty = secret?.value === ""; return ( From 609ce8e5cc91ce98853032c4b12cee8981ee6bcf Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Thu, 21 Mar 2024 03:06:14 +0100 Subject: [PATCH 19/19] Fix: Improved UI import indicators --- .../components/v2/SecretInput/SecretInput.tsx | 18 ++++++++++-- .../src/hooks/api/secretImports/queries.tsx | 4 +-- .../SecretOverviewPage/SecretOverviewPage.tsx | 7 ----- .../SecretOverviewFolderRow.tsx | 29 +++++++------------ .../SecretOverviewTableRow/SecretEditRow.tsx | 18 ++++++++++-- .../SecretOverviewTableRow.tsx | 9 +++++- 6 files changed, 52 insertions(+), 33 deletions(-) diff --git a/frontend/src/components/v2/SecretInput/SecretInput.tsx b/frontend/src/components/v2/SecretInput/SecretInput.tsx index 2dfafca74..7fe1c4829 100644 --- a/frontend/src/components/v2/SecretInput/SecretInput.tsx +++ b/frontend/src/components/v2/SecretInput/SecretInput.tsx @@ -14,7 +14,8 @@ const replaceContentWithDot = (str: string) => { return finalStr; }; -const syntaxHighlight = (content?: string | null, isVisible?: boolean) => { +const syntaxHighlight = (content?: string | null, isVisible?: boolean, isImport?: boolean) => { + if (isImport) return "IMPORTED"; if (content === "") return "EMPTY"; if (!content) return "EMPTY"; if (!isVisible) return replaceContentWithDot(content); @@ -46,6 +47,7 @@ const syntaxHighlight = (content?: string | null, isVisible?: boolean) => { type Props = TextareaHTMLAttributes & { value?: string | null; isVisible?: boolean; + isImport?: boolean; isReadOnly?: boolean; isDisabled?: boolean; containerClassName?: string; @@ -55,7 +57,17 @@ const commonClassName = "font-mono text-sm caret-white border-none outline-none export const SecretInput = forwardRef( ( - { value, isVisible, containerClassName, onBlur, isDisabled, isReadOnly, onFocus, ...props }, + { + value, + isVisible, + isImport, + containerClassName, + onBlur, + isDisabled, + isReadOnly, + onFocus, + ...props + }, ref ) => { const [isSecretFocused, setIsSecretFocused] = useToggle(); @@ -69,7 +81,7 @@ export const SecretInput = forwardRef(
             
               
-                {syntaxHighlight(value, isVisible || isSecretFocused)}
+                {syntaxHighlight(value, isVisible || isSecretFocused, isImport)}
               
             
           
diff --git a/frontend/src/hooks/api/secretImports/queries.tsx b/frontend/src/hooks/api/secretImports/queries.tsx index 1019c6c61..2879c859b 100644 --- a/frontend/src/hooks/api/secretImports/queries.tsx +++ b/frontend/src/hooks/api/secretImports/queries.tsx @@ -269,8 +269,8 @@ export const useGetImportedSecretsAllEnvs = ({ if (selectedEnvIndex !== -1) { const isPresent = secretImports?.[selectedEnvIndex]?.data?.find( - ({ secretPath, ...rest }) => - secretPath === secPath && rest.secrets.some((s) => s.key === secretName) + ({ secretPath, secrets }) => + secretPath === secPath && secrets.some((s) => s.key === secretName) ); return Boolean(isPresent); diff --git a/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx b/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx index 02741cc9f..4b78411a9 100644 --- a/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx +++ b/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx @@ -55,7 +55,6 @@ import { useCreateSecretV3, useDeleteSecretV3, useGetFoldersByEnv, - useGetImportedFoldersByEnv, useGetImportedSecretsAllEnvs, useGetProjectSecretsAllEnv, useGetUserWsKey, @@ -133,11 +132,6 @@ export const SecretOverviewPage = () => { environments: userAvailableEnvs.map(({ slug }) => slug) }); - const { isImportedFolderPresentInEnv } = useGetImportedFoldersByEnv({ - projectId: workspaceId, - environments: userAvailableEnvs.map(({ slug }) => slug) - }); - const { isImportedSecretPresentInEnv } = useGetImportedSecretsAllEnvs({ projectId: workspaceId, decryptFileKey: latestFileKey!, @@ -652,7 +646,6 @@ export const SecretOverviewPage = () => { boolean; - isImportedFolderPresentInEnv: (name: string, env: string) => boolean; onClick: (path: string) => void; }; @@ -16,7 +15,7 @@ export const SecretOverviewFolderRow = ({ folderName, environments = [], isFolderPresentInEnv, - isImportedFolderPresentInEnv, + onClick }: Props) => { return ( @@ -31,27 +30,21 @@ export const SecretOverviewFolderRow = ({ {environments.map(({ slug }, i) => { const isPresent = isFolderPresentInEnv(folderName, slug); - const isImportPresent = isImportedFolderPresentInEnv(folderName, slug); + return ( - -
- -
-
+
+ +
); })} diff --git a/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretEditRow.tsx b/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretEditRow.tsx index 9653a9bd0..628e3d008 100644 --- a/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretEditRow.tsx +++ b/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretEditRow.tsx @@ -2,6 +2,7 @@ import { Controller, useForm } from "react-hook-form"; import { subject } from "@casl/ability"; import { faCheck, faCopy, faTrash, faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { twMerge } from "tailwind-merge"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { ProjectPermissionCan } from "@app/components/permissions"; @@ -15,6 +16,7 @@ type Props = { secretId?: string; isCreatable?: boolean; isVisible?: boolean; + isImportedSecret: boolean; environment: string; secretPath: string; onSecretCreate: (env: string, key: string, value: string) => Promise; @@ -25,6 +27,7 @@ type Props = { export const SecretEditRow = ({ defaultValue, isCreatable, + isImportedSecret, onSecretUpdate, secretName, onSecretCreate, @@ -90,14 +93,25 @@ export const SecretEditRow = ({
( - + )} />
-
+
{isDirty ? ( <> @@ -162,6 +162,12 @@ export const SecretOverviewTableRow = ({ const secret = getSecretByKey(slug, secretKey); const isCreatable = !secret; + const isImportedSecret = isImportedSecretPresentInEnv( + secretPath, + slug, + secretKey + ); + return (