Răsfoiți Sursa

feat: expanded view for created preview env, banner styling fixes

Soham Parekh 3 ani în urmă
părinte
comite
c99ce62802

+ 96 - 0
dashboard/src/main/home/cluster-dashboard/preview-environments/components/PorterYAMLErrorsModal.tsx

@@ -0,0 +1,96 @@
+import React from "react";
+import styled from "styled-components";
+import TitleSection from "components/TitleSection";
+import danger from "assets/danger.svg";
+import info from "assets/info.svg";
+import Modal from "main/home/modals/Modal";
+
+interface PorterYAMLErrorsModalProps {
+  errors: string[];
+  onClose: (...args: any[]) => void;
+  repo: string;
+  branch?: string;
+}
+
+const PorterYAMLErrorsModal = ({
+  errors,
+  onClose,
+  repo,
+  branch,
+}: PorterYAMLErrorsModalProps) => {
+  if (!errors.length) {
+    return null;
+  }
+
+  return (
+    <Modal onRequestClose={() => onClose()} height="auto">
+      <TitleSection icon={danger}>
+        <Text>porter.yaml</Text>
+      </TitleSection>
+      <InfoRow>
+        <InfoTab>
+          <img src={info} /> <Bold>Repo:</Bold>
+          {repo}
+        </InfoTab>
+        <InfoTab>
+          <img src={info} /> <Bold>Branch:</Bold>
+          {branch ?? "default"}
+        </InfoTab>
+      </InfoRow>
+      <Message>
+        {errors.map((el) => {
+          return (
+            <div>
+              {"- "}
+              {el}
+            </div>
+          );
+        })}
+      </Message>
+    </Modal>
+  );
+};
+
+const Text = styled.div`
+  font-weight: 500;
+  font-size: 18px;
+  z-index: 999;
+`;
+
+const InfoTab = styled.div`
+  display: flex;
+  align-items: center;
+  opacity: 50%;
+  font-size: 13px;
+  margin-right: 15px;
+  justify-content: center;
+
+  > img {
+    width: 13px;
+    margin-right: 7px;
+  }
+`;
+
+const InfoRow = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: flex-start;
+  margin-bottom: 12px;
+`;
+
+const Bold = styled.div`
+  font-weight: 500;
+  margin-right: 5px;
+`;
+
+const Message = styled.div`
+  padding: 20px;
+  background: #26292e;
+  border-radius: 5px;
+  line-height: 1.5em;
+  border: 1px solid #aaaabb33;
+  font-size: 13px;
+  margin-top: 40px;
+`;
+
+export default PorterYAMLErrorsModal;

+ 51 - 22
dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/DeploymentDetail.tsx

@@ -16,6 +16,8 @@ import { capitalize } from "shared/string_utils";
 import Banner from "components/Banner";
 import Modal from "main/home/modals/Modal";
 import { validatePorterYAML } from "../utils";
+import Placeholder from "components/Placeholder";
+import GithubIcon from "assets/GithubIcon";
 
 const DeploymentDetail = () => {
   const { params } = useRouteMatch<{ id: string }>();
@@ -98,8 +100,7 @@ const DeploymentDetail = () => {
 
   const repository = `${prDeployment.gh_repo_owner}/${prDeployment.gh_repo_name}`;
 
-  // TODO (soham): Add a view linking to the current workflow
-  if (!prDeployment.namespace) {
+  if (!prDeployment.namespace && prDeployment.status === "creating") {
     return (
       <>
         <BreadcrumbRow>
@@ -158,19 +159,28 @@ const DeploymentDetail = () => {
                 {showRepoTooltip && <Tooltip>{repository}</Tooltip>}
               </DeploymentImageContainer>
               <Dot>•</Dot>
-              {prDeployment.last_workflow_run_url ? (
-                <GHALink
-                  to={prDeployment.last_workflow_run_url}
-                  target="_blank"
-                >
-                  <img src={github} /> View last workflow run
-                  <i className="material-icons">open_in_new</i>
-                </GHALink>
-              ) : null}
+              <GHALink
+                to={`https://github.com/${prDeployment.gh_repo_owner}/${prDeployment.gh_repo_name}/pulls/${prDeployment.pull_request_id}`}
+                target="_blank"
+              >
+                <GithubIcon />
+                View PR
+                <i className="material-icons">open_in_new</i>
+              </GHALink>
             </Flex>
             <LinkToActionsWrapper></LinkToActionsWrapper>
           </HeaderWrapper>
