import React from 'react';
import { code, Example, md } from '@atlaskit/docs';
export default md`
### Contents
* [Adding more information to an event](#adding-more-information-to-an-event)
* [Using a channel](#using-a-channel)
* [Passing an event to your consumers](#passing-an-event-to-your-consumers)
* [Cloning an event](#cloning-an-event)
* [Tracking events outside the UI](#tracking-events-outside-the-ui)
## Adding more information to an event
This package provides two methods for adding extra information to analytics events.
The first method is by adding data to the analytics events payload. The second method
is to provide contextual information to any event.
### Adding data to an event's payload
Before firing an analytics event, you can add data the payload by using the \`update\` method.
Here's an example of how that looks:
##### SaveButton.js
${code`
import Button from '@atlaskit/button';
const SaveButton = ({ onClick }) => (
);
`}
In addition to accepting an object, the \`update\` method accepts a function which is called
with the event's current payload and is expected to return a new payload.
Below is a fleshed out example demonstrating how to add extra information to the event's payload.
${(
)}
## Using a channel
The feature is likey more useful for component authors.
When calling \`fire\` on an analytics event, you can optionally specify a channel
to fire the event on. Only listeners on that channel will recieve the event.
##### Button.js (handleClick method)
${code`
handleClick = e => {
// Create our analytics event
const analyticsEvent = this.props.createAnalyticsEvent({ action: 'click' });
// Fire our analytics event on the 'atlaskit' channel
analyticsEvent.fire('atlaskit');
if (this.props.onClick) {
this.props.onClick(e);
}
};
`}
In the above example, we fire events on the \`'atlaskit'\` channel. To listen
on this channel we would set up our App like:
##### App.js (render method)
${code`
render() {
return (
);
}
`}
The \`AnalyticsListener\` component accepts \`channel\` and \`onEvent\` props. When an event is fired on this Listener's channel its onEvent function will be called.
## Passing an event to your consumers
The features described in this section are likey to be more useful to component
authors.
If you are building components, you might not want to fire an event as
soon as it's created - instead it is better to provide the event to the consumer
of your component. The consumer then has a chance to add more information and fire
the event when they're ready.
This is exactly the approach we took to instrument our own Atlaskit components.
This section will show you how we did it and how to use the same approach in
your components.
The most straight forward way is to pass the event as an extra argument to the
corresponding callback prop. Here's our updated Button component:
##### Button.js (handleClick method)
${code`
handleClick = e => {
// Create our analytics event
const analyticsEvent = this.props.createAnalyticsEvent({ action: 'click' });
if (this.props.onClick) {
// Pass the event through the corresponding callback prop
this.props.onClick(e, analyticsEvent);
}
};
`}
This is a pretty common pattern for component authors, so \`withAnalyticsEvents\`
provides a shortcut:
##### Button.js
${code`
const ButtonWithAnalytics = withAnalyticsEvents({
// simply provide a payload we'll automatically create an event for you
onClick: { action: 'click' }
})(Button);
`}
\`withAnalyticsEvents\` accepts an optional object mapping callback prop names to payloads.
This event will be automatically added as a final argument to the callback prop.
If the analytics event payload needs to include some information from the components
props, \`withAnalyticsEvents\` also accepts a function.
##### Button.js
${code`
const ButtonWithAnalytics = withAnalyticsEvents({
// this function should return an analytics event
onClick: (createEvent, props) => {
return createEvent({ action: 'click', appearance: props.appearance });
},
})(Button);
`}
${(
)}
## Cloning an event
The features described in this section are likey to be more useful to component
authors.
Once an event has been fired it cannot be updated or fired again. This poses a
problem for library component authors who want to record their own analytics
events, while also exposing analytics events to their consumers.
That's where \`.clone\` comes in.
Let's imagine a Form component. If it accepted an \`onSubmit\` callback prop we could do something like this:
##### Form.js (onSubmit method):
${code`
onSubmit = analyticsEvent => {
const { value } = this.state;
// Clone the analytics event
const publicEvent = analyticsEvent.clone();
// Add whatever data we want to know about to our event and fire it
analyticsEvent.update({ value }).fire('atlaskit');
if (this.props.onSubmit) {
// Pass the cloned event to the callback prop for consumers to use
this.props.onSubmit(value, publicEvent);
}
};
`}
This is a common enough usecase that we have built a helper to make this
easier to do.
##### Form.js (onSubmit method):
${code`
import { withAnalyticsEvents, createAndFireEvent } from '@atlaskit/analytics-next';
const FormWithAnalytics = withAnalyticsEvents({
onSubmit: createAndFireEvent('atlaskit')({ action: 'submit' })
})(Form);
`}
This will create the event with the payload, fire it on the specified channel
and return a clone of the event.
${(
)}
## Tracking events outside the UI
This library provides tools for tracking interactions with UI components, and makes it really easy to capture the UI context and state when these events occur. But what if the event you're tracking doesn't care about the UI? Can you still use this library to track it?
Well, sure - but you might not need to. An event has to be created by a UI component to get \`context\` and a \`.fire\` method. Without these properties an analytics event is basically a payload! It might be simpler to just create an object and pass it directly to the function that handles your events.
In case it is useful for you to have a consistent interface for your events, even if they're not coming from the UI, we do export the base \`AnalyticsEvent\` class. Here's an example of how you might use it:
${code`
import { AnalyticsEvent } from '@atlaskit/analytics-next';
import sendAnalyticsEventToBackend from './sendAnalyticsEventToBackend';
const fetchBacon = async () => {
const startTime = performance.now();
const data = (await (await fetch(
'https://baconipsum.com/api/?type=meat-and-filler',
)).json())[0];
const responseTime = performance.now() - startTime;
/** This event records server response time. This function doesn't live inside
* a component and we don't care what state the UI is in when it runs. It
* would be annoying if we had to create the event inside a component, then
* pass it to this function. We can create a generic AnalyticsEvent and handle
* it all inside this function instead. */
const analyticsEvent = new AnalyticsEvent({
payload: { action: 'server-request', data, responseTime },
});
/** Because we're not in the UI this event doesn't have any
* AnalyticsListeners, which means it doesn't have a .fire method. We need to
* explicitly pass it to the function that will handle it. */
sendAnalyticsEventToBackend(analyticsEvent);
return data;
};
`}
`;