From 720aaf7ae35726c22c07a950a189380bc4c60375 Mon Sep 17 00:00:00 2001 From: MarcMcIntosh Date: Tue, 17 May 2022 15:37:33 +0200 Subject: [PATCH 1/4] tracking: send event when user saves a custom type --- .../CustomTypeBuilder/Layout/Header.tsx | 14 ++- packages/slice-machine/src/tracker.ts | 9 ++ .../slice-machine/tests/pages/cts.spec.tsx | 92 +++++++++++++++++++ 3 files changed, 111 insertions(+), 4 deletions(-) diff --git a/packages/slice-machine/lib/builders/CustomTypeBuilder/Layout/Header.tsx b/packages/slice-machine/lib/builders/CustomTypeBuilder/Layout/Header.tsx index 9ec1a08071..6edc9b7954 100644 --- a/packages/slice-machine/lib/builders/CustomTypeBuilder/Layout/Header.tsx +++ b/packages/slice-machine/lib/builders/CustomTypeBuilder/Layout/Header.tsx @@ -2,18 +2,19 @@ import React from "react"; import { Box, Button, Spinner, Text } from "theme-ui"; import Header from "../../../../components/Header"; -import useSliceMachineActions from "@src/modules/useSliceMachineActions"; +import useSliceMachineActions from "../../../../src/modules/useSliceMachineActions"; import { MdSpaceDashboard } from "react-icons/md"; import { useSelector } from "react-redux"; -import { SliceMachineStoreType } from "@src/redux/type"; +import { SliceMachineStoreType } from "../../../../src/redux/type"; import { selectCurrentCustomType, selectCustomTypeStatus, selectIsCurrentCustomTypeHasPendingModifications, -} from "@src/modules/selectedCustomType"; +} from "../../../../src/modules/selectedCustomType"; import { isLoading } from "@src/modules/loading"; import { LoadingKeysEnum } from "@src/modules/loading/types"; -import { CustomTypeStatus } from "@src/modules/selectedCustomType/types"; +import { CustomTypeStatus } from "../../../../src/modules/selectedCustomType/types"; +import Tracker from "../../../../src/tracker"; const CustomTypeHeader = () => { const { @@ -39,6 +40,11 @@ const CustomTypeHeader = () => { return { onClick: () => { saveCustomType(); + void Tracker.get().trackCustomTypeSaved({ + id: currentCustomType.id, + name: currentCustomType.label, + type: currentCustomType.repeatable ? "repeatable" : "single", + }); }, children: ( diff --git a/packages/slice-machine/src/tracker.ts b/packages/slice-machine/src/tracker.ts index 80f64b89fc..6e58e909a3 100644 --- a/packages/slice-machine/src/tracker.ts +++ b/packages/slice-machine/src/tracker.ts @@ -17,6 +17,7 @@ enum EventType { CreateCustomType = "SliceMachine Custom Type Created", CustomTypeFieldAdded = "SliceMachine Custom Type Field Added", CustomTypeSliceZoneUpdated = "SliceMachine Slicezone Updated", + CustomTypeSaved = "SliceMachine Custom Type Saved", } export enum ContinueOnboardingType { @@ -240,6 +241,14 @@ export class SMTracker { }): Promise { return this.#trackEvent(EventType.CustomTypeSliceZoneUpdated, data); } + + async trackCustomTypeSaved(data: { + id: string; + name: string; + type: "single" | "repeatable"; + }): Promise { + return this.#trackEvent(EventType.CustomTypeSaved, data); + } } const Tracker = (() => { diff --git a/packages/slice-machine/tests/pages/cts.spec.tsx b/packages/slice-machine/tests/pages/cts.spec.tsx index 3b60b3c044..701f5e6b82 100644 --- a/packages/slice-machine/tests/pages/cts.spec.tsx +++ b/packages/slice-machine/tests/pages/cts.spec.tsx @@ -323,4 +323,96 @@ describe("Custom Type Builder", () => { { context: { groupId: { Repository: "repoName" } } } ); }); + + test("it should sendd a tracking event when the user saves a custoom-type", async () => { + const customTypeId = "a-page"; + + singletonRouter.push({ + pathname: "cts/[ct]", + query: { ct: customTypeId }, + }); + + const App = render(, { + preloadedState: { + environment: { + framework: "next", + mockConfig: { _cts: { [customTypeId]: {} } }, + }, + availableCustomTypes: { + [customTypeId]: { + local: { + id: customTypeId, + label: customTypeId, + repeatable: true, + status: true, + tabs: [ + { + key: "Main", + value: [], + }, + ], + }, + }, + }, + selectedCustomType: { + model: { + id: "a-page", + label: "a-page", + repeatable: true, + status: true, + tabs: [ + { + key: "Main", + value: [], + }, + ], + }, + initialModel: { + id: "a-page", + label: "a-page", + repeatable: true, + status: true, + tabs: [ + { + key: "Main", + value: [], + }, + ], + }, + mockConfig: {}, + initialMockConfig: {}, + }, + }, + }); + + const addButton = screen.getByTestId("empty-zone-add-new-field"); + fireEvent.click(addButton); + + const uid = screen.getByText("UID"); + fireEvent.click(uid); + + const saveFieldButton = screen.getByText("Add"); + + await act(async () => { + fireEvent.click(saveFieldButton); + }); + + expect(fakeTracker).toHaveBeenCalledWith( + "SliceMachine Custom Type Field Added", + { id: "uid", name: customTypeId, type: "UID", zone: "static" }, + { context: { groupId: { Repository: "repoName" } } } + ); + + const saveCustomType = screen.getByText("Save to File System"); + + await act(async () => { + fireEvent.click(saveCustomType); + }); + + expect(fakeTracker).toHaveBeenLastCalledWith( + "SliceMachine Custom Type Saved", + { type: "repeatable", id: customTypeId, name: customTypeId }, + { context: { groupId: { Repository: "repoName" } } } + ); + }); }); From b2c16d5dffaa49200a2de9480a88a1dd07c93280 Mon Sep 17 00:00:00 2001 From: MarcMcIntosh Date: Tue, 17 May 2022 16:38:24 +0200 Subject: [PATCH 2/4] refactor(tracking): send tracking event through the saga --- .../CustomTypeBuilder/Layout/Header.tsx | 6 ---- .../src/modules/selectedCustomType/sagas.ts | 28 +++++++++---------- .../src/modules/useSliceMachineActions.ts | 25 ++++++++--------- .../slice-machine/tests/pages/cts.spec.tsx | 13 +++++++++ 4 files changed, 38 insertions(+), 34 deletions(-) diff --git a/packages/slice-machine/lib/builders/CustomTypeBuilder/Layout/Header.tsx b/packages/slice-machine/lib/builders/CustomTypeBuilder/Layout/Header.tsx index 6edc9b7954..256d8965b9 100644 --- a/packages/slice-machine/lib/builders/CustomTypeBuilder/Layout/Header.tsx +++ b/packages/slice-machine/lib/builders/CustomTypeBuilder/Layout/Header.tsx @@ -14,7 +14,6 @@ import { import { isLoading } from "@src/modules/loading"; import { LoadingKeysEnum } from "@src/modules/loading/types"; import { CustomTypeStatus } from "../../../../src/modules/selectedCustomType/types"; -import Tracker from "../../../../src/tracker"; const CustomTypeHeader = () => { const { @@ -40,11 +39,6 @@ const CustomTypeHeader = () => { return { onClick: () => { saveCustomType(); - void Tracker.get().trackCustomTypeSaved({ - id: currentCustomType.id, - name: currentCustomType.label, - type: currentCustomType.repeatable ? "repeatable" : "single", - }); }, children: ( diff --git a/packages/slice-machine/src/modules/selectedCustomType/sagas.ts b/packages/slice-machine/src/modules/selectedCustomType/sagas.ts index f01330b3c3..8112f20d95 100644 --- a/packages/slice-machine/src/modules/selectedCustomType/sagas.ts +++ b/packages/slice-machine/src/modules/selectedCustomType/sagas.ts @@ -1,20 +1,15 @@ import { call, fork, put, select, takeLatest } from "redux-saga/effects"; -import { openToasterCreator, ToasterType } from "@src/modules/toaster"; +import { openToasterCreator, ToasterType } from "../toaster"; import { getType } from "typesafe-actions"; -import { withLoader } from "@src/modules/loading"; -import { LoadingKeysEnum } from "@src/modules/loading/types"; -import { - pushCustomTypeCreator, - saveCustomTypeCreator, -} from "@src/modules/selectedCustomType/actions"; -import { - selectCurrentCustomType, - selectCurrentMockConfig, -} from "@src/modules/selectedCustomType/index"; -import { pushCustomType, saveCustomType } from "@src/apiClient"; +import { withLoader } from "../loading"; +import { LoadingKeysEnum } from "../loading/types"; +import { pushCustomTypeCreator, saveCustomTypeCreator } from "./actions"; +import { selectCurrentCustomType, selectCurrentMockConfig } from "./index"; +import { pushCustomType, saveCustomType } from "../../../src/apiClient"; import axios from "axios"; -import { modalOpenCreator } from "@src/modules/modal"; -import { ModalKeysEnum } from "@src/modules/modal/types"; +import { modalOpenCreator } from "../modal"; +import { ModalKeysEnum } from "../modal/types"; +import Tracker from "../../../src/tracker"; export function* saveCustomTypeSaga() { try { @@ -30,6 +25,11 @@ export function* saveCustomTypeSaga() { } yield call(saveCustomType, currentCustomType, currentMockConfig); + void Tracker.get().trackCustomTypeSaved({ + id: currentCustomType.id, + name: currentCustomType.label || currentCustomType.id, + type: currentCustomType.repeatable ? "repeatable" : "single", + }); yield put(saveCustomTypeCreator.success()); yield put( openToasterCreator({ diff --git a/packages/slice-machine/src/modules/useSliceMachineActions.ts b/packages/slice-machine/src/modules/useSliceMachineActions.ts index 7db699bf79..b6621de29d 100644 --- a/packages/slice-machine/src/modules/useSliceMachineActions.ts +++ b/packages/slice-machine/src/modules/useSliceMachineActions.ts @@ -1,31 +1,28 @@ import { useDispatch } from "react-redux"; -import { LoadingKeysEnum } from "@src/modules/loading/types"; -import { ModalKeysEnum } from "@src/modules/modal/types"; -import { modalCloseCreator, modalOpenCreator } from "@src/modules/modal"; -import { - startLoadingActionCreator, - stopLoadingActionCreator, -} from "@src/modules/loading"; +import { LoadingKeysEnum } from "./loading/types"; +import { ModalKeysEnum } from "./modal/types"; +import { modalCloseCreator, modalOpenCreator } from "./modal"; +import { startLoadingActionCreator, stopLoadingActionCreator } from "./loading"; import { finishOnboardingCreator, sendAReviewCreator, skipReviewCreator, updatesViewedCreator, hasSeenTutorialsTooTipCreator, -} from "@src/modules/userContext"; -import { refreshStateCreator } from "@src/modules/environment"; +} from "./userContext"; +import { refreshStateCreator } from "./environment"; import { openSetupDrawerCreator, closeSetupDrawerCreator, toggleSetupDrawerStepCreator, checkSimulatorSetupCreator, connectToSimulatorIframeCreator, -} from "@src/modules/simulator"; +} from "./simulator"; import ServerState from "@models/server/ServerState"; -import { createCustomTypeCreator } from "@src/modules/availableCustomTypes"; -import { createSliceCreator } from "@src/modules/slices"; +import { createCustomTypeCreator } from "./availableCustomTypes"; +import { createSliceCreator } from "./slices"; import { UserContextStoreType } from "./userContext/types"; -import { openToasterCreator, ToasterType } from "@src/modules/toaster"; +import { openToasterCreator, ToasterType } from "./toaster"; import { initCustomTypeStoreCreator, createTabCreator, @@ -48,7 +45,7 @@ import { deleteGroupFieldMockConfigCreator, deleteFieldMockConfigCreator, updateFieldMockConfigCreator, -} from "@src/modules/selectedCustomType"; +} from "./selectedCustomType"; import { CustomTypeMockConfig } from "@models/common/MockConfig"; import { CustomTypeSM, diff --git a/packages/slice-machine/tests/pages/cts.spec.tsx b/packages/slice-machine/tests/pages/cts.spec.tsx index 701f5e6b82..b616a4c7bb 100644 --- a/packages/slice-machine/tests/pages/cts.spec.tsx +++ b/packages/slice-machine/tests/pages/cts.spec.tsx @@ -11,6 +11,7 @@ import { beforeEach, expect, beforeAll, + afterAll, } from "@jest/globals"; import React from "react"; import CreateCustomTypeBuilder from "../../pages/cts/[ct]"; @@ -20,9 +21,21 @@ import mockRouter from "next-router-mock"; import { AnalyticsBrowser } from "@segment/analytics-next"; import Tracker from "../../src/tracker"; import LibrariesProvider from "../../src/models/libraries/context"; +import { setupServer } from "msw/node"; +import { rest } from "msw"; jest.mock("next/dist/client/router", () => require("next-router-mock")); +const server = setupServer( + rest.post("/api/custom-types/save", (_, res, ctx) => { + return res(ctx.json({})); + }) +); + +beforeAll(() => server.listen()); +afterEach(() => server.resetHandlers()); +afterAll(() => server.close()); + describe("Custom Type Builder", () => { const fakeTracker = jest.fn().mockImplementation(() => Promise.resolve()); From db2b73b7c2ba2c2337f9530b87d083347a5213a2 Mon Sep 17 00:00:00 2001 From: MarcMcIntosh Date: Tue, 17 May 2022 17:27:26 +0200 Subject: [PATCH 3/4] tracking(save custom type): fix flacky test --- packages/slice-machine/tests/pages/cts.spec.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/slice-machine/tests/pages/cts.spec.tsx b/packages/slice-machine/tests/pages/cts.spec.tsx index b616a4c7bb..4c082b1602 100644 --- a/packages/slice-machine/tests/pages/cts.spec.tsx +++ b/packages/slice-machine/tests/pages/cts.spec.tsx @@ -16,7 +16,7 @@ import { import React from "react"; import CreateCustomTypeBuilder from "../../pages/cts/[ct]"; import singletonRouter from "next/router"; -import { render, fireEvent, act, screen } from "../test-utils"; +import { render, fireEvent, act, screen, waitFor } from "../test-utils"; import mockRouter from "next-router-mock"; import { AnalyticsBrowser } from "@segment/analytics-next"; import Tracker from "../../src/tracker"; @@ -422,10 +422,12 @@ describe("Custom Type Builder", () => { fireEvent.click(saveCustomType); }); - expect(fakeTracker).toHaveBeenLastCalledWith( - "SliceMachine Custom Type Saved", - { type: "repeatable", id: customTypeId, name: customTypeId }, - { context: { groupId: { Repository: "repoName" } } } - ); + await waitFor(() => { + expect(fakeTracker).toHaveBeenLastCalledWith( + "SliceMachine Custom Type Saved", + { type: "repeatable", id: customTypeId, name: customTypeId }, + { context: { groupId: { Repository: "repoName" } } } + ); + }); }); }); From 3fd987b23d0addcfb870d9c53112c1d3f0b6ce93 Mon Sep 17 00:00:00 2001 From: MarcMcIntosh Date: Wed, 18 May 2022 12:13:25 +0200 Subject: [PATCH 4/4] tracking(save ccustom-type): add test case for failure --- .../slice-machine/tests/pages/cts.spec.tsx | 97 ++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/packages/slice-machine/tests/pages/cts.spec.tsx b/packages/slice-machine/tests/pages/cts.spec.tsx index 4c082b1602..dcb50a04fb 100644 --- a/packages/slice-machine/tests/pages/cts.spec.tsx +++ b/packages/slice-machine/tests/pages/cts.spec.tsx @@ -337,7 +337,7 @@ describe("Custom Type Builder", () => { ); }); - test("it should sendd a tracking event when the user saves a custoom-type", async () => { + test("it should send a tracking event when the user saves a custoom-type", async () => { const customTypeId = "a-page"; singletonRouter.push({ @@ -430,4 +430,99 @@ describe("Custom Type Builder", () => { ); }); }); + + test("if saving fails a it should not send the save event", async () => { + server.use( + rest.post("/api/custom-types/save", (_, res, ctx) => { + return res(ctx.status(500), ctx.json({})); + }) + ); + const customTypeId = "a-page"; + + singletonRouter.push({ + pathname: "cts/[ct]", + query: { ct: customTypeId }, + }); + + const App = render(, { + preloadedState: { + environment: { + framework: "next", + mockConfig: { _cts: { [customTypeId]: {} } }, + }, + availableCustomTypes: { + [customTypeId]: { + local: { + id: customTypeId, + label: customTypeId, + repeatable: true, + status: true, + tabs: [ + { + key: "Main", + value: [], + }, + ], + }, + }, + }, + selectedCustomType: { + model: { + id: "a-page", + label: "a-page", + repeatable: true, + status: true, + tabs: [ + { + key: "Main", + value: [], + }, + ], + }, + initialModel: { + id: "a-page", + label: "a-page", + repeatable: true, + status: true, + tabs: [ + { + key: "Main", + value: [], + }, + ], + }, + mockConfig: {}, + initialMockConfig: {}, + }, + }, + }); + + const addButton = screen.getByTestId("empty-zone-add-new-field"); + fireEvent.click(addButton); + + const uid = screen.getByText("UID"); + fireEvent.click(uid); + + const saveFieldButton = screen.getByText("Add"); + + await act(async () => { + fireEvent.click(saveFieldButton); + }); + + expect(fakeTracker).toHaveBeenCalledWith( + "SliceMachine Custom Type Field Added", + { id: "uid", name: customTypeId, type: "UID", zone: "static" }, + { context: { groupId: { Repository: "repoName" } } } + ); + + const saveCustomType = screen.getByText("Save to File System"); + + await act(async () => { + fireEvent.click(saveCustomType); + }); + + await new Promise((r) => setTimeout(r, 1000)); + + expect(fakeTracker).toHaveBeenCalledTimes(1); + }); });