Przeglądaj źródła

Add analytics on porter app update failures (#3716)

Feroze Mohideen 2 lat temu
rodzic
commit
a43d5887bf

+ 14 - 0
api/server/handlers/porter_app/analytics.go

@@ -99,6 +99,20 @@ func (v *PorterAppAnalyticsHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 		}))
 	}
 
+	if request.Step == "porter-app-update-failure" {
+		v.Config().AnalyticsClient.Track(analytics.PorterAppUpdateFailureTrack(&analytics.PorterAppUpdateOpts{
+			ProjectScopedTrackOpts: analytics.GetProjectScopedTrackOpts(user.ID, project.ID),
+			StackName:              request.StackName,
+			Email:                  user.Email,
+			FirstName:              user.FirstName,
+			LastName:               user.LastName,
+			CompanyName:            user.CompanyName,
+			ErrorMessage:           request.ErrorMessage,
+			ErrorStackTrace:        request.ErrorStackTrace,
+			ValidateApplyV2:        validateApplyV2,
+		}))
+	}
+
 	v.WriteResult(w, r, user.ToUserType())
 }
 

+ 1 - 0
api/types/stack.go

@@ -31,5 +31,6 @@ type PorterAppAnalyticsRequest struct {
 	Step               string `json:"step" form:"required,max=255"`
 	StackName          string `json:"stack_name"`
 	ErrorMessage       string `json:"error_message"`
+	ErrorStackTrace    string `json:"error_stack_trace"`
 	DeleteWorkflowFile bool   `json:"delete_workflow_file"`
 }

+ 5 - 1
dashboard/src/lib/hooks/useAppAnalytics.ts

@@ -7,7 +7,8 @@ type AppStep =
   | "stack-launch-complete"
   | "stack-launch-success"
   | "stack-launch-failure"
-  | "stack-deletion";
+  | "stack-deletion"
+  | "porter-app-update-failure";
 
 export const useAppAnalytics = () => {
   const { currentCluster, currentProject } = useContext(Context);
@@ -16,12 +17,14 @@ export const useAppAnalytics = () => {
     appName,
     step,
     errorMessage = "",
+    errorStackTrace = "",
     deleteWorkflow = false,
   }: {
     appName?: string;
     step: AppStep;
     errorMessage?: string;
     deleteWorkflow?: boolean;
+    errorStackTrace?: string;
   }) => {
     try {
       if (!currentCluster?.id || !currentProject?.id) {
@@ -33,6 +36,7 @@ export const useAppAnalytics = () => {
           step,
           stack_name: appName,
           error_message: errorMessage,
+          error_stack_trace: errorStackTrace,
           delete_workflow_file: deleteWorkflow,
         },
         {

+ 22 - 6
dashboard/src/main/home/app-dashboard/app-view/AppDataContainer.tsx

@@ -33,6 +33,7 @@ import { z } from "zod";
 import { PorterApp } from "@porter-dev/api-contracts";
 import JobsTab from "./tabs/JobsTab";
 import ConfirmRedeployModal from "./ConfirmRedeployModal";
+import { useAppAnalytics } from "lib/hooks/useAppAnalytics";
 
 // commented out tabs are not yet implemented
 // will be included as support is available based on data from app revisions rather than helm releases
@@ -61,6 +62,8 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
   const queryClient = useQueryClient();
   const [confirmDeployModalOpen, setConfirmDeployModalOpen] = useState(false);
 
+  const { updateAppStep } = useAppAnalytics();
+
   const {
     porterApp,
     latestProto,
@@ -271,7 +274,20 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
 
       // redirect to the default tab after save
       history.push(`/apps/${porterApp.name}/${DEFAULT_TAB}`);
-    } catch (err) {}
+    } catch (err) {
+      let message = "Unable to get error message";
+      let stack = "Unable to get error stack";
+      if (err instanceof Error) {
+        message = err.message;
+        stack = err.stack ?? "(No error stack)";
+      }
+      updateAppStep({
+        step: "porter-app-update-failure",
+        errorMessage: message,
+        appName: latestProto.name,
+        errorStackTrace: stack,
+      });
+    }
   });
 
   const cancelRedeploy = useCallback(() => {
@@ -381,11 +397,11 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
             { label: "Environment", value: "environment" },
             ...(latestProto.build
               ? [
-                  {
-                    label: "Build Settings",
-                    value: "build-settings",
-                  },
-                ]
+                {
+                  label: "Build Settings",
+                  value: "build-settings",
+                },
+              ]
               : []),
             { label: "Settings", value: "settings" },
           ]}

