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>
This commit is contained in:
Juan Pablo Orsay
2023-09-11 12:40:15 +02:00
committed by GitHub
co-authored by Oli Juhl
parent 6273b4b160
commit 87e3a7d06a
6 changed files with 53 additions and 29 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@medusajs/admin-ui": patch
---
correctly skipping analytics when user opted out
@@ -2,10 +2,7 @@ import clsx from "clsx"
import { useForm, useWatch } from "react-hook-form" import { useForm, useWatch } from "react-hook-form"
import useNotification from "../../../hooks/use-notification" import useNotification from "../../../hooks/use-notification"
import { useAnalytics } from "../../../providers/analytics-provider" import { useAnalytics } from "../../../providers/analytics-provider"
import { import { useAdminCreateAnalyticsConfig } from "../../../services/analytics"
analytics,
useAdminCreateAnalyticsConfig,
} from "../../../services/analytics"
import { getErrorMessage } from "../../../utils/error-messages" import { getErrorMessage } from "../../../utils/error-messages"
import { nestedForm } from "../../../utils/nested-form" import { nestedForm } from "../../../utils/nested-form"
import Button from "../../fundamentals/button" import Button from "../../fundamentals/button"
@@ -38,7 +35,7 @@ const AnalyticsPreferencesModal = () => {
control, control,
} = form } = form
const { setSubmittingConfig } = useAnalytics() const { setSubmittingConfig, trackUserEmail } = useAnalytics()
const watchOptOut = useWatch({ const watchOptOut = useWatch({
control: control, control: control,
@@ -65,7 +62,7 @@ const AnalyticsPreferencesModal = () => {
) )
if (shouldTrackEmail) { if (shouldTrackEmail) {
analytics.track("userEmail", { email }) trackUserEmail({ email })
} }
setSubmittingConfig(false) setSubmittingConfig(false)
@@ -1,7 +1,9 @@
import { AxiosError } from "axios" import { AxiosError } from "axios"
import React, { ErrorInfo } from "react" import React, { ErrorInfo } from "react"
import { analytics, getAnalyticsConfig } from "../../../services/analytics" import { analyticsOptIn } from "../../../services/analytics"
import Button from "../../fundamentals/button" import Button from "../../fundamentals/button"
import { WRITE_KEY } from "../../../constants/analytics"
import { AnalyticsBrowser } from "@segment/analytics-next"
type State = { type State = {
hasError: boolean hasError: boolean
@@ -13,6 +15,18 @@ type Props = {
children?: React.ReactNode children?: React.ReactNode
} }
// Analytics instance used for tracking errors
let analyticsInstance: ReturnType<typeof AnalyticsBrowser.load> | undefined;
const analytics = () => {
if (!analyticsInstance) {
analyticsInstance = AnalyticsBrowser.load({
writeKey: WRITE_KEY,
})
}
return analyticsInstance
}
class ErrorBoundary extends React.Component<Props, State> { class ErrorBoundary extends React.Component<Props, State> {
public state: State = { public state: State = {
hasError: false, hasError: false,
@@ -40,7 +54,7 @@ class ErrorBoundary extends React.Component<Props, State> {
} }
const properties = getTrackingInfo(error, errorInfo) const properties = getTrackingInfo(error, errorInfo)
analytics.track("error", properties) analytics().track("error", properties)
} }
public dismissError = () => { public dismissError = () => {
@@ -98,19 +112,7 @@ const shouldTrackEvent = async (error: Error) => {
return false return false
} }
const res = await getAnalyticsConfig().catch(() => undefined) return await analyticsOptIn();
// 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
} }
const errorMessage = (status?: number) => { const errorMessage = (status?: number) => {
+4 -2
View File
@@ -12,7 +12,8 @@ import PublicLayout from "../components/templates/login-layout"
import useNotification from "../hooks/use-notification" import useNotification from "../hooks/use-notification"
import { getErrorMessage } from "../utils/error-messages" import { getErrorMessage } from "../utils/error-messages"
import FormValidator from "../utils/form-validator" 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, { import AnalyticsConfigForm, {
AnalyticsConfigFormType, AnalyticsConfigFormType,
} from "../components/organisms/analytics-config-form" } from "../components/organisms/analytics-config-form"
@@ -30,6 +31,7 @@ const InvitePage = () => {
const location = useLocation() const location = useLocation()
const parsed = qs.parse(location.search.substring(1)) const parsed = qs.parse(location.search.substring(1))
const [signUp, setSignUp] = useState(false) const [signUp, setSignUp] = useState(false)
const { trackUserEmail } = useAnalytics()
const first_run = !!parsed.first_run const first_run = !!parsed.first_run
@@ -117,7 +119,7 @@ const InvitePage = () => {
await createAnalyticsConfig(data.analytics) await createAnalyticsConfig(data.analytics)
if (shouldTrackEmail) { if (shouldTrackEmail) {
await analytics.track("userEmail", { trackUserEmail({
email: token?.user_email, email: token?.user_email,
}) })
} }
@@ -28,6 +28,7 @@ type Event =
| "regions" | "regions"
| "currencies" | "currencies"
| "storeName" | "storeName"
| "userEmail"
type AnalyticsContextType = { type AnalyticsContextType = {
trackCurrencies: (properties: TrackCurrenciesPayload) => void trackCurrencies: (properties: TrackCurrenciesPayload) => void
@@ -35,6 +36,7 @@ type AnalyticsContextType = {
trackNumberOfDiscounts: (properties: TrackCountPayload) => void trackNumberOfDiscounts: (properties: TrackCountPayload) => void
trackNumberOfProducts: (properties: TrackCountPayload) => void trackNumberOfProducts: (properties: TrackCountPayload) => void
trackRegions: (properties: TrackRegionsPayload) => void trackRegions: (properties: TrackRegionsPayload) => void
trackUserEmail: (properties: TrackUserEmailPayload) => void
setSubmittingConfig: (status: boolean) => void setSubmittingConfig: (status: boolean) => void
} }
@@ -140,6 +142,10 @@ export const AnalyticsProvider = ({ writeKey, children }: Props) => {
track("numDiscounts", properties) track("numDiscounts", properties)
} }
const trackUserEmail = (properties: TrackUserEmailPayload) => {
track("userEmail", properties)
}
// Track number of users // Track number of users
useEffect(() => { useEffect(() => {
if (users) { if (users) {
@@ -171,6 +177,7 @@ export const AnalyticsProvider = ({ writeKey, children }: Props) => {
trackNumberOfOrders, trackNumberOfOrders,
trackNumberOfProducts, trackNumberOfProducts,
trackNumberOfDiscounts, trackNumberOfDiscounts,
trackUserEmail,
setSubmittingConfig, setSubmittingConfig,
}} }}
> >
@@ -201,6 +208,10 @@ type TrackRegionsPayload = {
count: number count: number
} }
type TrackUserEmailPayload = {
email: string | undefined
}
export const useAnalytics = () => { export const useAnalytics = () => {
const context = useContext(AnalyticsContext) const context = useContext(AnalyticsContext)
+13 -6
View File
@@ -1,8 +1,6 @@
import { AdminAnalyticsConfigRes } from "@medusajs/medusa" import { AdminAnalyticsConfigRes } from "@medusajs/medusa"
import { AnalyticsBrowser } from "@segment/analytics-next"
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
import axios from "axios" import axios from "axios"
import { WRITE_KEY } from "../constants/analytics"
import { MEDUSA_BACKEND_URL } from "../constants/medusa-backend-url" import { MEDUSA_BACKEND_URL } from "../constants/medusa-backend-url"
import { useFeatureFlag } from "../providers/feature-flag-provider" import { useFeatureFlag } from "../providers/feature-flag-provider"
@@ -15,10 +13,19 @@ const client = axios.create({
withCredentials: true, withCredentials: true,
}) })
// Analytics instance used for tracking one-off events, such as errors and the initial permission request /**
export const analytics = AnalyticsBrowser.load({ * Returns true if analytics are enabled for the current user.
writeKey: WRITE_KEY, */
}) 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. * Fetches the analytics config for the current user.