Browse Source

chore: minor fixes

- fix preview deletion, creation
- fix last workflow run
Soham Parekh 3 years ago
parent
commit
55c778837c

+ 3 - 3
dashboard/package-lock.json

@@ -1549,9 +1549,9 @@
       }
       }
     },
     },
     "@tanstack/react-query-devtools": {
     "@tanstack/react-query-devtools": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-4.13.0.tgz",
-      "integrity": "sha512-Gv33auVlcUMrnj5qXJuhhTJtBs8bKp7v3TFRysnS+VlLOmdj9gwl5bc0e0/URL7m+PQoR1Rr55yzQ5bmZcWm1g==",
+      "version": "4.13.5",
+      "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-4.13.5.tgz",
+      "integrity": "sha512-ItXysFt7a2WJcodeoZ52/NFoMeWUgwzSaKL3NXhHvdyDOFsX945/AN/+Q9WgXRDG+YgqDJm6f/ozYIfFaORoZQ==",
       "requires": {
       "requires": {
         "@tanstack/match-sorter-utils": "^8.1.1",
         "@tanstack/match-sorter-utils": "^8.1.1",
         "superjson": "^1.10.0",
         "superjson": "^1.10.0",

+ 1 - 1
dashboard/package.json

@@ -10,7 +10,7 @@
     "@sentry/react": "^6.13.2",
     "@sentry/react": "^6.13.2",
     "@sentry/tracing": "^6.13.2",
     "@sentry/tracing": "^6.13.2",
     "@tanstack/react-query": "^4.13.0",
     "@tanstack/react-query": "^4.13.0",
-    "@tanstack/react-query-devtools": "^4.13.0",
+    "@tanstack/react-query-devtools": "^4.13.5",
     "@visx/axis": "^1.6.1",
     "@visx/axis": "^1.6.1",
     "@visx/curve": "^1.0.0",
     "@visx/curve": "^1.0.0",
     "@visx/event": "^1.3.0",
     "@visx/event": "^1.3.0",

+ 1 - 1
dashboard/src/components/Banner.tsx

@@ -38,7 +38,7 @@ const StyledBanner = styled.div<{ color?: string }>`
   display: flex;
   display: flex;
   border: 1px solid ${(props) => props.color || "#ffffff00"};
   border: 1px solid ${(props) => props.color || "#ffffff00"};
   border-radius: 8px;
   border-radius: 8px;
-  padding-left: 14px;
+  padding-inline: 14px;
   color: ${(props) => props.color || "#ffffff"};
   color: ${(props) => props.color || "#ffffff"};
   align-items: center;
   align-items: center;
   background: #ffffff11;
   background: #ffffff11;

+ 9 - 15
dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/DeploymentDetail.tsx

@@ -13,7 +13,6 @@ import ChartList from "../../chart/ChartList";
 import github from "assets/github-white.png";
 import github from "assets/github-white.png";
 import { integrationList } from "shared/common";
 import { integrationList } from "shared/common";
 import { capitalize } from "shared/string_utils";
 import { capitalize } from "shared/string_utils";
-import leftArrow from "assets/left-arrow.svg";
 import Banner from "components/Banner";
 import Banner from "components/Banner";
 import Modal from "main/home/modals/Modal";
 import Modal from "main/home/modals/Modal";
 import { validatePorterYAML } from "../utils";
 import { validatePorterYAML } from "../utils";
@@ -129,10 +128,15 @@ const DeploymentDetail = () => {
       </BreadcrumbRow>
       </BreadcrumbRow>
       <StyledExpandedChart>
       <StyledExpandedChart>
         <HeaderWrapper>
         <HeaderWrapper>
-          <Title 
-            icon={pr_icon} 
+          <Title
+            icon={pr_icon}
             iconWidth="25px"
             iconWidth="25px"
-            onClick={() => window.open(`https://github.com/${repository}/pull/${prDeployment.pull_request_id}`, '_blank')}
+            onClick={() =>
+              window.open(
+                `https://github.com/${repository}/pull/${prDeployment.pull_request_id}`,
+                "_blank"
+              )
+            }
           >
           >
             {prDeployment.gh_pr_name}
             {prDeployment.gh_pr_name}
           </Title>
           </Title>
@@ -168,19 +172,9 @@ const DeploymentDetail = () => {
               {showRepoTooltip && <Tooltip>{repository}</Tooltip>}
               {showRepoTooltip && <Tooltip>{repository}</Tooltip>}
             </DeploymentImageContainer>
             </DeploymentImageContainer>
             <Dot>•</Dot>
             <Dot>•</Dot>
-            <GHALink
-              to={`https://github.com/${repository}/pull/${prDeployment.pull_request_id}`}
-              target="_blank"
-            >
-              <img src={github} /> View last workflow run
-              <i className="material-icons">open_in_new</i>
-            </GHALink>
             {prDeployment.last_workflow_run_url ? (
             {prDeployment.last_workflow_run_url ? (
               <GHALink to={prDeployment.last_workflow_run_url} target="_blank">
               <GHALink to={prDeployment.last_workflow_run_url} target="_blank">
-                <span className="material-icons-outlined">
-                  play_circle_outline
-                </span>
-                Last workflow run
+                <img src={github} /> View last workflow run
                 <i className="material-icons">open_in_new</i>
                 <i className="material-icons">open_in_new</i>
               </GHALink>
               </GHALink>
             ) : null}
             ) : null}

+ 11 - 114
dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/DeploymentList.tsx

@@ -26,6 +26,7 @@ const AvailableStatusFilters = ["all", "created", "failed", "not_deployed"];
 
 
 type AvailableStatusFiltersType = typeof AvailableStatusFilters[number];
 type AvailableStatusFiltersType = typeof AvailableStatusFilters[number];
 
 
+
 const HARD_CODED_DEPLOYMENTS: PRDeployment[] = [
 const HARD_CODED_DEPLOYMENTS: PRDeployment[] = [
   {
   {
     id: 1,
     id: 1,
@@ -71,9 +72,9 @@ const DeploymentList = () => {
   const [sortOrder, setSortOrder] = useState("Newest");
   const [sortOrder, setSortOrder] = useState("Newest");
   const [isLoading, setIsLoading] = useState(true);
   const [isLoading, setIsLoading] = useState(true);
   const [hasError, setHasError] = useState(false);
   const [hasError, setHasError] = useState(false);
-  const [deploymentList, setDeploymentList] = useState<PRDeployment[]>(
-    HARD_CODED_DEPLOYMENTS
-  );
+  const [deploymentList, setDeploymentList] = useState<PRDeployment[]>([
+    ...HARD_CODED_DEPLOYMENTS
+  ]);
   const [pullRequests, setPullRequests] = useState<PullRequest[]>([]);
   const [pullRequests, setPullRequests] = useState<PullRequest[]>([]);
   const [searchValue, setSearchValue] = useState("");
   const [searchValue, setSearchValue] = useState("");
   const [newCommentsDisabled, setNewCommentsDisabled] = useState(false);
   const [newCommentsDisabled, setNewCommentsDisabled] = useState(false);
@@ -87,7 +88,9 @@ const DeploymentList = () => {
     setStatusSelectorVal,
     setStatusSelectorVal,
   ] = useState<AvailableStatusFiltersType>("all");
   ] = useState<AvailableStatusFiltersType>("all");
 
 
-  const { currentProject, currentCluster } = useContext(Context);
+  const { currentProject, currentCluster, setCurrentError } = useContext(
+    Context
+  );
   const { getQueryParam, pushQueryParams } = useRouting();
   const { getQueryParam, pushQueryParams } = useRouting();
   const location = useLocation();
   const location = useLocation();
   const history = useHistory();
   const history = useHistory();
@@ -169,9 +172,7 @@ const DeploymentList = () => {
           }
           }
 
 
           setPorterYAMLErrors(porterYAMLErrors);
           setPorterYAMLErrors(porterYAMLErrors);
-          setDeploymentList(
-            deploymentList.deployments || HARD_CODED_DEPLOYMENTS
-          );
+          setDeploymentList(deploymentList.deployments ?? HARD_CODED_DEPLOYMENTS);
           setPullRequests(deploymentList.pull_requests || []);
           setPullRequests(deploymentList.pull_requests || []);
 
 
           setNewCommentsDisabled(
           setNewCommentsDisabled(
@@ -181,8 +182,9 @@ const DeploymentList = () => {
           setIsLoading(false);
           setIsLoading(false);
         }
         }
       )
       )
-      .catch(() => {
-        setDeploymentList(HARD_CODED_DEPLOYMENTS);
+      .catch((err) => {
+        setDeploymentList([]);
+        setCurrentError(err);
       });
       });
 
 
     return () => {
     return () => {
@@ -207,19 +209,6 @@ const DeploymentList = () => {
     setIsLoading(false);
     setIsLoading(false);
   };
   };
 
 
-  const handlePreviewEnvironmentManualCreation = (pullRequest: PullRequest) => {
-    setPullRequests((prev) => {
-      return prev.filter((pr) => {
-        return (
-          pr.pr_title === pullRequest.pr_title &&
-          `${pr.repo_owner}/${pr.repo_name}` ===
-            `${pullRequest.repo_owner}/${pullRequest.repo_name}`
-        );
-      });
-    });
-    handleRefresh();
-  };
-
   const searchFilter = (value: string | number) => {
   const searchFilter = (value: string | number) => {
     const val = String(value);
     const val = String(value);
 
 
@@ -289,16 +278,6 @@ const DeploymentList = () => {
 
 
     return (
     return (
       <>
       <>
-        {/* Deprecated -> New Preview Env button */}
-        {/* {filteredPullRequests.map((pr) => {
-          return (
-            <PullRequestCard
-              key={pr.pr_title}
-              pullRequest={pr}
-              onCreation={handlePreviewEnvironmentManualCreation}
-            />
-          );
-        })} */}
         {filteredDeployments.map((d: any) => {
         {filteredDeployments.map((d: any) => {
           return (
           return (
             <DeploymentCard
             <DeploymentCard
@@ -394,44 +373,6 @@ const DeploymentList = () => {
           </LinkButton>
           </LinkButton>
         </Banner>
         </Banner>
       ) : null}
       ) : null}
-      {/* <Flex>
-        <ActionsWrapper>
-          <StyledStatusSelector>
-            <RefreshButton color={"#7d7d81"} onClick={handleRefresh}>
-              <i className="material-icons">refresh</i>
-            </RefreshButton>
-            <SearchRow>
-              <i className="material-icons">search</i>
-              <SearchInput
-                value={searchValue}
-                onChange={(e: any) => {
-                  setSearchValue(e.target.value);
-                }}
-                placeholder="Search"
-              />
-            </SearchRow>
-            <Selector
-              activeValue={statusSelectorVal}
-              setActiveValue={handleStatusFilterChange}
-              options={[
-                {
-                  value: "active",
-                  label: "Active",
-                },
-                {
-                  value: "inactive",
-                  label: "Inactive",
-                },
-              ]}
-              dropdownLabel="Status"
-              width="150px"
-              dropdownWidth="230px"
-              closeOverlay={true}
-            />
-            <EnvironmentSettings environmentId={environment_id} />
-          </StyledStatusSelector>
-        </ActionsWrapper>
-      </Flex> */}
       <FlexRow>
       <FlexRow>
         <Flex>
         <Flex>
           <SearchRowWrapper>
           <SearchRowWrapper>
@@ -584,41 +525,6 @@ const Flex = styled.div`
   align-items: center;
   align-items: center;
 `;
 `;
 
 
-const Div = styled.div`
-  margin-bottom: -7px;
-`;
-
-const FlexWrap = styled.div`
-  display: flex;
-  align-items: center;
-`;
-
-const BackButton = styled(DynamicLink)`
-  cursor: pointer;
-  font-size: 24px;
-  color: #969fbbaa;
-  padding: 3px;
-  border-radius: 100px;
-  :hover {
-    background: #ffffff11;
-  }
-`;
-
-const Icon = styled.img`
-  width: 25px;
-  height: 25px;
-  margin-right: 6px;
-`;
-
-const Title = styled.div`
-  font-size: 20px;
-  font-weight: 500;
-  font-family: "Work Sans", sans-serif;
-  margin-left: 10px;
-  border-radius: 2px;
-  color: #ffffff;
-`;
-
 const RefreshButton = styled.button`
 const RefreshButton = styled.button`
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
@@ -652,15 +558,6 @@ const EventsGrid = styled.div`
   grid-template-columns: 1;
   grid-template-columns: 1;
 `;
 `;
 
 
-const StyledStatusSelector = styled.div`
-  display: flex;
-  align-items: center;
-  font-size: 13px;
-  :not(:first-child) {
-    margin-left: 15px;
-  }
-`;
-
 const SearchInput = styled.input`
 const SearchInput = styled.input`
   outline: none;
   outline: none;
   border: none;
   border: none;

+ 0 - 33
dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/PullRequestCard.tsx

@@ -2,12 +2,10 @@ import React, { useState, useContext } from "react";
 import styled from "styled-components";
 import styled from "styled-components";
 import pr_icon from "assets/pull_request_icon.svg";
 import pr_icon from "assets/pull_request_icon.svg";
 import { PullRequest } from "../types";
 import { PullRequest } from "../types";
-import { integrationList } from "shared/common";
 import api from "shared/api";
 import api from "shared/api";
 import { Context } from "shared/Context";
 import { Context } from "shared/Context";
 import { ActionButton } from "../components/ActionButton";
 import { ActionButton } from "../components/ActionButton";
 import Loading from "components/Loading";
 import Loading from "components/Loading";
-import DynamicLink from "components/DynamicLink";
 import RecreateWorkflowFilesModal from "../components/RecreateWorkflowFilesModal";
 import RecreateWorkflowFilesModal from "../components/RecreateWorkflowFilesModal";
 import { EllipsisTextWrapper, RepoLink } from "../components/styled";
 import { EllipsisTextWrapper, RepoLink } from "../components/styled";
 
 
@@ -193,37 +191,6 @@ const StatusDot = styled.div`
   margin-left: 3px;
   margin-left: 3px;
 `;
 `;
 
 
-const DeploymentImageContainer = styled.div`
-  height: 20px;
-  font-size: 13px;
-  position: relative;
-  display: flex;
-  margin-left: 15px;
-  align-items: center;
-  font-weight: 400;
-  justify-content: center;
-  color: #ffffff66;
-  padding-left: 5px;
-`;
-
-const Icon = styled.img`
-  width: 100%;
-`;
-
-const DeploymentTypeIcon = styled(Icon)`
-  width: 20px;
-  margin-right: 10px;
-`;
-
-const RepositoryName = styled.div`
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  max-width: 390px;
-  position: relative;
-  margin-right: 3px;
-`;
-
 const Tooltip = styled.div`
 const Tooltip = styled.div`
   position: absolute;
   position: absolute;
   left: 14px;
   left: 14px;

+ 59 - 206
dashboard/src/main/home/cluster-dashboard/preview-environments/environments/CreateEnvironment.tsx

@@ -1,52 +1,24 @@
 import DynamicLink from "components/DynamicLink";
 import DynamicLink from "components/DynamicLink";
 import Loading from "components/Loading";
 import Loading from "components/Loading";
 import React, { useContext, useEffect, useState } from "react";
 import React, { useContext, useEffect, useState } from "react";
-import api from "shared/api";
 import styled from "styled-components";
 import styled from "styled-components";
-import { CellProps } from "react-table";
 import { Context } from "shared/Context";
 import { Context } from "shared/Context";
 import { useParams } from "react-router";
 import { useParams } from "react-router";
-import { PRDeployment, PullRequest } from "../types";
+import { PullRequest } from "../types";
 import DashboardHeader from "../../DashboardHeader";
 import DashboardHeader from "../../DashboardHeader";
 import PullRequestIcon from "assets/pull_request_icon.svg";
 import PullRequestIcon from "assets/pull_request_icon.svg";
 import Helper from "components/form-components/Helper";
 import Helper from "components/form-components/Helper";
-import Table from "components/Table";
 import pr_icon from "assets/pull_request_icon.svg";
 import pr_icon from "assets/pull_request_icon.svg";
+import api from "shared/api";
 import { EllipsisTextWrapper, RepoLink } from "../components/styled";
 import { EllipsisTextWrapper, RepoLink } from "../components/styled";
 import { useQuery, useQueryClient } from "@tanstack/react-query";
 import { useQuery, useQueryClient } from "@tanstack/react-query";
 import { getPRDeploymentList, validatePorterYAML } from "../utils";
 import { getPRDeploymentList, validatePorterYAML } from "../utils";
 import Banner from "components/Banner";
 import Banner from "components/Banner";
 import Modal from "main/home/modals/Modal";
 import Modal from "main/home/modals/Modal";
-
-const dummyData: any = [
-  {
-    pr_title: "pr_title1",
-    pr_number: 1,
-    repo_owner: "repo_owner",
-    repo_name: "repo_name",
-    branch_from: "test1",
-    branch_into: "test",
-  },
-  {
-    pr_title: "pr_title2",
-    pr_number: 2,
-    repo_owner: "repo_owner",
-    repo_name: "repo_name",
-    branch_from: "test2",
-    branch_into: "test",
-  },
-  {
-    pr_title: "pr_title3",
-    pr_number: 3,
-    repo_owner: "repo_owner",
-    repo_name: "repo_name",
-    branch_from: "test3",
-    branch_into: "test",
-  },
-];
+import { useRouting } from "shared/routing";
 
 
 const CreateEnvironment: React.FC = () => {
 const CreateEnvironment: React.FC = () => {
-  // TODO Soham: Replace any
+  const router = useRouting();
   const queryClient = useQueryClient();
   const queryClient = useQueryClient();
   const [modalContent, setModalContent] = useState<React.ReactNode>();
   const [modalContent, setModalContent] = useState<React.ReactNode>();
   const { currentProject, currentCluster, setCurrentError } = useContext(
   const { currentProject, currentCluster, setCurrentError } = useContext(
@@ -74,12 +46,9 @@ const CreateEnvironment: React.FC = () => {
       } catch (err) {
       } catch (err) {
         setCurrentError(err);
         setCurrentError(err);
       }
       }
-
-      // TODO Soham: Replace with actual data
-      return dummyData; // [];
     }
     }
   );
   );
-  
+
   const [selectedPR, setSelectedPR] = useState<PullRequest>();
   const [selectedPR, setSelectedPR] = useState<PullRequest>();
   const [loading, setLoading] = useState(false);
   const [loading, setLoading] = useState(false);
   const [porterYAMLErrors, setPorterYAMLErrors] = useState<string[]>([]);
   const [porterYAMLErrors, setPorterYAMLErrors] = useState<string[]>([]);
@@ -101,28 +70,20 @@ const CreateEnvironment: React.FC = () => {
     setLoading(false);
     setLoading(false);
   };
   };
 
 
-  const columns = React.useMemo(
-    () => [
-      {
-        Header: "Monitors",
-        columns: [
-          {
-            Header: "Open pull requests",
-            accessor: "name",
-            width: 140,
-            Cell: ({
-              row: { original: pullRequest },
-            }: CellProps<PullRequest>) => {
-              return (
-                <h1>asdf</h1>
-              );
-            },
-          },
-        ],
-      },
-    ],
-    [pullRequests]
-  );
+  const handleCreatePreviewDeployment = async () => {
+    try {
+      await api.createPreviewEnvironmentDeployment("<token>", selectedPR, {
+        cluster_id: currentCluster?.id,
+        project_id: currentProject?.id,
+      });
+
+      router.push(
+        `/preview-environments/deployments/${environment_id}/${selectedPR.repo_owner}/${selectedPR.repo_name}?status_filter=all`
+      );
+    } catch (err) {
+      setCurrentError(err);
+    }
+  };
 
 
   return (
   return (
     <>
     <>
@@ -151,42 +112,42 @@ const CreateEnvironment: React.FC = () => {
       </Helper>
       </Helper>
       <Br height="10px" />
       <Br height="10px" />
       <PullRequestList>
       <PullRequestList>
-        {
-          pullRequests.map((pullRequest: any, i: number) => {
-            return (
-              <PullRequestRow
-                onClick={() => {
-                  handlePRRowItemClick(pullRequest);
-                }}
-                isLast={i === dummyData.length - 1}
-                isSelected={pullRequest === selectedPR}
-              >
-                <PRName>
-                  <PRIcon src={pr_icon} alt="pull request icon" />
-                  <EllipsisTextWrapper tooltipText={pullRequest.pr_title}>
-                    {pullRequest.pr_title}
-                  </EllipsisTextWrapper>
-                </PRName>
-
-                <Flex>
-                  <DeploymentImageContainer>
-                    <InfoWrapper>
-                      <LastDeployed>#{pullRequest.pr_number} last updated xyz</LastDeployed>
-                    </InfoWrapper>
-                    <SepDot>•</SepDot>
-                    <MergeInfoWrapper>
-                      <MergeInfo>
-                        {pullRequest.branch_from}
-                        <i className="material-icons">arrow_forward</i>
-                        {pullRequest.branch_into}
-                      </MergeInfo>
-                    </MergeInfoWrapper>
-                  </DeploymentImageContainer>
-                </Flex>
-              </PullRequestRow>
-            )
-          })
-        }
+        {(pullRequests ?? []).map((pullRequest: PullRequest, i: number) => {
+          return (
+            <PullRequestRow
+              onClick={() => {
+                handlePRRowItemClick(pullRequest);
+              }}
+              isLast={i === pullRequests.length - 1}
+              isSelected={pullRequest === selectedPR}
+            >
+              <PRName>
+                <PRIcon src={pr_icon} alt="pull request icon" />
+                <EllipsisTextWrapper tooltipText={pullRequest.pr_title}>
+                  {pullRequest.pr_title}
+                </EllipsisTextWrapper>
+              </PRName>
+
+              <Flex>
+                <DeploymentImageContainer>
+                  <InfoWrapper>
+                    <LastDeployed>
+                      #{pullRequest.pr_number} last updated xyz
+                    </LastDeployed>
+                  </InfoWrapper>
+                  <SepDot>•</SepDot>
+                  <MergeInfoWrapper>
+                    <MergeInfo>
+                      {pullRequest.branch_from}
+                      <i className="material-icons">arrow_forward</i>
+                      {pullRequest.branch_into}
+                    </MergeInfo>
+                  </MergeInfoWrapper>
+                </DeploymentImageContainer>
+              </Flex>
+            </PullRequestRow>
+          );
+        })}
       </PullRequestList>
       </PullRequestList>
       {modalContent ? (
       {modalContent ? (
         <Modal onRequestClose={() => setModalContent(null)} height="auto">
         <Modal onRequestClose={() => setModalContent(null)} height="auto">
@@ -220,6 +181,7 @@ const CreateEnvironment: React.FC = () => {
         </ValidationErrorBannerWrapper>
         </ValidationErrorBannerWrapper>
       ) : null}
       ) : null}
       <SubmitButton
       <SubmitButton
+        onClick={handleCreatePreviewDeployment}
         disabled={loading || !selectedPR || porterYAMLErrors.length > 0}
         disabled={loading || !selectedPR || porterYAMLErrors.length > 0}
       >
       >
         Create preview deployment
         Create preview deployment
@@ -230,24 +192,18 @@ const CreateEnvironment: React.FC = () => {
 
 
 export default CreateEnvironment;
 export default CreateEnvironment;
 
 
-const PRNumber = styled.div`
-  color: #aaaabb;
-  font-weight: 400;
-  margin-left: 7px;
-`;
-
 const PullRequestList = styled.div`
 const PullRequestList = styled.div`
   border: 1px solid #494b4f;
   border: 1px solid #494b4f;
   border-radius: 5px;
   border-radius: 5px;
   overflow: hidden;
   overflow: hidden;
 `;
 `;
 
 
-const PullRequestRow = styled.div<{ isLast?: boolean, isSelected?: boolean }>`
+const PullRequestRow = styled.div<{ isLast?: boolean; isSelected?: boolean }>`
   width: 100%;
   width: 100%;
   padding: 15px;
   padding: 15px;
   cursor: pointer;
   cursor: pointer;
-  background: ${props => props.isSelected ? "#ffffff11" : "#26292e"};
-  border-bottom: ${props => props.isLast ? "" : "1px solid #494b4f"};
+  background: ${(props) => (props.isSelected ? "#ffffff11" : "#26292e")};
+  border-bottom: ${(props) => (props.isLast ? "" : "1px solid #494b4f")};
   :hover {
   :hover {
     background: #ffffff11;
     background: #ffffff11;
   }
   }
@@ -257,10 +213,6 @@ const Code = styled.span`
   font-family: monospace; ;
   font-family: monospace; ;
 `;
 `;
 
 
-const Spacer = styled.div`
-  width: 5px;
-`;
-
 const SepDot = styled.div`
 const SepDot = styled.div`
   color: #aaaabb66;
   color: #aaaabb66;
 `;
 `;
@@ -391,48 +343,11 @@ const DarkMatter = styled.div`
   margin-top: -15px;
   margin-top: -15px;
 `;
 `;
 
 
-const DeleteButton = styled.div`
-  height: 30px;
-  font-size: 13px;
-  font-weight: 500;
-  font-family: "Work Sans", sans-serif;
-  color: white;
-  display: flex;
-  width: 210px;
-  align-items: center;
-  padding: 0 15px;
-  margin-top: 20px;
-  text-align: left;
-  border-radius: 5px;
-  cursor: pointer;
-  user-select: none;
-  :focus {
-    outline: 0;
-  }
-  :hover {
-    filter: brightness(120%);
-  }
-  background: #b91133;
-  border: none;
-  :hover {
-    filter: brightness(120%);
-  }
-`;
-
 const Br = styled.div<{ height: string }>`
 const Br = styled.div<{ height: string }>`
   width: 100%;
   width: 100%;
   height: ${(props) => props.height || "2px"};
   height: ${(props) => props.height || "2px"};
 `;
 `;
 
 
-const StyledPlaceholder = styled.div`
-  width: 100%;
-  padding: 30px;
-  font-size: 13px;
-  border-radius: 5px;
-  background: #26292e;
-  border: 1px solid #494b4f;
-`;
-
 const Slash = styled.div`
 const Slash = styled.div`
   margin: 0 4px;
   margin: 0 4px;
   color: #aaaabb88;
   color: #aaaabb88;
@@ -477,68 +392,6 @@ const Breadcrumb = styled(DynamicLink)`
   }
   }
 `;
 `;
 
 
-const Relative = styled.div`
-  position: relative;
-`;
-
-const EnvironmentsGrid = styled.div`
-  padding-bottom: 150px;
-  display: grid;
-  grid-row-gap: 15px;
-`;
-
-const ControlRow = styled.div`
-  display: flex;
-  margin-left: auto;
-  justify-content: space-between;
-  align-items: center;
-  margin: 35px 0 30px;
-  padding-left: 0px;
-`;
-
-const Button = styled(DynamicLink)`
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: space-between;
-  font-size: 13px;
-  cursor: pointer;
-  font-family: "Work Sans", sans-serif;
-  border-radius: 20px;
-  color: white;
-  height: 35px;
-  padding: 0px 8px;
-  padding-bottom: 1px;
-  margin-right: 10px;
-  font-weight: 500;
-  padding-right: 15px;
-  overflow: hidden;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-  cursor: ${(props: { disabled?: boolean }) =>
-    props.disabled ? "not-allowed" : "pointer"};
-
-  background: ${(props: { disabled?: boolean }) =>
-    props.disabled ? "#aaaabbee" : "#616FEEcc"};
-  :hover {
-    background: ${(props: { disabled?: boolean }) =>
-      props.disabled ? "" : "#505edddd"};
-  }
-
-  > i {
-    color: white;
-    width: 18px;
-    height: 18px;
-    font-weight: 600;
-    font-size: 12px;
-    border-radius: 20px;
-    display: flex;
-    align-items: center;
-    margin-right: 5px;
-    justify-content: center;
-  }
-`;
-
 const ValidationErrorBannerWrapper = styled.div`
 const ValidationErrorBannerWrapper = styled.div`
   margin-block: 20px;
   margin-block: 20px;
 `;
 `;

+ 132 - 88
dashboard/src/main/home/cluster-dashboard/preview-environments/environments/EnvironmentSettings.tsx

@@ -1,6 +1,6 @@
 import DynamicLink from "components/DynamicLink";
 import DynamicLink from "components/DynamicLink";
 import Loading from "components/Loading";
 import Loading from "components/Loading";
-import React, { useContext, useEffect, useState } from "react";
+import React, { useContext, useEffect, useMemo, useState } from "react";
 import api from "shared/api";
 import api from "shared/api";
 import styled from "styled-components";
 import styled from "styled-components";
 import { useParams } from "react-router";
 import { useParams } from "react-router";
@@ -14,17 +14,15 @@ import SaveButton from "components/SaveButton";
 import _ from "lodash";
 import _ from "lodash";
 import { Context } from "shared/Context";
 import { Context } from "shared/Context";
 import PageNotFound from "components/PageNotFound";
 import PageNotFound from "components/PageNotFound";
+import Banner from "components/Banner";
+import InputRow from "components/form-components/InputRow";
+import Modal from "main/home/modals/Modal";
+import { useRouting } from "shared/routing";
 
 
-/**
- * 
- * TODO Soham:
- * 
- * - Handle errors when fetching environments
- * - Handle errors when the environment is not found
- * - Handle errors on saving and deleting the environment
- */
-const EnvironmentSettings: React.FC = () => {
-  const [error, setError] = useState("");
+const EnvironmentSettings = () => {
+  const router = useRouting();
+  const [showDeleteModal, setShowDeleteModal] = useState(false);
+  const [deleteConfirmationPrompt, setDeleteConfirmationPrompt] = useState("");
   const { currentProject, currentCluster, setCurrentError } = useContext(
   const { currentProject, currentCluster, setCurrentError } = useContext(
     Context
     Context
   );
   );
@@ -60,7 +58,7 @@ const EnvironmentSettings: React.FC = () => {
       );
       );
 
 
       setEnvironment(environment);
       setEnvironment(environment);
-      setNewCommentsDisabled(environment.disable_new_comments);
+      setNewCommentsDisabled(environment.new_comments_disabled);
       setDeploymentMode(environment.mode);
       setDeploymentMode(environment.mode);
     };
     };
 
 
@@ -95,8 +93,56 @@ const EnvironmentSettings: React.FC = () => {
     setSaveStatus("");
     setSaveStatus("");
   };
   };
 
 
+  const closeDeleteConfirmationModal = () => {
+    setShowDeleteModal(false);
+    setDeleteConfirmationPrompt("");
+  };
+
+  const canDelete = useMemo(() => {
+    return deleteConfirmationPrompt === `${repoOwner}/${repoName}`;
+  }, [deleteConfirmationPrompt]);
+
+  const handleDelete = async () => {
+    if (!canDelete) {
+      return;
+    }
+
+    try {
+      await api.deleteEnvironment(
+        "<token>",
+        {
+          name: environment?.name,
+        },
+        {
+          project_id: currentProject.id,
+          cluster_id: currentCluster.id,
+          git_installation_id: environment?.git_installation_id,
+          git_repo_owner: repoOwner,
+          git_repo_name: repoName,
+        }
+      );
+
+      closeDeleteConfirmationModal();
+      router.push(`/preview-environments`);
+    } catch (err) {
+      setCurrentError(JSON.stringify(err));
+      closeDeleteConfirmationModal();
+    }
+  };
+
   return (
   return (
     <>
     <>
+      {showDeleteModal ? (
+        <DeletePreviewEnvironmentModal
+          repoOwner={repoOwner}
+          repoName={repoName}
+          onClose={closeDeleteConfirmationModal}
+          prompt={deleteConfirmationPrompt}
+          setPrompt={setDeleteConfirmationPrompt}
+          onDelete={handleDelete}
+          disabled={!canDelete}
+        />
+      ) : null}
       <BreadcrumbRow>
       <BreadcrumbRow>
         <Breadcrumb to={`/preview-environments/deployments/settings`}>
         <Breadcrumb to={`/preview-environments/deployments/settings`}>
           <ArrowIcon src={PullRequestIcon} />
           <ArrowIcon src={PullRequestIcon} />
@@ -156,7 +202,12 @@ const EnvironmentSettings: React.FC = () => {
           Delete the Porter preview environment integration for this repo. All
           Delete the Porter preview environment integration for this repo. All
           preview deployments will also be destroyed.
           preview deployments will also be destroyed.
         </Helper>
         </Helper>
-        <DeleteButton disabled={saveStatus === "loading"} onClick={_.noop}>
+        <DeleteButton
+          disabled={saveStatus === "loading"}
+          onClick={() => {
+            setShowDeleteModal(true);
+          }}
+        >
           Delete preview environment
           Delete preview environment
         </DeleteButton>
         </DeleteButton>
       </StyledPlaceholder>
       </StyledPlaceholder>
@@ -164,37 +215,92 @@ const EnvironmentSettings: React.FC = () => {
   );
   );
 };
 };
 
 
+interface DeletePreviewEnvironmentModalProps {
+  repoName: string;
+  repoOwner: string;
+  prompt: string;
+  setPrompt: (prompt: string) => void;
+  onDelete: () => void;
+  onClose: () => void;
+  disabled: boolean;
+}
+
+const DeletePreviewEnvironmentModal = (
+  props: DeletePreviewEnvironmentModalProps
+) => {
+  return (
+    <Modal
+      height="fit-content"
+      title={`Remove Preview Envs for ${props.repoOwner}/${props.repoName}`}
+      onRequestClose={props.onClose}
+    >
+      <DeletePreviewEnvironmentModalContentsWrapper>
+        <Banner type="warning">
+          All Preview Environment deployments associated with this repo will be
+          deleted.
+        </Banner>
+        <InputRow
+          type="text"
+          label={`Enter ${props.repoOwner}/${props.repoName} to delete Preview Environments:`}
+          value={props.prompt}
+          placeholder={`${props.repoOwner}/${props.repoName}`}
+          setValue={(x: string) => props.setPrompt(x)}
+          width={"500px"}
+        />
+        <Flex justifyContent="center" alignItems="center">
+          <DeleteButton
+            onClick={() => props.onDelete()}
+            disabled={props.disabled}
+          >
+            Delete
+          </DeleteButton>
+        </Flex>
+      </DeletePreviewEnvironmentModalContentsWrapper>
+    </Modal>
+  );
+};
+
 export default EnvironmentSettings;
 export default EnvironmentSettings;
 
 
+const DeletePreviewEnvironmentModalContentsWrapper = styled.div`
+  margin-block-start: 25px;
+`;
+
 const SavePreviewEnvironmentSettings = styled(SaveButton)`
 const SavePreviewEnvironmentSettings = styled(SaveButton)`
   margin-top: 30px;
   margin-top: 30px;
 `;
 `;
 
 
-const DeleteButton = styled.button`
-  height: 30px;
+const Flex = styled.div<{
+  justifyContent?: string;
+  alignItems?: string;
+}>`
+  display: flex;
+  align-items: ${({ alignItems }) => alignItems || "flex-start"};
+  justify-content: ${({ justifyContent }) => justifyContent || "flex-start"};
+`;
+
+const DeleteButton = styled.button<{ disabled?: boolean }>`
   font-size: 13px;
   font-size: 13px;
   font-weight: 500;
   font-weight: 500;
   font-family: "Work Sans", sans-serif;
   font-family: "Work Sans", sans-serif;
   color: white;
   color: white;
   display: flex;
   display: flex;
-  width: 210px;
   align-items: center;
   align-items: center;
-  padding: 0 15px;
+  padding: 10px 15px;
   margin-top: 20px;
   margin-top: 20px;
   text-align: left;
   text-align: left;
   border-radius: 5px;
   border-radius: 5px;
-  cursor: pointer;
   user-select: none;
   user-select: none;
-  :focus {
-    outline: 0;
-  }
-  :hover {
-    filter: brightness(120%);
-  }
   background: #b91133;
   background: #b91133;
   border: none;
   border: none;
-  :hover {
-    filter: brightness(120%);
+  cursor: ${({ disabled }) => (disabled ? "not-allowed" : "pointer")};
+  filter: ${({ disabled }) => (disabled ? "brightness(0.8)" : "none")};
+
+  &:focus {
+    outline: 0;
+  }
+  &:hover {
+    filter: ${({ disabled }) => (disabled ? "brightness(0.8)" : "none")};
   }
   }
 `;
 `;
 
 
@@ -255,65 +361,3 @@ const Breadcrumb = styled(DynamicLink)`
     background: #ffffff11;
     background: #ffffff11;
   }
   }
 `;
 `;
-
-const Relative = styled.div`
-  position: relative;
-`;
-
-const EnvironmentsGrid = styled.div`
-  padding-bottom: 150px;
-  display: grid;
-  grid-row-gap: 15px;
-`;
-
-const ControlRow = styled.div`
-  display: flex;
-  margin-left: auto;
-  justify-content: space-between;
-  align-items: center;
-  margin: 35px 0 30px;
-  padding-left: 0px;
-`;
-
-const Button = styled(DynamicLink)`
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: space-between;
-  font-size: 13px;
-  cursor: pointer;
-  font-family: "Work Sans", sans-serif;
-  border-radius: 20px;
-  color: white;
-  height: 35px;
-  padding: 0px 8px;
-  padding-bottom: 1px;
-  margin-right: 10px;
-  font-weight: 500;
-  padding-right: 15px;
-  overflow: hidden;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-  cursor: ${(props: { disabled?: boolean }) =>
-    props.disabled ? "not-allowed" : "pointer"};
-
-  background: ${(props: { disabled?: boolean }) =>
-    props.disabled ? "#aaaabbee" : "#616FEEcc"};
-  :hover {
-    background: ${(props: { disabled?: boolean }) =>
-      props.disabled ? "" : "#505edddd"};
-  }
-
-  > i {
-    color: white;
-    width: 18px;
-    height: 18px;
-    font-weight: 600;
-    font-size: 12px;
-    border-radius: 20px;
-    display: flex;
-    align-items: center;
-    margin-right: 5px;
-    justify-content: center;
-  }
-`;

+ 1 - 34
dashboard/src/main/home/cluster-dashboard/preview-environments/environments/EnvironmentsList.tsx

@@ -10,45 +10,12 @@ import { Environment } from "../types";
 import EnvironmentCard from "./EnvironmentCard";
 import EnvironmentCard from "./EnvironmentCard";
 import Placeholder from "components/Placeholder";
 import Placeholder from "components/Placeholder";
 
 
-const HARD_CODED_ENVS: Environment[] = [
-  {
-    id: 12,
-    project_id: 1234,
-    cluster_id: 4321,
-    git_installation_id: 55,
-    name: "asdf",
-    git_repo_owner: "owned",
-    git_repo_name: "this-is-a-repo",
-    last_deployment_status: "failed",
-    deployment_count: 12,
-    mode: "manual",
-    git_repo_branches: [],
-    disable_new_comments: true,
-  },
-  {
-    id: 13,
-    project_id: 1234,
-    cluster_id: 4321,
-    git_installation_id: 55,
-    name: "asdf",
-    git_repo_owner: "owned",
-    git_repo_name: "this-is-a-repo",
-    last_deployment_status: "failed",
-    deployment_count: 12,
-    mode: "manual",
-    git_repo_branches: [],
-    disable_new_comments: true,
-  },
-];
-
 const EnvironmentsList = () => {
 const EnvironmentsList = () => {
   const { currentCluster, currentProject } = useContext(Context);
   const { currentCluster, currentProject } = useContext(Context);
   const [isLoading, setIsLoading] = useState(true);
   const [isLoading, setIsLoading] = useState(true);
   const [buttonIsReady, setButtonIsReady] = useState(false);
   const [buttonIsReady, setButtonIsReady] = useState(false);
 
 
-  const [environments, setEnvironments] = useState<Environment[]>(
-    HARD_CODED_ENVS
-  );
+  const [environments, setEnvironments] = useState<Environment[]>([]);
 
 
   const removeEnvironmentFromList = (deletedEnv: Environment) => {
   const removeEnvironmentFromList = (deletedEnv: Environment) => {
     setEnvironments((prev) => {
     setEnvironments((prev) => {

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/preview-environments/types.ts

@@ -40,7 +40,7 @@ export type Environment = {
   git_repo_owner: string;
   git_repo_owner: string;
   git_repo_name: string;
   git_repo_name: string;
   git_repo_branches: string[];
   git_repo_branches: string[];
-  disable_new_comments: boolean;
+  new_comments_disabled: boolean;
   last_deployment_status: DeploymentStatusUnion;
   last_deployment_status: DeploymentStatusUnion;
   deployment_count: number;
   deployment_count: number;
   mode: EnvironmentDeploymentMode;
   mode: EnvironmentDeploymentMode;

+ 3 - 0
dashboard/src/shared/routing.tsx

@@ -91,6 +91,9 @@ export const useRouting = () => {
   const history = useHistory();
   const history = useHistory();
 
 
   return {
   return {
+    push(path: string, state?: any) {
+      history.push(path, state);
+    },
     pushQueryParams: (
     pushQueryParams: (
       params: { [key: string]: unknown },
       params: { [key: string]: unknown },
       removedParams?: string[]
       removedParams?: string[]