Skip to content

Commit

Permalink
Custom event tracking (#2151)
Browse files Browse the repository at this point in the history
* add plausible custom event tracking

* refactor: better comments for analytics tracking
  • Loading branch information
Tymek authored and sighphyre committed Oct 13, 2022
1 parent 03c592b commit 2b83b8a
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 86 deletions.
73 changes: 37 additions & 36 deletions frontend/src/component/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import Loader from 'component/common/Loader/Loader';
import NotFound from 'component/common/NotFound/NotFound';
import { ProtectedRoute } from 'component/common/ProtectedRoute/ProtectedRoute';
import { SWRProvider } from 'component/providers/SWRProvider/SWRProvider';
import { PlausibleProvider } from 'component/providers/PlausibleProvider/PlausibleProvider';
import ToastRenderer from 'component/common/ToastRenderer/ToastRenderer';
import { routes } from 'component/menu/routes';
import { useAuthDetails } from 'hooks/api/getters/useAuth/useAuthDetails';
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
import { SplashPageRedirect } from 'component/splash/SplashPageRedirect/SplashPageRedirect';
import { useStyles } from './App.styles';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';

export const App = () => {
Expand All @@ -24,7 +24,6 @@ export const App = () => {
const { user } = useAuthUser();
const { isOss } = useUiConfig();
const hasFetchedAuth = Boolean(authDetails || user);
usePlausibleTracker();

const availableRoutes = isOss()
? routes.filter(route => !route.enterprise)
Expand All @@ -34,45 +33,47 @@ export const App = () => {
<ErrorBoundary FallbackComponent={Error}>
<SWRProvider>
<Suspense fallback={<Loader />}>
<ConditionallyRender
condition={!hasFetchedAuth}
show={<Loader />}
elseShow={
<div className={styles.container}>
<ToastRenderer />
<LayoutPicker>
<Routes>
{availableRoutes.map(route => (
<PlausibleProvider>
<ConditionallyRender
condition={!hasFetchedAuth}
show={<Loader />}
elseShow={
<div className={styles.container}>
<ToastRenderer />
<LayoutPicker>
<Routes>
{availableRoutes.map(route => (
<Route
key={route.path}
path={route.path}
element={
<ProtectedRoute
route={route}
/>
}
/>
))}
<Route
key={route.path}
path={route.path}
path="/"
element={
<ProtectedRoute
route={route}
<Navigate
to="/features"
replace
/>
}
/>
))}
<Route
path="/"
element={
<Navigate
to="/features"
replace
/>
}
/>
<Route
path="*"
element={<NotFound />}
/>
</Routes>
<FeedbackNPS openUrl="http://feedback.unleash.run" />
<SplashPageRedirect />
</LayoutPicker>
</div>
}
/>
<Route
path="*"
element={<NotFound />}
/>
</Routes>
<FeedbackNPS openUrl="http://feedback.unleash.run" />
<SplashPageRedirect />
</LayoutPicker>
</div>
}
/>
</PlausibleProvider>
</Suspense>
</SWRProvider>
</ErrorBoundary>
Expand Down
11 changes: 10 additions & 1 deletion frontend/src/component/admin/users/InviteLink/InviteLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { useInviteTokenApi } from 'hooks/api/actions/useInviteTokenApi/useInvite
import { useInviteTokens } from 'hooks/api/getters/useInviteTokens/useInviteTokens';
import { LinkField } from '../LinkField/LinkField';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';

interface ICreateInviteLinkProps {}

Expand Down Expand Up @@ -64,12 +65,12 @@ export const InviteLink: VFC<ICreateInviteLinkProps> = () => {
const { data, loading } = useInviteTokens();
const [inviteLink, setInviteLink] = useState('');
const { mutate } = useSWRConfig();
const { trackEvent } = usePlausibleTracker();
const [expiry, setExpiry] = useState(expiryOptions[0].key);
const [showDisableDialog, setDisableDialogue] = useState(false);
const defaultToken = data?.tokens?.find(token => token.name === 'default');
const isUpdating = Boolean(defaultToken);
const formatApiCode = useFormatApiCode(isUpdating, expiry);

const [isSending, setIsSending] = useState(false);
const { setToastApiError } = useToast();
const { createToken, updateToken } = useInviteTokenApi();
Expand All @@ -78,6 +79,14 @@ export const InviteLink: VFC<ICreateInviteLinkProps> = () => {
e.preventDefault();
setIsSending(true);

trackEvent('invite', {
props: {
eventType: isUpdating
? 'link update submitted'
: 'link created',
},
});

try {
if (isUpdating) {
await updateToken(defaultToken!.secret, {
Expand Down
18 changes: 16 additions & 2 deletions frontend/src/component/admin/users/InviteLinkBar/InviteLinkBar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { VFC } from 'react';
import { useEffect, VFC } from 'react';
import { useNavigate } from 'react-router-dom';
import { Box, Button, Typography } from '@mui/material';
import useLoading from 'hooks/useLoading';
Expand All @@ -8,11 +8,13 @@ import { LinkField } from '../LinkField/LinkField';
import { add, formatDistanceToNowStrict, isAfter, parseISO } from 'date-fns';
import { formatDateYMD } from 'utils/formatDate';
import { useLocationSettings } from 'hooks/useLocationSettings';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';

export const InviteLinkBar: VFC = () => {
const navigate = useNavigate();
const { data, loading } = useInviteTokens();
const ref = useLoading(loading);
const { trackEvent } = usePlausibleTracker();
const inviteToken =
data?.tokens?.find(token => token.name === 'default') ?? null;
const inviteLink = inviteToken?.url;
Expand Down Expand Up @@ -40,6 +42,18 @@ export const InviteLinkBar: VFC = () => {
</Typography>
);

const onInviteLinkActionClick = () => {
trackEvent('invite', {
props: {
eventType: Boolean(inviteLink)
? 'link bar action: edit'
: 'link bar action: create',
},
});

navigate('/admin/invite-link');
};

return (
<Box
sx={{
Expand Down Expand Up @@ -111,7 +125,7 @@ export const InviteLinkBar: VFC = () => {
>
<Button
variant="outlined"
onClick={() => navigate('/admin/invite-link')}
onClick={onInviteLinkActionClick}
data-loading
>
{inviteLink ? 'Update' : 'Create'} invite link
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { FC, useState, useEffect } from 'react';
import Plausible from 'plausible-tracker';
import { PlausibleContext } from 'contexts/PlausibleContext';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';

const PLAUSIBLE_UNLEASH_API_HOST = 'https://plausible.getunleash.io';
const PLAUSIBLE_UNLEASH_DOMAIN = 'app.unleash-hosted.com';
const LOCAL_TESTING = false;

export const PlausibleProvider: FC = ({ children }) => {
const [context, setContext] = useState<ReturnType<typeof Plausible> | null>(
null
);
const { uiConfig } = useUiConfig();
const isEnabled = Boolean(uiConfig?.flags?.T || LOCAL_TESTING);

useEffect(() => {
if (isEnabled) {
try {
const plausible = Plausible({
domain: LOCAL_TESTING
? undefined
: PLAUSIBLE_UNLEASH_DOMAIN,
apiHost: LOCAL_TESTING
? 'http://localhost:8000'
: PLAUSIBLE_UNLEASH_API_HOST,
trackLocalhost: true,
});
setContext(() => plausible);
return plausible.enableAutoPageviews();
} catch (error) {
console.warn(error);
}
}
}, [isEnabled]);

return (
<PlausibleContext.Provider value={context}>
{children}
</PlausibleContext.Provider>
);
};
7 changes: 7 additions & 0 deletions frontend/src/component/user/NewUser/NewUser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom';
import { Box, TextField, Typography } from '@mui/material';
import { CREATED, OK } from 'constants/statusCodes';
import useToast from 'hooks/useToast';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import useResetPassword from 'hooks/api/getters/useResetPassword/useResetPassword';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useUserInvite } from 'hooks/api/getters/useUserInvite/useUserInvite';
Expand Down Expand Up @@ -39,11 +40,17 @@ export const NewUser = () => {
const { resetPassword, loading: isPasswordSubmitting } =
useAuthResetPasswordApi();
const passwordDisabled = authDetails?.defaultHidden === true;
const { trackEvent } = usePlausibleTracker();

const onSubmitInvitedUser = async (password: string) => {
try {
const res = await addUser(secret, { name, email, password });
if (res.status === CREATED) {
trackEvent('invite', {
props: {
eventType: 'user created',
},
});
navigate('/login?invited=true');
} else {
setToastApiError(
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/contexts/PlausibleContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createContext } from 'react';
import Plausible from 'plausible-tracker';

export const PlausibleContext = createContext<ReturnType<
typeof Plausible
> | null>(null);
17 changes: 0 additions & 17 deletions frontend/src/hooks/usePlausibleTracker.test.ts

This file was deleted.

57 changes: 27 additions & 30 deletions frontend/src/hooks/usePlausibleTracker.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,34 @@
import Plausible from 'plausible-tracker';
import { useEffect } from 'react';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { IFlags } from 'interfaces/uiConfig';
import { useCallback, useContext, useEffect } from 'react';
import { PlausibleContext } from 'contexts/PlausibleContext';
import { EventOptions, PlausibleOptions } from 'plausible-tracker';

const PLAUSIBLE_UNLEASH_API_HOST = 'https://plausible.getunleash.io';
const PLAUSIBLE_UNLEASH_DOMAIN = 'app.unleash-hosted.com';
/**
* Allowed event names. Makes it easy to remove, since TS will complain.
* Add those to Plausible as Custom event goals.
* @see https://plausible.io/docs/custom-event-goals#2-create-a-custom-event-goal-in-your-plausible-analytics-account
* @example `'download | 'invite' | 'signup'`
**/
type CustomEvents = 'invite';

export const usePlausibleTracker = () => {
const { uiConfig } = useUiConfig();
const enabled = enablePlausibleTracker(uiConfig.flags);
const plausible = useContext(PlausibleContext);

useEffect(() => {
if (enabled) {
try {
return initPlausibleTracker();
} catch (error) {
console.warn(error);
const trackEvent = useCallback(
(
eventName: CustomEvents,
options?: EventOptions | undefined,
eventData?: PlausibleOptions | undefined
) => {
if (plausible?.trackEvent) {
plausible.trackEvent(eventName, options, eventData);
} else {
if (options?.callback) {
options.callback();
}
}
}
}, [enabled]);
};

const initPlausibleTracker = (): (() => void) => {
const plausible = Plausible({
domain: PLAUSIBLE_UNLEASH_DOMAIN,
apiHost: PLAUSIBLE_UNLEASH_API_HOST,
trackLocalhost: true,
});

return plausible.enableAutoPageviews();
};
},
[plausible]
);

// Enable Plausible if we're on the Unleash SaaS domain.
export const enablePlausibleTracker = (flags: Partial<IFlags>): boolean => {
return Boolean(flags.T);
return { trackEvent };
};

0 comments on commit 2b83b8a

Please sign in to comment.