+ 36 - 48
dashboard/src/shared/api.tsx

@@ -352,9 +352,8 @@ const getFeedEvents = baseApi<
   }
 >("GET", (pathParams) => {
   let { project_id, cluster_id, stack_name, page } = pathParams;
-  return `/api/projects/${project_id}/clusters/${cluster_id}/applications/${stack_name}/events?page=${
-    page || 1
-  }`;
+  return `/api/projects/${project_id}/clusters/${cluster_id}/applications/${stack_name}/events?page=${page || 1
+    }`;
 });
 
 const createEnvironment = baseApi<
@@ -779,11 +778,9 @@ const detectBuildpack = baseApi<
     branch: string;
   }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.project_id}/gitrepos/${
-    pathParams.git_repo_id
-  }/repos/${pathParams.kind}/${pathParams.owner}/${
-    pathParams.name
-  }/${encodeURIComponent(pathParams.branch)}/buildpack/detect`;
+  return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id
+    }/repos/${pathParams.kind}/${pathParams.owner}/${pathParams.name
+    }/${encodeURIComponent(pathParams.branch)}/buildpack/detect`;
 });
 
 const detectGitlabBuildpack = baseApi<
@@ -814,11 +811,9 @@ const getBranchContents = baseApi<
     branch: string;
   }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.project_id}/gitrepos/${
-    pathParams.git_repo_id
-  }/repos/${pathParams.kind}/${pathParams.owner}/${
-    pathParams.name
-  }/${encodeURIComponent(pathParams.branch)}/contents`;
+  return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id
+    }/repos/${pathParams.kind}/${pathParams.owner}/${pathParams.name
+    }/${encodeURIComponent(pathParams.branch)}/contents`;
 });
 
 const getProcfileContents = baseApi<
@@ -834,11 +829,9 @@ const getProcfileContents = baseApi<
     branch: string;
   }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.project_id}/gitrepos/${
-    pathParams.git_repo_id
-  }/repos/${pathParams.kind}/${pathParams.owner}/${
-    pathParams.name
-  }/${encodeURIComponent(pathParams.branch)}/procfile`;
+  return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id
+    }/repos/${pathParams.kind}/${pathParams.owner}/${pathParams.name
+    }/${encodeURIComponent(pathParams.branch)}/procfile`;
 });
 
 const getPorterYamlContents = baseApi<
@@ -854,11 +847,9 @@ const getPorterYamlContents = baseApi<
     branch: string;
   }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.project_id}/gitrepos/${
-    pathParams.git_repo_id
-  }/repos/${pathParams.kind}/${pathParams.owner}/${
-    pathParams.name
-  }/${encodeURIComponent(pathParams.branch)}/porteryaml`;
+  return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id
+    }/repos/${pathParams.kind}/${pathParams.owner}/${pathParams.name
+    }/${encodeURIComponent(pathParams.branch)}/porteryaml`;
 });
 
 const parsePorterYaml = baseApi<
@@ -895,11 +886,9 @@ const getBranchHead = baseApi<
     branch: string;
   }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.project_id}/gitrepos/${
-    pathParams.git_repo_id
-  }/repos/${pathParams.kind}/${pathParams.owner}/${
-    pathParams.name
-  }/${encodeURIComponent(pathParams.branch)}/head`;
+  return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id
+    }/repos/${pathParams.kind}/${pathParams.owner}/${pathParams.name
+    }/${encodeURIComponent(pathParams.branch)}/head`;
 });
 
 const validatePorterApp = baseApi<
