Bladeren bron

UI preview env (#3029)

* Preview Clean Up

* Preview Clean Up

* fix button

* fix button

* fix button

* merge

* merge

* Deleted View

* Updated Search func

* Clean Up CSS

* Clean Up CSS
sdess09 3 jaren geleden
bovenliggende
commit
921cc1cdee

+ 5 - 5
dashboard/src/components/repo-selector/ActionConfBranchSelector.tsx

@@ -68,11 +68,11 @@ const ActionConfEditorStack: React.FC<Props> = (props) => {
       <BackButton
         width="145px"
         onClick={() => {
-          setBranch("");
-          props.setFolderPath("");
-          props.setDockerfilePath("");
-          props.setActionConfig(actionConfig);
-          props.setBuildView("buildpacks");
+          setBranch ? setBranch("") : null;
+          props.setFolderPath ? props.setFolderPath("") : null;
+          props.setDockerfilePath ? props.setDockerfilePath("") : null;
+          props.setActionConfig ? props.setActionConfig(actionConfig) : null;
+          props.setBuildView ? props.setBuildView("buildpacks") : null;
           setPorterYamlPath("");
         }}
       >

+ 10 - 10
dashboard/src/components/repo-selector/ActionConfEditorStack.tsx

@@ -10,17 +10,17 @@ import Input from "components/porter/Input";
 type Props = {
   actionConfig: ActionConfigType | null;
   setActionConfig: (x: ActionConfigType) => void;
-  setBranch: (x: string) => void;
-  setDockerfilePath: (x: string) => void;
-  setFolderPath: (x: string) => void;
+  setBranch?: (x: string) => void;
+  setDockerfilePath?: (x: string) => void;
+  setFolderPath?: (x: string) => void;
   setBuildView?: (x: string) => void;
   setPorterYamlPath?: (x: string) => void;
 };
 
 const defaultActionConfig: ActionConfigType = {
-  git_repo: "",
-  image_repo_uri: "",
-  git_branch: "",
+  git_repo: null,
+  image_repo_uri: null,
+  git_branch: null,
   git_repo_id: 0,
   kind: "github",
 };
@@ -59,10 +59,10 @@ const ActionConfEditorStack: React.FC<Props> = ({
           width="135px"
           onClick={() => {
             setActionConfig({ ...defaultActionConfig });
-            setBranch("");
-            setFolderPath("");
-            setDockerfilePath("");
-            setBuildView("buildpacks");
+            setBranch ? setBranch("") : null;
+            setFolderPath ? setFolderPath("") : null;
+            setDockerfilePath ? setDockerfilePath("") : null;
+            setBuildView ? setBuildView("buildpacks") : null;
             setPorterYamlPath("");
           }}
         >

+ 42 - 29
dashboard/src/main/home/app-dashboard/new-app-flow/NewAppFlow.tsx

@@ -86,15 +86,15 @@ interface GithubAppAccessData {
 }
 type Provider =
   | {
-    provider: "github";
-    name: string;
-    installation_id: number;
-  }
+      provider: "github";
+      name: string;
+      installation_id: number;
+    }
   | {
-    provider: "gitlab";
-    instance_url: string;
-    integration_id: number;
-  };
+      provider: "gitlab";
+      instance_url: string;
+      integration_id: number;
+    };
 const NewAppFlow: React.FC<Props> = ({ ...props }) => {
   const [templateName, setTemplateName] = useState("");
   const [porterYamlPath, setPorterYamlPath] = useState("");
@@ -143,7 +143,7 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
     setAccessData(data);
     setShowGithubConnectModal(
       !hasClickedDoNotConnect &&
-      (accessError || !data.accounts || data.accounts?.length === 0)
+        (accessError || !data.accounts || data.accounts?.length === 0)
     );
   };
 
@@ -151,7 +151,7 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
     setAccessError(error);
     setShowGithubConnectModal(
       !hasClickedDoNotConnect &&
-      (error || !accessData.accounts || accessData.accounts?.length === 0)
+        (error || !accessData.accounts || accessData.accounts?.length === 0)
     );
   };
 
@@ -174,7 +174,7 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
     } catch (err) {
       // TODO: handle analytics error
     }
-  }
+  };
 
   const validatePorterYaml = (yamlString: string) => {
     let parsedYaml;
@@ -198,11 +198,21 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
         }
       }
       if (!formState.releaseJob.length && porterYamlToJson.release != null) {
-        newReleaseJob.push(Service.default("pre-deploy", "release", porterYamlToJson) as ReleaseService);
+        newReleaseJob.push(
+          Service.default(
+            "pre-deploy",
+            "release",
+            porterYamlToJson
+          ) as ReleaseService
+        );
       }
       const newServiceList = [...formState.serviceList, ...newServices];
       const newReleaseJobList = [...formState.releaseJob, ...newReleaseJob];
