Justin Rhee пре 3 година
родитељ
комит
60fc8336f5

+ 6 - 5
api/server/handlers/stacks/create_porter_app.go

@@ -58,11 +58,12 @@ func (c *CreatePorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 		GitRepoID: request.GitRepoID,
 		GitBranch: request.GitBranch,
 
-		BuildContext: request.BuildContext,
-		Builder:      request.Builder,
-		Buildpacks:   request.Buildpacks,
-		Dockerfile:   request.Dockerfile,
-		ImageRepoURI: request.ImageRepoURI,
+		BuildContext:   request.BuildContext,
+		Builder:        request.Builder,
+		Buildpacks:     request.Buildpacks,
+		Dockerfile:     request.Dockerfile,
+		ImageRepoURI:   request.ImageRepoURI,
+		PullRequestURL: request.PullRequestURL,
 	}
 
 	porterApp, err := c.Repo().PorterApp().UpdatePorterApp(app)

+ 25 - 22
api/types/porter_app.go

@@ -15,35 +15,38 @@ type PorterApp struct {
 	GitBranch string `json:"git_branch,omitempty"`
 
 	// Build settings (optional)
-	BuildContext string `json:"build_context,omitempty"`
-	Builder      string `json:"builder,omitempty"`
-	Buildpacks   string `json:"build_packs,omitempty"`
-	Dockerfile   string `json:"dockerfile,omitempty"`
+	BuildContext   string `json:"build_context,omitempty"`
+	Builder        string `json:"builder,omitempty"`
+	Buildpacks     string `json:"build_packs,omitempty"`
+	Dockerfile     string `json:"dockerfile,omitempty"`
+	PullRequestURL string `json:"pull_request_url,omitempty"`
 }
 
 // swagger:model
 type CreatePorterAppRequest struct {
-	Name         string `json:"name" form:"required"`
-	ClusterID    uint   `json:"cluster_id"`
-	ProjectID    uint   `json:"project_id"`
-	RepoName     string `json:"repo_name"`
-	GitBranch    string `json:"git_branch"`
-	GitRepoID    uint   `json:"git_repo_id"`
-	BuildContext string `json:"build_context"`
-	Builder      string `json:"builder"`
-	Buildpacks   string `json:"buildpacks"`
-	Dockerfile   string `json:"dockerfile"`
-	ImageRepoURI string `json:"image_repo_uri"`
+	Name           string `json:"name" form:"required"`
+	ClusterID      uint   `json:"cluster_id"`
+	ProjectID      uint   `json:"project_id"`
+	RepoName       string `json:"repo_name"`
+	GitBranch      string `json:"git_branch"`
+	GitRepoID      uint   `json:"git_repo_id"`
+	BuildContext   string `json:"build_context"`
+	Builder        string `json:"builder"`
+	Buildpacks     string `json:"buildpacks"`
+	Dockerfile     string `json:"dockerfile"`
+	ImageRepoURI   string `json:"image_repo_uri"`
+	PullRequestURL string `json:"pull_request_url"`
 }
 
 type UpdatePorterAppRequest struct {
-	RepoName     string `json:"repo_name"`
-	GitBranch    string `json:"git_branch"`
-	BuildContext string `json:"build_context"`
-	Builder      string `json:"builder"`
-	Buildpacks   string `json:"buildpacks"`
-	Dockerfile   string `json:"dockerfile"`
-	ImageRepoURI string `json:"image_repo_uri"`
+	RepoName       string `json:"repo_name"`
+	GitBranch      string `json:"git_branch"`
+	BuildContext   string `json:"build_context"`
+	Builder        string `json:"builder"`
+	Buildpacks     string `json:"buildpacks"`
+	Dockerfile     string `json:"dockerfile"`
+	ImageRepoURI   string `json:"image_repo_uri"`
+	PullRequestURL string `json:"pull_request_url"`
 }
 
 type ListPorterAppResponse []*PorterApp

+ 3 - 2
dashboard/src/components/Banner.tsx

