Просмотр исходного кода

Merge pull request #1801 from porter-dev/nafees/preview-env-delete-deployment

[Improvement] Implement deletion for preview environments
Porter Support 4 лет назад
Родитель
Сommit
e7ead7218a

+ 37 - 23
dashboard/src/components/repo-selector/RepoList.tsx

@@ -20,6 +20,7 @@ type Props = {
   setActionConfig: (x: ActionConfigType) => void;
   setActionConfig: (x: ActionConfigType) => void;
   userId?: number;
   userId?: number;
   readOnly: boolean;
   readOnly: boolean;
+  filteredRepos?: string[];
 };
 };
 
 
 const RepoList: React.FC<Props> = ({
 const RepoList: React.FC<Props> = ({
@@ -27,6 +28,7 @@ const RepoList: React.FC<Props> = ({
   setActionConfig,
   setActionConfig,
   userId,
   userId,
   readOnly,
   readOnly,
+  filteredRepos,
 }) => {
 }) => {
   const [repos, setRepos] = useState<RepoType[]>([]);
   const [repos, setRepos] = useState<RepoType[]>([]);
   const [repoLoading, setRepoLoading] = useState(true);
   const [repoLoading, setRepoLoading] = useState(true);
@@ -123,7 +125,6 @@ const RepoList: React.FC<Props> = ({
       });
       });
   }, []);
   }, []);
 
 
