fix: update tracking on homepage widgets (#24153)

This commit is contained in:
Adrien L 2025-08-13 11:32:10 +02:00 committed by GitHub
parent 9651e7ce33
commit 330c50cc41
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 95 additions and 24 deletions

View File

@ -1,4 +1,4 @@
import { useAuth } from '@strapi/admin/strapi-admin'; import { useAuth, useTracking } from '@strapi/admin/strapi-admin';
import { Avatar, Badge, Box, Flex, Typography } from '@strapi/design-system'; import { Avatar, Badge, Box, Flex, Typography } from '@strapi/design-system';
import { Earth, Images, User, Key, Files, Layout, Graph, Webhooks } from '@strapi/icons'; import { Earth, Images, User, Key, Files, Layout, Graph, Webhooks } from '@strapi/icons';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
@ -85,6 +85,7 @@ const LinkCell = styled(Link)`
`; `;
const KeyStatisticsWidget = () => { const KeyStatisticsWidget = () => {
const { trackUsage } = useTracking();
const { formatMessage, locale } = useIntl(); const { formatMessage, locale } = useIntl();
const { data: countDocuments, isLoading: isLoadingCountDocuments } = useGetCountDocumentsQuery(); const { data: countDocuments, isLoading: isLoadingCountDocuments } = useGetCountDocumentsQuery();
const { data: countKeyStatistics, isLoading: isLoadingKeyStatistics } = const { data: countKeyStatistics, isLoading: isLoadingKeyStatistics } =
@ -216,6 +217,7 @@ const KeyStatisticsWidget = () => {
to={item.link} to={item.link}
key={`key-statistics-${key}`} key={`key-statistics-${key}`}
data-testid={`stat-${key}`} data-testid={`stat-${key}`}
onClick={() => trackUsage('didOpenKeyStatisticsWidgetLink', { itemKey: key })}
> >
<Flex alignItems="center" gap={2}> <Flex alignItems="center" gap={2}>
<Flex <Flex

View File

@ -7,7 +7,11 @@ import { Widget } from '../WidgetHelpers';
describe('Homepage Widget component', () => { describe('Homepage Widget component', () => {
it('should render the widget with info from props', () => { it('should render the widget with info from props', () => {
render( render(
<WidgetRoot title={{ defaultMessage: 'Cool widget title', id: 'notarealid' }} icon={Cog}> <WidgetRoot
title={{ defaultMessage: 'Cool widget title', id: 'notarealid' }}
icon={Cog}
uid="plugin::test.test-widget"
>
actual widget content actual widget content
</WidgetRoot> </WidgetRoot>
); );
@ -19,7 +23,10 @@ describe('Homepage Widget component', () => {
it('should render a spinner while a widget is loading', () => { it('should render a spinner while a widget is loading', () => {
render( render(
<WidgetRoot title={{ defaultMessage: 'Cool widget title', id: 'notarealid' }}> <WidgetRoot
title={{ defaultMessage: 'Cool widget title', id: 'notarealid' }}
uid="plugin::test.test-widget"
>
<Widget.Loading /> <Widget.Loading />
</WidgetRoot> </WidgetRoot>
); );
@ -30,7 +37,10 @@ describe('Homepage Widget component', () => {
it('should render an error message when a widget fails to load', () => { it('should render an error message when a widget fails to load', () => {
render( render(
<WidgetRoot title={{ defaultMessage: 'Cool widget title', id: 'notarealid' }}> <WidgetRoot
title={{ defaultMessage: 'Cool widget title', id: 'notarealid' }}
uid="plugin::test.test-widget"
>
<Widget.Error /> <Widget.Error />
</WidgetRoot> </WidgetRoot>
); );
@ -41,7 +51,10 @@ describe('Homepage Widget component', () => {
it('should render a custom error message when provided', () => { it('should render a custom error message when provided', () => {
render( render(
<WidgetRoot title={{ defaultMessage: 'Cool widget title', id: 'notarealid' }}> <WidgetRoot
title={{ defaultMessage: 'Cool widget title', id: 'notarealid' }}
uid="plugin::test.test-widget"
>
<Widget.Error>Custom error message</Widget.Error> <Widget.Error>Custom error message</Widget.Error>
</WidgetRoot> </WidgetRoot>
); );
@ -52,7 +65,10 @@ describe('Homepage Widget component', () => {
it('should render a no data message when a widget has no data', () => { it('should render a no data message when a widget has no data', () => {
render( render(
<WidgetRoot title={{ defaultMessage: 'Cool widget title', id: 'notarealid' }}> <WidgetRoot
title={{ defaultMessage: 'Cool widget title', id: 'notarealid' }}
uid="plugin::test.test-widget"
>
<Widget.NoData /> <Widget.NoData />
</WidgetRoot> </WidgetRoot>
); );
@ -62,7 +78,10 @@ describe('Homepage Widget component', () => {
it('should render a custom no data message when provided', () => { it('should render a custom no data message when provided', () => {
render( render(
<WidgetRoot title={{ defaultMessage: 'Cool widget title', id: 'notarealid' }}> <WidgetRoot
title={{ defaultMessage: 'Cool widget title', id: 'notarealid' }}
uid="plugin::test.test-widget"
>
<Widget.NoData>Custom no data message</Widget.NoData> <Widget.NoData>Custom no data message</Widget.NoData>
</WidgetRoot> </WidgetRoot>
); );

View File

@ -175,7 +175,6 @@ export interface EventWithoutProperties {
| 'willEditEditLayout' | 'willEditEditLayout'
| 'willEditEmailTemplates' | 'willEditEmailTemplates'
| 'willEditEntryFromButton' | 'willEditEntryFromButton'
| 'willEditEntryFromHome'
| 'willEditEntryFromList' | 'willEditEntryFromList'
| 'willEditReleaseFromHome' | 'willEditReleaseFromHome'
| 'willEditFieldOfContentType' | 'willEditFieldOfContentType'
@ -187,7 +186,7 @@ export interface EventWithoutProperties {
| 'willEditStage' | 'willEditStage'
| 'willFilterEntries' | 'willFilterEntries'
| 'willInstallPlugin' | 'willInstallPlugin'
| 'willOpenAuditLogDetails' | 'willOpenAuditLogDetailsFromHome'
| 'willUnpublishEntry' | 'willUnpublishEntry'
| 'willSaveComponent' | 'willSaveComponent'
| 'willSaveContentType' | 'willSaveContentType'
@ -411,6 +410,27 @@ interface DidStartGuidedTour {
}; };
} }
interface WillEditEntryFromHome {
name: 'willEditEntryFromHome';
properties: {
entryType: 'edited' | 'published' | 'assigned';
};
}
interface DidOpenHomeWidgetLink {
name: 'didOpenHomeWidgetLink';
properties: {
widgetUID: string;
};
}
interface DidOpenKeyStatisticsWidgetLink {
name: 'didOpenKeyStatisticsWidgetLink';
properties: {
itemKey: string;
};
}
type EventsWithProperties = type EventsWithProperties =
| CreateEntryEvents | CreateEntryEvents
| PublishEntryEvents | PublishEntryEvents
@ -436,7 +456,10 @@ type EventsWithProperties =
| DidUpdateCTBSchema | DidUpdateCTBSchema
| DidSkipGuidedTour | DidSkipGuidedTour
| DidCompleteGuidedTour | DidCompleteGuidedTour
| DidStartGuidedTour; | DidStartGuidedTour
| DidOpenHomeWidgetLink
| DidOpenKeyStatisticsWidgetLink
| WillEditEntryFromHome;
export type TrackingEvent = EventWithoutProperties | EventsWithProperties; export type TrackingEvent = EventWithoutProperties | EventsWithProperties;
export interface UseTrackingReturn { export interface UseTrackingReturn {

View File

@ -12,6 +12,7 @@ import { Widget } from '../../components/WidgetHelpers';
import { useEnterprise } from '../../ee'; import { useEnterprise } from '../../ee';
import { useAuth } from '../../features/Auth'; import { useAuth } from '../../features/Auth';
import { useStrapiApp } from '../../features/StrapiApp'; import { useStrapiApp } from '../../features/StrapiApp';
import { useTracking } from '../../features/Tracking';
import { FreeTrialEndedModal } from './components/FreeTrialEndedModal'; import { FreeTrialEndedModal } from './components/FreeTrialEndedModal';
import { FreeTrialWelcomeModal } from './components/FreeTrialWelcomeModal'; import { FreeTrialWelcomeModal } from './components/FreeTrialWelcomeModal';
@ -23,15 +24,21 @@ import type { WidgetType } from '@strapi/admin/strapi-admin';
* WidgetRoot * WidgetRoot
* -----------------------------------------------------------------------------------------------*/ * -----------------------------------------------------------------------------------------------*/
interface WidgetRootProps extends Pick<WidgetType, 'title' | 'icon' | 'permissions' | 'link'> { interface WidgetRootProps
extends Pick<WidgetType, 'title' | 'icon' | 'permissions' | 'link' | 'uid'> {
children: React.ReactNode; children: React.ReactNode;
} }
export const WidgetRoot = ({ title, icon = PuzzlePiece, children, link }: WidgetRootProps) => { export const WidgetRoot = ({ title, icon = PuzzlePiece, children, link, uid }: WidgetRootProps) => {
const { trackUsage } = useTracking();
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const id = React.useId(); const id = React.useId();
const Icon = icon; const Icon = icon;
const handleClickOnLink = () => {
trackUsage('didOpenHomeWidgetLink', { widgetUID: uid });
};
return ( return (
<Flex <Flex
width="100%" width="100%"
@ -61,6 +68,7 @@ export const WidgetRoot = ({ title, icon = PuzzlePiece, children, link }: Widget
style={{ textDecoration: 'none' }} style={{ textDecoration: 'none' }}
textAlign="right" textAlign="right"
to={link.href} to={link.href}
onClick={handleClickOnLink}
> >
{formatMessage(link.label)} {formatMessage(link.label)}
</Typography> </Typography>
@ -157,7 +165,12 @@ const HomePageCE = () => {
<Grid.Root gap={5}> <Grid.Root gap={5}>
{filteredWidgets.map((widget) => ( {filteredWidgets.map((widget) => (
<Grid.Item col={6} s={12} key={widget.uid}> <Grid.Item col={6} s={12} key={widget.uid}>
<WidgetRoot title={widget.title} icon={widget.icon} link={widget.link}> <WidgetRoot
title={widget.title}
icon={widget.icon}
link={widget.link}
uid={widget.uid}
>
<WidgetComponent component={widget.component} /> <WidgetComponent component={widget.component} />
</WidgetRoot> </WidgetRoot>
</Grid.Item> </Grid.Item>

View File

@ -30,7 +30,7 @@ const LastActivityTable = ({ items }: { items: AuditLog[] }) => {
}; };
const handleRowClick = (document: AuditLog) => () => { const handleRowClick = (document: AuditLog) => () => {
trackUsage('willOpenAuditLogDetails'); trackUsage('willOpenAuditLogDetailsFromHome');
const link = getAuditLogDetailsLink(document); const link = getAuditLogDetailsLink(document);
navigate(link); navigate(link);
}; };
@ -74,7 +74,7 @@ const LastActivityTable = ({ items }: { items: AuditLog[] }) => {
<IconButton <IconButton
tag={Link} tag={Link}
to={getAuditLogDetailsLink(item)} to={getAuditLogDetailsLink(item)}
onClick={() => trackUsage('willOpenAuditLogDetails')} onClick={() => trackUsage('willOpenAuditLogDetailsFromHome')}
label={formatMessage({ label={formatMessage({
id: 'global.details', id: 'global.details',
defaultMessage: 'Details', defaultMessage: 'Details',

View File

@ -32,7 +32,13 @@ const CellTypography = styled(Typography)`
white-space: nowrap; white-space: nowrap;
`; `;
const RecentDocumentsTable = ({ documents }: { documents: RecentDocument[] }) => { const RecentDocumentsTable = ({
documents,
type,
}: {
documents: RecentDocument[];
type: 'edited' | 'published';
}) => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const { trackUsage } = useTracking(); const { trackUsage } = useTracking();
const navigate = useNavigate(); const navigate = useNavigate();
@ -46,7 +52,9 @@ const RecentDocumentsTable = ({ documents }: { documents: RecentDocument[] }) =>
}; };
const handleRowClick = (document: RecentDocument) => () => { const handleRowClick = (document: RecentDocument) => () => {
trackUsage('willEditEntryFromHome'); trackUsage('willEditEntryFromHome', {
entryType: type,
});
const link = getEditViewLink(document); const link = getEditViewLink(document);
navigate(link); navigate(link);
}; };
@ -95,7 +103,7 @@ const RecentDocumentsTable = ({ documents }: { documents: RecentDocument[] }) =>
<IconButton <IconButton
tag={Link} tag={Link}
to={getEditViewLink(document)} to={getEditViewLink(document)}
onClick={() => trackUsage('willEditEntryFromHome')} onClick={() => trackUsage('willEditEntryFromHome', { type })}
label={formatMessage({ label={formatMessage({
id: 'content-manager.actions.edit.label', id: 'content-manager.actions.edit.label',
defaultMessage: 'Edit', defaultMessage: 'Edit',
@ -140,7 +148,7 @@ const LastEditedWidget = () => {
); );
} }
return <RecentDocumentsTable documents={data} />; return <RecentDocumentsTable documents={data} type="edited" />;
}; };
/* ------------------------------------------------------------------------------------------------- /* -------------------------------------------------------------------------------------------------
@ -170,7 +178,7 @@ const LastPublishedWidget = () => {
); );
} }
return <RecentDocumentsTable documents={data} />; return <RecentDocumentsTable documents={data} type="published" />;
}; };
/* ------------------------------------------------------------------------------------------------- /* -------------------------------------------------------------------------------------------------

View File

@ -19,7 +19,13 @@ const CellTypography = styled(Typography)`
white-space: nowrap; white-space: nowrap;
`; `;
const RecentDocumentsTable = ({ documents }: { documents: RecentDocument[] }) => { const RecentDocumentsTable = ({
documents,
type,
}: {
documents: RecentDocument[];
type: 'assigned';
}) => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const { trackUsage } = useTracking(); const { trackUsage } = useTracking();
const navigate = useNavigate(); const navigate = useNavigate();
@ -33,7 +39,7 @@ const RecentDocumentsTable = ({ documents }: { documents: RecentDocument[] }) =>
}; };
const handleRowClick = (document: RecentDocument) => () => { const handleRowClick = (document: RecentDocument) => () => {
trackUsage('willEditEntryFromHome'); trackUsage('willEditEntryFromHome', { entryType: type });
const link = getEditViewLink(document); const link = getEditViewLink(document);
navigate(link); navigate(link);
}; };
@ -85,7 +91,7 @@ const RecentDocumentsTable = ({ documents }: { documents: RecentDocument[] }) =>
<IconButton <IconButton
tag={Link} tag={Link}
to={getEditViewLink(document)} to={getEditViewLink(document)}
onClick={() => trackUsage('willEditEntryFromHome')} onClick={() => trackUsage('willEditEntryFromHome', { entryType: type })}
label={formatMessage({ label={formatMessage({
id: 'content-manager.actions.edit.label', id: 'content-manager.actions.edit.label',
defaultMessage: 'Edit', defaultMessage: 'Edit',
@ -130,7 +136,7 @@ const AssignedWidget = () => {
); );
} }
return <RecentDocumentsTable documents={data} />; return <RecentDocumentsTable documents={data} type="assigned" />;
}; };
export { AssignedWidget }; export { AssignedWidget };