Przeglądaj źródła

by golly it works

Feroze Mohideen 3 lat temu
rodzic
commit
1e560ec701

+ 7 - 1
api/server/handlers/stacks/create_secret_and_open_pr.go

@@ -12,6 +12,7 @@ import (
 	"github.com/porter-dev/porter/api/server/shared"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
 	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/server/shared/requestutils"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/auth/token"
 	"github.com/porter-dev/porter/internal/integrations/ci/actions"
@@ -36,6 +37,11 @@ func (c *OpenStackPRHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	user, _ := r.Context().Value(types.UserScope).(*models.User)
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+	stackName, reqErr := requestutils.GetURLParamString(r, types.URLParamStackName)
+	if reqErr != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(reqErr, http.StatusBadRequest))
+		return
+	}
 
 	request := &types.CreateSecretAndOpenGHPRRequest{}
 	if ok := c.DecodeAndValidate(w, r, request); !ok {
@@ -80,7 +86,7 @@ func (c *OpenStackPRHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 			Client:        client,
 			GitRepoOwner:  request.GithubRepoOwner,
 			GitRepoName:   request.GithubRepoName,
-			StackName:     request.StackName,
+			StackName:     stackName,
 			ProjectID:     project.ID,
 			ClusterID:     cluster.ID,
 			ServerURL:     c.Config().ServerConf.ServerURL,

+ 3 - 1
api/server/router/stack.go

@@ -1,6 +1,8 @@
 package router
 
 import (
+	"fmt"
+
 	"github.com/go-chi/chi"
 	"github.com/porter-dev/porter/api/server/handlers/stacks"
 	"github.com/porter-dev/porter/api/server/shared"
@@ -147,7 +149,7 @@ func getStackRoutes(
 			Method: types.HTTPVerbPost,
 			Path: &types.Path{
 				Parent:       basePath,
-				RelativePath: relPath + "/{stack}/pr",
+				RelativePath: fmt.Sprintf("%s/{%s}/pr", relPath, types.URLParamStackName),
 			},
 			Scopes: []types.PermissionScope{
 				types.UserScope,

+ 1 - 0
api/types/request.go

@@ -48,6 +48,7 @@ const (
 	URLParamWildcard              URLParam = "*"
 	URLParamIntegrationID         URLParam = "integration_id"
 	URLParamAPIContractRevisionID URLParam = "contract_revision_id"
+	URLParamStackName             URLParam = "stack_name"
 )
 
 type Path struct {

+ 0 - 1
api/types/stack.go

@@ -16,7 +16,6 @@ type Dependency struct {
 }
 
 type CreateSecretAndOpenGHPRRequest struct {
-	StackName               string `json:"stack_name" form:"required,dns1123"`
 	GithubAppInstallationID int64  `json:"github_app_installation_id" form:"required"`
 	GithubRepoOwner         string `json:"github_repo_owner" form:"required"`
 	GithubRepoName          string `json:"github_repo_name" form:"required"`

+ 56 - 5
dashboard/src/main/home/app-dashboard/new-app-flow/GithubActionModal.tsx

@@ -1,5 +1,5 @@
 import Modal from "components/porter/Modal";
-import React from "react";
+import React, { useContext } from "react";
 import Text from "components/porter/Text";
 import Spacer from "components/porter/Spacer";
 import ExpandableSection from "components/porter/ExpandableSection";
@@ -8,14 +8,63 @@ import styled from "styled-components";
 import Button from "components/porter/Button";
 import Input from "components/porter/Input";
 import Select from "components/porter/Select";
+import api from "shared/api";
+import { Context } from "shared/Context";
 
 interface GithubActionModalProps {
     closeModal: () => void;
+    githubAppInstallationID?: number;
+    githubRepoOwner?: string;
+    githubRepoName?: string;
+    branch?: string;
+    stackName?: string;
 }
 
+type Choice = "open_pr" | "copy";
+
 const GithubActionModal: React.FC<GithubActionModalProps> = ({
     closeModal,
+    githubAppInstallationID,
+    githubRepoOwner,
+    githubRepoName,
+    branch,
+    stackName
 }) => {
+    const [choice, setChoice] = React.useState<Choice>("open_pr");
+    const [loading, setLoading] = React.useState<boolean>(false);
+    const { currentProject, currentCluster } = useContext(Context);
+
+    const submit = async () => {
+        if (githubAppInstallationID && githubRepoOwner && githubRepoName && branch && stackName) {
+            try {
+                setLoading(true)
+                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: currentProject.id,
+                        cluster_id: currentCluster.id,
+                        stack_name: stackName,
+                    }
+                );
+                if (res?.data?.url) {
+                    window.open(res.data.url, "_blank", "noreferrer")
+                }
+            } catch (error) {
+                console.log(error)
+            } finally {
+                setLoading(false)
+            }
+        } else {
+            console.log("missing information")
+        }
+    }
     return (
         <Modal closeModal={closeModal}>
             <Text size={16}>
@@ -58,15 +107,17 @@ const GithubActionModal: React.FC<GithubActionModalProps> = ({
             <Spacer y={1} />
             <Select
                 options={[
-                    { label: "I authorize Porter to open a PR on my behalf", value: "I authorize Porter to open a PR on my behalf" },
-                    { label: "I will copy the file into my repository myself", value: "I will copy the file into my repository myself" },
+                    { label: "I authorize Porter to open a PR on my behalf", value: "open_pr" },
+                    { label: "I will copy the file into my repository myself", value: "copy" },
                 ]}
-                onChange={(x: any) => console.log(x)}
+                onChange={(x: Choice) => setChoice(x)}
                 width="100%"
             />
             <Button
-                onClick={closeModal}
+                onClick={submit}
                 width={"100%"}
+                status={loading ? "loading" : undefined}
+                loadingText="Opening PR..."
             >
                 Complete
             </Button>

+ 23 - 8
dashboard/src/main/home/app-dashboard/new-app-flow/NewAppFlow.tsx

@@ -29,11 +29,11 @@ import EnvGroupArray, {
 } from "main/home/cluster-dashboard/env-groups/EnvGroupArray";
 import Select from "components/porter/Select";
 import GithubActionModal from "./GithubActionModal";
-import { ActionConfigType, FullActionConfigType } from "shared/types";
+import { ActionConfigType, FullActionConfigType, FullGithubActionConfigType, GithubActionConfigType } from "shared/types";
 
 type Props = RouteComponentProps & {};
 
-const defaultActionConfig: ActionConfigType = {
+const defaultActionConfig: GithubActionConfigType = {
   git_repo: "",
   image_repo_uri: "",
   git_branch: "",
@@ -46,6 +46,7 @@ interface FormState {
   selectedSourceType: SourceType | undefined;
   serviceList: any[];
   envVariables: KeyValueType[];
+  releaseCommand: string;
 }
 
 const INITIAL_STATE: FormState = {
@@ -53,6 +54,7 @@ const INITIAL_STATE: FormState = {
   selectedSourceType: undefined,
   serviceList: [],
   envVariables: [],
+  releaseCommand: "",
 };
 
 const Validators: {
@@ -62,6 +64,7 @@ const Validators: {
   selectedSourceType: (value: SourceType | undefined) => value !== undefined,
   serviceList: (value: any[]) => value.length > 0,
   envVariables: (value: KeyValueType[]) => true,
+  releaseCommand: (value: string) => true,
 };
 
 const NewAppFlow: React.FC<Props> = ({ ...props }) => {
@@ -73,7 +76,7 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
   const [isLoading, setIsLoading] = useState<boolean>(true);
   const [currentStep, setCurrentStep] = useState<number>(0);
   const [formState, setFormState] = useState<FormState>(INITIAL_STATE);
-  const [actionConfig, setActionConfig] = useState<ActionConfigType>({
+  const [actionConfig, setActionConfig] = useState<GithubActionConfigType>({
     ...defaultActionConfig,
   });
   const [procfileProcess, setProcfileProcess] = useState("");
@@ -85,7 +88,7 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
   const [selectedRegistry, setSelectedRegistry] = useState(null);
   const [shouldCreateWorkflow, setShouldCreateWorkflow] = useState(true);
   const [buildConfig, setBuildConfig] = useState();
-  const getFullActionConfig = (): FullActionConfigType => {
+  const getFullActionConfig = (): FullGithubActionConfigType => {
     let imageRepoURI = `${selectedRegistry?.url}/${templateName}`;
     return {
       kind: "github",
@@ -122,7 +125,7 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
                 <Text color="helper">
                   Lowercase letters, numbers, and "-" only.
                 </Text>
-                <Spacer y={0.5}></Spacer> 
+                <Spacer y={0.5}></Spacer>
                 <Input
                   placeholder="ex: academic-sophon"
                   value={formState.applicationName}
@@ -221,9 +224,14 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
                 <Spacer y={0.5} />
                 <Input
                   placeholder="yarn ./scripts/run-migrations.js"
-                  value={""}
+                  value={formState.releaseCommand}
                   width="300px"
-                  setValue={(e) => {}}
+                  setValue={(e) => {
+                    setFormState({ ...formState, releaseCommand: e });
+                    if (Validators.releaseCommand(e)) {
+                      setCurrentStep(Math.max(currentStep, 6));
+                    }
+                  }}
                 />
               </>,
               <Button onClick={() => setShowGHAModal(true)}>Deploy app</Button>
@@ -233,7 +241,14 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
         </StyledConfigureTemplate>
       </Div>
       {showGHAModal && (
-        <GithubActionModal closeModal={() => setShowGHAModal(false)} />
+        <GithubActionModal
+          closeModal={() => setShowGHAModal(false)}
+          githubAppInstallationID={actionConfig.git_repo_id}
+          githubRepoOwner={actionConfig.git_repo.split("/")[0]}
+          githubRepoName={actionConfig.git_repo.split("/")[1]}
+          branch={branch}
+          stackName={formState.applicationName}
+        />
       )}
     </CenterWrapper>
   );

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

@@ -2423,6 +2423,25 @@ const removeStackEnvGroup = baseApi<
 
 const getGithubStatus = baseApi<{}, {}>("GET", ({ }) => `/api/status/github`);
 
+const createSecretAndOpenGitHubPullRequest = baseApi<
+  {
+    github_app_installation_id: number;
+    github_repo_owner: string;
+    github_repo_name: string;
+    open_pr: boolean;
+    branch: string;
+  },
+  {
+    project_id: number;
+    cluster_id: number;
+    stack_name: string;
+  }
+>(
+  "POST",
+  ({ project_id, cluster_id, stack_name }) =>
+    `/api/projects/${project_id}/clusters/${cluster_id}/stacks/${stack_name}/pr`
+);
+
 // Bundle export to allow default api import (api.<method> is more readable)
 export default {
   checkAuth,
@@ -2627,6 +2646,7 @@ export default {
   createContract,
   getContracts,
   deleteContract,
+  createSecretAndOpenGitHubPullRequest,
   // TRACKING
   updateOnboardingStep,
   // STACKS

+ 13 - 6
dashboard/src/shared/types.tsx

@@ -243,15 +243,15 @@ export interface FormElement {
 export type RepoType = {
   FullName: string;
 } & (
-  | {
+    | {
       Kind: "github";
       GHRepoID: number;
     }
-  | {
+    | {
       Kind: "gitlab";
       GitIntegrationId: number;
     }
-);
+  );
 
 export interface FileType {
   path: string;
@@ -309,15 +309,15 @@ export type ActionConfigType = {
   image_repo_uri: string;
   dockerfile_path?: string;
 } & (
-  | {
+    | {
       kind: "gitlab";
       gitlab_integration_id: number;
     }
-  | {
+    | {
       kind: "github";
       git_repo_id: number;
     }
-);
+  );
 
 export type GithubActionConfigType = ActionConfigType & {
   kind: "github";
@@ -330,6 +330,13 @@ export type FullActionConfigType = ActionConfigType & {
   should_create_workflow: boolean;
 };
 
+export type FullGithubActionConfigType = GithubActionConfigType & {
+  dockerfile_path: string;
+  folder_path: string;
+  registry_id: number;
+  should_create_workflow: boolean;
+};
+
 export interface CapabilityType {
   github: boolean;
   provisioner: boolean;