@@ -40,15 +40,16 @@ const StyledBanner = styled.div<{
   color?: string;
   noMargin?: boolean;
 }>`
-  height: 40px;
+  min-height: 40px;
   width: 100%;
   margin: ${(props) => (props.noMargin ? "5px 0 10px" : "")};
   font-size: 13px;
   font-family: "Work Sans", sans-serif;
   display: flex;
+  line-height: 1.5;
   border: 1px solid ${(props) => props.color || "#ffffff00"};
   border-radius: 8px;
-  padding: 14px;
+  padding: 10px 14px;
   color: ${(props) => props.color || "#ffffff"};
   align-items: center;
   background: #ffffff11;

+ 6 - 1
dashboard/src/components/repo-selector/BuildpackStack.tsx

@@ -53,6 +53,7 @@ export const BuildpackStack: React.FC<{
   hide: boolean;
   onChange: (config: BuildConfig) => void;
   currentBuildConfig?: BuildConfig;
+  setBuildConfig?: (config: BuildConfig) => void;
 }> = ({
   actionConfig,
   folderPath,
@@ -60,6 +61,7 @@ export const BuildpackStack: React.FC<{
   hide,
   onChange,
   currentBuildConfig,
+  setBuildConfig,
 }) => {
   const { currentProject } = useContext(Context);
 
@@ -76,7 +78,6 @@ export const BuildpackStack: React.FC<{
     []
   );
   const renderModalContent = () => {
-    console.log(selectedBuildpacks);
     return (
       <>
         <Text size={16}>Buildpack Configuration</Text>
@@ -118,6 +119,10 @@ export const BuildpackStack: React.FC<{
 
     if (typeof onChange === "function") {
       onChange(buildConfig);
+
+      if (currentBuildConfig) {
+        setBuildConfig(buildConfig);
+      }
     }
   }, [selectedStack, selectedBuildpacks]);
 

+ 3 - 1
dashboard/src/components/repo-selector/DetectContentsList.tsx

@@ -224,7 +224,9 @@ const DetectContentsList: React.FC<PropsType> = (props) => {
   };
   return (
     <>
-      {renderContentList() && props.dockerfilePath != "" ? (
+      {renderContentList() &&
+      props.dockerfilePath != "" &&
+      props.dockerfilePath != null ? (
         <AdvancedBuildSettings
           dockerfilePath={props.dockerfilePath}
           setDockerfilePath={props.setDockerfilePath}

+ 46 - 0
dashboard/src/main/home/app-dashboard/expanded-app/AppEvents.tsx

@@ -0,0 +1,46 @@
+import Loading from "components/Loading";
+import Fieldset from "components/porter/Fieldset";
+import Link from "components/porter/Link";
+import Spacer from "components/porter/Spacer";
+import Text from "components/porter/Text";
+import React, { useEffect, useState } from "react";
+import styled from "styled-components";
+
+type Props = {
+  repoName: string; 
+  branchName: string;
+};
+
+const AppEvents: React.FC<Props> = ({
+  repoName,
+  branchName,
+}) => {
+  const [isExpanded, setIsExpanded] = useState(false);
+
+  useEffect(() => {
+    // Do something
+  }, []);
+
+  return (
+    <StyledAppEvents>
+      <Fieldset>
+        <Text size={16}>
+          Approval required for Porter GitHub Action
+        </Text>
+        <Spacer y={0.5} />
+        <Text color="helper">
+          We've opened a PR to add the Porter GitHub Action to the {branchName} branch of {repoName}.
+        </Text>
+        <Spacer y={0.5} />
+        <Text color="helper">
+          <Link to="/">Merge Porter PR</Link>
+        </Text>
+      </Fieldset>
+    </StyledAppEvents>
+  );
+};
+
+export default AppEvents;
+
+const StyledAppEvents = styled.div`
+`;

+ 189 - 27
dashboard/src/main/home/app-dashboard/expanded-app/BuildSettingsTabStack.tsx

@@ -1,5 +1,13 @@
 import AnimateHeight from "react-animate-height";
-import React, { Component, Dispatch, useMemo, useRef, useState } from "react";
+import React, {
+  Component,
+  Dispatch,
+  useContext,
+  useEffect,
+  useMemo,
+  useRef,
+  useState,
+} from "react";
 import Text from "components/porter/Text";
 import Spacer from "components/porter/Spacer";
 import Input from "components/porter/Input";
@@ -24,12 +32,21 @@ import Loading from "components/Loading";
 import { BuildpackSelection } from "components/repo-selector/BuildpackSelection";
 import BuildpackConfigSection from "main/home/cluster-dashboard/expanded-chart/build-settings/_BuildpackConfigSection";
 import { BuildpackStack } from "components/repo-selector/BuildpackStack";
+import MultiSaveButton from "components/MultiSaveButton";
+import api from "shared/api";
+import { AxiosError } from "axios";
 type Props = {
   appData: any;
   setAppData: Dispatch<any>;
+  onTabSwitch: () => void;
 };
 
-const BuildSettingsTabStack: React.FC<Props> = ({ appData, setAppData }) => {
+const BuildSettingsTabStack: React.FC<Props> = ({
+  appData,
+  setAppData,
+  onTabSwitch,
+}) => {
+  const { setCurrentError } = useContext(Context);
   const [updated, setUpdated] = useState(null);
   const [branch, setBranch] = useState(appData.app.git_branch);
   const [showSettings, setShowSettings] = useState(false);
@@ -52,28 +69,161 @@ const BuildSettingsTabStack: React.FC<Props> = ({ appData, setAppData }) => {
   const [buildConfig, setBuildConfig] = useState<BuildConfig>({
     ...defaultBuildConfig,
   });
+  const [runningWorkflowURL, setRunningWorkflowURL] = useState("");
+
   const [actionConfig, setActionConfig] = useState<ActionConfigType>({
     ...defaultActionConfig,
   });
+  const [buttonStatus, setButtonStatus] = useState<
+    "loading" | "successful" | string
+  >("");
 
   const [imageUrl, setImageUrl] = useState(appData.chart.image_uri);
 
-  const buildpackConfigRef = useRef<{
-    isLoading: boolean;
-    getBuildConfig: () => BuildConfig;
-  }>(null);
+  const clearButtonStatus = (time: number = 800) => {
+    setTimeout(() => {
+      setButtonStatus("");
+    }, time);
+  };
+  const triggerWorkflow = async () => {
+    try {
+      await api.reRunGHWorkflow(
+        "",
+        {},
+        {
+          project_id: appData.app.project_id,
+          cluster_id: appData.app.cluster_id,
+          git_installation_id: appData.app.git_repo_id,
+          owner: appData.app.repo_name?.split("/")[0],
+          name: appData.app.repo_name?.split("/")[1],
+          branch: branch,
+          release_name: "stack_" + appData.chart.name,
+        }
+      );
+    } catch (error) {
+      if (!error?.response) {
+        throw error;
+      }
+
+      let tmpError: AxiosError = error;
+
+      /**
+       * @smell
+       * Currently the expanded chart is clearing all the state when a chart update is triggered (saveEnvVariables).
+       * Temporary usage of setCurrentError until a context is applied to keep the state of the ReRunError during re renders.
+       */
+
+      if (tmpError.response.status === 400) {
+        // setReRunError({
+        //   title: "No previous run found",
+        //   description:
+        //     "There are no previous runs for this workflow, please trigger manually a run before changing the build settings.",
+        // });
+        setCurrentError(
+          "There are no previous runs for this workflow. Please manually trigger a run before changing build settings."
+        );
+        return;
+      }
+
+      if (tmpError.response.status === 409) {
+        // setReRunError({
+        //   title: "The workflow is still running",
+        //   description:
+        //     'If you want to make more changes, please choose the option "Save" until the workflow finishes.',
+        // });
 
-  const currentActionConfig = useMemo(() => {
-    console.log(appData.chart.config);
+        if (typeof tmpError.response.data === "string") {
+          setRunningWorkflowURL(tmpError.response.data);
+        }
+        setCurrentError(
+          'The workflow is still running. You can "Save" the current build settings for the next workflow run and view the current status of the workflow here: ' +
+            tmpError.response.data
+        );
+        return;
+      }
+
+      if (tmpError.response.status === 404) {
+        let description = "No action file matching this deployment was found.";
+        if (typeof tmpError.response.data === "string") {
+          const filename = tmpError.response.data;
+          description = description.concat(
+            `Please check that the file "${filename}" exists in your repository.`
+          );
+        }
+        // setReRunError({
+        //   title: "The action doesn't seem to exist",
+        //   description,
+        // });
+
+        setCurrentError(description);
+        return;
+      }
+      throw error;
+    }
+  };
+  const saveConfig = async () => {
     console.log(appData);
-    const actionConf = appData.chart.config;
+    try {
+      await api.updatePorterApp(
+        "<token>",
+        {
+          repo_name: appData.app.repo_name,
+          git_branch: branch,
+          build_context: appData.app.build_context,
+          builder: buildConfig.builder,
+          buildpacks: buildConfig.buildpacks?.join(","),
+          dockerfile: appData.app.dockerfile,
+          image_repo_uri: appData.chart.image_repo_uri,
+        },
+        {
+          project_id: appData.app.project_id,
+          cluster_id: appData.app.cluster_id,
+          name: appData.app.name,
+        }
+      );
+      onTabSwitch();
+    } catch (err) {
+      throw err;
+    }
+  };
+  const handleSave = async () => {
+    setButtonStatus("loading");
+
+    try {
+      console.log(buildConfig.builder);
 
-    return {
-      kind: "github",
-      ...actionConf,
-    } as FullActionConfigType;
-  }, [appData.chart]);
+      await saveConfig();
+      setAppData(appData);
 
+      onTabSwitch();
+      setButtonStatus("successful");
+    } catch (error) {
+      setButtonStatus("Something went wrong");
+      console.log(error);
+    } finally {
+      clearButtonStatus();
+    }
+  };
+  const handleSaveAndReDeploy = async () => {
+    setButtonStatus("loading");
+
+    try {
+      console.log(buildConfig.builder);
+
+      await saveConfig();
+      setAppData(appData);
+
+      await triggerWorkflow();
+
+      onTabSwitch();
+      setButtonStatus("successful");
+    } catch (error) {
+      setButtonStatus("Something went wrong");
+      console.log(error);
+    } finally {
+      clearButtonStatus();
+    }
+  };
   return (
     <>
       <Text size={16}>Build settings</Text>
@@ -145,10 +295,35 @@ const BuildSettingsTabStack: React.FC<Props> = ({ appData, setAppData }) => {
               }}
               hide={!showSettings}
               currentBuildConfig={buildConfig}
+              setBuildConfig={setBuildConfig}
             />
           )}
         </StyledSourceBox>
       </AnimateHeight>
+
+      <MultiSaveButton
+        options={[
+          {
+            text: "Save",
+            onClick: handleSave,
+            description:
+              "Save the build settings to be used in the next workflow run",
+          },
+          {
+            text: "Save and Redeploy",
+            onClick: handleSaveAndReDeploy,
+            description:
+              "Immediately trigger a workflow run with updated build settings",
+          },
+        ]}
+        disabled={false}
+        makeFlush={true}
+        clearPosition={true}
+        statusPosition="left"
+        expandTo="left"
+        saveText=""
+        status={buttonStatus}
+      ></MultiSaveButton>
     </>
   );
 };
@@ -162,19 +337,6 @@ const DarkMatter = styled.div<{ antiHeight?: string }>`
   margin-top: ${(props) => props.antiHeight || "-15px"};
 `;
 
-const Subtitle = styled.div`
-  padding: 11px 0px 16px;
-  font-family: "Work Sans", sans-serif;
-  font-size: 13px;
-  color: #aaaabb;
-  line-height: 1.6em;
-`;
-
-const Required = styled.div`
-  margin-left: 8px;
-  color: #fc4976;
-  display: inline-block;
-`;
 const AdvancedBuildTitle = styled.div`
   display: flex;
   align-items: center;