-      setFormState({ ...formState, serviceList: newServiceList, releaseJob: newReleaseJobList });
+      setFormState({
+        ...formState,
+        serviceList: newServiceList,
+        releaseJob: newReleaseJobList,
+      });
       if (Validators.serviceList(newServiceList)) {
         setCurrentStep(Math.max(currentStep, 5));
       }
@@ -213,7 +223,9 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
       ) {
         setDetected({
           detected: true,
-          message: `Detected ${Object.keys(porterYamlToJson.apps).length} service${Object.keys(porterYamlToJson.apps).length === 1 ? "" : "s"} from porter.yaml`,
+          message: `Detected ${
+            Object.keys(porterYamlToJson.apps).length
+          } services from porter.yaml`,
         });
       } else {
         setDetected({
@@ -311,12 +323,9 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
       setDeploymentError(undefined);
 
       // log analytics event that we started form submission
-      await updateStackStep('stack-launch-complete');
+      await updateStackStep("stack-launch-complete");
 
-      if (
-        currentProject?.id == null ||
-        currentCluster?.id == null
-      ) {
+      if (currentProject?.id == null || currentCluster?.id == null) {
         throw "Project or cluster not found";
       }
 
@@ -327,7 +336,8 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
         formState.envVariables,
         porterJson,
         // if we are using a heroku buildpack, inject a PORT env variable
-        (buildConfig as any)?.builder != null && (buildConfig as any)?.builder.includes("heroku")
+        (buildConfig as any)?.builder != null &&
+          (buildConfig as any)?.builder.includes("heroku")
       );
 
       const yamlString = yaml.dump(finalPorterYaml);
@@ -341,7 +351,7 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
           repository: imageUrl,
           tag: imageTag,
         };
-      };
+      }
 
       await api.createPorterApp(
         "<token>",
@@ -374,7 +384,7 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
       }
 
       // log analytics event that we successfully deployed
-      await updateStackStep('stack-launch-success');
+      await updateStackStep("stack-launch-success");
 
       return true;
     } catch (err) {
@@ -562,12 +572,15 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
                   limitOne={true}
                   customOnClick={() => {
                     setFormState({
-                      ...formState, releaseJob: [Service.default(
-                        "pre-deploy",
-                        "release",
-                        porterJson
-                      ) as ReleaseService],
-                    })
+                      ...formState,
+                      releaseJob: [
+                        Service.default(
+                          "pre-deploy",
+                          "release",
+                          porterJson
+                        ) as ReleaseService,
+                      ],
+                    });
                   }}
                   addNewText={"Add a new pre-deploy job"}
                 />
