0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-02-17 22:04:19 -05:00

refactor(console): update conversion report timing (#5833)

This commit is contained in:
Gao Sun 2024-05-09 11:57:56 +08:00 committed by GitHub
parent 39e239753e
commit f8221a38db
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 46 additions and 123 deletions

View file

@ -3,16 +3,11 @@ import { Helmet } from 'react-helmet';
import useCurrentUser from '@/hooks/use-current-user';
import { useRetry } from './use-retry';
import {
shouldReport,
gtagAwTrackingId,
redditPixelId,
hashEmail,
type GtagConversionId,
type RedditReportType,
reportToGoogle,
reportToReddit,
plausibleDataDomain,
} from './utils';
@ -109,25 +104,3 @@ export function GlobalScripts() {
</>
);
}
type ReportConversionOptions = {
transactionId?: string;
gtagId?: GtagConversionId;
redditType?: RedditReportType;
};
export const useReportConversion = ({
gtagId,
redditType,
transactionId,
}: ReportConversionOptions) => {
useRetry({
precondition: Boolean(shouldReport && gtagId),
execute: () => (gtagId ? reportToGoogle(gtagId, { transactionId }) : false),
});
useRetry({
precondition: Boolean(shouldReport && redditType),
execute: () => (redditType ? reportToReddit(redditType) : false),
});
};

View file

@ -1,52 +0,0 @@
import { useEffect } from 'react';
type UseRetryOptions = {
/** The precondition to check before executing the function. */
precondition: boolean;
/** The function to execute when the precondition is not met. */
onPreconditionFailed?: () => void;
/** The function to execute. If it returns `true`, the retry will stop. */
execute: () => boolean;
/**
* The maximum number of retries.
*
* @default 3
*/
maxRetry?: number;
};
/**
* A hook to retry a function until the condition is met. The retry interval is 1 second.
*/
export const useRetry = ({
precondition,
onPreconditionFailed,
execute,
maxRetry = 3,
}: UseRetryOptions) => {
useEffect(() => {
if (!precondition) {
onPreconditionFailed?.();
}
}, [onPreconditionFailed, precondition]);
useEffect(() => {
if (!precondition) {
return;
}
// eslint-disable-next-line @silverhand/fp/no-let
let retry = 0;
const interval = setInterval(() => {
if (execute() || retry >= maxRetry) {
clearInterval(interval);
}
// eslint-disable-next-line @silverhand/fp/no-mutation
retry += 1;
}, 1000);
return () => {
clearInterval(interval);
};
}, [execute, maxRetry, precondition]);
};

View file

