diff --git a/.changeset/analytics-haunts-dismembered-constellations.md b/.changeset/analytics-haunts-dismembered-constellations.md index 851527d852..e7cfc52ce1 100644 --- a/.changeset/analytics-haunts-dismembered-constellations.md +++ b/.changeset/analytics-haunts-dismembered-constellations.md @@ -4,17 +4,22 @@ The `` component now automatically instruments all link clicks using the new Analytics API. Each click triggers a `click` event, containing the -location the user clicked to. In addition, these events inherit plugin-level -metadata, allowing clicks to be attributed to the plugin containing the link: +text of the link the user clicked on, as well as the location to which the user +clicked. In addition, these events inherit plugin/extension-level metadata, +allowing clicks to be attributed to the plugin/extension/route containing the +link: ```json { "action": "click", - "subject": "/value/of-the/to-prop/passed-to-the-link", + "subject": "Text content of the link that was clicked", + "attributes": { + "to": "/value/of-the/to-prop/passed-to-the-link" + }, "context": { - "extension": "SomeAssociatedExtension", + "extension": "ExtensionInWhichTheLinkWasClicked", "pluginId": "plugin-in-which-link-was-clicked", - "routeRef": "any-associated-route-ref-id" + "routeRef": "route-ref-in-which-the-link-was-clicked" } } ``` diff --git a/packages/core-components/src/components/Link/Link.test.tsx b/packages/core-components/src/components/Link/Link.test.tsx index 071b6fcffe..df1bcfac03 100644 --- a/packages/core-components/src/components/Link/Link.test.tsx +++ b/packages/core-components/src/components/Link/Link.test.tsx @@ -62,7 +62,10 @@ describe('', () => { await waitFor(() => { expect(analyticsApi.getEvents()[0]).toMatchObject({ action: 'click', - subject: '/test', + subject: linkText, + attributes: { + to: '/test', + }, }); // Custom onClick handler should have still been fired too. diff --git a/packages/core-components/src/components/Link/Link.tsx b/packages/core-components/src/components/Link/Link.tsx index fcfd2483de..d20773a8bb 100644 --- a/packages/core-components/src/components/Link/Link.tsx +++ b/packages/core-components/src/components/Link/Link.tsx @@ -34,6 +34,29 @@ export type LinkProps = MaterialLinkProps & declare function LinkType(props: LinkProps): JSX.Element; +/** + * Given a react node, try to retrieve its text content. + */ +const getNodeText = (node: React.ReactNode): string => { + // If the node is an array of children, recurse and join. + if (node instanceof Array) { + return node.map(getNodeText).join(' ').trim(); + } + + // If the node is a react element, recurse on its children. + if (typeof node === 'object' && node) { + return getNodeText((node as React.ReactElement)?.props?.children); + } + + // Base case: the node is just text. Return it. + if (['string', 'number'].includes(typeof node)) { + return String(node); + } + + // Base case: just return an empty string. + return ''; +}; + /** * Thin wrapper on top of material-ui's Link component, which... * - Makes the Link use react-router @@ -43,12 +66,13 @@ const ActualLink = React.forwardRef( ({ onClick, ...props }, ref) => { const analytics = useAnalytics(); const to = String(props.to); + const linkText = getNodeText(props.children) || to; const external = isExternalUri(to); const newWindow = external && !!/^https?:/.exec(to); const handleClick = (event: React.MouseEvent) => { onClick?.(event); - analytics.captureEvent('click', to); + analytics.captureEvent('click', linkText, { attributes: { to } }); }; return external ? (