Przeglądaj źródła

Merge pull request #1933 from porter-dev/staging

Preview env improvements -> production
abelanger5 4 lat temu
rodzic
commit
4de956f48e

+ 17 - 6
api/server/handlers/environment/list_deployments_by_cluster.go

@@ -59,10 +59,15 @@ func (c *ListDeploymentsByClusterHandler) ServeHTTP(w http.ResponseWriter, r *ht
 
 
 			env, err := c.Repo().Environment().ReadEnvironmentByID(project.ID, cluster.ID, deployment.EnvironmentID)
 			env, err := c.Repo().Environment().ReadEnvironmentByID(project.ID, cluster.ID, deployment.EnvironmentID)
 
 
-			if err == nil {
-				updateDeploymentWithGithubWorkflowRunStatus(r.Context(), c.Config(), env, deployment)
+			if err != nil {
+				c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+				return
 			}
 			}
 
 
+			updateDeploymentWithGithubWorkflowRunStatus(r.Context(), c.Config(), env, deployment)
+
+			deployment.InstallationID = env.GitInstallationID
+
 			deployments = append(deployments, deployment)
 			deployments = append(deployments, deployment)
 		}
 		}
 
 
@@ -108,6 +113,8 @@ func (c *ListDeploymentsByClusterHandler) ServeHTTP(w http.ResponseWriter, r *ht
 
 
 			updateDeploymentWithGithubWorkflowRunStatus(r.Context(), c.Config(), env, deployment)
 			updateDeploymentWithGithubWorkflowRunStatus(r.Context(), c.Config(), env, deployment)
 
 
+			deployment.InstallationID = env.GitInstallationID
+
 			deployments = append(deployments, deployment)
 			deployments = append(deployments, deployment)
 		}
 		}
 
 
@@ -140,6 +147,10 @@ func updateDeploymentWithGithubWorkflowRunStatus(
 			ctx, deployment.RepoOwner, deployment.RepoName,
 			ctx, deployment.RepoOwner, deployment.RepoName,
 			fmt.Sprintf("porter_%s_env.yml", env.Name), &github.ListWorkflowRunsOptions{
 			fmt.Sprintf("porter_%s_env.yml", env.Name), &github.ListWorkflowRunsOptions{
 				Branch: deployment.PRBranchFrom,
 				Branch: deployment.PRBranchFrom,
+				ListOptions: github.ListOptions{
+					Page:    1,
+					PerPage: 1,
+				},
 			},
 			},
 		)
 		)
 
 
