Explorar o código

add banner to show porter.yaml errors

Mohammed Nafees %!s(int64=3) %!d(string=hai) anos
pai
achega
6d1178c7e6

+ 161 - 71
dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/DeploymentDetail.tsx

@@ -13,6 +13,9 @@ import github from "assets/github-white.png";
 import { integrationList } from "shared/common";
 import { capitalize } from "shared/string_utils";
 import leftArrow from "assets/left-arrow.svg";
+import Banner from "components/Banner";
+import Modal from "main/home/modals/Modal";
+import document from "assets/document.svg";
 
 const DeploymentDetail = () => {
   const { params } = useRouteMatch<{ namespace: string }>();
@@ -20,6 +23,10 @@ const DeploymentDetail = () => {
   const [prDeployment, setPRDeployment] = useState<PRDeployment>(null);
   const [environmentId, setEnvironmentId] = useState("");
   const [showRepoTooltip, setShowRepoTooltip] = useState(false);
+  const [porterYAMLErrors, setPorterYAMLErrors] = useState<string[]>([]);
+  const [expandedPorterYAMLErrors, setExpandedPorterYAMLErrors] = useState(
+    null
+  );
 
   const { currentProject, currentCluster } = useContext(Context);
 
@@ -61,84 +68,146 @@ const DeploymentDetail = () => {
     return <Loading />;
   }
 
+  useEffect(() => {
+    let isSubscribed = true;
+    let environment_id = parseInt(searchParams.get("environment_id"));
+
+    api
+      .validatePorterYAML(
+        "<token>",
+        {
+          branch: prDeployment.gh_pr_branch_from,
+        },
+        {
+          project_id: currentProject.id,
+          cluster_id: currentCluster.id,
+          environment_id: environment_id,
+        }
+      )
+      .then(({ data }) => {
+        if (!isSubscribed) {
+          return;
+        }
+
+        setPorterYAMLErrors(data.errors);
+      })
+      .catch((err) => {
+        console.error(err);
+        if (isSubscribed) {
+          setPorterYAMLErrors([]);
+        }
+      });
+  });
+
   let repository = `${prDeployment.gh_repo_owner}/${prDeployment.gh_repo_name}`;
 
   return (
-    <StyledExpandedChart>
-      <BreadcrumbRow>
-        <Breadcrumb
-          to={`/preview-environments/deployments/${environmentId}/${repository}`}
+    <>
+      {expandedPorterYAMLErrors && (
+        <Modal
+          onRequestClose={() => setExpandedPorterYAMLErrors(null)}
+          height="auto"
         >
-          <ArrowIcon src={leftArrow} />
-          <Wrap>Back</Wrap>
-        </Breadcrumb>
-      </BreadcrumbRow>
-      <HeaderWrapper>
-        <Title icon={pr_icon} iconWidth="25px">
-          {prDeployment.gh_pr_name}
-        </Title>
-        <InfoWrapper>
-          {prDeployment.subdomain && (
-            <PRLink to={prDeployment.subdomain} target="_blank">
-              <i className="material-icons">link</i>
-              {prDeployment.subdomain}
-            </PRLink>
-          )}
-          <TagWrapper>
-            Namespace <NamespaceTag>{params.namespace}</NamespaceTag>
-          </TagWrapper>
-        </InfoWrapper>
-        <Flex>
-          <Status>
-            <StatusDot status={prDeployment.status} />
-            {capitalize(prDeployment.status)}
-          </Status>
-          <Dot>•</Dot>
-          <DeploymentImageContainer>
-            <DeploymentTypeIcon src={integrationList.repo.icon} />
-            <RepositoryName
-              onMouseOver={() => {
-                setShowRepoTooltip(true);
-              }}
-              onMouseOut={() => {
-                setShowRepoTooltip(false);
-              }}
-            >
-              {repository}
-            </RepositoryName>
-            {showRepoTooltip && <Tooltip>{repository}</Tooltip>}
-          </DeploymentImageContainer>
-          <Dot>•</Dot>
-          <GHALink
-            to={`https://github.com/${repository}/pull/${prDeployment.pull_request_id}`}
-            target="_blank"
+          <Message>
+            <img src={document} />
+            {expandedPorterYAMLErrors}
+          </Message>
+        </Modal>
+      )}
+      <StyledExpandedChart>
+        <BreadcrumbRow>
+          <Breadcrumb
+            to={`/preview-environments/deployments/${environmentId}/${repository}`}
           >
-            <img src={github} /> GitHub PR
-            <i className="material-icons">open_in_new</i>
-          </GHALink>
-          {prDeployment.last_workflow_run_url ? (
-            <GHALink to={prDeployment.last_workflow_run_url} target="_blank">
-              <span className="material-icons-outlined">
-                play_circle_outline
-              </span>
-              Last workflow run
+            <ArrowIcon src={leftArrow} />
+            <Wrap>Back</Wrap>
+          </Breadcrumb>
+        </BreadcrumbRow>
+        <HeaderWrapper>
+          <Title icon={pr_icon} iconWidth="25px">
+            {prDeployment.gh_pr_name}
+          </Title>
+          <InfoWrapper>
+            {prDeployment.subdomain && (
+              <PRLink to={prDeployment.subdomain} target="_blank">
+                <i className="material-icons">link</i>
+                {prDeployment.subdomain}
+              </PRLink>
+            )}
+            <TagWrapper>
+              Namespace <NamespaceTag>{params.namespace}</NamespaceTag>
+            </TagWrapper>
+          </InfoWrapper>
+          <Flex>
+            <Status>
+              <StatusDot status={prDeployment.status} />
+              {capitalize(prDeployment.status)}
+            </Status>
+            <Dot>•</Dot>
+            <DeploymentImageContainer>
+              <DeploymentTypeIcon src={integrationList.repo.icon} />
+              <RepositoryName
+                onMouseOver={() => {
+                  setShowRepoTooltip(true);
+                }}
+                onMouseOut={() => {
+                  setShowRepoTooltip(false);
+                }}
+              >
+                {repository}
+              </RepositoryName>
+              {showRepoTooltip && <Tooltip>{repository}</Tooltip>}
+            </DeploymentImageContainer>
+            <Dot>•</Dot>
+            <GHALink
+              to={`https://github.com/${repository}/pull/${prDeployment.pull_request_id}`}
+              target="_blank"
+            >
+              <img src={github} /> GitHub PR
               <i className="material-icons">open_in_new</i>
             </GHALink>
-          ) : null}
-        </Flex>
-        <LinkToActionsWrapper></LinkToActionsWrapper>
-      </HeaderWrapper>
-      <ChartListWrapper>
-        <ChartList
-          currentCluster={context.currentCluster}
-          currentView="cluster-dashboard"
-          sortType="Newest"
-          namespace={params.namespace}
-          disableBottomPadding
-          closeChartRedirectUrl={`${window.location.pathname}${window.location.search}`}
-        />
-      </ChartListWrapper>
-    </StyledExpandedChart>
+            {prDeployment.last_workflow_run_url ? (
+              <GHALink to={prDeployment.last_workflow_run_url} target="_blank">
+                <span className="material-icons-outlined">
+                  play_circle_outline
+                </span>
+                Last workflow run
+                <i className="material-icons">open_in_new</i>
+              </GHALink>
+            ) : null}
+          </Flex>
+          <LinkToActionsWrapper></LinkToActionsWrapper>
+        </HeaderWrapper>
+        {porterYAMLErrors.length > 0 ? (
+          <Banner type="error">
+            Your porter.yaml file has errors. Please fix them before deploying.
+            <LinkButton
+              onClick={() => {
+                let yamlErrors = "";
+
+                porterYAMLErrors.forEach((err) => {
+                  yamlErrors += "- " + err + "\n";
+                });
+
+                setExpandedPorterYAMLErrors(yamlErrors);
+              }}
+            >
+              View details
+            </LinkButton>
+          </Banner>
+        ) : null}
+        <ChartListWrapper>
+          <ChartList
+            currentCluster={context.currentCluster}
+            currentView="cluster-dashboard"
+            sortType="Newest"
+            namespace={params.namespace}
+            disableBottomPadding
+            closeChartRedirectUrl={`${window.location.pathname}${window.location.search}`}
+          />
+        </ChartListWrapper>
+      </StyledExpandedChart>
+    </>
   );
 };
 
@@ -150,6 +219,27 @@ const ArrowIcon = styled.img`
   opacity: 50%;
 `;
 
+const LinkButton = styled.a`
+  text-decoration: underline;
+  margin-left: 7px;
+  cursor: pointer;
+`;
+
+const Message = styled.div`
+  padding: 20px;
+  background: #26292e;
+  border-radius: 5px;
+  line-height: 1.5em;
+  border: 1px solid #aaaabb33;
+  font-size: 13px;
+  display: flex;
+  align-items: center;
+  > img {
+    width: 13px;
+    margin-right: 20px;
+  }
+`;
+
 const BreadcrumbRow = styled.div`
   width: 100%;
   display: flex;

+ 108 - 20
dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/DeploymentList.tsx

@@ -13,7 +13,10 @@ import DynamicLink from "components/DynamicLink";
 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 document from "assets/document.svg";
 import pullRequestIcon from "assets/pull_request_icon.svg";
 import filterOutline from "assets/filter-outline.svg";
 import sort from "assets/sort.svg";
@@ -74,6 +77,10 @@ const DeploymentList = () => {
   const [pullRequests, setPullRequests] = useState<PullRequest[]>([]);
   const [searchValue, setSearchValue] = useState("");
   const [newCommentsDisabled, setNewCommentsDisabled] = useState(false);
+  const [porterYAMLErrors, setPorterYAMLErrors] = useState<string[]>([]);
+  const [expandedPorterYAMLErrors, setExpandedPorterYAMLErrors] = useState(
+    null
+  );
 
   const [
     statusSelectorVal,
@@ -118,6 +125,18 @@ const DeploymentList = () => {
     );
   };
 
+  const validatePorterYAML = () => {
+    return api.validatePorterYAML(
+      "<token>",
+      {},
+      {
+        project_id: currentProject.id,
+        cluster_id: currentCluster.id,
+        environment_id: Number(environment_id),
+      }
+    );
+  };
+
   useEffect(() => {
     const status_filter = getQueryParam("status_filter");
 
@@ -139,28 +158,47 @@ const DeploymentList = () => {
     let isSubscribed = true;
     setIsLoading(true);
 
-    Promise.allSettled([getPRDeploymentList(), getEnvironment()])
-      .then(([getDeploymentsResponse, getEnvironmentResponse]) => {
-        const deploymentList =
-          getDeploymentsResponse.status === "fulfilled"
-            ? getDeploymentsResponse.value.data
-            : {};
-        const environmentList =
-          getEnvironmentResponse.status === "fulfilled"
-            ? getEnvironmentResponse.value.data
-            : {};
-
-        if (!isSubscribed) {
-          return;
-        }
-
-        setDeploymentList(deploymentList.deployments || HARD_CODED_DEPLOYMENTS);
-        setPullRequests(deploymentList.pull_requests || []);
+    Promise.allSettled([
+      validatePorterYAML(),
+      getPRDeploymentList(),
+      getEnvironment(),
+    ])
+      .then(
+        ([
+          validatePorterYAMLResponse,
+          getDeploymentsResponse,
+          getEnvironmentResponse,
+        ]) => {
+          const deploymentList =
+            getDeploymentsResponse.status === "fulfilled"
+              ? getDeploymentsResponse.value.data
+              : {};
+          const environmentList =
+            getEnvironmentResponse.status === "fulfilled"
+              ? getEnvironmentResponse.value.data
+              : {};
+          const porterYAMLErrors =
+            validatePorterYAMLResponse.status === "fulfilled"
+              ? validatePorterYAMLResponse.value.data
+              : [];
+
+          if (!isSubscribed) {
+            return;
+          }
+
+          setPorterYAMLErrors(porterYAMLErrors.errors);
+          setDeploymentList(
+            deploymentList.deployments || HARD_CODED_DEPLOYMENTS
+          );
+          setPullRequests(deploymentList.pull_requests || []);
 
-        setNewCommentsDisabled(environmentList.new_comments_disabled || false);
+          setNewCommentsDisabled(
+            environmentList.new_comments_disabled || false
+          );
 
-        setIsLoading(false);
-      })
+          setIsLoading(false);
+        }
+      )
       .catch(() => {
         setDeploymentList(HARD_CODED_DEPLOYMENTS);
       });
@@ -307,6 +345,17 @@ const DeploymentList = () => {
 
   return (
     <>
+      {expandedPorterYAMLErrors && (
+        <Modal
+          onRequestClose={() => setExpandedPorterYAMLErrors(null)}
+          height="auto"
+        >
+          <Message>
+            <img src={document} />
+            {expandedPorterYAMLErrors}
+          </Message>
+        </Modal>
+      )}
       <BreadcrumbRow>
         <Breadcrumb to="/preview-environments">
           <ArrowIcon src={pullRequestIcon} />
@@ -334,6 +383,24 @@ const DeploymentList = () => {
         disableLineBreak
         capitalize={false}
       />
+      {porterYAMLErrors.length > 0 ? (
+        <Banner type="error">
+          Your porter.yaml file has errors. Please fix them before deploying.
+          <LinkButton
+            onClick={() => {
+              let yamlErrors = "";
+
+              porterYAMLErrors.forEach((err) => {
+                yamlErrors += "- " + err + "\n";
+              });
+
+              setExpandedPorterYAMLErrors(yamlErrors);
+            }}
+          >
+            View details
+          </LinkButton>
+        </Banner>
+      ) : null}
       {/* <Flex>
         <ActionsWrapper>
           <StyledStatusSelector>
@@ -469,6 +536,27 @@ const StyledLink = styled(DynamicLink)`
   }
 `;
 
+const LinkButton = styled.a`
+  text-decoration: underline;
+  margin-left: 7px;
+  cursor: pointer;
+`;
+
+const Message = styled.div`
+  padding: 20px;
+  background: #26292e;
+  border-radius: 5px;
+  line-height: 1.5em;
+  border: 1px solid #aaaabb33;
+  font-size: 13px;
+  display: flex;
+  align-items: center;
+  > img {
+    width: 13px;
+    margin-right: 20px;
+  }
+`;
+
 const BreadcrumbRow = styled.div`
   width: 100%;
   display: flex;

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

@@ -241,6 +241,20 @@ const toggleNewCommentForEnvironment = baseApi<
   return `/api/projects/${project_id}/clusters/${cluster_id}/environments/${environment_id}/toggle_new_comment`;
 });
 
+const validatePorterYAML = baseApi<
+  {
+    branch?: string;
+  },
+  {
+    project_id: number;
+    cluster_id: number;
+    environment_id: number;
+  }
+>("GET", (pathParams) => {
+  let { project_id, cluster_id, environment_id } = pathParams;
+  return `/api/projects/${project_id}/clusters/${cluster_id}/environments/${environment_id}/validate_porter_yaml`;
+});
+
 const createGCPIntegration = baseApi<
   {
     gcp_key_data: string;
@@ -2204,6 +2218,7 @@ export default {
   listEnvironments,
   getEnvironment,
   toggleNewCommentForEnvironment,
+  validatePorterYAML,
   createGCPIntegration,
   createInvite,
   createNamespace,