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] 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<TGetImportedSecrets, "decryptFileKey">) =>
     [{ 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) => (
                     <SecretOverviewTableRow
                       secretPath={secretPath}
+                      isImportedSecretPresentInEnv={isImportedSecretPresentInEnv}
                       onSecretCreate={handleSecretCreate}
                       onSecretDelete={handleSecretDelete}
                       onSecretUpdate={handleSecretUpdate}
diff --git a/frontend/src/views/SecretOverviewPage/components/SecretOverviewFolderRow/SecretOverviewFolderRow.tsx b/frontend/src/views/SecretOverviewPage/components/SecretOverviewFolderRow/SecretOverviewFolderRow.tsx
index ca506f7cb..fd225c9e1 100644
--- a/frontend/src/views/SecretOverviewPage/components/SecretOverviewFolderRow/SecretOverviewFolderRow.tsx
+++ b/frontend/src/views/SecretOverviewPage/components/SecretOverviewFolderRow/SecretOverviewFolderRow.tsx
@@ -40,7 +40,11 @@ export const SecretOverviewFolderRow = ({
               isPresent || isImportPresent ? "text-green-600" : "text-red-600"
             )}
           >
-            <Tooltip isDisabled={!isImportPresent} content="Folder is imported">
+            <Tooltip
+              center
+              isDisabled={!isImportPresent}
+              content="Folder is imported from another environment"
+            >
               <div className="flex justify-center">
                 <FontAwesomeIcon
                   // eslint-disable-next-line no-nested-ternary