@@ -148,12 +159,12 @@ func updateDeploymentWithGithubWorkflowRunStatus(
 
 
 			deployment.LastWorkflowRunURL = latestWorkflowRun.GetHTMLURL()
 			deployment.LastWorkflowRunURL = latestWorkflowRun.GetHTMLURL()
 
 
-			if deployment.Status != types.DeploymentStatusCreating &&
-				(latestWorkflowRun.GetStatus() == "in_progress" ||
-					latestWorkflowRun.GetStatus() == "queued") {
+			if (latestWorkflowRun.GetStatus() == "in_progress" ||
+				latestWorkflowRun.GetStatus() == "queued") &&
+				deployment.Status != types.DeploymentStatusCreating {
 				deployment.Status = types.DeploymentStatusUpdating
 				deployment.Status = types.DeploymentStatusUpdating
 			} else if latestWorkflowRun.GetStatus() == "completed" {
 			} else if latestWorkflowRun.GetStatus() == "completed" {
-				if latestWorkflowRun.GetConclusion() == "failed" {
+				if latestWorkflowRun.GetConclusion() == "failure" {
 					deployment.Status = types.DeploymentStatusFailed
 					deployment.Status = types.DeploymentStatusFailed
 				} else if latestWorkflowRun.GetConclusion() == "timed_out" {
 				} else if latestWorkflowRun.GetConclusion() == "timed_out" {
 					deployment.Status = types.DeploymentStatusTimedOut
 					deployment.Status = types.DeploymentStatusTimedOut

+ 131 - 0
api/server/handlers/environment/trigger_deployment_workflow.go

@@ -0,0 +1,131 @@
+package environment
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"net/http"
+	"strconv"
+
+	"github.com/google/go-github/v41/github"
+	"github.com/porter-dev/porter/api/server/handlers"
+	"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/models"
+)
+
+var ErrNoWorkflowRuns = errors.New("no previous workflow runs found")
+
+type TriggerDeploymentWorkflowHandler struct {
+	handlers.PorterHandlerReadWriter
+}
+
+func NewTriggerDeploymentWorkflowHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *TriggerDeploymentWorkflowHandler {
+	return &TriggerDeploymentWorkflowHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+	}
+}
+
+func (c *TriggerDeploymentWorkflowHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
+	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+
+	deplID, reqErr := requestutils.GetURLParamUint(r, "deployment_id")
+
+	if reqErr != nil {
+		c.HandleAPIError(w, r, reqErr)
+		return
+	}
+
+	depl, err := c.Repo().Environment().ReadDeploymentByID(project.ID, cluster.ID, deplID)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	if depl.Status == types.DeploymentStatusInactive {
+		return
+	}
+
+	env, err := c.Repo().Environment().ReadEnvironmentByID(project.ID, cluster.ID, depl.EnvironmentID)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	client, err := getGithubClientFromEnvironment(c.Config(), env)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	latestWorkflowRun, err := getLatestWorkflowRun(client, env.GitRepoOwner, env.GitRepoName,
+		fmt.Sprintf("porter_%s_env.yml", env.Name))
+
+	if err != nil && errors.Is(err, ErrNoWorkflowRuns) {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, 400))
+		return
+	} else if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	if latestWorkflowRun.GetStatus() == "in_progress" || latestWorkflowRun.GetStatus() == "queued" {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("workflow already in progress"), 409))
+		return
+	}
+
+	ghResp, err := client.Actions.CreateWorkflowDispatchEventByFileName(
+		r.Context(), env.GitRepoOwner, env.GitRepoName, fmt.Sprintf("porter_%s_env.yml", env.Name),
+		github.CreateWorkflowDispatchEventRequest{
+			Ref: depl.PRBranchFrom,
+			Inputs: map[string]interface{}{
+				"pr_number":      strconv.FormatUint(uint64(depl.PullRequestID), 10),
+				"pr_title":       depl.PRName,
+				"pr_branch_from": depl.PRBranchFrom,
+				"pr_branch_into": depl.PRBranchInto,
+			},
+		},
+	)
+
+	if ghResp != nil && ghResp.StatusCode == 404 {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("workflow file not found"), 404))
+		return
+	}
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+}
+
+func getLatestWorkflowRun(client *github.Client, owner, repo, filename string) (*github.WorkflowRun, error) {
+	workflowRuns, _, err := client.Actions.ListWorkflowRunsByFileName(
+		context.Background(), owner, repo, filename, &github.ListWorkflowRunsOptions{
+			ListOptions: github.ListOptions{
+				Page:    1,
+				PerPage: 1,
+			},
+		},
+	)
+
+	if err != nil {
+		return nil, err
+	}
+
+	if workflowRuns.GetTotalCount() == 0 {
+		return nil, ErrNoWorkflowRuns
+	}
+
+	return workflowRuns.WorkflowRuns[0], nil
+}

+ 6 - 1
api/server/handlers/gitinstallation/rerun_workflow.go