-          <ChartListWrapper></ChartListWrapper>
+          <ChartListWrapper>
+            <Placeholder height="370px">
+              This preview deployment has not been created yet.{" "}
+              <ViewLastWorkflowLink
+                to={`https://github.com/${prDeployment.gh_repo_owner}/${prDeployment.gh_repo_name}/actions`}
+                target="_blank"
+              >
+                View last workflow
+              </ViewLastWorkflowLink>
+            </Placeholder>
+          </ChartListWrapper>
         </StyledExpandedChart>
       </>
     );
@@ -264,16 +274,19 @@ const DeploymentDetail = () => {
           <LinkToActionsWrapper></LinkToActionsWrapper>
         </HeaderWrapper>
         {porterYAMLErrors.length > 0 ? (
-          <Banner type="error">
-            Your porter.yaml file has errors. Please fix them before deploying.
-            <LinkButton
-              onClick={() => {
-                setExpandedPorterYAMLErrors(porterYAMLErrors);
-              }}
-            >
-              View details
-            </LinkButton>
-          </Banner>
+          <ErrorBannerWrapper>
+            <Banner type="error">
+              Your porter.yaml file has errors. Please fix them before
+              deploying.
+              <LinkButton
+                onClick={() => {
+                  setExpandedPorterYAMLErrors(porterYAMLErrors);
+                }}
+              >
+                View details
+              </LinkButton>
+            </Banner>
+          </ErrorBannerWrapper>
         ) : null}
         <ChartListWrapper>
           <ChartList
@@ -292,6 +305,10 @@ const DeploymentDetail = () => {
 
 export default DeploymentDetail;
 
+const ErrorBannerWrapper = styled.div`
+  margin-block: 20px;
+`;
+
 const Slash = styled.div`
   margin: 0 4px;
   color: #aaaabb88;
@@ -388,6 +405,18 @@ const GHALink = styled(DynamicLink)`
   }
 `;
 
+const ViewLastWorkflowLink = styled(DynamicLink)`
+  display: flex;
+  align-items: center;
+  text-decoration: underline;
+  margin-left: 7px;
+  color: currentcolor;
+
+  :hover {
+    color: white;
+  }
+`;
+
 const LineBreak = styled.div`
   width: calc(100% - 0px);
   height: 1px;

+ 13 - 37
dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/DeploymentList.tsx

@@ -5,7 +5,7 @@ import styled from "styled-components";
 import Loading from "components/Loading";
 import _ from "lodash";
 import DeploymentCard from "./DeploymentCard";
-import { PRDeployment, PullRequest } from "../types";
+import { Environment, PRDeployment, PullRequest } from "../types";
 import { useRouting } from "shared/routing";
 import { useHistory, useLocation, useParams } from "react-router";
 import { deployments, pull_requests } from "../mocks";
@@ -14,13 +14,14 @@ import DashboardHeader from "../../DashboardHeader";
 import RadioFilter from "components/RadioFilter";
 import Placeholder from "components/Placeholder";
 import Banner from "components/Banner";
-import Modal from "main/home/modals/Modal";
 
 import pullRequestIcon from "assets/pull_request_icon.svg";
 import filterOutline from "assets/filter-outline.svg";
 import sort from "assets/sort.svg";
 import { search } from "shared/search";
 import { getPRDeploymentList, validatePorterYAML } from "../utils";
+import { PorterYAMLErrors } from "../errors";
+import PorterYAMLErrorsModal from "../components/PorterYAMLErrorsModal";
 
 const AvailableStatusFilters = ["all", "created", "failed", "not_deployed"];
 
@@ -249,47 +250,19 @@ const DeploymentList = () => {
     );
   };
 