@@ -731,7 +744,7 @@ const ConnectToGithubButton = styled.a`
     props.disabled ? "#aaaabbee" : "#2E3338"};
   :hover {
     background: ${(props: { disabled?: boolean }) =>
-    props.disabled ? "" : "#353a3e"};
+      props.disabled ? "" : "#353a3e"};
   }
 
   > i {

+ 163 - 77
dashboard/src/main/home/cluster-dashboard/preview-environments/ConnectNewRepo.tsx

@@ -3,7 +3,7 @@ import Heading from "components/form-components/Heading";
 import RepoList from "components/repo-selector/RepoList";
 import SaveButton from "components/SaveButton";
 import DocsHelper from "components/DocsHelper";
-import { GithubActionConfigType } from "shared/types";
+import { ActionConfigType, GithubActionConfigType } from "shared/types";
 import React, { useContext, useEffect, useState } from "react";
 import styled from "styled-components";
 import api from "shared/api";
@@ -16,6 +16,10 @@ import CheckboxRow from "components/form-components/CheckboxRow";
 import BranchFilterSelector from "./components/BranchFilterSelector";
 import Helper from "components/form-components/Helper";
 import NamespaceLabels, { KeyValueType } from "./components/NamespaceLabels";
+import ActionConfEditorStack from "components/repo-selector/ActionConfEditorStack";
+import AnimateHeight from "react-animate-height";
+import Text from "components/porter/Text";
+import Spacer from "components/porter/Spacer";
 
 const ConnectNewRepo: React.FC = () => {
   const { currentProject, currentCluster, setCurrentError } = useContext(
@@ -29,9 +33,10 @@ const ConnectNewRepo: React.FC = () => {
 
   const [status, setStatus] = useState(null);
   const { pushFiltered } = useRouting();
+  const [showSettings, setShowSettings] = useState<boolean>(false);
 
   // NOTE: git_repo_id is a misnomer as this actually refers to the github app's installation id.
-  const [actionConfig, setActionConfig] = useState<GithubActionConfigType>({
+  const [actionConfig, setActionConfig] = useState<ActionConfigType>({
     git_repo: null,
     image_repo_uri: null,
     git_branch: null,
@@ -113,7 +118,7 @@ const ConnectNewRepo: React.FC = () => {
   }, [actionConfig]);
 
   const addRepo = () => {
-    let [owner, repoName] = repo.split("/");
+    let [owner, repoName] = actionConfig.git_repo.split("/");
     const labels: Record<string, string> = {};
 
     setStatus("loading");
@@ -203,7 +208,7 @@ const ConnectNewRepo: React.FC = () => {
 
       <Heading>Select a Repository</Heading>
       <br />
-      <RepoList
+      {/* <RepoList
         actionConfig={actionConfig}
         setActionConfig={(a: GithubActionConfigType) => {
           setActionConfig(a);
@@ -211,6 +216,15 @@ const ConnectNewRepo: React.FC = () => {
         }}
         readOnly={false}
         filteredRepos={filteredRepos}
+      /> */}
+      <ActionConfEditorStack
+        actionConfig={actionConfig}
+        setActionConfig={(actionConfig: ActionConfigType) => {
+          setActionConfig((currentActionConfig: ActionConfigType) => ({
+            ...currentActionConfig,
+            ...actionConfig,
+          }));
+        }}
       />
       <HelperContainer>
         Note: you will need to add a <CodeBlock>porter.yaml</CodeBlock> file to
@@ -222,80 +236,105 @@ const ConnectNewRepo: React.FC = () => {
         />
       </HelperContainer>
 
-      <Heading>Automatic pull request deployments</Heading>
-      <Helper>
-        If you enable this option, the new pull requests will be automatically
-        deployed.
-      </Helper>
-      <CheckboxWrapper>
-        <CheckboxRow
-          label="Enable automatic deploys"
-          checked={enableAutomaticDeployments}
-          toggle={() =>
-            setEnableAutomaticDeployments(!enableAutomaticDeployments)
-          }
-          wrapperStyles={{
-            disableMargin: true,
-          }}
-        />
-      </CheckboxWrapper>
-
-      <Heading>Disable new comments for new deployments</Heading>
-      <Helper>
-        When enabled new comments will not be created for new deployments.
-        Instead the last comment will be updated.
-      </Helper>
-      <CheckboxWrapper>
-        <CheckboxRow
-          label="Disable new comments for deployments"
-          checked={isNewCommentsDisabled}
-          toggle={() => setIsNewCommentsDisabled(!isNewCommentsDisabled)}
-          wrapperStyles={{
-            disableMargin: true,
-          }}
-        />
-      </CheckboxWrapper>
-
-      <Heading>Deploy from branches</Heading>
-      <Helper>
-        Choose the list of branches that you want to deploy changes from.
-      </Helper>
-      <BranchFilterSelector
-        onChange={setDeployBranches}
-        options={availableBranches}
-        value={deployBranches}
-        showLoading={isLoadingBranches}
-      />
-
-      <Heading>Select allowed branches</Heading>
-      <Helper>
-        If the pull request has a base branch included in this list, it will be
-        allowed to be deployed.
-        <br />
-        (Leave empty to allow all branches)
-      </Helper>
-      <BranchFilterSelector
-        onChange={setBaseBranches}
-        options={availableBranches}
-        value={baseBranches}
-        showLoading={isLoadingBranches}
-      />
-
-      <Heading>Namespace labels</Heading>
-      <Helper>
-        Custom labels to be injected into the Kubernetes namespace created for
-        each deployment.
-      </Helper>
-      <NamespaceLabels
-        values={namespaceLabels}
-        setValues={(x: KeyValueType[]) => {
-          let labels: KeyValueType[] = [];
-          x.forEach((entry) => {
-            labels.push({ key: entry.key, value: entry.value });
-          });
-          setNamespaceLabels(labels);
+      {/* <StyledAdvancedBuildSettings
+        showSettings={showSettings}
+        isCurrent={true}
+        onClick={() => {
+          setShowSettings(!showSettings);
         }}
-      />
+      > */}
+      <StyledAdvancedBuildSettings
+        showSettings={showSettings}
+        isCurrent={true}
+        onClick={() => {
+          setShowSettings(!showSettings);
+        }}
+      >
+        <AdvancedBuildTitle>
+          <i className="material-icons dropdown">arrow_drop_down</i>
+          Configure Additonal settings
+        </AdvancedBuildTitle>
+      </StyledAdvancedBuildSettings>
+      <AnimateHeight height={showSettings ? "auto" : 0} duration={1000}>
+        <StyledSourceBox>
+          <Text size={16}>Deploy from branches</Text>
+          <Helper style={{ marginTop: "10px", marginBottom: "10px" }}>
+            {" "}
+            Choose the list of branches that you want to deploy changes from.
+          </Helper>
+          <BranchFilterSelector
+            onChange={setDeployBranches}
+            options={availableBranches}
+            value={deployBranches}
+            showLoading={isLoadingBranches}
+          />
+
+          <Text size={16}>Select allowed branches</Text>
+          <Helper style={{ marginTop: "10px", marginBottom: "10px" }}>
+            {" "}
+            If the pull request has a base branch included in this list, it will
+            be allowed to be deployed.
+            <br />
+            (Leave empty to allow all branches)
+          </Helper>
+          <BranchFilterSelector
+            onChange={setBaseBranches}
+            options={availableBranches}
+            value={baseBranches}
+            showLoading={isLoadingBranches}
+          />
+          <Text size={16}>Automatic pull request deployments</Text>
+          <Helper style={{ marginTop: "10px", marginBottom: "10px" }}>
+            If you enable this option, the new pull requests will be
+            automatically deployed.
+          </Helper>
+          <CheckboxWrapper>
+            <CheckboxRow
+              label="Enable automatic deploys"
+              checked={enableAutomaticDeployments}
+              toggle={() =>
+                setEnableAutomaticDeployments(!enableAutomaticDeployments)
+              }
+              wrapperStyles={{
+                disableMargin: true,
+              }}
+            />
+          </CheckboxWrapper>
+          <Spacer y={2} />
+          <Text size={16}>Disable new comments for new deployments</Text>
+          <Helper style={{ marginTop: "10px", marginBottom: "10px" }}>
+            When enabled new comments will not be created for new deployments.
+            Instead the last comment will be updated.
+          </Helper>
+          <CheckboxWrapper>
+            <CheckboxRow
+              label="Disable new comments for deployments"
+              checked={isNewCommentsDisabled}
+              toggle={() => setIsNewCommentsDisabled(!isNewCommentsDisabled)}
+              wrapperStyles={{
+                disableMargin: true,
+              }}
+            />
+          </CheckboxWrapper>
+          <Spacer y={2} />
+
+          <Text size={16}>Namespace labels</Text>
+          <Helper style={{ marginTop: "10px", marginBottom: "10px" }}>
+            Custom labels to be injected into the Kubernetes namespace created
+            for each deployment.
+          </Helper>
+          <NamespaceLabels
+            values={namespaceLabels}
+            setValues={(x: KeyValueType[]) => {
+              let labels: KeyValueType[] = [];
+              x.forEach((entry) => {
+                labels.push({ key: entry.key, value: entry.value });
+              });
+              setNamespaceLabels(labels);
+            }}
+          />
+        </StyledSourceBox>
+      </AnimateHeight>
 
       <ActionContainer>
         <SaveButton
@@ -430,3 +469,50 @@ const CheckboxWrapper = styled.div`
   align-items: center;
   margin-top: 20px;
 `;
+const StyledSourceBox = styled.div`
+  width: 100%;
+  color: #ffffff;
+  padding: 25px 35px 25px;
+  position: relative;
+  font-size: 13px;
+  border-radius: 5px;
+  background: ${(props) => props.theme.fg};
+  border: 1px solid #494b4f;
+  border-top: 0px;
+  border-top-left-radius: 0px;
+  border-top-right-radius: 0px;
+`;
+const StyledAdvancedBuildSettings = styled.div`
+  color: ${({ showSettings }) => (showSettings ? "white" : "#aaaabb")};
+  background: ${({ theme }) => theme.fg};
+  border: 1px solid #494b4f;
+  :hover {
+    border: 1px solid #7a7b80;
+    color: white;
+  }
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-top: 15px;
+  border-radius: 5px;
+  height: 40px;
+  font-size: 13px;
+  width: 100%;
+  padding-left: 10px;
+  cursor: pointer;
+  border-bottom-left-radius: ${({ showSettings }) => showSettings && "0px"};
+  border-bottom-right-radius: ${({ showSettings }) => showSettings && "0px"};
+
+  .dropdown {
+    margin-right: 8px;
+    font-size: 20px;
+    cursor: pointer;
+    border-radius: 20px;
+    transform: ${(props: { showSettings: boolean; isCurrent: boolean }) =>
+      props.showSettings ? "" : "rotate(-90deg)"};
+  }
+`;
+const AdvancedBuildTitle = styled.div`
+  display: flex;
+  align-items: center;
+`;

+ 93 - 14
dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/DeploymentDetail.tsx

@@ -18,8 +18,13 @@ import Modal from "main/home/modals/Modal";
 import { validatePorterYAML } from "../utils";
 import Placeholder from "components/Placeholder";
 import GithubIcon from "assets/GithubIcon";
+import Dropdown from "components/Dropdown";
+import { useHistory } from "react-router-dom";
+import PreviewEnvDeleted from "./PreviewEnvDeleted";
+import Button from "components/porter/Button";
 
 const DeploymentDetail = () => {
+  const [showDropdown, setShowDropdown] = useState(false);
   const { params } = useRouteMatch<{ id: string }>();
   const context = useContext(Context);
   const [prDeployment, setPRDeployment] = useState<PRDeployment>(null);
@@ -38,6 +43,7 @@ const DeploymentDetail = () => {
 
   const { search } = useLocation();
   let searchParams = new URLSearchParams(search);
+  const history = useHistory();
 
   useEffect(() => {
     let isSubscribed = true;
@@ -99,11 +105,31 @@ const DeploymentDetail = () => {
   }, [prDeployment]);
 
   if (!prDeployment) {
-    return <Loading />;
+    return <PreviewEnvDeleted />;
   }
-
   const repository = `${prDeployment.gh_repo_owner}/${prDeployment.gh_repo_name}`;
 
+  const deleteDeployment = () => {
+    //setIsDeleting(true);
+
+    api
+      .deletePRDeployment(
+        "<token>",
+        {},
+        {
+          cluster_id: currentCluster.id,
+          project_id: currentProject.id,
+          deployment_id: prDeployment.id,
+        }
+      )
+      .then(() => {
+        //setIsDeleting(false);
+        history.push(
+          `/preview-environments/deployments/${currentProject.id}/${repository}`
+        ); // Navigate to deployments page
+      });
+  };
+
   if (
     !prDeployment.namespace &&
     ["creating", "updating"].includes(prDeployment.status)
@@ -125,18 +151,31 @@ const DeploymentDetail = () => {
         </BreadcrumbRow>
         <StyledExpandedChart>
           <HeaderWrapper>
-            <Title
-              icon={pr_icon}
-              iconWidth="25px"
-              onClick={() =>
-                window.open(
-                  `https://github.com/${repository}/pull/${prDeployment.pull_request_id}`,
-                  "_blank"
-                )
-              }
-            >
-              {prDeployment.gh_pr_name}
-            </Title>
+            <Flex>
+              <Title
+                icon={pr_icon}
+                iconWidth="25px"
+                onClick={() =>
+                  window.open(
+                    `https://github.com/${repository}/pull/${prDeployment.pull_request_id}`,
+                    "_blank"
+                  )
+                }
+              >
+                {prDeployment.gh_pr_name}
+              </Title>
+              <span
+                onClick={() => setShowDropdown(!showDropdown)}
+                style={{ cursor: "pointer" }}
+              >
+                <I className="material-icons">settings</I>
+                {showDropdown && (
+                  <DeleteDropdown>
+                    <Button onClick={deleteDeployment}>Delete</Button>
+                  </DeleteDropdown>
+                )}
+              </span>
+            </Flex>
             <InfoWrapper>
               {prDeployment.subdomain && (
                 <PRLink to={prDeployment.subdomain} target="_blank">
@@ -145,6 +184,7 @@ const DeploymentDetail = () => {
                 </PRLink>
               )}
             </InfoWrapper>
+
             <Flex>
               <Status>
                 <StatusDot status={prDeployment.status} />
@@ -686,3 +726,42 @@ const Tooltip = styled.div`
     }
   }
 `;
+const I = styled.i`
+  font-size: 18px;
+  user-select: none;
+  margin-left: 15px;
+  color: #aaaabb;
+  margin-bottom: -3px;
+  cursor: pointer;
+  width: 30px;
+  border-radius: 40px;
+  height: 30px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  :hover {
+    background: #26292e;
+    border: 1px solid #494b4f;
+  }
+`;
+const DeleteDropdown = styled.div`
+  position: absolute;
+  border-radius: 3px;
+  padding: 10px;
+  min-width: 150px;
+  z-index: 999;
+`;
+
+const DeleteButton = styled.button`
+  background-color: #f44336;
+  color: white;
+  padding: 6px 12px;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 14px;
+
+  &:hover {
+    background-color: #d32f2f;
+  }
+`;

+ 3 - 1
dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/DeploymentList.tsx

@@ -200,6 +200,8 @@ const DeploymentList = () => {
       filteredByStatus,
       searchValue,
       {
+        threshold: 0.2,
+        distance: 50,
         isCaseSensitive: false,
         keys: ["gh_pr_name", "gh_repo_name", "gh_repo_owner"],
       }
@@ -300,7 +302,7 @@ const DeploymentList = () => {
             <DynamicLink
               to={`/preview-environments/deployments/${environment_id}/${repo_owner}/${repo_name}/settings`}
             >
-              <I className="material-icons">more_vert</I>
+              <I className="material-icons">settings</I>
             </DynamicLink>
           </Flex>
         }

+ 76 - 0
dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/PreviewEnvDeleted.tsx

@@ -0,0 +1,76 @@
+import React from "react";
+import styled from "styled-components";
+import { ProjectType } from "shared/types";
+import { useHistory } from "react-router-dom";
+import Button from "components/porter/Button";
+import Text from "components/porter/Text";
+import Spacer from "components/porter/Spacer";
+
+interface PreviewEnvDeletedProps {
+  repository?: string;
+  currentProject?: ProjectType;
+}
+
+const PreviewEnvDeleted: React.FC<PreviewEnvDeletedProps> = ({}) => {
+  const history = useHistory();
+
+  const handleBackButtonClick = () => {
+    history.push("/preview-environments/deployments/");
+  };
+
+  return (
+    <DeletedContainer>
+      <Text size={16}>This preview environment has been deleted.</Text>
+      <Spacer y={0.5} />
+      <Button width="75px" onClick={handleBackButtonClick}>
+        <i className="material-icons">keyboard_backspace</i>
+        Back
+      </Button>
+    </DeletedContainer>
+  );
+};
+
+const DeletedContainer = styled.div`
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 100%;
+  width: 100%;
+`;
+
+const ClusterPlaceholder = styled.div`
+  padding: 25px;
+  border-radius: 5px;
+  background: ${(props) => props.theme.fg};
+  border: 1px solid #494b4f;
+  padding-bottom: 35px;
+`;
+const BackButton = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-top: 22px;
+  cursor: pointer;
+  font-size: 13px;
+  height: 35px;
+  padding: 5px 13px;
+  margin-bottom: -7px;
+  padding-right: 15px;
+  border: 1px solid #ffffff55;
+  border-radius: 100px;
+  width: ${(props: { width: string }) => props.width};
+  color: white;
+  background: #ffffff11;
+
+  :hover {
+    background: #ffffff22;
+  }
+
+  > i {
+    color: white;
+    font-size: 16px;
+    margin-right: 6px;
+  }
+`;
+export default PreviewEnvDeleted;

+ 59 - 42
dashboard/src/main/home/cluster-dashboard/preview-environments/environments/CreateBranchEnvironment.tsx

@@ -17,6 +17,10 @@ import pr_icon from "assets/pull_request_icon.svg";
 import { search } from "shared/search";
 import RadioFilter from "components/RadioFilter";
 import sort from "assets/sort.svg";
+import Modal from "components/porter/Modal";
+import Text from "components/porter/Text";
+import Button from "components/porter/Button";
+import Spacer from "components/porter/Spacer";
 
 interface Props {
   environmentID: string;
@@ -29,11 +33,9 @@ const CreateBranchEnvironment = ({ environmentID }: Props) => {
   const [sortOrder, setSortOrder] = useState("Newest");
   const [loading, setLoading] = useState<boolean>(false);
   const [showErrorsModal, setShowErrorsModal] = useState<boolean>(false);
-  const {
-    currentProject,
-    currentCluster,
-    setCurrentError,
-  } = useContext(Context);
+  const { currentProject, currentCluster, setCurrentError } = useContext(
+    Context
+  );
 
   const {
     data: environment,
@@ -82,7 +84,9 @@ const CreateBranchEnvironment = ({ environmentID }: Props) => {
       enabled: !!environment,
     }
   );
-
+  const [showCreatePreviewModal, setShowCreatePreviewModal] = useState<boolean>(
+    false
+  );
   const environmentGitDeployBranches = environment?.git_deploy_branches ?? [];
   const [selectedBranch, setSelectedBranch] = useState<string>(null);
   const [porterYAMLErrors, setPorterYAMLErrors] = useState<string[]>([]);
@@ -90,6 +94,7 @@ const CreateBranchEnvironment = ({ environmentID }: Props) => {
   const handleRowItemClick = async (branch: string) => {
     setSelectedBranch(branch);
     setLoading(true);
+    setShowCreatePreviewModal(true);
 
     const res = await validatePorterYAML({
       projectID: currentProject.id,
@@ -110,13 +115,11 @@ const CreateBranchEnvironment = ({ environmentID }: Props) => {
   };
 
   const filteredBranches = useMemo(() => {
-    const filteredBySearch = search<string>(
-      branches ?? [],
-      searchValue,
-      {
-        isCaseSensitive: false,
-      }
-    );
+    const filteredBySearch = search<string>(branches ?? [], searchValue, {
+      isCaseSensitive: false,
+      threshold: 0.2, // Adjust this value to fine-tune the matching
+      distance: 50,
+    });
 
     switch (sortOrder) {
       case "Alphabetical":
@@ -124,7 +127,10 @@ const CreateBranchEnvironment = ({ environmentID }: Props) => {
         return _.sortBy(filteredBySearch);
     }
   }, [branches, searchValue, sortOrder]);
-
+  const handleModalSubmit = () => {
+    updateDeployBranchesMutation.mutate();
+    setShowCreatePreviewModal(false);
+  };
   const updateDeployBranchesMutation = useMutation({
     mutationFn: () => {
       return api.updateEnvironment(
@@ -132,12 +138,10 @@ const CreateBranchEnvironment = ({ environmentID }: Props) => {
         {
           disable_new_comments: environment.new_comments_disabled,
           ...environment,
-          git_deploy_branches: _.uniq(
-            [
-              ...environmentGitDeployBranches,
-              selectedBranch,
-            ]
-          ),
+          git_deploy_branches: _.uniq([
+            ...environmentGitDeployBranches,
+            selectedBranch,
+          ]),
         },
         {
           project_id: currentProject.id,
@@ -206,17 +210,14 @@ const CreateBranchEnvironment = ({ environmentID }: Props) => {
             icon={sort}
             selected={sortOrder}
             setSelected={setSortOrder}
-            options={[
-              { label: "Alphabetical", value: "Alphabetical" },
-            ]}
+            options={[{ label: "Alphabetical", value: "Alphabetical" }]}
             name="Sort"
           />
         </Flex>
       </FlexRow>
       <Br height="10px" />
       <BranchList>
-      {
-        (filteredBranches ?? []).map((branch, i) => (
+        {(filteredBranches ?? []).map((branch, i) => (
           <BranchRow
             onClick={() => handleRowItemClick(branch)}
             isLast={i === filteredBranches.length - 1}
@@ -224,13 +225,12 @@ const CreateBranchEnvironment = ({ environmentID }: Props) => {
           >
             <BranchName>
               <BranchIcon src={pr_icon} alt="branch icon" />
-                <EllipsisTextWrapper tooltipText={branch}>
-                  {branch}
-                </EllipsisTextWrapper>
+              <EllipsisTextWrapper tooltipText={branch}>
+                {branch}
+              </EllipsisTextWrapper>
             </BranchName>
           </BranchRow>
-        ))
-      }
+        ))}
       </BranchList>
       {showErrorsModal && selectedBranch ? (
         <PorterYAMLErrorsModal
@@ -252,17 +252,34 @@ const CreateBranchEnvironment = ({ environmentID }: Props) => {
         </ValidationErrorBannerWrapper>
       ) : null}
       <CreatePreviewDeploymentWrapper>
-        <SubmitButton
-          onClick={() => updateDeployBranchesMutation.mutate()}
-          disabled={
-            updateDeployBranchesMutation.isLoading || loading
-            || porterYAMLErrors.length > 0 || !selectedBranch
-          }
-        >
-          {
-            updateDeployBranchesMutation.isLoading ? 'Creating...' : 'Create Preview Deployment'
-          }
-        </SubmitButton>
+        {showCreatePreviewModal &&
+          selectedBranch &&
+          porterYAMLErrors.length == 0 && (
+            <Modal
+              title="Create Preview Environment"
+              closeModal={() => setShowCreatePreviewModal(false)}
+            >
+              <>
+                <Text color="helper">
+                  Create Preview Deployment for branch: {selectedBranch}?
+                </Text>
+                <Spacer y={1} />
+                <SubmitButton
+                  onClick={() => updateDeployBranchesMutation.mutate()}
+                  disabled={
+                    updateDeployBranchesMutation.isLoading ||
+                    loading ||
+                    porterYAMLErrors.length > 0 ||
+                    !selectedBranch
+                  }
+                >
+                  {updateDeployBranchesMutation.isLoading
+                    ? "Creating..."
+                    : "Create Preview Deployment"}
+                </SubmitButton>
+              </>
+            </Modal>
+          )}
         {selectedBranch && porterYAMLErrors.length ? (
           <RevalidatePorterYAMLSpanWrapper>
             Please fix your porter.yaml file to continue.{" "}
@@ -518,4 +535,4 @@ const RefreshButton = styled.button`
     background-color: rgb(97 98 102 / 44%);
     color: white;
   }
-`;
+`;

+ 55 - 13
dashboard/src/main/home/cluster-dashboard/preview-environments/environments/CreatePREnvironment.tsx

@@ -20,6 +20,10 @@ import _, { create } from "lodash";
 import { readableDate } from "shared/string_utils";
 import dayjs from "dayjs";
 import Loading from "components/Loading";
+import Modal from "components/porter/Modal";
+import Text from "components/porter/Text";
+import Spacer from "components/porter/Spacer";
+import Button from "components/porter/Button";
 
 interface Props {
   environmentID: string;
@@ -58,6 +62,9 @@ const CreatePREnvironment = ({ environmentID }: Props) => {
   const [selectedPR, setSelectedPR] = useState<PullRequest>();
   const [loading, setLoading] = useState(false);
   const [porterYAMLErrors, setPorterYAMLErrors] = useState<string[]>([]);
+  const [showCreatePreviewModal, setShowCreatePreviewModal] = useState<boolean>(
+    false
+  );
 
   const handleCreatePreviewDeployment = async () => {
     try {
@@ -97,11 +104,16 @@ const CreatePREnvironment = ({ environmentID }: Props) => {
       environmentID: Number(environmentID),
       branch: pullRequest.branch_from,
     });
+    setShowCreatePreviewModal(true);
 
     setPorterYAMLErrors(res.data.errors ?? []);
 
     setLoading(false);
   };
+  const handleModalSubmit = () => {
+    createPreviewDeploymentMutation.mutate();
+    setShowCreatePreviewModal(false);
+  };
 
   const filteredPullRequests = useMemo(() => {
     const filteredBySearch = search<PullRequest>(
@@ -109,6 +121,8 @@ const CreatePREnvironment = ({ environmentID }: Props) => {
       searchValue,
       {
         isCaseSensitive: false,
+        threshold: 0.2, // Adjust this value to fine-tune the matching
+        distance: 50,
         keys: ["pr_title"],
       }
     );
@@ -262,19 +276,47 @@ const CreatePREnvironment = ({ environmentID }: Props) => {
         </ValidationErrorBannerWrapper>
       ) : null}
       <CreatePreviewDeploymentWrapper>
-        <SubmitButton
-          onClick={() => createPreviewDeploymentMutation.mutate()}
-          disabled={
-            loading ||
-            !selectedPR ||
-            porterYAMLErrors.length > 0 ||
-            createPreviewDeploymentMutation.isLoading
-          }
-        >
-          {createPreviewDeploymentMutation.isLoading
-            ? "Creating..."
-            : "Create preview deployment"}
-        </SubmitButton>
+        {showCreatePreviewModal && selectedPR && porterYAMLErrors.length == 0 && (
+          <Modal
+            title="Create Preview Environment"
+            closeModal={() => setShowCreatePreviewModal(false)}
+          >
+            <>
+              <Text color="helper">
+                Create Preview Deployment for {selectedPR.pr_title}?
+              </Text>
+              <Spacer y={1} />
+              {/* <Button
+                onClick={() => handleModalSubmit}
+                loadingText="Submitting..."
+                withBorder
+                status={loading ? "loading" : undefined}
+                disabled={
+                  createPreviewDeploymentMutation.isLoading ||
+                  loading ||
+                  porterYAMLErrors.length > 0
+                }
+              >
+                {createPreviewDeploymentMutation.isLoading
+                  ? "Creating..."
+                  : "Create Preview Deployment"}
+              </Button> */}
+              <SubmitButton
+                onClick={() => createPreviewDeploymentMutation.mutate()}
+                disabled={
+                  loading ||
+                  !selectedPR ||
+                  porterYAMLErrors.length > 0 ||
+                  createPreviewDeploymentMutation.isLoading
+                }
+              >
+                {createPreviewDeploymentMutation.isLoading
+                  ? "Creating..."
+                  : "Create preview deployment"}
+              </SubmitButton>
+            </>
+          </Modal>
+        )}
         {selectedPR && porterYAMLErrors.length ? (
           <RevalidatePorterYAMLSpanWrapper>
             Please fix your porter.yaml file to continue.{" "}