Make 'to' prop an attribute, button text the primary subject.

Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
Eric Peterson
2021-09-26 19:53:21 +02:00
parent b500fac551
commit 0da4df22a3
3 changed files with 39 additions and 7 deletions
@@ -4,17 +4,22 @@
The `<Link />` 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"
}
}
```
@@ -62,7 +62,10 @@ describe('<Link />', () => {
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.
@@ -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<any, LinkProps>(
({ 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<any, MouseEvent>) => {
onClick?.(event);
analytics.captureEvent('click', to);
analytics.captureEvent('click', linkText, { attributes: { to } });
};
return external ? (