From ad110f490c38cda0234eceec497c10346bbb5e67 Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Mon, 17 Mar 2025 19:13:06 -0300 Subject: [PATCH] Show when folders have imports in secret overview page --- .../src/hooks/api/secretImports/queries.tsx | 1 + frontend/src/hooks/api/secretImports/types.ts | 21 +++ frontend/src/hooks/utils/secrets-overview.tsx | 39 +++++ .../OverviewPage/OverviewPage.tsx | 41 ++++- .../SecretOverviewImportListView.tsx | 160 ++++++++++++++++++ .../SecretOverviewImportListView/index.tsx | 1 + 6 files changed, 256 insertions(+), 7 deletions(-) create mode 100644 frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewImportListView/SecretOverviewImportListView.tsx create mode 100644 frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewImportListView/index.tsx diff --git a/frontend/src/hooks/api/secretImports/queries.tsx b/frontend/src/hooks/api/secretImports/queries.tsx index dfc8c6497..828bd52e1 100644 --- a/frontend/src/hooks/api/secretImports/queries.tsx +++ b/frontend/src/hooks/api/secretImports/queries.tsx @@ -167,6 +167,7 @@ export const useGetImportedSecretsAllEnvs = ({ select: useCallback( (data: Awaited>) => data.map((el) => ({ + currentEnv: env, environment: el.environment, secretPath: el.secretPath, environmentInfo: el.environmentInfo, diff --git a/frontend/src/hooks/api/secretImports/types.ts b/frontend/src/hooks/api/secretImports/types.ts index 1bf60bd4e..83abd300d 100644 --- a/frontend/src/hooks/api/secretImports/types.ts +++ b/frontend/src/hooks/api/secretImports/types.ts @@ -1,4 +1,5 @@ import { SecretV3Raw } from "../secrets/types"; +import { WsTag } from "../types"; import { WorkspaceEnv } from "../workspace/types"; export type TSecretImport = { @@ -90,3 +91,23 @@ export type TDeleteSecretImportDTO = { environment: string; path?: string; }; + +export type SecretImportData = { + currentEnv: string; + environment: string; + secretPath: string; + environmentInfo: WorkspaceEnv; + folderId: string; + secrets: { + id: string; + env: string; + key: string; + value: string; + secretValueHidden: boolean; + tags?: WsTag[]; + comment?: string; + createdAt: string; + updatedAt: string; + version: number; + }[]; +}; diff --git a/frontend/src/hooks/utils/secrets-overview.tsx b/frontend/src/hooks/utils/secrets-overview.tsx index 89fc679c4..4076cb5c0 100644 --- a/frontend/src/hooks/utils/secrets-overview.tsx +++ b/frontend/src/hooks/utils/secrets-overview.tsx @@ -2,6 +2,8 @@ import { useCallback, useMemo } from "react"; import { DashboardProjectSecretsOverview } from "@app/hooks/api/dashboard/types"; +import { SecretImportData } from "../api/secretImports/types"; + type FolderNameAndDescription = { name: string; description?: string; @@ -96,3 +98,40 @@ export const useSecretOverview = (secrets: DashboardProjectSecretsOverview["secr return { secKeys, getSecretByKey, getEnvSecretKeyCount }; }; + +export const useSecretImportOverview = (secretImports: SecretImportData[][] | undefined) => { + const uniqueEnvSecretPaths = useMemo(() => { + const uniqueMap: Record = {}; + secretImports?.forEach((importData) => { + importData?.forEach((envImport) => { + if (envImport) { + const key = `${envImport.environment}-${envImport.secretPath}`; + const existing = uniqueMap[key]; + uniqueMap[key] = existing ? [...existing, envImport] : [envImport]; + } + }); + }); + return uniqueMap; + }, [secretImports]); + + const isSecretImportPresent = useCallback( + (sourceEnv: string, targetEnv: string, secretPath: string) => { + return ( + secretImports?.some((importData) => + importData?.some( + (envImport) => + envImport?.currentEnv === sourceEnv && + envImport?.environment === targetEnv && + envImport?.secretPath === secretPath + ) + ) ?? false + ); + }, + [secretImports] + ); + + return { + uniqueEnvSecretPaths, + isSecretImportPresent + }; +}; diff --git a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx index 78a2380d0..4c8a93279 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx @@ -67,15 +67,22 @@ import { DashboardSecretsOrderBy } from "@app/hooks/api/dashboard/types"; import { OrderByDirection } from "@app/hooks/api/generic/types"; import { useUpdateFolderBatch } from "@app/hooks/api/secretFolders/queries"; import { TUpdateFolderBatchDTO } from "@app/hooks/api/secretFolders/types"; +import { SecretImportData } from "@app/hooks/api/secretImports/types"; import { SecretType, SecretV3RawSanitized, TSecretFolder } from "@app/hooks/api/types"; import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types"; -import { useDynamicSecretOverview, useFolderOverview, useSecretOverview } from "@app/hooks/utils"; +import { + useDynamicSecretOverview, + useFolderOverview, + useSecretImportOverview, + useSecretOverview +} from "@app/hooks/utils"; import { FolderForm } from "../SecretDashboardPage/components/ActionBar/FolderForm"; import { CreateSecretForm } from "./components/CreateSecretForm"; import { FolderBreadCrumbs } from "./components/FolderBreadCrumbs"; import { SecretOverviewDynamicSecretRow } from "./components/SecretOverviewDynamicSecretRow"; import { SecretOverviewFolderRow } from "./components/SecretOverviewFolderRow"; +import { SecretOverviewImportListView } from "./components/SecretOverviewImportListView"; import { SecretNoAccessOverviewTableRow, SecretOverviewTableRow @@ -185,12 +192,22 @@ export const OverviewPage = () => { setVisibleEnvs(userAvailableEnvs); }, [userAvailableEnvs]); - const { isImportedSecretPresentInEnv, getImportedSecretByKey, getEnvImportedSecretKeyCount } = - useGetImportedSecretsAllEnvs({ - projectId: workspaceId, - path: secretPath, - environments: (userAvailableEnvs || []).map(({ slug }) => slug) - }); + const { + secretImports, + isImportedSecretPresentInEnv, + getImportedSecretByKey, + getEnvImportedSecretKeyCount + } = useGetImportedSecretsAllEnvs({ + projectId: workspaceId, + path: secretPath, + environments: (userAvailableEnvs || []).map(({ slug }) => slug) + }); + const secretImportsData = useMemo( + () => secretImports?.map((s) => s.data as SecretImportData[]) ?? [], + [secretImports] + ); + const { uniqueEnvSecretPaths, isSecretImportPresent } = + useSecretImportOverview(secretImportsData); const { isPending: isOverviewLoading, data: overview } = useGetProjectSecretsOverview( { @@ -1054,6 +1071,16 @@ export const OverviewPage = () => { key={`overview-${dynamicSecretName}-${index + 1}`} /> ))} + {Object.entries(uniqueEnvSecretPaths).map(([key, secretImportsAllEnvs]) => ( + + ))} {secKeys.map((key, index) => ( boolean; + scrollOffset: number; + allSecretImports: SecretImportData[][] | undefined; +}; + +export const SecretOverviewImportListView = ({ + secretImport, + environments = [], + isImportedSecretPresentInEnv, + scrollOffset, + allSecretImports +}: Props) => { + const [isFormExpanded, setIsFormExpanded] = useToggle(); + const { permission } = useProjectPermission(); + const environmentImportDetails = secretImport.environmentInfo; + const totalCols = environments.length + 1; + + const canReadSecretImports = permission.can( + ProjectPermissionActions.Read, + subject(ProjectPermissionSub.SecretImports, { + environment: environmentImportDetails.slug, + secretPath: secretImport.secretPath + }) + ); + + const computeImportedSecrets = + canReadSecretImports && allSecretImports + ? computeImportedSecretRows( + environmentImportDetails.slug, + secretImport.secretPath, + (allSecretImports?.flatMap((s) => s ?? []) ?? []).filter(Boolean) as SecretImportData[] + ) + : []; + return ( + <> + canReadSecretImports && setIsFormExpanded.toggle()} + className={`group ${isFormExpanded ? "border-t-2 border-mineshaft-500" : ""}`} + > + +
+
+ +
+
+ +
+
+ + {environments.map(({ slug }, i) => { + const isPresent = isImportedSecretPresentInEnv( + slug, + secretImport.environment, + secretImport.secretPath + ); + + return ( + +
+
+ +
+
+ + ); + })} + + {canReadSecretImports && isFormExpanded && ( + + +
+ + + + + + + {/* */} + + + + {computeImportedSecrets?.length === 0 && ( + + + + )} + {computeImportedSecrets.map(({ key, value }, index) => ( + + + + + ))} + +
KeyValueOverride
+ +
+ {key} + + +
+
+
+ + + )} + + ); +}; diff --git a/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewImportListView/index.tsx b/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewImportListView/index.tsx new file mode 100644 index 000000000..daf7f71b4 --- /dev/null +++ b/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewImportListView/index.tsx @@ -0,0 +1 @@ +export { SecretOverviewImportListView } from "./SecretOverviewImportListView";