mirror of
https://github.com/strapi/strapi.git
synced 2025-11-01 18:33:55 +00:00
future: Guided Tour Tracking (#23926)
Co-authored-by: Mark Kaylor <mark.kaylor@strapi.io>
This commit is contained in:
parent
a970fea681
commit
fc585c658c
@ -3,6 +3,7 @@ import * as React from 'react';
|
||||
import { produce } from 'immer';
|
||||
|
||||
import { GetGuidedTourMeta } from '../../../../shared/contracts/admin';
|
||||
import { useTracking } from '../../features/Tracking';
|
||||
import { usePersistentState } from '../../hooks/usePersistentState';
|
||||
import { createContext } from '../Context';
|
||||
|
||||
@ -71,7 +72,6 @@ function reducer(state: State, action: Action): State {
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'STRAPI_GUIDED_TOUR';
|
||||
|
||||
const UnstableGuidedTourContext = ({
|
||||
children,
|
||||
enabled = true,
|
||||
|
||||
@ -4,6 +4,7 @@ import { useIntl } from 'react-intl';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { styled, useTheme } from 'styled-components';
|
||||
|
||||
import { useTracking } from '../../features/Tracking';
|
||||
import { useGetGuidedTourMetaQuery } from '../../services/admin';
|
||||
import { ConfirmDialog } from '../ConfirmDialog';
|
||||
|
||||
@ -144,6 +145,7 @@ export const UnstableGuidedTourOverview = () => {
|
||||
const enabled = unstableUseGuidedTour('Overview', (s) => s.state.enabled);
|
||||
const { data: guidedTourMeta } = useGetGuidedTourMetaQuery();
|
||||
const tourNames = Object.keys(tours) as ValidTourName[];
|
||||
const { trackUsage } = useTracking();
|
||||
|
||||
const completedTours = tourNames.filter((tourName) => tours[tourName].isCompleted);
|
||||
const completionPercentage =
|
||||
@ -153,6 +155,16 @@ export const UnstableGuidedTourOverview = () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleConfirmDialog = () => {
|
||||
trackUsage('didSkipGuidedTour', { name: 'all' });
|
||||
dispatch({ type: 'skip_all_tours' });
|
||||
};
|
||||
|
||||
const handleClickOnLink = (tourName: ValidTourName) => {
|
||||
trackUsage('didCompleteGuidedTour', { name: tourName });
|
||||
dispatch({ type: 'skip_tour', payload: tourName });
|
||||
};
|
||||
|
||||
return (
|
||||
<Container tag="section" gap={0}>
|
||||
{/* Greeting */}
|
||||
@ -192,7 +204,7 @@ export const UnstableGuidedTourOverview = () => {
|
||||
})}
|
||||
</Button>
|
||||
</Dialog.Trigger>
|
||||
<ConfirmDialog onConfirm={() => dispatch({ type: 'skip_all_tours' })}>
|
||||
<ConfirmDialog onConfirm={handleConfirmDialog}>
|
||||
{formatMessage({
|
||||
id: 'tours.overview.close.description',
|
||||
defaultMessage: 'Are you sure you want to close the guided tour?',
|
||||
@ -211,14 +223,11 @@ export const UnstableGuidedTourOverview = () => {
|
||||
</Typography>
|
||||
<Box width="100%" borderColor="neutral150" marginTop={4} hasRadius>
|
||||
{TASK_CONTENT.map((task) => {
|
||||
const tour = tours[task.tourName as ValidTourName];
|
||||
const tourName = task.tourName as ValidTourName;
|
||||
const tour = tours[tourName];
|
||||
|
||||
return (
|
||||
<TourTaskContainer
|
||||
key={task.tourName}
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<TourTaskContainer key={tourName} alignItems="center" justifyContent="space-between">
|
||||
{tour.isCompleted ? (
|
||||
<>
|
||||
<Flex gap={2}>
|
||||
@ -243,14 +252,19 @@ export const UnstableGuidedTourOverview = () => {
|
||||
<Link
|
||||
isExternal
|
||||
href={task.link.to}
|
||||
onClick={() =>
|
||||
dispatch({ type: 'skip_tour', payload: task.tourName as ValidTourName })
|
||||
}
|
||||
onClick={() => handleClickOnLink(task.tourName as ValidTourName)}
|
||||
>
|
||||
{formatMessage(task.link.label)}
|
||||
</Link>
|
||||
) : (
|
||||
<Link endIcon={<ChevronRight />} to={task.link.to} tag={NavLink}>
|
||||
<Link
|
||||
endIcon={<ChevronRight />}
|
||||
to={task.link.to}
|
||||
tag={NavLink}
|
||||
onClick={() =>
|
||||
trackUsage('didStartGuidedTourFromHomepage', { name: tourName })
|
||||
}
|
||||
>
|
||||
{formatMessage(task.link.label)}
|
||||
</Link>
|
||||
)}
|
||||
|
||||
@ -13,6 +13,8 @@ import { FormattedMessage, type MessageDescriptor } from 'react-intl';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { styled } from 'styled-components';
|
||||
|
||||
import { type EventWithoutProperties, useTracking } from '../../features/Tracking';
|
||||
|
||||
import { unstableUseGuidedTour, ValidTourName } from './Context';
|
||||
|
||||
/* -------------------------------------------------------------------------------------------------
|
||||
@ -137,7 +139,23 @@ const createStepComponents = (tourName: ValidTourName): Step => ({
|
||||
),
|
||||
|
||||
Actions: ({ showStepCount = true, showSkip = false, to, children, ...flexProps }) => {
|
||||
const { trackUsage } = useTracking();
|
||||
const dispatch = unstableUseGuidedTour('GuidedTourPopover', (s) => s.dispatch);
|
||||
const state = unstableUseGuidedTour('GuidedTourPopover', (s) => s.state);
|
||||
const currentStep = state.tours[tourName].currentStep + 1;
|
||||
const actualTourLength = state.tours[tourName].length;
|
||||
|
||||
const handleSkipAction = () => {
|
||||
trackUsage('didSkipGuidedTour', { name: tourName });
|
||||
dispatch({ type: 'skip_tour', payload: tourName });
|
||||
};
|
||||
|
||||
const handleNextStep = () => {
|
||||
if (currentStep === actualTourLength) {
|
||||
trackUsage('didCompleteGuidedTour', { name: tourName });
|
||||
}
|
||||
dispatch({ type: 'next_step', payload: tourName });
|
||||
};
|
||||
|
||||
return (
|
||||
<ActionsContainer
|
||||
@ -154,23 +172,16 @@ const createStepComponents = (tourName: ValidTourName): Step => ({
|
||||
{showStepCount && <StepCount tourName={tourName} />}
|
||||
<Flex gap={2}>
|
||||
{showSkip && (
|
||||
<Button
|
||||
variant="tertiary"
|
||||
onClick={() => dispatch({ type: 'skip_tour', payload: tourName })}
|
||||
>
|
||||
<Button variant="tertiary" onClick={handleSkipAction}>
|
||||
<FormattedMessage id="tours.skip" defaultMessage="Skip" />
|
||||
</Button>
|
||||
)}
|
||||
{to ? (
|
||||
<LinkButton
|
||||
tag={NavLink}
|
||||
to={to}
|
||||
onClick={() => dispatch({ type: 'next_step', payload: tourName })}
|
||||
>
|
||||
<LinkButton tag={NavLink} to={to} onClick={handleNextStep}>
|
||||
<FormattedMessage id="tours.next" defaultMessage="Next" />
|
||||
</LinkButton>
|
||||
) : (
|
||||
<Button onClick={() => dispatch({ type: 'next_step', payload: tourName })}>
|
||||
<Button onClick={handleNextStep}>
|
||||
<FormattedMessage id="tours.next" defaultMessage="Next" />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@ -2,6 +2,7 @@ import * as React from 'react';
|
||||
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
|
||||
import { Tours } from '../components/UnstableGuidedTour/Tours';
|
||||
import { useInitQuery, useTelemetryPropertiesQuery } from '../services/admin';
|
||||
|
||||
import { useAppInfo } from './AppInfo';
|
||||
@ -67,7 +68,7 @@ const TrackingProvider = ({ children }: TrackingProviderProps) => {
|
||||
* Meanwhile those with properties have different property shapes corresponding to the specific
|
||||
* event so understanding which properties go with which event is very helpful.
|
||||
*/
|
||||
interface EventWithoutProperties {
|
||||
export interface EventWithoutProperties {
|
||||
name:
|
||||
| 'changeComponentsOrder'
|
||||
| 'didAddComponentToDynamicZone'
|
||||
@ -161,7 +162,8 @@ interface EventWithoutProperties {
|
||||
| 'willSaveContentType'
|
||||
| 'willSaveContentTypeLayout'
|
||||
| 'didEditFieldNameOnContentType'
|
||||
| 'didCreateRelease';
|
||||
| 'didCreateRelease'
|
||||
| 'didLaunchGuidedtour';
|
||||
properties?: never;
|
||||
}
|
||||
|
||||
@ -357,6 +359,27 @@ interface DidUpdateCTBSchema {
|
||||
};
|
||||
}
|
||||
|
||||
interface DidSkipGuidedTour {
|
||||
name: 'didSkipGuidedTour';
|
||||
properties: {
|
||||
name: keyof Tours | 'all';
|
||||
};
|
||||
}
|
||||
|
||||
interface DidCompleteGuidedTour {
|
||||
name: 'didCompleteGuidedTour';
|
||||
properties: {
|
||||
name: keyof Tours;
|
||||
};
|
||||
}
|
||||
|
||||
interface DidStartGuidedTour {
|
||||
name: 'didStartGuidedTourFromHomepage';
|
||||
properties: {
|
||||
name: keyof Tours;
|
||||
};
|
||||
}
|
||||
|
||||
type EventsWithProperties =
|
||||
| CreateEntryEvents
|
||||
| PublishEntryEvents
|
||||
@ -379,7 +402,10 @@ type EventsWithProperties =
|
||||
| WillNavigateEvent
|
||||
| DidPublishRelease
|
||||
| MediaEvents
|
||||
| DidUpdateCTBSchema;
|
||||
| DidUpdateCTBSchema
|
||||
| DidSkipGuidedTour
|
||||
| DidCompleteGuidedTour
|
||||
| DidStartGuidedTour;
|
||||
|
||||
export type TrackingEvent = EventWithoutProperties | EventsWithProperties;
|
||||
export interface UseTrackingReturn {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user