@@ -925,21 +914,21 @@ const validatePorterApp = baseApi<
 
 const createApp = baseApi<
   | {
-      name: string;
-      type: "github";
-      git_repo_id: number;
-      git_branch: string;
-      git_repo_name: string;
-      porter_yaml_path: string;
-    }
+    name: string;
+    type: "github";
+    git_repo_id: number;
+    git_branch: string;
+    git_repo_name: string;
+    porter_yaml_path: string;
+  }
   | {
-      name: string;
-      type: "docker-registry";
-      image: {
-        repository: string;
-        tag: string;
-      };
-    },
+    name: string;
+    type: "docker-registry";
+    image: {
+      repository: string;
+      tag: string;
+    };
+  },
   {
     project_id: number;
     cluster_id: number;
@@ -1941,11 +1930,9 @@ const getEnvGroup = baseApi<
     version?: number;
   }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.id}/clusters/${
-    pathParams.cluster_id
-  }/namespaces/${pathParams.namespace}/envgroup?name=${pathParams.name}${
-    pathParams.version ? "&version=" + pathParams.version : ""
-  }`;
+  return `/api/projects/${pathParams.id}/clusters/${pathParams.cluster_id
+    }/namespaces/${pathParams.namespace}/envgroup?name=${pathParams.name}${pathParams.version ? "&version=" + pathParams.version : ""
+    }`;
 });
 
 const getConfigMap = baseApi<
@@ -2819,6 +2806,7 @@ const updateStackStep = baseApi<
     stack_name?: string;
     error_message?: string;
     delete_workflow_file?: boolean;
+    error_stack_trace?: string;
   },
   {
     project_id: number;
@@ -3002,7 +2990,7 @@ const removeStackEnvGroup = baseApi<
     `/api/v1/projects/${project_id}/clusters/${cluster_id}/namespaces/${namespace}/stacks/${stack_id}/remove_env_group/${env_group_name}`
 );
 
-const getGithubStatus = baseApi<{}, {}>("GET", ({}) => `/api/status/github`);
+const getGithubStatus = baseApi<{}, {}>("GET", ({ }) => `/api/status/github`);
 
 const createSecretAndOpenGitHubPullRequest = baseApi<
   {

+ 2 - 0
internal/analytics/track_events.go

@@ -58,4 +58,6 @@ const (
 	StackBuildProgressing SegmentEvent = "Stack Build Progressing"
 	StackBuildFailure     SegmentEvent = "Stack Build Failure"
 	StackBuildSuccess     SegmentEvent = "Stack Build Success"
+
+	PorterAppUpdateFailure SegmentEvent = "Porter App Update Failure"
 )

+ 31 - 0
internal/analytics/tracks.go

@@ -974,3 +974,34 @@ func StackBuildProgressingTrack(opts *StackBuildOpts) segmentTrack {
 		getDefaultSegmentTrack(additionalProps, StackBuildProgressing),
 	)
 }
+
+// PorterAppUpdateOpts are the options for creating a track when a user updates a porter app
+type PorterAppUpdateOpts struct {
+	*ProjectScopedTrackOpts
+
+	StackName       string
+	Email           string
+	FirstName       string
+	LastName        string
+	CompanyName     string
+	ErrorMessage    string
+	ErrorStackTrace string
+	ValidateApplyV2 bool
+}
+
+// PorterAppUpdateFailureTrack returns a track for when a user attempts to update an app and receives an error
+func PorterAppUpdateFailureTrack(opts *PorterAppUpdateOpts) segmentTrack {
+	additionalProps := make(map[string]interface{})
+	additionalProps["stack_name"] = opts.StackName
+	additionalProps["email"] = opts.Email
+	additionalProps["name"] = opts.FirstName + " " + opts.LastName
+	additionalProps["company"] = opts.CompanyName
+	additionalProps["error_message"] = opts.ErrorMessage
+	additionalProps["error_stack_trace"] = opts.ErrorStackTrace
+	additionalProps["validate_apply_v2"] = opts.ValidateApplyV2
+
+	return getSegmentProjectTrack(
+		opts.ProjectScopedTrackOpts,
+		getDefaultSegmentTrack(additionalProps, PorterAppUpdateFailure),
+	)
+}