@@ -85,7 +85,12 @@ func (c *RerunWorkflowHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 
 
 func getLatestWorkflowRun(client *github.Client, owner, repo, filename string) (*github.WorkflowRun, error) {
 func getLatestWorkflowRun(client *github.Client, owner, repo, filename string) (*github.WorkflowRun, error) {
 	workflowRuns, _, err := client.Actions.ListWorkflowRunsByFileName(
 	workflowRuns, _, err := client.Actions.ListWorkflowRunsByFileName(
-		context.Background(), owner, repo, filename, &github.ListWorkflowRunsOptions{},
+		context.Background(), owner, repo, filename, &github.ListWorkflowRunsOptions{
+			ListOptions: github.ListOptions{
+				Page:    1,
+				PerPage: 1,
+			},
+		},
 	)
 	)
 
 
 	if err != nil {
 	if err != nil {

+ 29 - 0
api/server/router/cluster.go

@@ -405,6 +405,35 @@ func getClusterRoutes(
 			Router:   r,
 			Router:   r,
 		})
 		})
 
 
+		// POST /api/projects/{project_id}/clusters/{cluster_id}/deployments/{deployment_id}/trigger_workflow -> environment.NewTriggerDeploymentWorkflowHandler
+		triggerDeploymentWorkflowEndpoint := factory.NewAPIEndpoint(
+			&types.APIRequestMetadata{
+				Verb:   types.APIVerbCreate,
+				Method: types.HTTPVerbPost,
+				Path: &types.Path{
+					Parent:       basePath,
+					RelativePath: relPath + "/deployments/{deployment_id}/trigger_workflow",
+				},
+				Scopes: []types.PermissionScope{
+					types.UserScope,
+					types.ProjectScope,
+					types.ClusterScope,
+				},
+			},
+		)
+
+		triggerDeploymentWorkflowHandler := environment.NewTriggerDeploymentWorkflowHandler(
+			config,
+			factory.GetDecoderValidator(),
+			factory.GetResultWriter(),
+		)
+
+		routes = append(routes, &Route{
+			Endpoint: triggerDeploymentWorkflowEndpoint,
+			Handler:  triggerDeploymentWorkflowHandler,
+			Router:   r,
+		})
+
 		// POST /api/projects/{project_id}/clusters/{cluster_id}/deployments/pull_request -> environment.NewEnablePullRequestHandler
 		// POST /api/projects/{project_id}/clusters/{cluster_id}/deployments/pull_request -> environment.NewEnablePullRequestHandler
 		enablePullRequestEndpoint := factory.NewAPIEndpoint(
 		enablePullRequestEndpoint := factory.NewAPIEndpoint(
 			&types.APIRequestMetadata{
 			&types.APIRequestMetadata{

+ 1 - 1
api/types/environment.go

@@ -48,12 +48,12 @@ type Deployment struct {
 	ID                 uint             `json:"id"`
 	ID                 uint             `json:"id"`
 	CreatedAt          time.Time        `json:"created_at"`
 	CreatedAt          time.Time        `json:"created_at"`
 	UpdatedAt          time.Time        `json:"updated_at"`
 	UpdatedAt          time.Time        `json:"updated_at"`
-	GitInstallationID  uint             `json:"git_installation_id"`
 	EnvironmentID      uint             `json:"environment_id"`
 	EnvironmentID      uint             `json:"environment_id"`
 	Namespace          string           `json:"namespace"`
 	Namespace          string           `json:"namespace"`
 	Status             DeploymentStatus `json:"status"`
 	Status             DeploymentStatus `json:"status"`
 	Subdomain          string           `json:"subdomain"`
 	Subdomain          string           `json:"subdomain"`
 	PullRequestID      uint             `json:"pull_request_id"`
 	PullRequestID      uint             `json:"pull_request_id"`
+	InstallationID     uint             `json:"gh_installation_id"`
 	LastWorkflowRunURL string           `json:"last_workflow_run_url"`
 	LastWorkflowRunURL string           `json:"last_workflow_run_url"`
 }
 }
 
 

+ 77 - 23
dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/DeploymentCard.tsx

@@ -1,8 +1,7 @@
 import React, { useState } from "react";
 import React, { useState } from "react";
 import styled, { keyframes } from "styled-components";
 import styled, { keyframes } from "styled-components";
-import { PRDeployment } from "../types";
+import { DeploymentStatus, PRDeployment } from "../types";
 import pr_icon from "assets/pull_request_icon.svg";
 import pr_icon from "assets/pull_request_icon.svg";
-import { useRouteMatch } from "react-router";
 import DynamicLink from "components/DynamicLink";
 import DynamicLink from "components/DynamicLink";
 import { capitalize, readableDate } from "shared/string_utils";
 import { capitalize, readableDate } from "shared/string_utils";
 import api from "shared/api";
 import api from "shared/api";
@@ -11,12 +10,14 @@ import { Context } from "shared/Context";
 import Loading from "components/Loading";
 import Loading from "components/Loading";
 import { ActionButton } from "../components/ActionButton";
 import { ActionButton } from "../components/ActionButton";
 import { EllipsisTextWrapper, RepoLink } from "../components/styled";
 import { EllipsisTextWrapper, RepoLink } from "../components/styled";
+import MaterialTooltip from "@material-ui/core/Tooltip";
 
 
 const DeploymentCard: React.FC<{
 const DeploymentCard: React.FC<{
   deployment: PRDeployment;
   deployment: PRDeployment;
   onDelete: () => void;
   onDelete: () => void;
   onReEnable: () => void;
   onReEnable: () => void;
-}> = ({ deployment, onDelete, onReEnable }) => {
+  onReRun: () => void;
+}> = ({ deployment, onDelete, onReEnable, onReRun }) => {
   const {
   const {
     setCurrentOverlay,
     setCurrentOverlay,
     currentProject,
     currentProject,
@@ -27,6 +28,8 @@ const DeploymentCard: React.FC<{
   const [isLoading, setIsLoading] = useState(false);
   const [isLoading, setIsLoading] = useState(false);
   const [hasErrorOnReEnabling, setHasErrorOnReEnabling] = useState(false);
   const [hasErrorOnReEnabling, setHasErrorOnReEnabling] = useState(false);
   const [showMergeInfoTooltip, setShowMergeInfoTooltip] = useState(false);
   const [showMergeInfoTooltip, setShowMergeInfoTooltip] = useState(false);
+  const [isReRunningWorkflow, setIsReRunningWorkflow] = useState(false);
+  const [hasErrorOnReRun, setHasErrorOnReRun] = useState(false);
 
 
   const deleteDeployment = () => {
   const deleteDeployment = () => {
     setIsDeleting(true);
     setIsDeleting(true);
@@ -51,11 +54,10 @@ const DeploymentCard: React.FC<{
       });
       });
   };
   };
 
 
-  const reEnablePreviewEnvironment = () => {
+  const reEnablePreviewEnvironment = async () => {
     setIsLoading(true);
     setIsLoading(true);
-
-    api
-      .reenablePreviewEnvironmentDeployment(
+    try {
+      await api.reenablePreviewEnvironmentDeployment(
         "<token>",
         "<token>",
         {},
         {},
         {
         {
@@ -63,19 +65,42 @@ const DeploymentCard: React.FC<{
           project_id: currentProject.id,
           project_id: currentProject.id,
           deployment_id: deployment.id,
           deployment_id: deployment.id,
         }
         }
-      )
-      .then(() => {
-        setIsLoading(false);
-        onReEnable();
-      })
-      .catch((err) => {
-        setHasErrorOnReEnabling(true);
-        setIsLoading(false);
-        setCurrentError(err?.response?.data?.error || err);
-        setTimeout(() => {
-          setHasErrorOnReEnabling(false);
-        }, 500);
-      });
+      );
+
+      setIsLoading(false);
+      onReEnable();
+    } catch (err) {
+      setHasErrorOnReEnabling(true);
+      setIsLoading(false);
+      setCurrentError(err?.response?.data?.error || err);
+      setTimeout(() => {
+        setHasErrorOnReEnabling(false);
+      }, 500);
+    }
+  };
+
+  const reRunWorkflow = async () => {
+    setIsReRunningWorkflow(true);
+    try {
+      await api.triggerPreviewEnvWorkflow(
+        "<token>",
+        {},
+        {
+          project_id: currentProject.id,
+          cluster_id: currentCluster.id,
+          deployment_id: deployment.id,
+        }
+      );
+      setIsReRunningWorkflow(false);
+      onReEnable();
+    } catch (error) {
+      setHasErrorOnReRun(true);
+      setIsReRunningWorkflow(false);
+      setCurrentError(error);
+      setTimeout(() => {
+        setHasErrorOnReRun(false);
+      }, 500);
+    }
   };
   };
 
 
   return (
   return (
@@ -111,6 +136,12 @@ const DeploymentCard: React.FC<{
             <i className="material-icons">open_in_new</i>
             <i className="material-icons">open_in_new</i>
             View PR
             View PR
           </RepoLink>
           </RepoLink>
+          {deployment.last_workflow_run_url ? (
+            <RepoLink to={deployment.last_workflow_run_url} target="_blank">
+              <i className="material-icons">open_in_new</i>
+              View last workflow
+            </RepoLink>
+          ) : null}
         </PRName>
         </PRName>
 
 
         <Flex>
         <Flex>
@@ -133,8 +164,23 @@ const DeploymentCard: React.FC<{
       <Flex>
       <Flex>
         {!isDeleting ? (
         {!isDeleting ? (
           <>
           <>
-            {deployment.status !== "creating" &&
-              deployment.status !== "inactive" && (
+            {deployment.status === DeploymentStatus.Failed ||
+            deployment.status === DeploymentStatus.TimedOut ? (
+              <>
+                <MaterialTooltip title="Re run last github workflow">
+                  <ReRunButton
+                    onClick={() => reRunWorkflow()}
+                    disabled={isReRunningWorkflow}
+                    hasError={hasErrorOnReRun}
+                  >
+                    <i className="material-icons-outlined">loop</i>
+                  </ReRunButton>
+                </MaterialTooltip>
+              </>
+            ) : null}
+
+            {deployment.status !== DeploymentStatus.Creating &&
+              deployment.status !== DeploymentStatus.Inactive && (
                 <>
                 <>
                   <RowButton
                   <RowButton
                     to={`/preview-environments/details/${deployment.namespace}?environment_id=${deployment.environment_id}`}
                     to={`/preview-environments/details/${deployment.namespace}?environment_id=${deployment.environment_id}`}
@@ -153,7 +199,7 @@ const DeploymentCard: React.FC<{
                   </RowButton>
                   </RowButton>
                 </>
                 </>
               )}
               )}
-            {deployment.status === "inactive" ? (
+            {deployment.status === DeploymentStatus.Inactive ? (
               <ActionButton
               <ActionButton
                 onClick={reEnablePreviewEnvironment}
                 onClick={reEnablePreviewEnvironment}
                 disabled={isLoading}
                 disabled={isLoading}
@@ -198,6 +244,14 @@ const DeploymentCard: React.FC<{
 
 
 export default DeploymentCard;
 export default DeploymentCard;
 
 
+const ReRunButton = styled(ActionButton)`
+  min-width: unset;
+
+  > i {
+    margin-right: unset;
+  }
+`;
+
 const SepDot = styled.div`
 const SepDot = styled.div`
   color: #aaaabb66;
   color: #aaaabb66;
 `;
 `;

+ 1 - 0
dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/DeploymentList.tsx

@@ -219,6 +219,7 @@ const DeploymentList = () => {
               deployment={d}
               deployment={d}
               onDelete={handleRefresh}
               onDelete={handleRefresh}
               onReEnable={handleRefresh}
               onReEnable={handleRefresh}
+              onReRun={handleRefresh}
             />
             />
           );
           );
         })}
         })}

+ 19 - 7
dashboard/src/main/home/cluster-dashboard/preview-environments/mocks.ts

@@ -1,3 +1,5 @@
+import { PRDeployment } from "./types";
+
 export const environments = [
 export const environments = [
   {
   {
     id: 29,
     id: 29,
@@ -102,54 +104,64 @@ export const environments = [
   },
   },
 ];
 ];
 
 
-export const deployments = [
+export const deployments: PRDeployment[] = [
   {
   {
     gh_deployment_id: 534980099,
     gh_deployment_id: 534980099,
-    gh_pr_name: "Update porter.yaml",
+    gh_pr_name: "Update porter.yaml to enable preview environments on porter",
+    gh_pr_branch_from: "some-branch-name",
+    gh_pr_branch_into: "master",
     gh_repo_name: "preview",
     gh_repo_name: "preview",
     gh_repo_owner: "porter-dev",
     gh_repo_owner: "porter-dev",
     gh_commit_sha: "74a1191",
     gh_commit_sha: "74a1191",
     id: 43,
     id: 43,
     created_at: "2022-03-28T19:28:11.012729Z",
     created_at: "2022-03-28T19:28:11.012729Z",
     updated_at: "2022-03-28T19:31:53.871666Z",
     updated_at: "2022-03-28T19:31:53.871666Z",
-    git_installation_id: 0,
+    gh_installation_id: 0,
     environment_id: 43,
     environment_id: 43,
     namespace: "pr-3-preview",
     namespace: "pr-3-preview",
     status: "failed",
     status: "failed",
     subdomain: "",
     subdomain: "",
     pull_request_id: 3,
     pull_request_id: 3,
+    last_workflow_run_url: "https://something.com",
   },
   },
   {
   {
     gh_deployment_id: 532608734,
     gh_deployment_id: 532608734,
     gh_pr_name: "Testing pr preview",
     gh_pr_name: "Testing pr preview",
+    gh_pr_branch_from: "some-branch-name",
+    gh_pr_branch_into: "master",
     gh_repo_name: "porter-docs",
     gh_repo_name: "porter-docs",
     gh_repo_owner: "jnfrati",
     gh_repo_owner: "jnfrati",
     gh_commit_sha: "6a4b67e",
     gh_commit_sha: "6a4b67e",
     id: 41,
     id: 41,
     created_at: "2022-03-24T20:24:17.103471Z",
     created_at: "2022-03-24T20:24:17.103471Z",
     updated_at: "2022-03-24T20:45:06.684096Z",
     updated_at: "2022-03-24T20:45:06.684096Z",
-    git_installation_id: 0,
+    gh_installation_id: 0,
     environment_id: 37,
     environment_id: 37,
     namespace: "pr-1-porter-docs",
     namespace: "pr-1-porter-docs",
     status: "inactive",
     status: "inactive",
-    subdomain: "https://docs-web-7b93751b98e68139.staging-onporter.run",
+    subdomain: "",
     pull_request_id: 1,
     pull_request_id: 1,
+    last_workflow_run_url: "",
   },
   },
   {
   {
     gh_deployment_id: 514002155,
     gh_deployment_id: 514002155,
-    gh_pr_name: "Testing PR with job run",
+    gh_pr_name:
+      "Testing PR with job run and a really long name to explain what's going on over this pull request",
+    gh_pr_branch_from: "some-branch-name",
+    gh_pr_branch_into: "master",
     gh_repo_name: "porter-docs",
     gh_repo_name: "porter-docs",
     gh_repo_owner: "porter-dev",
     gh_repo_owner: "porter-dev",
     gh_commit_sha: "443d930",
     gh_commit_sha: "443d930",
     id: 32,
     id: 32,
     created_at: "2022-01-30T11:04:14.496147Z",
     created_at: "2022-01-30T11:04:14.496147Z",
     updated_at: "2022-02-24T22:02:27.17928Z",
     updated_at: "2022-02-24T22:02:27.17928Z",
-    git_installation_id: 0,
+    gh_installation_id: 0,
     environment_id: 29,
     environment_id: 29,
     namespace: "pr-20-porter-docs",
     namespace: "pr-20-porter-docs",
     status: "created",
     status: "created",
     subdomain: "https://docs-web-78a048205ac7869b.staging-onporter.run",
     subdomain: "https://docs-web-78a048205ac7869b.staging-onporter.run",
     pull_request_id: 20,
     pull_request_id: 20,
+    last_workflow_run_url: "https://something.com",
   },
   },
 ];
 ];
 
 

+ 16 - 3
dashboard/src/main/home/cluster-dashboard/preview-environments/types.ts

@@ -1,19 +1,32 @@
+export enum DeploymentStatus {
+  Failed = "failed",
+  Created = "created",
+  Creating = "creating",
+  Inactive = "inactive",
+  TimedOut = "timed_out",
+  Updating = "updating",
+}
+
+export type DeploymentStatusUnion = `${DeploymentStatus}`;
+
 export type PRDeployment = {
 export type PRDeployment = {
   id: number;
   id: number;
   created_at: string;
   created_at: string;
   updated_at: string;
   updated_at: string;
   subdomain: string;
   subdomain: string;
-  status: "creating" | "failed" | "created" | "inactive";
+  status: DeploymentStatusUnion;
   environment_id: number;
   environment_id: number;
   pull_request_id: number;
   pull_request_id: number;
   namespace: string;
   namespace: string;
+  last_workflow_run_url: string;
+  gh_installation_id: number;
+  gh_deployment_id: number;
   gh_pr_name: string;
   gh_pr_name: string;
   gh_repo_owner: string;
   gh_repo_owner: string;
   gh_repo_name: string;
   gh_repo_name: string;
   gh_commit_sha: string;
   gh_commit_sha: string;
   gh_pr_branch_from?: string;
   gh_pr_branch_from?: string;
   gh_pr_branch_into?: string;
   gh_pr_branch_into?: string;
-  last_workflow_run_url: string;
 };
 };
 
 
 export type Environment = {
 export type Environment = {
@@ -24,7 +37,7 @@ export type Environment = {
   name: string;
   name: string;
   git_repo_owner: string;
   git_repo_owner: string;
   git_repo_name: string;
   git_repo_name: string;
-  last_deployment_status: "failed" | "created" | "inactive" | "disabled";
+  last_deployment_status: DeploymentStatusUnion;
   deployment_count: number;
   deployment_count: number;
   mode: "manual" | "auto";
   mode: "manual" | "auto";
 };
 };

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

@@ -1699,6 +1699,31 @@ const upgradePorterAgent = baseApi<
     `/api/projects/${project_id}/clusters/${cluster_id}/agent/upgrade`
     `/api/projects/${project_id}/clusters/${cluster_id}/agent/upgrade`
 );
 );
 
 
+const reRunGHWorkflow = baseApi<
+  {},
+  {
+    project_id: number;
+    cluster_id: number;
+    git_installation_id: number;
+    owner: string;
+    name: string;
+    filename: string;
+  }
+>(
+  "POST",
+  ({ project_id, git_installation_id, owner, name, cluster_id, filename }) =>
+    `/api/projects/${project_id}/gitrepos/${git_installation_id}/${owner}/${name}/clusters/${cluster_id}/rerun_workflow?filename=${filename}`
+);
+
+const triggerPreviewEnvWorkflow = baseApi<
+  {},
+  { project_id: number; cluster_id: number; deployment_id: number }
+>(
+  "POST",
+  ({ project_id, cluster_id, deployment_id }) =>
+    `/api/projects/${project_id}/clusters/${cluster_id}/deployments/${deployment_id}/trigger_workflow`
+);
+
 // Bundle export to allow default api import (api.<method> is more readable)
 // Bundle export to allow default api import (api.<method> is more readable)
 export default {
 export default {
   checkAuth,
   checkAuth,
@@ -1860,4 +1885,6 @@ export default {
   getIncidentLogsByLogId,
   getIncidentLogsByLogId,
   upgradePorterAgent,
   upgradePorterAgent,
   deletePRDeployment,
   deletePRDeployment,
+  reRunGHWorkflow,
+  triggerPreviewEnvWorkflow,
 };
 };

+ 0 - 39
internal/models/environment.go

@@ -79,42 +79,3 @@ func (d *Deployment) ToDeploymentType() *types.Deployment {
 		GitHubMetadata: ghMetadata,
 		GitHubMetadata: ghMetadata,
 	}
 	}
 }
 }
-
-type DeploymentWithEnvironment struct {
-	gorm.Model
-
-	Environment    *Environment
-	Namespace      string
-	Status         types.DeploymentStatus
-	Subdomain      string
-	PullRequestID  uint
-	GHDeploymentID int64
-	PRName         string
-	RepoName       string
-	RepoOwner      string
-	CommitSHA      string
-}
-
-func (d *DeploymentWithEnvironment) ToDeploymentType() *types.Deployment {
-
-	ghMetadata := &types.GitHubMetadata{
-		DeploymentID: d.GHDeploymentID,
-		PRName:       d.PRName,
-		RepoName:     d.RepoName,
-		RepoOwner:    d.RepoOwner,
-		CommitSHA:    d.CommitSHA,
-	}
-
-	return &types.Deployment{
-		CreatedAt:         d.CreatedAt,
-		UpdatedAt:         d.UpdatedAt,
-		ID:                d.Model.ID,
-		EnvironmentID:     d.Environment.ID,
-		GitInstallationID: d.Environment.GitInstallationID,
-		Namespace:         d.Namespace,
-		Status:            d.Status,
-		Subdomain:         d.Subdomain,
-		PullRequestID:     d.PullRequestID,
-		GitHubMetadata:    ghMetadata,
-	}
-}