Browse Source

add analytics for stack launch failure and deletion (#3223)

Feroze Mohideen 2 years ago
parent
commit
c3730ed9d2

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

@@ -67,5 +67,28 @@ func (v *PorterAppAnalyticsHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 		}))
 	}
 
+	if request.Step == "stack-launch-failure" {
+		v.Config().AnalyticsClient.Track(analytics.StackLaunchFailureTrack(&analytics.StackLaunchFailureOpts{
+			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,
+		}))
+	}
+
+	if request.Step == "stack-deletion" {
+		v.Config().AnalyticsClient.Track(analytics.StackDeletionTrack(&analytics.StackDeletionOpts{
+			ProjectScopedTrackOpts: analytics.GetProjectScopedTrackOpts(user.ID, project.ID),
+			StackName:              request.StackName,
+			Email:                  user.Email,
+			FirstName:              user.FirstName,
+			LastName:               user.LastName,
+			CompanyName:            user.CompanyName,
+		}))
+	}
+
 	v.WriteResult(w, r, user.ToUserType())
 }

+ 3 - 2
api/types/stack.go

@@ -27,6 +27,7 @@ type CreateSecretAndOpenGHPRResponse struct {
 type GetStackResponse PorterApp
 
 type PorterAppAnalyticsRequest struct {
-	Step      string `json:"step" form:"required,max=255"`
-	StackName string `json:"stack_name"`
+	Step         string `json:"step" form:"required,max=255"`
+	StackName    string `json:"stack_name"`
+	ErrorMessage string `json:"error_message"`
 }

+ 12 - 0
dashboard/src/main/home/app-dashboard/expanded-app/ExpandedApp.tsx

@@ -308,6 +308,18 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
           namespace: `porter-stack-${appName}`,
         }
       );
+      // intentionally do not await this promise
+      api.updateStackStep(
+        "<token>",
+        {
+          step: "stack-deletion",
+          stack_name: appName,
+        },
+        {
+          project_id: currentProject.id,
+          cluster_id: currentCluster.id,
+        }
+      );
       props.history.push("/apps");
     } catch (err) {
       setError(err);

+ 3 - 2
dashboard/src/main/home/app-dashboard/new-app-flow/NewAppFlow.tsx

@@ -113,7 +113,7 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
     );
   };
 
-  const updateStackStep = async (step: string) => {
+  const updateStackStep = async (step: string, errorMessage: string = "") => {
     try {
       if (currentCluster?.id == null || currentProject?.id == null) {
         throw "Unable to capture analytics, project or cluster not found";
@@ -123,6 +123,7 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
         {
           step,
           stack_name: porterApp.name,
+          error_message: errorMessage,
         },
         {
           cluster_id: currentCluster.id,
@@ -331,7 +332,7 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
         err?.toString() ??
         "An error occurred while deploying your app. Please try again.";
       setDeploymentError(errMessage);
-
+      updateStackStep("stack-launch-failure", errMessage);
       return false;
     } finally {
       setDeploying(false);

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

@@ -2440,6 +2440,7 @@ const updateStackStep = baseApi<
   {
     step: string;
     stack_name?: string;
+    error_message?: string;
   },
   {
     project_id: number;

+ 2 - 0
internal/analytics/track_events.go

@@ -45,4 +45,6 @@ const (
 	StackLaunchStart    SegmentEvent = "Stack Launch Started"
 	StackLaunchComplete SegmentEvent = "Stack Launch Complete"
 	StackLaunchSuccess  SegmentEvent = "Stack Launch Success"
+	StackLaunchFailure  SegmentEvent = "Stack Launch Failure"
+	StackDeletion       SegmentEvent = "Stack Deletion"
 )

+ 54 - 2
internal/analytics/tracks.go

@@ -135,7 +135,7 @@ func ProjectCreateTrack(opts *ProjectCreateTrackOpts) segmentTrack {
 // CostConsentOpenedTrackOpts are the options for creating a track when a user opens the cost consent
 type CostConsentOpenedTrackOpts struct {
 	*UserScopedTrackOpts
-	Provider string
+	Provider    string
 	Email       string
 	FirstName   string
 	LastName    string
@@ -159,7 +159,7 @@ func CostConsentOpenedTrack(opts *CostConsentOpenedTrackOpts) segmentTrack {
 // CostConsentCompletedTrackOpts are the options for creating a track when a user completes the cost consent
 type CostConsentCompletedTrackOpts struct {
 	*UserScopedTrackOpts
-	Provider string
+	Provider    string
 	Email       string
 	FirstName   string
 	LastName    string
@@ -673,3 +673,55 @@ func StackLaunchSuccessTrack(opts *StackLaunchSuccessOpts) segmentTrack {
 		getDefaultSegmentTrack(additionalProps, StackLaunchSuccess),
 	)
 }
+
+// StackLaunchFailureOpts are the options for creating a track when a user fails in creating a stack
+type StackLaunchFailureOpts struct {
+	*ProjectScopedTrackOpts
+
+	StackName    string
+	Email        string
+	FirstName    string
+	LastName     string
+	CompanyName  string
+	ErrorMessage string
+}
+
+// StackLaunchFailureTrack returns a track for when a user fails creating a stack
+func StackLaunchFailureTrack(opts *StackLaunchFailureOpts) 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
+
+	return getSegmentProjectTrack(
+		opts.ProjectScopedTrackOpts,
+		getDefaultSegmentTrack(additionalProps, StackLaunchFailure),
+	)
+}
+
+// StackDeletionOpts are the options for creating a track when a user deletes a stack
+type StackDeletionOpts struct {
+	*ProjectScopedTrackOpts
+
+	StackName   string
+	Email       string
+	FirstName   string
+	LastName    string
+	CompanyName string
+}
+
+// StackDeletionTrack returns a track for when a user deletes a stack
+func StackDeletionTrack(opts *StackDeletionOpts) 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
+
+	return getSegmentProjectTrack(
+		opts.ProjectScopedTrackOpts,
+		getDefaultSegmentTrack(additionalProps, StackDeletion),
+	)
+}