From cd17eb1527bfeab54d364e269e1155887a1e2a0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Tue, 7 Jan 2025 10:10:01 +0100 Subject: [PATCH 1/7] Fix identify users in analytics --- src/auth/hooks/useAuthProvider.ts | 4 ---- src/components/ProductAnalytics/index.tsx | 2 +- src/components/ProductAnalytics/useAnalytics.ts | 13 ++----------- 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/src/auth/hooks/useAuthProvider.ts b/src/auth/hooks/useAuthProvider.ts index 30ab804c3f8..b298079a35b 100644 --- a/src/auth/hooks/useAuthProvider.ts +++ b/src/auth/hooks/useAuthProvider.ts @@ -1,6 +1,5 @@ import { ApolloClient, ApolloError } from "@apollo/client"; import { IMessageContext } from "@dashboard/components/messages"; -import { useAnalytics } from "@dashboard/components/ProductAnalytics/useAnalytics"; import { DEMO_MODE } from "@dashboard/config"; import { AccountErrorCode, useUserDetailsQuery } from "@dashboard/graphql"; import useLocalStorage from "@dashboard/hooks/useLocalStorage"; @@ -38,7 +37,6 @@ type AuthErrorCodes = `${AccountErrorCode}`; export function useAuthProvider({ intl, notify, apolloClient }: UseAuthProviderOpts): UserContext { const { login, getExternalAuthUrl, getExternalAccessToken, logout } = useAuth(); - const analytics = useAnalytics(); const navigate = useNavigator(); const { authenticated, authenticating, user } = useAuthState(); const [requestedExternalPluginId] = useLocalStorage("requestedExternalPluginId", null); @@ -85,8 +83,6 @@ export function useAuthProvider({ intl, notify, apolloClient }: UseAuthProviderO } }; const handleLogout = async () => { - analytics.reset(); - const returnTo = urlJoin(window.location.origin, getAppMountUriForRedirect()); const result = await logout({ input: JSON.stringify({ diff --git a/src/components/ProductAnalytics/index.tsx b/src/components/ProductAnalytics/index.tsx index 26e70d5e9af..ecb6c60845c 100644 --- a/src/components/ProductAnalytics/index.tsx +++ b/src/components/ProductAnalytics/index.tsx @@ -13,7 +13,7 @@ const useConfig = () => { }, } satisfies Partial; const apiKey = process.env.POSTHOG_KEY; - const isCloudInstance = process.env.IS_CLOUD_INSTANCE; + const isCloudInstance = true; const canRenderAnalytics = () => { if (!isCloudInstance) { return false; diff --git a/src/components/ProductAnalytics/useAnalytics.ts b/src/components/ProductAnalytics/useAnalytics.ts index 473aa1f37d9..5f4eb80eb81 100644 --- a/src/components/ProductAnalytics/useAnalytics.ts +++ b/src/components/ProductAnalytics/useAnalytics.ts @@ -2,7 +2,6 @@ import { usePostHog } from "posthog-js/react"; interface Analytics { initialize: (details: Record) => void; - reset: () => void; trackEvent: (event: string, properties?: Record) => void; } @@ -13,15 +12,7 @@ export function useAnalytics(): Analytics { // According to docs, posthog can be briefly undefined if (!posthog) return; - const id = posthog.get_distinct_id(); - - posthog.identify(id, details); - } - - function reset() { - if (!posthog) return; - - posthog.reset(); + posthog.setPersonProperties(details); } function trackEvent(event: string, properties?: Record) { @@ -30,5 +21,5 @@ export function useAnalytics(): Analytics { posthog.capture(event, properties); } - return { initialize, reset, trackEvent }; + return { initialize, trackEvent }; } From 0a04c815ad3f8140cdde47ced93ef5a88359d2b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Tue, 7 Jan 2025 10:11:47 +0100 Subject: [PATCH 2/7] Add changeset --- .changeset/strong-olives-collect.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/strong-olives-collect.md diff --git a/.changeset/strong-olives-collect.md b/.changeset/strong-olives-collect.md new file mode 100644 index 00000000000..b8ce8f2e8c2 --- /dev/null +++ b/.changeset/strong-olives-collect.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": patch +--- + +Users are now properly anonymously identified From 6f710fe578457290b0cda62355f7630da6bde4dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Tue, 7 Jan 2025 10:18:28 +0100 Subject: [PATCH 3/7] Restore IS_CLOUD_INSTANCE from env --- src/components/ProductAnalytics/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ProductAnalytics/index.tsx b/src/components/ProductAnalytics/index.tsx index ecb6c60845c..26e70d5e9af 100644 --- a/src/components/ProductAnalytics/index.tsx +++ b/src/components/ProductAnalytics/index.tsx @@ -13,7 +13,7 @@ const useConfig = () => { }, } satisfies Partial; const apiKey = process.env.POSTHOG_KEY; - const isCloudInstance = true; + const isCloudInstance = process.env.IS_CLOUD_INSTANCE; const canRenderAnalytics = () => { if (!isCloudInstance) { return false; From b22f9f79efd413c3dc81122c954d8c308c51d0fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Tue, 7 Jan 2025 13:16:14 +0100 Subject: [PATCH 4/7] Track and set user properties --- .../ProductAnalytics/useAnalytics.ts | 24 +++++++++++-------- src/components/Shop/index.tsx | 18 +------------- 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/src/components/ProductAnalytics/useAnalytics.ts b/src/components/ProductAnalytics/useAnalytics.ts index 5f4eb80eb81..cfcae49e1f2 100644 --- a/src/components/ProductAnalytics/useAnalytics.ts +++ b/src/components/ProductAnalytics/useAnalytics.ts @@ -1,25 +1,29 @@ +import { useUser } from "@dashboard/auth"; +import useShop from "@dashboard/hooks/useShop"; import { usePostHog } from "posthog-js/react"; +import { extractEmailDomain } from "./utils"; + interface Analytics { - initialize: (details: Record) => void; trackEvent: (event: string, properties?: Record) => void; } export function useAnalytics(): Analytics { const posthog = usePostHog(); - - function initialize(details: Record) { - // According to docs, posthog can be briefly undefined - if (!posthog) return; - - posthog.setPersonProperties(details); - } + const { user } = useUser(); + const shop = useShop(); function trackEvent(event: string, properties?: Record) { if (!posthog) return; - posthog.capture(event, properties); + posthog.capture(event, { + $set: { + domain: shop?.domain?.host, + email_domain: extractEmailDomain(user?.email ?? ""), + }, + ...properties, + }); } - return { initialize, trackEvent }; + return { trackEvent }; } diff --git a/src/components/Shop/index.tsx b/src/components/Shop/index.tsx index 56d3f7c07d5..712727f0bcd 100644 --- a/src/components/Shop/index.tsx +++ b/src/components/Shop/index.tsx @@ -5,35 +5,19 @@ import favicon32 from "@assets/favicons/favicon-32x32.png"; import safariPinnedTab from "@assets/favicons/safari-pinned-tab.svg"; import { useUser } from "@dashboard/auth"; import { ShopInfoQuery, useShopInfoQuery } from "@dashboard/graphql"; -import React, { useEffect } from "react"; +import React from "react"; import Helmet from "react-helmet"; -import { useAnalytics } from "../ProductAnalytics/useAnalytics"; -import { extractEmailDomain } from "../ProductAnalytics/utils"; - type ShopContext = ShopInfoQuery["shop"]; export const ShopContext = React.createContext(undefined); export const ShopProvider: React.FC = ({ children }) => { const { authenticated, user } = useUser(); - const analytics = useAnalytics(); const { data } = useShopInfoQuery({ skip: !authenticated || !user, }); - useEffect(() => { - if (data) { - const { shop } = data; - - analytics.initialize({ - domain: shop.domain.host, - email_domain: extractEmailDomain(user.email), - }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [data]); - return ( <> From d473e6633138a68da609dbfbe9b8a8cd4a06fbb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Wed, 8 Jan 2025 11:48:33 +0100 Subject: [PATCH 5/7] Detect when user properties change and set only then --- src/components/ProductAnalytics/index.tsx | 2 +- .../ProductAnalytics/useAnalytics.ts | 50 ++++++++++++++----- src/components/Shop/index.tsx | 18 ++++++- 3 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/components/ProductAnalytics/index.tsx b/src/components/ProductAnalytics/index.tsx index 26e70d5e9af..ecb6c60845c 100644 --- a/src/components/ProductAnalytics/index.tsx +++ b/src/components/ProductAnalytics/index.tsx @@ -13,7 +13,7 @@ const useConfig = () => { }, } satisfies Partial; const apiKey = process.env.POSTHOG_KEY; - const isCloudInstance = process.env.IS_CLOUD_INSTANCE; + const isCloudInstance = true; const canRenderAnalytics = () => { if (!isCloudInstance) { return false; diff --git a/src/components/ProductAnalytics/useAnalytics.ts b/src/components/ProductAnalytics/useAnalytics.ts index cfcae49e1f2..547d9063778 100644 --- a/src/components/ProductAnalytics/useAnalytics.ts +++ b/src/components/ProductAnalytics/useAnalytics.ts @@ -1,29 +1,53 @@ -import { useUser } from "@dashboard/auth"; -import useShop from "@dashboard/hooks/useShop"; +import useLocalStorage from "@dashboard/hooks/useLocalStorage"; import { usePostHog } from "posthog-js/react"; -import { extractEmailDomain } from "./utils"; +interface UserProperties { + domain: string; + email_domain: string; +} interface Analytics { + initialize: (userProperties: UserProperties) => void; trackEvent: (event: string, properties?: Record) => void; } export function useAnalytics(): Analytics { const posthog = usePostHog(); - const { user } = useUser(); - const shop = useShop(); + const [lastUserProperties, setLastUserProperties] = useLocalStorage( + "analyticsUserProperties", + { + domain: "", + email_domain: "", + }, + ); + + function initialize(userProperties: UserProperties) { + if (!posthog) return; + + if (!hasUserPropertiesChanged(userProperties, lastUserProperties)) return; + + const id = posthog.get_distinct_id(); + + posthog.identify(id, userProperties); + + setLastUserProperties(userProperties); + } function trackEvent(event: string, properties?: Record) { if (!posthog) return; - posthog.capture(event, { - $set: { - domain: shop?.domain?.host, - email_domain: extractEmailDomain(user?.email ?? ""), - }, - ...properties, - }); + posthog.capture(event, properties); } - return { trackEvent }; + return { trackEvent, initialize }; +} + +function hasUserPropertiesChanged( + userProperties: UserProperties, + lastInitializedUserProperties: UserProperties, +) { + return ( + userProperties.domain !== lastInitializedUserProperties.domain || + userProperties.email_domain !== lastInitializedUserProperties.email_domain + ); } diff --git a/src/components/Shop/index.tsx b/src/components/Shop/index.tsx index 712727f0bcd..56d3f7c07d5 100644 --- a/src/components/Shop/index.tsx +++ b/src/components/Shop/index.tsx @@ -5,19 +5,35 @@ import favicon32 from "@assets/favicons/favicon-32x32.png"; import safariPinnedTab from "@assets/favicons/safari-pinned-tab.svg"; import { useUser } from "@dashboard/auth"; import { ShopInfoQuery, useShopInfoQuery } from "@dashboard/graphql"; -import React from "react"; +import React, { useEffect } from "react"; import Helmet from "react-helmet"; +import { useAnalytics } from "../ProductAnalytics/useAnalytics"; +import { extractEmailDomain } from "../ProductAnalytics/utils"; + type ShopContext = ShopInfoQuery["shop"]; export const ShopContext = React.createContext(undefined); export const ShopProvider: React.FC = ({ children }) => { const { authenticated, user } = useUser(); + const analytics = useAnalytics(); const { data } = useShopInfoQuery({ skip: !authenticated || !user, }); + useEffect(() => { + if (data) { + const { shop } = data; + + analytics.initialize({ + domain: shop.domain.host, + email_domain: extractEmailDomain(user.email), + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [data]); + return ( <> From 7681c5a2d3752ac0547b93849a058e18cb37e68c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Wed, 8 Jan 2025 12:05:00 +0100 Subject: [PATCH 6/7] Fix isCloudInstance --- src/components/ProductAnalytics/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ProductAnalytics/index.tsx b/src/components/ProductAnalytics/index.tsx index ecb6c60845c..26e70d5e9af 100644 --- a/src/components/ProductAnalytics/index.tsx +++ b/src/components/ProductAnalytics/index.tsx @@ -13,7 +13,7 @@ const useConfig = () => { }, } satisfies Partial; const apiKey = process.env.POSTHOG_KEY; - const isCloudInstance = true; + const isCloudInstance = process.env.IS_CLOUD_INSTANCE; const canRenderAnalytics = () => { if (!isCloudInstance) { return false; From 441a867ea7b79f8f31bf200db425f1d2de30e04a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Thu, 16 Jan 2025 12:16:36 +0100 Subject: [PATCH 7/7] Update ls data and add comment --- .../ProductAnalytics/useAnalytics.ts | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/src/components/ProductAnalytics/useAnalytics.ts b/src/components/ProductAnalytics/useAnalytics.ts index 547d9063778..b97a7651dba 100644 --- a/src/components/ProductAnalytics/useAnalytics.ts +++ b/src/components/ProductAnalytics/useAnalytics.ts @@ -13,24 +13,23 @@ interface Analytics { export function useAnalytics(): Analytics { const posthog = usePostHog(); - const [lastUserProperties, setLastUserProperties] = useLocalStorage( - "analyticsUserProperties", - { - domain: "", - email_domain: "", - }, + const [hasBeenUserSet, setHasBeenUserSet] = useLocalStorage( + "analyticsHasBeenUserSet", + false, ); function initialize(userProperties: UserProperties) { if (!posthog) return; - if (!hasUserPropertiesChanged(userProperties, lastUserProperties)) return; + // initialize function is called on each reload that cause generates new used id by identify function + // to avoid this we need to check if user has been set + if (hasBeenUserSet) return; const id = posthog.get_distinct_id(); posthog.identify(id, userProperties); - setLastUserProperties(userProperties); + setHasBeenUserSet(true); } function trackEvent(event: string, properties?: Record) { @@ -41,13 +40,3 @@ export function useAnalytics(): Analytics { return { trackEvent, initialize }; } - -function hasUserPropertiesChanged( - userProperties: UserProperties, - lastInitializedUserProperties: UserProperties, -) { - return ( - userProperties.domain !== lastInitializedUserProperties.domain || - userProperties.email_domain !== lastInitializedUserProperties.email_domain - ); -}