From 87e3a7d06ae76b6bee92b1bb97d0c8f8da07d4e9 Mon Sep 17 00:00:00 2001 From: Juan Pablo Orsay Date: Mon, 11 Sep 2023 12:40:15 +0200 Subject: [PATCH] fix(admin-ui): disabling analytics when opted out (#4939) * fixes #4423 by either using the `useAnalytics` hook or using an ErrorBoundary specific instance Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com> --- .changeset/seven-weeks-exist.md | 5 +++ .../organisms/analytics-preferences/index.tsx | 9 ++---- .../organisms/error-boundary/index.tsx | 32 ++++++++++--------- packages/admin-ui/ui/src/pages/invite.tsx | 6 ++-- .../ui/src/providers/analytics-provider.tsx | 11 +++++++ .../admin-ui/ui/src/services/analytics.ts | 19 +++++++---- 6 files changed, 53 insertions(+), 29 deletions(-) create mode 100644 .changeset/seven-weeks-exist.md diff --git a/.changeset/seven-weeks-exist.md b/.changeset/seven-weeks-exist.md new file mode 100644 index 0000000000..5fc597b9bd --- /dev/null +++ b/.changeset/seven-weeks-exist.md @@ -0,0 +1,5 @@ +--- +"@medusajs/admin-ui": patch +--- + +correctly skipping analytics when user opted out diff --git a/packages/admin-ui/ui/src/components/organisms/analytics-preferences/index.tsx b/packages/admin-ui/ui/src/components/organisms/analytics-preferences/index.tsx index c5575ae79b..c684861e99 100644 --- a/packages/admin-ui/ui/src/components/organisms/analytics-preferences/index.tsx +++ b/packages/admin-ui/ui/src/components/organisms/analytics-preferences/index.tsx @@ -2,10 +2,7 @@ import clsx from "clsx" import { useForm, useWatch } from "react-hook-form" import useNotification from "../../../hooks/use-notification" import { useAnalytics } from "../../../providers/analytics-provider" -import { - analytics, - useAdminCreateAnalyticsConfig, -} from "../../../services/analytics" +import { useAdminCreateAnalyticsConfig } from "../../../services/analytics" import { getErrorMessage } from "../../../utils/error-messages" import { nestedForm } from "../../../utils/nested-form" import Button from "../../fundamentals/button" @@ -38,7 +35,7 @@ const AnalyticsPreferencesModal = () => { control, } = form - const { setSubmittingConfig } = useAnalytics() + const { setSubmittingConfig, trackUserEmail } = useAnalytics() const watchOptOut = useWatch({ control: control, @@ -65,7 +62,7 @@ const AnalyticsPreferencesModal = () => { ) if (shouldTrackEmail) { - analytics.track("userEmail", { email }) + trackUserEmail({ email }) } setSubmittingConfig(false) diff --git a/packages/admin-ui/ui/src/components/organisms/error-boundary/index.tsx b/packages/admin-ui/ui/src/components/organisms/error-boundary/index.tsx index 2c5bf5061b..30b14fa54a 100644 --- a/packages/admin-ui/ui/src/components/organisms/error-boundary/index.tsx +++ b/packages/admin-ui/ui/src/components/organisms/error-boundary/index.tsx @@ -1,7 +1,9 @@ import { AxiosError } from "axios" import React, { ErrorInfo } from "react" -import { analytics, getAnalyticsConfig } from "../../../services/analytics" +import { analyticsOptIn } from "../../../services/analytics" import Button from "../../fundamentals/button" +import { WRITE_KEY } from "../../../constants/analytics" +import { AnalyticsBrowser } from "@segment/analytics-next" type State = { hasError: boolean @@ -13,6 +15,18 @@ type Props = { children?: React.ReactNode } +// Analytics instance used for tracking errors +let analyticsInstance: ReturnType | undefined; + +const analytics = () => { + if (!analyticsInstance) { + analyticsInstance = AnalyticsBrowser.load({ + writeKey: WRITE_KEY, + }) + } + return analyticsInstance +} + class ErrorBoundary extends React.Component { public state: State = { hasError: false, @@ -40,7 +54,7 @@ class ErrorBoundary extends React.Component { } const properties = getTrackingInfo(error, errorInfo) - analytics.track("error", properties) + analytics().track("error", properties) } public dismissError = () => { @@ -98,19 +112,7 @@ const shouldTrackEvent = async (error: Error) => { return false } - const res = await getAnalyticsConfig().catch(() => undefined) - - // Don't track if we have no config to ensure we have permission - if (!res) { - return false - } - - // Don't track if user has opted out from sharing usage insights - if (res.analytics_config.opt_out) { - return false - } - - return true + return await analyticsOptIn(); } const errorMessage = (status?: number) => { diff --git a/packages/admin-ui/ui/src/pages/invite.tsx b/packages/admin-ui/ui/src/pages/invite.tsx index 23cc867ef5..b18303768d 100644 --- a/packages/admin-ui/ui/src/pages/invite.tsx +++ b/packages/admin-ui/ui/src/pages/invite.tsx @@ -12,7 +12,8 @@ import PublicLayout from "../components/templates/login-layout" import useNotification from "../hooks/use-notification" import { getErrorMessage } from "../utils/error-messages" import FormValidator from "../utils/form-validator" -import { analytics, useAdminCreateAnalyticsConfig } from "../services/analytics" +import { useAdminCreateAnalyticsConfig } from "../services/analytics" +import { useAnalytics } from "../providers/analytics-provider" import AnalyticsConfigForm, { AnalyticsConfigFormType, } from "../components/organisms/analytics-config-form" @@ -30,6 +31,7 @@ const InvitePage = () => { const location = useLocation() const parsed = qs.parse(location.search.substring(1)) const [signUp, setSignUp] = useState(false) + const { trackUserEmail } = useAnalytics() const first_run = !!parsed.first_run @@ -117,7 +119,7 @@ const InvitePage = () => { await createAnalyticsConfig(data.analytics) if (shouldTrackEmail) { - await analytics.track("userEmail", { + trackUserEmail({ email: token?.user_email, }) } diff --git a/packages/admin-ui/ui/src/providers/analytics-provider.tsx b/packages/admin-ui/ui/src/providers/analytics-provider.tsx index 1bfdcc30ba..42833682f8 100644 --- a/packages/admin-ui/ui/src/providers/analytics-provider.tsx +++ b/packages/admin-ui/ui/src/providers/analytics-provider.tsx @@ -28,6 +28,7 @@ type Event = | "regions" | "currencies" | "storeName" + | "userEmail" type AnalyticsContextType = { trackCurrencies: (properties: TrackCurrenciesPayload) => void @@ -35,6 +36,7 @@ type AnalyticsContextType = { trackNumberOfDiscounts: (properties: TrackCountPayload) => void trackNumberOfProducts: (properties: TrackCountPayload) => void trackRegions: (properties: TrackRegionsPayload) => void + trackUserEmail: (properties: TrackUserEmailPayload) => void setSubmittingConfig: (status: boolean) => void } @@ -140,6 +142,10 @@ export const AnalyticsProvider = ({ writeKey, children }: Props) => { track("numDiscounts", properties) } + const trackUserEmail = (properties: TrackUserEmailPayload) => { + track("userEmail", properties) + } + // Track number of users useEffect(() => { if (users) { @@ -171,6 +177,7 @@ export const AnalyticsProvider = ({ writeKey, children }: Props) => { trackNumberOfOrders, trackNumberOfProducts, trackNumberOfDiscounts, + trackUserEmail, setSubmittingConfig, }} > @@ -201,6 +208,10 @@ type TrackRegionsPayload = { count: number } +type TrackUserEmailPayload = { + email: string | undefined +} + export const useAnalytics = () => { const context = useContext(AnalyticsContext) diff --git a/packages/admin-ui/ui/src/services/analytics.ts b/packages/admin-ui/ui/src/services/analytics.ts index 3d1f8a97e9..a13e39a93c 100644 --- a/packages/admin-ui/ui/src/services/analytics.ts +++ b/packages/admin-ui/ui/src/services/analytics.ts @@ -1,8 +1,6 @@ import { AdminAnalyticsConfigRes } from "@medusajs/medusa" -import { AnalyticsBrowser } from "@segment/analytics-next" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import axios from "axios" -import { WRITE_KEY } from "../constants/analytics" import { MEDUSA_BACKEND_URL } from "../constants/medusa-backend-url" import { useFeatureFlag } from "../providers/feature-flag-provider" @@ -15,10 +13,19 @@ const client = axios.create({ withCredentials: true, }) -// Analytics instance used for tracking one-off events, such as errors and the initial permission request -export const analytics = AnalyticsBrowser.load({ - writeKey: WRITE_KEY, -}) +/** + * Returns true if analytics are enabled for the current user. + */ +export const analyticsOptIn = async () => { + const res = await getAnalyticsConfig().catch(() => undefined) + + // Don't track if we have no config to ensure we have permission + if (!res) { + return false + } + + return !res.analytics_config.opt_out +} /** * Fetches the analytics config for the current user.