-  const handleToggleCommentStatus = (currentlyDisabled: boolean) => {
-    api
-      .toggleNewCommentForEnvironment(
-        "<token>",
-        {
-          disable: !currentlyDisabled,
-        },
-        {
-          project_id: currentProject.id,
-          cluster_id: currentCluster.id,
-          environment_id: Number(environment_id),
-        }
-      )
-      .then(() => {
-        setNewCommentsDisabled(!currentlyDisabled);
-      });
-  };
-
   useEffect(() => {
     pushQueryParams({ status_filter: statusSelectorVal });
   }, [statusSelectorVal]);
 
   return (
     <>
-      {expandedPorterYAMLErrors.length > 0 && (
-        <Modal
-          onRequestClose={() => setExpandedPorterYAMLErrors([])}
-          height="auto"
-        >
-          <Message>
-            {expandedPorterYAMLErrors.map((el) => {
-              return (
-                <div>
-                  {"- "}
-                  {el}
-                </div>
-              );
-            })}
-          </Message>
-        </Modal>
-      )}
+      <PorterYAMLErrorsModal
+        errors={expandedPorterYAMLErrors}
+        onClose={() => setExpandedPorterYAMLErrors([])}
+        repo={selectedRepo}
+        branch=""
+      />
+
       <BreadcrumbRow>
         <Breadcrumb to="/preview-environments">
           <ArrowIcon src={pullRequestIcon} />
@@ -370,6 +343,9 @@ const DeploymentList = () => {
             name="Sort"
           />
           <CreatePreviewEnvironmentButton
+            disabled={porterYAMLErrors.some(
+              (err) => err === PorterYAMLErrors.FileNotFound
+            )}
             to={`/preview-environments/deployments/${environment_id}/${repo_owner}/${repo_name}/create`}
           >
             <i className="material-icons">add</i> New preview deployment

+ 60 - 34
dashboard/src/main/home/cluster-dashboard/preview-environments/environments/CreateEnvironment.tsx

@@ -16,11 +16,12 @@ import { getPRDeploymentList, validatePorterYAML } from "../utils";
 import Banner from "components/Banner";
 import Modal from "main/home/modals/Modal";
 import { useRouting } from "shared/routing";
+import PorterYAMLErrorsModal from "../components/PorterYAMLErrorsModal";
 
 const CreateEnvironment: React.FC = () => {
   const router = useRouting();
   const queryClient = useQueryClient();
-  const [modalContent, setModalContent] = useState<React.ReactNode>();
+  const [showErrorsModal, setShowErrorsModal] = useState<boolean>(false);
   const { currentProject, currentCluster, setCurrentError } = useContext(
     Context
   );
@@ -130,12 +131,12 @@ const CreateEnvironment: React.FC = () => {
 
               <Flex>
                 <DeploymentImageContainer>
-                  <InfoWrapper>
+                  {/* <InfoWrapper>
                     <LastDeployed>
                       #{pullRequest.pr_number} last updated xyz
                     </LastDeployed>
                   </InfoWrapper>
-                  <SepDot>•</SepDot>
+                  <SepDot>•</SepDot> */}
                   <MergeInfoWrapper>
                     <MergeInfo>
                       {pullRequest.branch_from}
@@ -149,43 +150,52 @@ const CreateEnvironment: React.FC = () => {
           );
         })}
       </PullRequestList>
-      {modalContent ? (
-        <Modal onRequestClose={() => setModalContent(null)} height="auto">
-          {modalContent}
-        </Modal>
+      {showErrorsModal ? (
+        <PorterYAMLErrorsModal
+          errors={porterYAMLErrors}
+          onClose={() => setShowErrorsModal(false)}
+          repo={selectedPR.repo_owner + "/" + selectedPR.repo_name}
+          branch={selectedPR.branch_from}
+        />
       ) : null}
-      {selectedPR && porterYAMLErrors.length ? (
+      {selectedPR ? (
         <ValidationErrorBannerWrapper>
           <Banner type="warning">
             We found some errors in the porter.yaml file on your default branch.
             &nbsp;
-            <LearnMoreButton
-              onClick={() =>
-                setModalContent(
-                  <Message>
-                    {porterYAMLErrors.map((el) => {
-                      return (
-                        <div>
-                          {"- "}
-                          {el}
-                        </div>
-                      );
-                    })}
-                  </Message>
-                )
-              }
-            >
+            <LearnMoreButton onClick={() => setShowErrorsModal(true)}>
               Learn more
             </LearnMoreButton>
           </Banner>
         </ValidationErrorBannerWrapper>
       ) : null}
-      <SubmitButton
-        onClick={handleCreatePreviewDeployment}
-        disabled={loading || !selectedPR || porterYAMLErrors.length > 0}
-      >
-        Create preview deployment
-      </SubmitButton>
+      <CreatePreviewDeploymentWrapper>
+        <SubmitButton
+          onClick={handleCreatePreviewDeployment}
+          disabled={loading || !selectedPR || porterYAMLErrors.length > 0}
+        >
+          Create preview deployment
+        </SubmitButton>
+        {selectedPR ? (
+          <RevalidatePorterYAMLSpanWrapper>
+            Please fix your porter.yaml file to continue.{" "}
+            <RevalidateSpan
+              onClick={(e) => {
+                e.preventDefault();
+                e.stopPropagation();
+
+                if (!selectedPR) {
+                  return;
+                }
+
+                handlePRRowItemClick(selectedPR);
+              }}
+            >
+              Refresh
+            </RevalidateSpan>
+          </RevalidatePorterYAMLSpanWrapper>
+        ) : null}
+      </CreatePreviewDeploymentWrapper>
     </>
   );
 };
@@ -310,7 +320,6 @@ const SubmitButton = styled.div`
   height: 30px;
   padding: 0 8px;
   width: 200px;
-  margin-top: 30px;
   overflow: hidden;
   white-space: nowrap;
   text-overflow: ellipsis;
@@ -397,11 +406,9 @@ const ValidationErrorBannerWrapper = styled.div`
 `;
 
 const LearnMoreButton = styled.div`
+  text-decoration: underline;
   fontweight: bold;
   cursor: pointer;
-  &:hover {
-    text-decoration: underline;
-  }
 `;
 
 const Message = styled.div`
@@ -413,3 +420,22 @@ const Message = styled.div`
   font-size: 13px;
   margin-top: 40px;
 `;
+
+const CreatePreviewDeploymentWrapper = styled.div`
+  margin-top: 30px;
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+  gap: 10px;
+`;
+
+const RevalidatePorterYAMLSpanWrapper = styled.div`
+  font-size: 13px;
+  color: #aaaabb;
+`;
+
+const RevalidateSpan = styled.span`
+  color: #aaaabb;
+  text-decoration: underline;
+  cursor: pointer;
+`;

+ 62 - 5
dashboard/src/main/home/cluster-dashboard/preview-environments/environments/EnvironmentSettings.tsx

@@ -21,14 +21,18 @@ import { useRouting } from "shared/routing";
 import NamespaceAnnotations, {
   KeyValueType,
 } from "../components/NamespaceAnnotations";
+import BranchFilterSelector from "../components/BranchFilterSelector";
 
 const EnvironmentSettings = () => {
   const router = useRouting();
+  const [isLoadingBranches, setIsLoadingBranches] = useState<boolean>(false);
+  const [availableBranches, setAvailableBranches] = useState<string[]>([]);
   const [showDeleteModal, setShowDeleteModal] = useState(false);
   const [deleteConfirmationPrompt, setDeleteConfirmationPrompt] = useState("");
   const { currentProject, currentCluster, setCurrentError } = useContext(
     Context
   );
+  const [selectedBranches, setSelectedBranches] = useState([]);
   const [environment, setEnvironment] = useState<Environment>();
   const [saveStatus, setSaveStatus] = useState("");
   const [newCommentsDisabled, setNewCommentsDisabled] = useState(false);
@@ -64,6 +68,7 @@ const EnvironmentSettings = () => {
       );
 
       setEnvironment(environment);
+      setSelectedBranches(environment.git_repo_branches);
       setNewCommentsDisabled(environment.new_comments_disabled);
       setDeploymentMode(environment.mode);
 
@@ -88,6 +93,38 @@ const EnvironmentSettings = () => {
     }
   }, []);
 
+  useEffect(() => {
+    if (!environment) {
+      return;
+    }
+
+    const repoName = environment.git_repo_name;
+    const repoOwner = environment.git_repo_owner;
+    setIsLoadingBranches(true);
+    api
+      .getBranches<string[]>(
+        "<token>",
+        {},
+        {
+          project_id: currentProject.id,
+          kind: "github",
+          name: repoName,
+          owner: repoOwner,
+          git_repo_id: environment.git_installation_id,
+        }
+      )
+      .then(({ data }) => {
+        setIsLoadingBranches(false);
+        setAvailableBranches(data);
+      })
+      .catch(() => {
+        setIsLoadingBranches(false);
+        setCurrentError(
+          "Couldn't load branches for this repository, using all branches by default."
+        );
+      });
+  }, [environment]);
+
   const handleSave = async () => {
     let annotations: Record<string, string> = {};
 
@@ -121,7 +158,7 @@ const EnvironmentSettings = () => {
         {
           mode: deploymentMode,
           disable_new_comments: newCommentsDisabled,
-          git_repo_branches: [],
+          git_repo_branches: selectedBranches,
           namespace_annotations: annotations,
         },
         {
@@ -208,6 +245,12 @@ const EnvironmentSettings = () => {
         capitalize={false}
       />
       <StyledPlaceholder>
+        <WarningBannerWrapper>
+          <Banner type="warning">
+            Changes made here will not affect existing deployments in this
+            preview environment.
+          </Banner>
+        </WarningBannerWrapper>
         <Heading isAtTop>Pull request comment settings</Heading>
         <Helper>
           Update the most recent PR comment on every deploy. If disabled, a new
@@ -234,15 +277,25 @@ const EnvironmentSettings = () => {
           }
         />
         <Br />
+        <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={setSelectedBranches}
+          options={availableBranches}
+          value={selectedBranches}
+          showLoading={isLoadingBranches}
+        />
+        <Br />
         <Heading>Namespace annotations</Heading>
         <Helper>
           Custom annotations to be injected into the Kubernetes namespace
           created for each deployment.
         </Helper>
-        <Banner type="warning">
-          Changes made here will not affect existing deployments in this preview
-          environment.
-        </Banner>
         <NamespaceAnnotations
           values={namespaceAnnotations}
           setValues={(x: KeyValueType[]) => {
@@ -425,3 +478,7 @@ const Breadcrumb = styled(DynamicLink)`
     background: #ffffff11;
   }
 `;
+
+const WarningBannerWrapper = styled.div`
+  margin-block: 20px;
+`;

+ 3 - 0
dashboard/src/main/home/cluster-dashboard/preview-environments/errors.ts

@@ -0,0 +1,3 @@
+export enum PorterYAMLErrors {
+  FileNotFound = "porter.yaml does not exist in the root of this repository",
+}