Skip to content

Commit

Permalink
feat(MetricsElement): add MetricsElement component
Browse files Browse the repository at this point in the history
taak77 committed Jul 16, 2016

Verified

This commit was signed with the committer’s verified signature. The key has expired.
tdmorello Tim Morello
1 parent 7ec7ba1 commit b7c108e
Showing 16 changed files with 715 additions and 16 deletions.
41 changes: 40 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -93,7 +93,7 @@ Your application will now automatically trigger page view tracking.

### 3. Add Custom Link Tracking

Use `data-` attributes to enable custom link tracking on your DOM elements.
a. Use `data-` attributes to enable custom link tracking on your DOM elements.

```javascript
// PaginationComponent.js
@@ -127,6 +127,45 @@ class PaginationComponent extends React.Component {
}
```

b. Use [`MetricsElement`](/docs/api/ReactMetrics.md#MetricsElement) for custom link tracking on a nested DOM element.

Please see [`MetricsElement`](/docs/api/ReactMetrics.md#MetricsElement) for more use cases.

```javascript
import {MetricsElement} from "react-metrics";
// PaginationComponent.js
class PaginationComponent extends React.Component {
render() {
const {commentId, totalPage, currentPage} = this.props;
return (
<ul>
<li className={currentPage > 0 ? "active" : ""}>
<MetricsElement
element="a"
href="#"
data-metrics-event-name="commentPageClick"
data-metrics-comment-id={commentId}
data-metrics-page-num={currentPage - 1}>
<span className="button">Back</span>
</MetricsElement>
</li>
<li>...</li>
<li className={currentPage < totalPage - 1 ? "active" : ""}>
<MetricsElement
element="a"
href="#"
data-metrics-event-name="commentPageClick"
data-metrics-comment-id={commentId}
data-metrics-page-num={currentPage + 1}>
<span className="button">Next</span>
</MetricsElement>
</li>
</ul>
);
}
}
```

### 4. Analytics Vendor Implementations

`react-metrics` does not automatically supply any vendor analytics. You need to integrate with an analytics vendor to actually track something for reporting.
38 changes: 38 additions & 0 deletions docs/GettingStarted.md
Original file line number Diff line number Diff line change
@@ -242,6 +242,44 @@ There are 2 ways to call `track` api from your component.
}
```
In a case where the element you are tracking is not the click target, you can use [`MetricsElement`](/docs/api/ReactMetrics.md#MetricsElement).
If you use [`MetricsElement`](/docs/api/ReactMetrics.md#MetricsElement) for all declarative tracking, we recommend turning off default track-binding by passing `useTrackBinding: false` in the [`metrics`](/docs/api/ReactMetrics.md#metrics) options.
Example:
```javascript
import {MetricsElement} from "react-metrics";

class PaginationComponent extends React.Component {
render() {
const {commentId, totalPage, currentPage} = this.props;
return (
<MetricsElement element="ul">
<li className={currentPage > 0 ? "active" : ""}>
<a
href="#"
data-metrics-event-name="commentPageClick"
data-metrics-comment-id={commentId}
data-metrics-page-num={currentPage - 1}>
<span className="back">Back</span>
</a>
</li>
<li>...</li>
<li className={currentPage < totalPage - 1 ? "active" : ""}>
<a
href="#"
data-metrics-event-name="commentPageClick"
data-metrics-comment-id={commentId}
data-metrics-page-num={currentPage + 1}>
<span className="next">Next</span>
</a>
</li>
</MetricsElement>
);
}
}
```
2. **Imperative** by calling the API explicitly. To do this, define `metrics` context as one of `contextTypes` in your child component. This allows you to call the `track` API. You can pass either an Object or a Promise as a second argument. It's your responsibility to implement the `track` API in your service, otherwise calling the API simply throws an error.
Example:
1 change: 1 addition & 0 deletions docs/api/README.md
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@
* [exposeMetrics](/docs/api/ReactMetrics.md#exposeMetrics)
* [PropTypes](/docs/api/ReactMetrics.md#PropTypes)
* [createMetrics(config)](/docs/api/ReactMetrics.md#createMetrics)
* [MetricsElement](/docs/api/ReactMetrics.md#MetricsElement)

### Metrics API

85 changes: 84 additions & 1 deletion docs/api/ReactMetrics.md
Original file line number Diff line number Diff line change
@@ -81,13 +81,15 @@ Example:
{
autoTrackPageView: false,
useTrackBinding: true,
attributePrefix: "custom"
attributePrefix: "custom",
suppressTrackBindingWarning: true
}
```

- `autoTrackPageView`(optional) - A flag to indicate whether a page view is triggered automatically by a route change detection or not. Default value is `true`.
- `useTrackBinding`(optional) - A flag to indicate whether metrics should use track binding. Default value is `true`.
- `attributePrefix`(optional) - An element attribute prefix to use for tracking bining. Default value is `data-metrics`.
- `suppressTrackBindingWarning`(optional) - A flag to indicate whether the warning which is presented when both the default track binding and [`MetricsElement`](#MetricsElement) are used should be suppressed or not. Default value is `false`.

### <a id='exposeMetrics'></a>[`exposeMetrics`](#exposeMetrics)

@@ -159,3 +161,84 @@ export default function metricsMiddleware() {
};
}
```

### <a id='MetricsElement'></a>[`MetricsElement`](#MetricsElement)

`MetricsElement` is a react component which detects click event on tracking elements within its children including itself and sends tracking data.

To minimize the child element traversing, it is recommended that you add `MetricsElement` as the closest common parent against the children you are tracking.

Also, when you use `MetricsElement` in your application, you should stick with `MetricsElement` for all the declarative tracking and turn off the default track binding by passing `useTrackBinding=false` to the [`metrics`](#metrics) options to avoid double tracking accidentally.

#### Props

- `element`(optional) - Either a string to indicate a html element, a component class or a component instance to render.
- any arbitrary tracking attributes.

#### Usage

Sends tracking data defined as its own props. You can pass the `element` prop with html tag string, component class or component instance.
If a component instance is passed as `element` props, it will be cloned with new props merged into the original props of the component instance.

Example:

```javascript
import React from "react";
import {MetricsElement} from "react-metrics";

const MyComponent = () => (
<div>
<MetricsElement element="a" data-metrics-event-name="SomeEvent" data-metrics-value="SomeValue">
<img src="..."/>
</MetricsElement>
</div>
);
```

If `element` is not set, it renders its children only.

Example:

```javascript
import React from "react";
import {MetricsElement} from "react-metrics";

const MyComponent = () => (
<div>
<MetricsElement>
<a data-tracking-event-name="SomeEvent" data-tracking-value="SomeValue">
<img src="..." />
</a>
</MetricsElement>
</div>
);
```

Sends tracking data defined as child component's props.

Example:

```javascript
import React from "react";
import {MetricsElement} from "react-metrics";

const MyComponent = (props) => {
const listItem = props.items.map(item => (
<li
key={item.id}
data-metrics-event-name="SomeEvent"
data-metrics-key={item.id}
data-metrics-value={item.value}
>
<img src={item.imageUrl} alt={item.title} />
</li>
));
return (
<div>
<MetricsElement element="ul">
{listItem}
</MetricsElement>
</div>
);
};
```
1 change: 1 addition & 0 deletions examples/react-router/index.html
Original file line number Diff line number Diff line change
@@ -5,5 +5,6 @@
<h1 class="breadcrumbs"><a href="../index.html">React Metrics Examples</a> / React Router</h1>
<ul>
<li><a href="basic/index.html">Basic Example</a></li>
<li><a href="metricsElement/index.html">Metrics Element Example</a></li>
</ul>

51 changes: 51 additions & 0 deletions examples/react-router/metricsElement/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/* eslint-disable react/no-multi-comp */
import React, {Component, PropTypes} from "react";
import ReactDOM from "react-dom";
import {Router, Route, IndexRoute, Link, IndexLink, hashHistory} from "react-router";
import {metrics, MetricsElement} from "react-metrics"; // eslint-disable-line import/no-unresolved
import MetricsConfig from "./metrics.config";
import Home from "./home";
import Page from "./page";

class App extends Component {
static displayName = "My Application";

static propTypes = {
children: PropTypes.node
};

render() {
const link = (<Link to="/page/A" data-tracking-event-name="linkClick" data-tracking-value="A" ><span>Page A</span></Link>);
return (
<div>
<ul>
<li><MetricsElement><IndexLink to="/" data-tracking-event-name="linkClick"><span>Home</span></IndexLink></MetricsElement></li>
<li><MetricsElement element={link}><span>This span won't render</span></MetricsElement></li>
<li><MetricsElement to="/page/B" data-tracking-event-name="linkClick" data-tracking-value="B" element={Link}><span>Page B</span></MetricsElement></li>
</ul>
{this.props.children && React.cloneElement(this.props.children, {
appName: App.displayName
})}
</div>
);
}
}
const DecoratedApp = metrics(MetricsConfig, {useTrackBinding: false, attributePrefix: "data-tracking"})(App);

class NotFound extends Component {
render() {
return (
<h1>404!</h1>
);
}
}

ReactDOM.render((
<Router history={hashHistory}>
<Route path="/" component={DecoratedApp}>
<IndexRoute component={Home}/>
<Route path="page/:id" component={Page}/>
<Route path="*" component={NotFound}/>
</Route>
</Router>
), document.getElementById("example"));
9 changes: 9 additions & 0 deletions examples/react-router/metricsElement/home.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from "react";

class Home extends React.Component {
render() {
return <h1>Home</h1>;
}
}

export default Home;
9 changes: 9 additions & 0 deletions examples/react-router/metricsElement/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!doctype html>
<title>React Router Example</title>
<link href="../../global.css" rel="stylesheet"/>
<body>
<h1 class="breadcrumbs"><a href="../../index.html">React Metrics Examples</a> / <a href="../index.html">React Router</a> / MetricsElement Example</h1>
<div id="example"></div>
<script src="/__build__/shared.js"></script>
<script src="/__build__/react-router_metricsElement.js"></script>

49 changes: 49 additions & 0 deletions examples/react-router/metricsElement/metrics.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import pkg from "../../../package.json";
const config = {
vendors: [
{
api: {
name: "Test",
pageView(eventName, params) {
return new Promise(resolve => {
setTimeout(() => {
resolve({
eventName,
params
});
}, 0 * 1000);
});
},
track(eventName, params) {
return new Promise(resolve => {
resolve({
eventName,
params
});
});
},
user(user) {
return new Promise(resolve => {
resolve({
user
});
});
}
}
}
],
pageDefaults: (routeState) => {
const paths = routeState.pathname.substr(1).split("/");
const timestamp = new Date();
return {
timestamp,
build: pkg.version,
siteName: "React Metrics Example",
category: !paths[0] ? "landing" : paths[0]
};
},
pageViewEvent: "pageLoad",
debug: true
};

export default config;
48 changes: 48 additions & 0 deletions examples/react-router/metricsElement/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, {PropTypes} from "react";
import {MetricsElement} from "react-metrics"; // eslint-disable-line import/no-unresolved

// Note: `data-tracking` prefix is set in app.js as `attributePrefix` option

class Page extends React.Component {
static propTypes = {
params: PropTypes.object
}

render() {
const {params} = this.props;
const listItem = Array.from("123").map(key => (
<li
key={key}
data-tracking-event-name="SomeEvent"
data-tracking-key={key}
data-tracking-page={params.id}
>
<img src={`http://placehold.it/200x50?text=Item+${key}`}/>
</li>
));

return (
<div>
<h1>Page {params.id}</h1>
{/* Ex 1: self target */}
<MetricsElement element="a" data-tracking-event-name="SomeEvent" data-tracking-value="SomeValue">Link</MetricsElement>
{/* Ex 2: render children only */}
<MetricsElement>
<a data-tracking-event-name="SomeEvent" data-tracking-value="SomeValue">
<img src="http://placehold.it/200x150?text=Image+1" style={{padding: 5}} />
</a>
</MetricsElement>
{/* Ex 3: event bubbling */}
<MetricsElement element="a" data-tracking-event-name="SomeEvent" data-tracking-value="SomeValue">
<img src="http://placehold.it/200x150?text=Image+2" style={{padding: 5}} />
</MetricsElement>
{/* Ex 4: multiple tracking items */}
<MetricsElement element="ul" style={{listStyle: "none", width: 200, padding: 0}}>
{listItem}
</MetricsElement>
</div>
);
}
}

export default Page;
Loading

0 comments on commit b7c108e

Please sign in to comment.