@ -1,6 +1,7 @@
import { cond } from '@silverhand/essentials';
import debug from 'debug';
import { isProduction } from '@/consts/env';
const log = debug('conversion');
export const gtagAwTrackingId = 'AW-11124811245';
export enum GtagConversionId {
@ -54,30 +55,18 @@ export const hashEmail = async (email?: string) => {
return sha256(canonicalizedEmail);
};
/** Print debug message if not in production. */
const debug = (...args: Parameters<(typeof console)['debug']>) => {
if (!isProduction) {
console.debug(...args);
}
};
/**
* Add more if needed: https://reddit.my.site.com/helpcenter/s/article/Install-the-Reddit-Pixel-on-your-website
*/
export type RedditReportType =
| 'PageVisit'
| 'ViewContent'
| 'Search'
| 'Purchase'
| 'Lead'
| 'SignUp';
type RedditReportType = 'PageVisit' | 'ViewContent' | 'Search' | 'Purchase' | 'Lead' | 'SignUp';
export const reportToReddit = (redditType: RedditReportType) => {
const reportToReddit = (redditType: RedditReportType) => {
if (!window.rdt) {
log('report:', 'window.rdt is not available');
return false;
}
debug('report:', 'redditType =', redditType);
log('report:', 'redditType =', redditType);
window.rdt('track', redditType);
return true;
@ -88,13 +77,14 @@ export const reportToGoogle = (
{ transactionId }: { transactionId?: string } = {}
) => {
if (!window.gtag) {
log('report:', 'window.gtag is not available');
return false;
}
const run = async () => {
const transaction = cond(transactionId && { transaction_id: await sha256(transactionId) });
debug('report:', 'gtagId =', gtagId, 'transaction =', transaction);
log('report:', 'gtagId =', gtagId, 'transaction =', transaction);
window.gtag?.('event', 'conversion', {
send_to: gtagId,
...transaction,
@ -105,3 +95,25 @@ export const reportToGoogle = (
return true;
};
type ReportConversionOptions = {
transactionId?: string;
gtagId?: GtagConversionId;
redditType?: RedditReportType;
};
export const reportConversion = async ({
gtagId,
redditType,
transactionId,
}: ReportConversionOptions) => {
if (!shouldReport) {
log('skip reporting conversion:', { gtagId, redditType, transactionId });
return;
}
return Promise.all([
gtagId ? reportToGoogle(gtagId, { transactionId }) : undefined,
redditType ? reportToReddit(redditType) : undefined,
]);
};

View file

@ -1,6 +1,6 @@
import { yes } from '@silverhand/essentials';
export const isProduction = process.env.NODE_ENV === 'production';
const isProduction = process.env.NODE_ENV === 'production';
export const isCloud = yes(process.env.IS_CLOUD);
export const adminEndpoint = process.env.ADMIN_ENDPOINT;

View file

@ -3,14 +3,11 @@ import { useContext, useEffect } from 'react';
import { Route, Navigate, Outlet, Routes } from 'react-router-dom';
import { SWRConfig } from 'swr';
import { useReportConversion } from '@/components/Conversion';
import { GtagConversionId } from '@/components/Conversion/utils';
import AppBoundary from '@/containers/AppBoundary';
import ProtectedRoutes from '@/containers/ProtectedRoutes';
import TenantAccess from '@/containers/TenantAccess';
import { AppThemeContext } from '@/contexts/AppThemeProvider';
import Toast from '@/ds-components/Toast';
import useCurrentUser from '@/hooks/use-current-user';
import useSwrOptions from '@/hooks/use-swr-options';
import useTenantPathname from '@/hooks/use-tenant-pathname';
@ -27,23 +24,6 @@ function Layout() {
const { setThemeOverride } = useContext(AppThemeContext);
const { match, getTo } = useTenantPathname();
// User object should be available at this point as it's rendered by the `<AppRoutes />`
// component in `packages/console/src/App.tsx`.
const { user } = useCurrentUser();
/**
* Report a sign-up conversion.
*
* Note it may run multiple times (e.g. a user visit multiple times to finish the onboarding process,
* which rarely happens). We should turn on deduplication settings in the provider's dashboard. For
* example, in Google, we should set conversion's "Count" to "One".
*/
useReportConversion({
gtagId: GtagConversionId.SignUp,
redditType: 'SignUp',
transactionId: user?.id,
});
useEffect(() => {
setThemeOverride(Theme.Light);

View file

@ -8,6 +8,7 @@ import useSWR from 'swr';
import Tools from '@/assets/icons/tools.svg';
import ActionBar from '@/components/ActionBar';
import { GtagConversionId, reportConversion } from '@/components/Conversion/utils';
import PageMeta from '@/components/PageMeta';
import { TenantsContext } from '@/contexts/TenantsProvider';
import Button from '@/ds-components/Button';
@ -18,6 +19,7 @@ import TextInput from '@/ds-components/TextInput';
import ImageUploaderField from '@/ds-components/Uploader/ImageUploaderField';
import useApi from '@/hooks/use-api';
import type { RequestError } from '@/hooks/use-api';
import useCurrentUser from '@/hooks/use-current-user';
import useTenantPathname from '@/hooks/use-tenant-pathname';
import useUserAssetsService from '@/hooks/use-user-assets-service';
import { CardSelector, MultiCardSelector } from '@/onboarding/components/CardSelector';
@ -53,6 +55,7 @@ function SignInExperience() {
const api = useApi();
const { isReady: isUserAssetsServiceReady } = useUserAssetsService();
const { update } = useUserOnboardingData();
const { user } = useCurrentUser();
const { navigateTenant, currentTenantId } = useContext(TenantsContext);
const enterAdminConsole = async () => {
@ -115,11 +118,18 @@ function SignInExperience() {
}
}
const updatedData = await api
.patch(buildUrl('api/sign-in-exp', { removeUnusedDemoSocialConnector: '1' }), {
json: formDataParser.toUpdateOnboardingSieData(formData, signInExperience),
})
.json<SignInExperienceType>();
const [updatedData] = await Promise.all([
api
.patch(buildUrl('api/sign-in-exp', { removeUnusedDemoSocialConnector: '1' }), {
json: formDataParser.toUpdateOnboardingSieData(formData, signInExperience),
})
.json<SignInExperienceType>(),
reportConversion({
gtagId: GtagConversionId.SignUp,
redditType: 'SignUp',
transactionId: user?.id,
}),
]);
void mutate(updatedData);