-
   // clear out actionConfig and SelectedRepository if new search is performed
   // clear out actionConfig and SelectedRepository if new search is performed
   useEffect(() => {
   useEffect(() => {
     setActionConfig({
     setActionConfig({
@@ -132,15 +133,15 @@ const RepoList: React.FC<Props> = ({
       git_branch: null,
       git_branch: null,
       git_repo_id: 0,
       git_repo_id: 0,
     });
     });
-    setSelectedRepo(null)
-  }, [searchFilter])
+    setSelectedRepo(null);
+  }, [searchFilter]);
 
 
   const setRepo = (x: RepoType) => {
   const setRepo = (x: RepoType) => {
     let updatedConfig = actionConfig;
     let updatedConfig = actionConfig;
     updatedConfig.git_repo = x.FullName;
     updatedConfig.git_repo = x.FullName;
     updatedConfig.git_repo_id = x.GHRepoID;
     updatedConfig.git_repo_id = x.GHRepoID;
     setActionConfig(updatedConfig);
     setActionConfig(updatedConfig);
-    setSelectedRepo(x.FullName)
+    setSelectedRepo(x.FullName);
   };
   };
 
 
   const renderRepoList = () => {
   const renderRepoList = () => {
@@ -191,6 +192,9 @@ const RepoList: React.FC<Props> = ({
       return <LoadingWrapper>No matching Github repos found.</LoadingWrapper>;
       return <LoadingWrapper>No matching Github repos found.</LoadingWrapper>;
     } else {
     } else {
       return results.map((repo: RepoType, i: number) => {
       return results.map((repo: RepoType, i: number) => {
+        const shouldDisable = !!filteredRepos?.find(
+          (filteredRepo) => repo.FullName === filteredRepo
+        );
         return (
         return (
           <RepoName
           <RepoName
             key={i}
             key={i}
@@ -198,9 +202,11 @@ const RepoList: React.FC<Props> = ({
             lastItem={i === repos.length - 1}
             lastItem={i === repos.length - 1}
             onClick={() => setRepo(repo)}
             onClick={() => setRepo(repo)}
             readOnly={readOnly}
             readOnly={readOnly}
+            disabled={shouldDisable}
           >
           >
             <img src={github} alt={"github icon"} />
             <img src={github} alt={"github icon"} />
             {repo.FullName}
             {repo.FullName}
+            {shouldDisable && ` - This repo was already added`}
           </RepoName>
           </RepoName>
         );
         );
       });
       });
@@ -237,32 +243,40 @@ const RepoListWrapper = styled.div`
   overflow-y: auto;
   overflow-y: auto;
 `;
 `;
 
 
-const RepoName = styled.div`
+type RepoNameProps = {
+  lastItem: boolean;
+  isSelected: boolean;
+  readOnly: boolean;
+  disabled: boolean;
+};
+
+const RepoName = styled.div<RepoNameProps>`
   display: flex;
   display: flex;
   width: 100%;
   width: 100%;
   font-size: 13px;
   font-size: 13px;
   border-bottom: 1px solid
   border-bottom: 1px solid
-    ${(props: { lastItem: boolean; isSelected: boolean; readOnly: boolean }) =>
-      props.lastItem ? "#00000000" : "#606166"};
-  color: #ffffff;
+    ${(props) => (props.lastItem ? "#00000000" : "#606166")};
+  color: ${(props) => (props.disabled ? "#ffffff88" : "#ffffff")};
   user-select: none;
   user-select: none;
   align-items: center;
   align-items: center;
   padding: 10px 0px;
   padding: 10px 0px;
-  cursor: ${(props: {
-    lastItem: boolean;
-    isSelected: boolean;
-    readOnly: boolean;
-  }) => (props.readOnly ? "default" : "pointer")};
-  pointer-events: ${(props: {
-    lastItem: boolean;
-    isSelected: boolean;
-    readOnly: boolean;
-  }) => (props.readOnly ? "none" : "auto")};
-  background: ${(props: {
-    lastItem: boolean;
-    isSelected: boolean;
-    readOnly: boolean;
-  }) => (props.isSelected ? "#ffffff22" : "#ffffff11")};
+  cursor: ${(props) =>
+    props.readOnly || props.disabled ? "default" : "pointer"};
+  pointer-events: ${(props) =>
+    props.readOnly || props.disabled ? "none" : "auto"};
+
+  ${(props) => {
+    if (props.disabled) {
+      return "";
+    }
+
+    if (props.isSelected) {
+      return `background: #ffffff22;`;
+    }
+
+    return `background: #ffffff11;`;
+  }}
+
   :hover {
   :hover {
     background: #ffffff22;
     background: #ffffff22;
 
 

+ 20 - 3
dashboard/src/main/home/cluster-dashboard/dashboard/preview-environments/EnvironmentList.tsx

@@ -200,7 +200,7 @@ const EnvironmentList = () => {
         if (!Array.isArray(data)) {
         if (!Array.isArray(data)) {
           throw Error("Data is not an array");
           throw Error("Data is not an array");
         }
         }
-        setEnvironmentList(data);
+        setDeploymentList(data);
       })
       })
       .catch((err) => {
       .catch((err) => {
         setHasError(true);
         setHasError(true);
@@ -226,7 +226,7 @@ const EnvironmentList = () => {
     );
     );
   }
   }
 
 
-  if (isLoading || !hasPermissionsLoaded) {
+  if (!hasPermissionsLoaded) {
     return (
     return (
       <Placeholder>
       <Placeholder>
         <Loading />
         <Loading />
@@ -252,6 +252,14 @@ const EnvironmentList = () => {
   }
   }
 
 
   let renderDeploymentList = () => {
   let renderDeploymentList = () => {
+    if (isLoading) {
+      return (
+        <Placeholder>
+          <Loading />
+        </Placeholder>
+      );
+    }
+
     if (!deploymentList.length) {
     if (!deploymentList.length) {
       return (
       return (
         <Placeholder>
         <Placeholder>
@@ -262,7 +270,16 @@ const EnvironmentList = () => {
     }
     }
 
 
     return deploymentList.map((d) => {
     return deploymentList.map((d) => {
-      return <EnvironmentCard deployment={d} />;
+      const environment = environmentList?.find((e) => {
+        return e.id === d.environment_id;
+      });
+      return (
+        <EnvironmentCard
+          deployment={d}
+          environment={environment}
+          onDelete={handleRefresh}
+        />
+      );
     });
     });
   };
   };
 
 

+ 28 - 3
dashboard/src/main/home/cluster-dashboard/dashboard/preview-environments/components/ConnectNewRepo.tsx

@@ -12,15 +12,15 @@ import styled from "styled-components";
 import api from "shared/api";
 import api from "shared/api";
 import { Context } from "shared/Context";
 import { Context } from "shared/Context";
 import { useRouting } from "shared/routing";
 import { useRouting } from "shared/routing";
-
-const porterYamlDocsLink =
-  "https://docs.porter.run/preview-environments/porter-yaml-reference";
+import { Environment } from "../EnvironmentList";
 
 
 const ConnectNewRepo: React.FC = () => {
 const ConnectNewRepo: React.FC = () => {
   const { currentProject, currentCluster, setCurrentError } = useContext(
   const { currentProject, currentCluster, setCurrentError } = useContext(
     Context
     Context
   );
   );
   const [repo, setRepo] = useState(null);
   const [repo, setRepo] = useState(null);
+  const [filteredRepos, setFilteredRepos] = useState<string[]>([]);
+
   const [status, setStatus] = useState(null);
   const [status, setStatus] = useState(null);
   const { pushFiltered } = useRouting();
   const { pushFiltered } = useRouting();
 
 
@@ -36,6 +36,30 @@ const ConnectNewRepo: React.FC = () => {
 
 
   const { url } = useRouteMatch();
   const { url } = useRouteMatch();
 
 
+  useEffect(() => {
+    api
+      .listEnvironments<Environment[]>(
+        "<token>",
+        {},
+        {
+          project_id: currentProject.id,
+          cluster_id: currentCluster.id,
+        }
+      )
+      .then(({ data }) => {
+        console.log("github account", data);
+
+        if (!Array.isArray(data)) {
+          throw Error("Data is not an array");
+        }
+        const newFilteredRepos = data.map((env) => {
+          return `${env.git_repo_owner}/${env.git_repo_name}`;
+        });
+        setFilteredRepos(newFilteredRepos || []);
+      })
+      .catch(() => {});
+  }, []);
+
   const addRepo = () => {
   const addRepo = () => {
     let [owner, repoName] = repo.split("/");
     let [owner, repoName] = repo.split("/");
     setStatus("loading");
     setStatus("loading");
@@ -84,6 +108,7 @@ const ConnectNewRepo: React.FC = () => {
           setRepo(a.git_repo);
           setRepo(a.git_repo);
         }}
         }}
         readOnly={false}
         readOnly={false}
+        filteredRepos={filteredRepos}
       />
       />
       <HelperContainer>
       <HelperContainer>
         Note: you will need to add a <CodeBlock>porter.yaml</CodeBlock> file to
         Note: you will need to add a <CodeBlock>porter.yaml</CodeBlock> file to

+ 121 - 20
dashboard/src/main/home/cluster-dashboard/dashboard/preview-environments/components/EnvironmentCard.tsx

@@ -1,20 +1,51 @@
 import React, { useState } from "react";
 import React, { useState } from "react";
-import styled from "styled-components";
-import { PRDeployment } from "../EnvironmentList";
+import styled, { keyframes } from "styled-components";
+import { Environment, PRDeployment } from "../EnvironmentList";
 import pr_icon from "assets/pull_request_icon.svg";
 import pr_icon from "assets/pull_request_icon.svg";
 import { integrationList } from "shared/common";
 import { integrationList } from "shared/common";
 import { useRouteMatch } from "react-router";
 import { useRouteMatch } from "react-router";
 import DynamicLink from "components/DynamicLink";
 import DynamicLink from "components/DynamicLink";
 import { capitalize, readableDate } from "shared/string_utils";
 import { capitalize, readableDate } from "shared/string_utils";
+import api from "shared/api";
+import { useContext } from "react";
+import { Context } from "shared/Context";
 
 
-const EnvironmentCard: React.FC<{ deployment: PRDeployment }> = ({
-  deployment,
-}) => {
+const EnvironmentCard: React.FC<{
+  deployment: PRDeployment;
+  environment: Environment;
+  onDelete: () => void;
+}> = ({ deployment, environment, onDelete }) => {
+  const { setCurrentOverlay } = useContext(Context);
   const [showRepoTooltip, setShowRepoTooltip] = useState(false);
   const [showRepoTooltip, setShowRepoTooltip] = useState(false);
+  const [isDeleting, setIsDeleting] = useState(false);
   const { url: currentUrl } = useRouteMatch();
   const { url: currentUrl } = useRouteMatch();
 
 
   let repository = `${deployment.gh_repo_owner}/${deployment.gh_repo_name}`;
   let repository = `${deployment.gh_repo_owner}/${deployment.gh_repo_name}`;
 
 
+  const deleteDeployment = () => {
+    setIsDeleting(true);
+
+    api
+      .deletePRDeployment(
+        "<token>",
+        {
+          namespace: deployment.namespace,
+        },
+        {
+          cluster_id: environment.cluster_id,
+          project_id: environment.project_id,
+          git_installation_id: environment.git_installation_id,
+          git_repo_owner: environment.git_repo_owner,
+          git_repo_name: environment.git_repo_name,
+        }
+      )
+      .then(() => {
+        setIsDeleting(false);
+        onDelete();
+        setCurrentOverlay(null);
+      });
+  };
+
   return (
   return (
     <EnvironmentCardWrapper key={deployment.id}>
     <EnvironmentCardWrapper key={deployment.id}>
       <DataContainer>
       <DataContainer>
@@ -52,27 +83,97 @@ const EnvironmentCard: React.FC<{ deployment: PRDeployment }> = ({
         </Flex>
         </Flex>
       </DataContainer>
       </DataContainer>
       <Flex>
       <Flex>
-        <RowButton
-          to={`${currentUrl}/pr-env-detail/${deployment.namespace}?environment_id=${deployment.environment_id}`}
-          key={deployment.id}
-        >
-          <i className="material-icons-outlined">info</i>
-          Details
-        </RowButton>
-        <RowButton
-          to={deployment.subdomain}
-          key={deployment.subdomain}
-          target="_blank"
-        >
-          <i className="material-icons">open_in_new</i>
-          View Live
-        </RowButton>
+        {!isDeleting ? (
+          <>
+            {deployment.status !== "creating" && (
+              <>
+                <RowButton
+                  to={`${currentUrl}/pr-env-detail/${deployment.namespace}?environment_id=${deployment.environment_id}`}
+                  key={deployment.id}
+                >
+                  <i className="material-icons-outlined">info</i>
+                  Details
+                </RowButton>
+                <RowButton
+                  to={deployment.subdomain}
+                  key={deployment.subdomain}
+                  target="_blank"
+                >
+                  <i className="material-icons">open_in_new</i>
+                  View Live
+                </RowButton>
+              </>
+            )}
+            <RowButton
+              to={"#"}
+              key={deployment.subdomain}
+              onClick={() =>
+                setCurrentOverlay({
+                  message: `Are you sure you want to delete this deployment?`,
+                  onYes: deleteDeployment,
+                  onNo: () => setCurrentOverlay(null),
+                })
+              }
+            >
+              <i className="material-icons">delete</i>
+              Delete
+            </RowButton>
+          </>
+        ) : (
+          <DeleteMessage>
+            Deleting
+            <Dot delay="0s" />
+            <Dot delay="0.1s" />
+            <Dot delay="0.2s" />
+          </DeleteMessage>
+        )}
       </Flex>
       </Flex>
     </EnvironmentCardWrapper>
     </EnvironmentCardWrapper>
   );
   );
 };
 };
 
 
 export default EnvironmentCard;
 export default EnvironmentCard;
+
+const DeleteMessage = styled.div`
+  display: flex;
+  align-items: flex-end;
+  justify-content: center;
+`;
+
+export const DissapearAnimation = keyframes`
+  0% { 
+    background-color: #ffffff; 
+  }
+
+  25% {
+    background-color: #ffffff50;
+  }
+
+  50% { 
+    background-color: none;
+  }
+
+  75% {
+    background-color: #ffffff50;
+  }
+
+  100% { 
+    background-color: #ffffff;
+  }
+`;
+
+const Dot = styled.div`
+  background-color: black;
+  border-radius: 50%;
+  width: 5px;
+  height: 5px;
+  margin: 0 0.25rem;
+  margin-bottom: 2px;
+  //Animation
+  animation: ${DissapearAnimation} 0.5s linear infinite;
+  animation-delay: ${(props: { delay: string }) => props.delay};
+`;
+
 const Flex = styled.div`
 const Flex = styled.div`
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;

+ 23 - 0
dashboard/src/shared/api.tsx

@@ -337,6 +337,28 @@ const getPRDeployment = baseApi<
   return `/api/projects/${project_id}/gitrepos/${git_installation_id}/${git_repo_owner}/${git_repo_name}/clusters/${cluster_id}/deployment`;
   return `/api/projects/${project_id}/gitrepos/${git_installation_id}/${git_repo_owner}/${git_repo_name}/clusters/${cluster_id}/deployment`;
 });
 });
 
 
+const deletePRDeployment = baseApi<
+  {
+    namespace: string;
+  },
+  {
+    cluster_id: number;
+    project_id: number;
+    git_installation_id: number;
+    git_repo_owner: string;
+    git_repo_name: string;
+  }
+>("DELETE", (pathParams) => {
+  const {
+    cluster_id,
+    project_id,
+    git_installation_id,
+    git_repo_owner,
+    git_repo_name,
+  } = pathParams;
+  return `/api/projects/${project_id}/gitrepos/${git_installation_id}/${git_repo_owner}/${git_repo_name}/clusters/${cluster_id}/deployment`;
+});
+
 const getNotificationConfig = baseApi<
 const getNotificationConfig = baseApi<
   {},
   {},
   {
   {
@@ -1741,4 +1763,5 @@ export default {
   provisionDatabase,
   provisionDatabase,
   getDatabases,
   getDatabases,
   getPreviousLogsForContainer,
   getPreviousLogsForContainer,
+  deletePRDeployment,
 };
 };