+ 43 - 22
dashboard/src/main/home/app-dashboard/expanded-app/ExpandedApp.tsx

@@ -29,6 +29,8 @@ import Services from "../new-app-flow/Services";
 import { Service } from "../new-app-flow/serviceTypes";
 import ConfirmOverlay from "components/porter/ConfirmOverlay";
 import Fieldset from "components/porter/Fieldset";
+import Banner from "components/Banner";
+import AppEvents from "./AppEvents";
 
 type Props = RouteComponentProps & {};
 
@@ -310,7 +312,11 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
         }
       case "build-settings":
         return (
-          <BuildSettingsTabStack appData={appData} setAppData={setAppData} />
+          <BuildSettingsTabStack
+            appData={appData}
+            setAppData={setAppData}
+            onTabSwitch={getPorterApp}
+          />
         );
       case "settings":
         return (
@@ -331,6 +337,13 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
             </Button>
           </>
         );
+      case "events":
+        return (
+          <AppEvents
+            repoName={appData.app.repo_name}
+            branchName={appData.app.git_branch}
+          />
+        );
       default:
         return <div>dream on</div>;
     }
@@ -410,30 +423,38 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
             </Fieldset>
           ) : (
             <>
-              <DarkMatter />
-              <RevisionSection
-                showRevisions={showRevisions}
-                toggleShowRevisions={() => {
-                  setShowRevisions(!showRevisions);
-                }}
-                chart={appData.chart}
-                refreshChart={() => getChartData(appData.chart)}
-                setRevision={setRevision}
-                forceRefreshRevisions={forceRefreshRevisions}
-                refreshRevisionsOff={() => setForceRefreshRevisions(false)}
-                shouldUpdate={
-                  appData.chart.latest_version &&
-                  appData.chart.latest_version !==
-                  appData.chart.chart.metadata.version
-                }
-                latestVersion={appData.chart.latest_version}
-                upgradeVersion={appUpgradeVersion}
-              />
-              <DarkMatter antiHeight="-18px" />
+              {true ? (
+                <Banner type="warning">
+                  Your application won't be available until you approve and merge this PR in your GitHub repository.
+                </Banner>
+              ) : (
+                <>
+                  <DarkMatter />
+                  <RevisionSection
+                    showRevisions={showRevisions}
+                    toggleShowRevisions={() => {
+                      setShowRevisions(!showRevisions);
+                    }}
+                    chart={appData.chart}
+                    refreshChart={() => getChartData(appData.chart)}
+                    setRevision={setRevision}
+                    forceRefreshRevisions={forceRefreshRevisions}
+                    refreshRevisionsOff={() => setForceRefreshRevisions(false)}
+                    shouldUpdate={
+                      appData.chart.latest_version &&
+                      appData.chart.latest_version !==
+                      appData.chart.chart.metadata.version
+                    }
+                    latestVersion={appData.chart.latest_version}
+                    upgradeVersion={appUpgradeVersion}
+                  />
+                  <DarkMatter antiHeight="-18px" />
+                </>
+              )}
               <Spacer y={1} />
               <TabSelector
                 options={
-                  appData.app.build_packs
+                  appData.app.git_repo_id
                     ? [
                       { label: "Events", value: "events" },
                       { label: "Logs", value: "logs" },

+ 0 - 1
dashboard/src/main/home/app-dashboard/new-app-flow/AdvancedBuildSettings.tsx

@@ -66,7 +66,6 @@ const AdvancedBuildSettings: React.FC<AdvancedBuildSettingsProps> = (props) => {
         <BuildpackStack
           actionConfig={props.actionConfig}
           branch={props.branch}
-          defaultBuildConfig={props.buildConfig}
           folderPath={props.folderPath}
           onChange={(config) => {
             props.setBuildConfig(config);

+ 18 - 16
internal/models/porter_app.go

@@ -22,26 +22,28 @@ type PorterApp struct {
 	RepoName  string
 	GitBranch string
 
-	BuildContext string
-	Builder      string
-	Buildpacks   string
-	Dockerfile   string
+	BuildContext   string
+	Builder        string
+	Buildpacks     string
+	Dockerfile     string
+	PullRequestURL string
 }
 
 // ToPorterAppType generates an external types.PorterApp to be shared over REST
 func (a *PorterApp) ToPorterAppType() *types.PorterApp {
 	return &types.PorterApp{
-		ID:           a.ID,
-		ProjectID:    a.ProjectID,
-		ClusterID:    a.ClusterID,
-		Name:         a.Name,
-		ImageRepoURI: a.ImageRepoURI,
-		GitRepoID:    a.GitRepoID,
-		RepoName:     a.RepoName,
-		GitBranch:    a.GitBranch,
-		BuildContext: a.BuildContext,
-		Builder:      a.Builder,
-		Buildpacks:   a.Buildpacks,
-		Dockerfile:   a.Dockerfile,
+		ID:             a.ID,
+		ProjectID:      a.ProjectID,
+		ClusterID:      a.ClusterID,
+		Name:           a.Name,
+		ImageRepoURI:   a.ImageRepoURI,
+		GitRepoID:      a.GitRepoID,
+		RepoName:       a.RepoName,
+		GitBranch:      a.GitBranch,
+		BuildContext:   a.BuildContext,
+		Builder:        a.Builder,
+		Buildpacks:     a.Buildpacks,
+		Dockerfile:     a.Dockerfile,
+		PullRequestURL: a.PullRequestURL,
 	}
 }