Justin Rhee il y a 3 ans
Parent
commit
fb1b9fe086

+ 9 - 2
dashboard/src/components/CopyToClipboard.tsx

@@ -10,7 +10,7 @@ type PropsType = {
   onError?: (e: ClipboardJS.Event) => void;
   wrapperProps?: any;
   as?: any;
-  children: JSX.Element[];
+  children: JSX.Element[] | JSX.Element | string;
 };
 
 type StateType = {
@@ -108,4 +108,11 @@ export default class CopyToClipboard extends Component<PropsType, StateType> {
   }
 }
 
-const DynamicSpanComponent = styled.span``;
+const DynamicSpanComponent = styled.span`
+  :hover {
+    cursor: pointer;
+    color: #ffffff;
+  }
+  color: #aaaabb;
+  font-size: 18px;
+`;

+ 30 - 9
dashboard/src/components/porter/ExpandableSection.tsx

@@ -1,6 +1,7 @@
 import React, { useEffect, useState } from "react";
 import styled from "styled-components";
 import Container from "./Container";
+import CopyToClipboard from "components/CopyToClipboard";
 
 type Props = {
   isInitiallyExpanded?: boolean;
@@ -40,15 +41,29 @@ const ExpandableSection: React.FC<Props> = ({
       {noWrapper ? (
         <Container row spaced={spaced}>
           {Header}
-          {copy ? (<ExpandButton onClick={() => setIsExpanded(!isExpanded)}>
-            {isExpanded ? collapseText : expandText}
-          </ExpandButton>) : (<div>          <ExpandButton onClick={() => setIsExpanded(!isExpanded)}>
-            {isExpanded ? collapseText : expandText}
-          </ExpandButton>          <ExpandButton onClick={() => setIsExpanded(!isExpanded)}>
-              {isExpanded ? collapseText : expandText}
-            </ExpandButton></div>)}
-
-
+          {copy ?
+            (
+              <CopyWrapper>
+                <ExpandButton onClick={() => setIsExpanded(!isExpanded)}>
+                  {isExpanded ? collapseText : expandText}
+                </ExpandButton>
+                <CopyToClipboard
+                  as="i"
+                  text={copy}
+                  wrapperProps={{
+                    className: "material-icons",
+                  }}
+                >
+                  content_copy
+                </CopyToClipboard>
+              </CopyWrapper>
+            ) :
+            (
+              <ExpandButton onClick={() => setIsExpanded(!isExpanded)}>
+                {isExpanded ? collapseText : expandText}
+              </ExpandButton>
+            )
+          }
         </Container>
       ) : (
         <HeaderRow
@@ -132,4 +147,10 @@ const StyledExpandableSection = styled.div<{
       max-height: 300px;
     }
   }
+`;
+
+const CopyWrapper = styled.div`
+  display: flex;
+  align-items: center;
+  gap: 10px;
 `;

+ 44 - 29
dashboard/src/main/home/app-dashboard/new-app-flow/GithubActionModal.tsx

@@ -12,6 +12,7 @@ import { CopyBlock } from "react-code-blocks";
 import { getGithubAction } from "./utils";
 import AceEditor from "react-ace";
 import YamlEditor from "components/YamlEditor";
+import Error from "components/porter/Error";
 
 
 interface GithubActionModalProps {
@@ -23,7 +24,8 @@ interface GithubActionModalProps {
   stackName?: string;
   projectId?: number;
   clusterId?: number;
-  deployPorterApp: () => void;
+  deployPorterApp: () => Promise<boolean>;
+  deploymentError?: string;
 }
 
 type Choice = "open_pr" | "copy";
@@ -38,6 +40,7 @@ const GithubActionModal: React.FC<GithubActionModalProps> = ({
   projectId,
   clusterId,
   deployPorterApp,
+  deploymentError,
 }) => {
   const [choice, setChoice] = React.useState<Choice>("open_pr");
   const [loading, setLoading] = React.useState<boolean>(false);
@@ -47,26 +50,29 @@ const GithubActionModal: React.FC<GithubActionModalProps> = ({
       try {
         setLoading(true)
         // this creates the dummy chart
-        deployPorterApp();
 
-        // this creates the secret and possily the PR
-        const res = await api.createSecretAndOpenGitHubPullRequest(
-          "<token>",
-          {
-            github_app_installation_id: githubAppInstallationID,
-            github_repo_owner: githubRepoOwner,
-            github_repo_name: githubRepoName,
-            branch,
-            open_pr: choice === "open_pr",
-          },
-          {
-            project_id: projectId,
-            cluster_id: clusterId,
-            stack_name: stackName,
+        const success = await deployPorterApp();
+
+        if (success) {
+          // this creates the secret and possibly the PR
+          const res = await api.createSecretAndOpenGitHubPullRequest(
+            "<token>",
+            {
+              github_app_installation_id: githubAppInstallationID,
+              github_repo_owner: githubRepoOwner,
+              github_repo_name: githubRepoName,
+              branch,
+              open_pr: choice === "open_pr",
+            },
+            {
+              project_id: projectId,
+              cluster_id: clusterId,
+              stack_name: stackName,
+            }
+          );
+          if (res?.data?.url) {
+            window.open(res.data.url, "_blank", "noreferrer")
           }
-        );
-        if (res?.data?.url) {
-          window.open(res.data.url, "_blank", "noreferrer")
         }
       } catch (error) {
         console.log(error)
@@ -96,6 +102,7 @@ const GithubActionModal: React.FC<GithubActionModalProps> = ({
         }
         isInitiallyExpanded
         spaced
+        copy={getGithubAction(projectId, clusterId, stackName)}
         ExpandedSection={
           <YamlEditor
             value={getGithubAction(projectId, clusterId, stackName)}
@@ -106,7 +113,7 @@ const GithubActionModal: React.FC<GithubActionModalProps> = ({
       />
       <Spacer y={1} />
       <Text color="helper">
-        Porter can open a PR for you to approve and merge this file into your repository, or you can add it yourself. If you allow Porter to open a PR, you will be redirected to the PR in a new tab after hitting Complete below.
+        Porter can open a PR for you to approve and merge this file into your repository, or you can add it yourself. If you allow Porter to open a PR, you will be redirected to the PR in a new tab after submitting below.
       </Text>
       <Spacer y={1} />
       <Select
@@ -117,14 +124,18 @@ const GithubActionModal: React.FC<GithubActionModalProps> = ({
         setValue={(x: string) => setChoice(x as Choice)}
         width="100%"
       />
-      <Button
-        onClick={submit}
-        width={"100%"}
-        status={loading ? "loading" : undefined}
-        loadingText="Opening PR..."
-      >
-        Complete
-      </Button>
+      <StyledButton>
+        <Button
+          onClick={submit}
+          width={"150px"}
+          loadingText={"Submitting..."}
+          status={loading ? "loading" : deploymentError ? (
+            <Error message={deploymentError} />
+          ) : undefined}
+        >
+          Deploy app
+        </Button>
+      </StyledButton>
     </Modal>
   )
 }
@@ -140,4 +151,8 @@ const ModalHeader = styled.div`
   font-weight: 600;
   font-size: 1.5vw;
   font-family: monospace; ;
-`;
+`;
+
+const StyledButton = styled.div`
+  margin-top: 10px;
+`;

+ 59 - 41
dashboard/src/main/home/app-dashboard/new-app-flow/NewAppFlow.tsx

@@ -35,6 +35,7 @@ import {
   FullGithubActionConfigType,
   GithubActionConfigType,
 } from "shared/types";
+import Error from "components/porter/Error";
 import { z } from "zod";
 import { AppsSchema, EnvSchema, PorterYamlSchema } from "./schema";
 import { Service } from "./serviceTypes";
@@ -87,7 +88,8 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
   const [imageUrl, setImageUrl] = useState("");
   const [imageTag, setImageTag] = useState("latest");
   const { currentCluster, currentProject } = useContext(Context);
-  const [isLoading, setIsLoading] = useState<boolean>(true);
+  const [deploying, setDeploying] = useState<boolean>(false);
+  const [deploymentError, setDeploymentError] = useState<string | undefined>(undefined);
   const [currentStep, setCurrentStep] = useState<number>(0);
   const [existingStep, setExistingStep] = useState<number>(0);
   const [formState, setFormState] = useState<FormState>(INITIAL_STATE);
@@ -151,9 +153,8 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
       ) {
         setDetected({
           detected: true,
-          message: `Detected ${
-            Object.keys(porterYamlToJson.apps).length
-          } apps from porter.yaml`,
+          message: `Detected ${Object.keys(porterYamlToJson.apps).length
+            } apps from porter.yaml`,
         });
       } else {
         setDetected({
@@ -193,6 +194,8 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
 
   const deployPorterApp = async () => {
     try {
+      setDeploying(true);
+      setDeploymentError(undefined);
       if (
         currentProject == null ||
         currentCluster == null ||
@@ -208,48 +211,56 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
       const base64Encoded = btoa(yamlString);
       const imageInfo = imageUrl
         ? {
-            image_info: {
-              repository: imageUrl,
-              tag: imageTag,
-            },
-          }
+          image_info: {
+            repository: imageUrl,
+            tag: imageTag,
+          },
+        }
         : {};
 
-      // write to the db + deploy
-      await Promise.all([
-        api.createPorterApp(
-          "<token>",
-          {
-            name: formState.applicationName,
-            repo_name: actionConfig.git_repo,
-            git_branch: branch,
-            build_context: folderPath,
-            builder: (buildConfig as any)?.builder,
-            buildpacks: (buildConfig as any)?.buildpacks?.join(",") ?? "",
-            dockerfile: dockerfilePath,
-            image_repo_uri: imageUrl,
-          },
-          {
-            cluster_id: currentCluster.id,
-            project_id: currentProject.id,
-          }
-        ),
-        api.updatePorterStack(
-          "<token>",
-          {
-            stack_name: formState.applicationName,
-            porter_yaml: base64Encoded,
-            ...imageInfo,
-          },
-          {
-            cluster_id: currentCluster.id,
-            project_id: currentProject.id,
-          }
-        ),
-      ]);
+      // write to the db
+      await api.createPorterApp(
+        "<token>",
+        {
+          name: formState.applicationName,
+          repo_name: actionConfig.git_repo,
+          git_branch: branch,
+          build_context: folderPath,
+          builder: (buildConfig as any)?.builder,
+          buildpacks: (buildConfig as any)?.buildpacks.join(",") ?? "",
+          dockerfile: dockerfilePath,
+          image_repo_uri: imageUrl,
+        },
+        {
+          cluster_id: currentCluster.id,
+          project_id: currentProject.id,
+        }
+      );
+
+      // deploy dummy chart
+      await api.updatePorterStack(
+        "<token>",
+        {
+          stack_name: formState.applicationName,
+          porter_yaml: base64Encoded,
+          ...imageInfo,
+        },
+        {
+          cluster_id: currentCluster.id,
+          project_id: currentProject.id,
+        }
+      )
+
+      return true;
     } catch (err) {
       // TODO: better error handling
       console.log(err);
+      const errMessage = err?.response?.data?.error ?? err?.toString() ?? 'An error occurred while deploying your app. Please try again.'
+      setDeploymentError(errMessage);
+
+      return false;
+    } finally {
+      setDeploying(false);
     }
   };
 
@@ -468,9 +479,15 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
                   if (imageUrl) {
                     deployPorterApp();
                   } else {
+                    setDeploymentError(undefined)
                     setShowGHAModal(true);
                   }
                 }}
+                status={deploying ? "loading" : deploymentError ? (
+                  <Error message={deploymentError} />
+                ) : undefined}
+                loadingText={"Deploying..."}
+                width={"150px"}
               >
                 Deploy app
               </Button>,
@@ -490,6 +507,7 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
           projectId={currentProject.id}
           clusterId={currentCluster.id}
           deployPorterApp={deployPorterApp}
+          deploymentError={deploymentError}
         />
       )}
     </CenterWrapper>