Collect Analytics

Setup Pinpoint

The Analytics category of the Amplify enables you to collect analytics data for your App. The Analytics category comes with built-in support for Amazon Pinpoint.

  1. Run this Amplify CLI command to setup Analytics:
amplify analytics add
  • Select Amazon Pinpoint as Analytics provider
  • Set Pinpoint resource name, e.g. to traveldeals
? Select an Analytics provider Amazon Pinpoint
? Provide your pinpoint resource name: traveldeals
Auth configuration is required to allow unauthenticated users, but it is not configured properly.
Adding analytics would add the Auth category to the project if not already added.
? Apps need authorization to send analytics events. Do you want to allow guests and unauthenticated users to send analytics events? (we recommend you allo
w this when getting started) Yes
Successfully updated auth resource locally.
Successfully added resource traveldeals locally
  1. Push backend changes to the cloud:
amplify push

Confirm your decision, when prompted:

While the backend is pushed, you can continue with following sections to modify Web Application. However, functionality will not be available until amplify push is complete.

Add events tracking to Web Application

You can read about required code changes, but use Complete code to apply all of them.

Tracking events

Update the Web Application code in src/App.js to records events. Start with importing Analytics:

import Analytics from '@aws-amplify/analytics';

Custom events

To record custom events call the record method. We are going to track when a User is opening a Deal Creation modal. For this, update the code of DealCreation.handleOpen() in src/App.js:

Analytics.record({ name: 'createDeal-start'});

Session tracking

Analytics Auto Tracking helps you to automatically track user behaviors like sessions start/stop, page view change and web events like clicking, mouseover.

You can track the session in a web app by using Analytics. A web session can be defined in different ways. To keep it simple we define that the web session is active when the page is not hidden and inactive when the page is hidden. When the page is loaded, the Analytics module will send an event to the Amazon Pinpoint Service.

The auto tracking of the session is enabled by default.

Analytics.autoTrack('session', {
  enable: true
});

You can turn it off by setting enable property value to false.

Page view tracking

If you want to track which page/url in your webapp is the most frequently viewed one, you can use this feature. It will automatically send events containing url information when the page is visited. Add this after Amplify.configure() in src/App.js:

Analytics.autoTrack('pageView', {
  enable: true,
  type: 'SPA'
});

Page event tracking

If you want to track user interactions with elements on the page, you can use this feature. All you need to do is attach the specified selectors to your dom element and turn on the auto tracking.

Add this code block to turn page event tracking on:

Analytics.autoTrack('event', {
  enable: true
});

We are going to track a click of a Save button in the Deal Creation modal. Update the Button declaration with these additional properties:

<Button positive labelPosition='right' icon='checkmark' content='Save' href='/'
  disabled = {!(name && category)} 
  onClick={handleSave}
  data-amplify-analytics-on='click'
  data-amplify-analytics-name='createDeal-complete'
  data-amplify-analytics-attrs={`category:${category}`}/>

Complete code

Complete Code of src/App.js:

import React from 'react';
import './App.css';

import PropTypes from 'prop-types';
import { BrowserRouter as Router, Route, NavLink, Link } from 'react-router-dom';
import { Divider, Form, Icon, Input, Modal, Button, Card, Menu, Dropdown, 
  Container, Header, Segment, Placeholder } from 'semantic-ui-react';

import Amplify, { Auth } from 'aws-amplify';
import Analytics from '@aws-amplify/analytics';
import API, { graphqlOperation } from '@aws-amplify/api';
import { AuthState, onAuthUIStateChange } from '@aws-amplify/ui-components';
import { AmplifyAuthenticator, AmplifySignUp } from '@aws-amplify/ui-react';

import * as queries from './graphql/queries';
import * as mutations from './graphql/mutations';
import * as subscriptions from './graphql/subscriptions';

import faker from 'faker';

import awsconfig from './aws-exports';
Amplify.configure(awsconfig);

Analytics.autoTrack('pageView', {
  enable: true,
  type: 'SPA'
});

Analytics.autoTrack('event', {
  enable: true
});

const CATEGORIES = ['Outdoors', 'Cities'];
const COLORS = ['orange', 'yellow', 'green', 'blue', 'violet', 'purple', 'pink'];

function DealCardImage({dealName, minHeight, fontSize}) {
  function dealColor(name) {
    if (!name) name = '';
    return COLORS[Math.floor(name.length % COLORS.length)];
  }

  return (
    <Segment style={{minHeight, display: 'flex'}} inverted color={dealColor(dealName)} vertical>
      <Header style={{margin: 'auto auto', fontSize}}>{dealName}</Header>
    </Segment>
  );
}

DealCardImage.propTypes = {
  dealName: PropTypes.string,
  minHeight: PropTypes.number,
  fontSize: PropTypes.number
};

function DealCreation() {
  const [modalOpen, setModalOpen] = React.useState(false);
  const [name, setName] = React.useState();
  const [category, setCategory] = React.useState();
  
  function handleOpen() {
    handleReset();
    setModalOpen(true);
    Analytics.record({ name: 'createDeal-start'});
  };

  function handleReset() {
    setName(faker.address.city())
    setCategory(CATEGORIES[Math.floor(Math.random() * CATEGORIES.length)]);
  }

  function handleClose() {
    setModalOpen(false);
  };

  async function handleSave(event) {
    event.preventDefault();
    await API.graphql(graphqlOperation(mutations.createDeal, { input: { name, category }}));
    handleClose();
  };

  const options = CATEGORIES.map(c => ({ key: c, value: c, text: c}));

  return (
    <Modal
      closeIcon
      size='small'
      open={modalOpen}
      onOpen={handleOpen}
      onClose={handleClose}
      trigger={<p><Icon name='plus'/>Create new Deal</p>}>
      <Modal.Header>Create new Deal</Modal.Header>
      <Modal.Content>
        <Form>
            <Form.Field>
              <label>Deal Name</label>
              <Input fluid type='text' placeholder='Set Name' name='name' value={name || ''}
                onChange={(e) => { setName(e.target.value); } }/>
            </Form.Field>
            <Form.Field>
              <label>Category</label>
              <Dropdown fluid placeholder='Select Category' selection options={options} value={category}
                onChange={(e, data) => { setCategory(data.value); } }/>
            </Form.Field>
            {name ? (
              <DealCardImage dealName={name} minHeight={320} fontSize={48}/>
            ) : (
              <Segment style={{minHeight: 320}} secondary/>
            )}
        </Form>
      </Modal.Content>
      <Modal.Actions>
        <Button content='Cancel' onClick={handleClose}/>
        <Button primary labelPosition='right' content='Reset' icon='refresh' onClick={handleReset}/>
        <Button positive labelPosition='right' icon='checkmark' content='Save' href='/'
          disabled = {!(name && category)} 
          onClick={handleSave}
          data-amplify-analytics-on='click'
          data-amplify-analytics-name='createDeal-complete'
          data-amplify-analytics-attrs={`category:${category}`}/>
      </Modal.Actions>
    </Modal>
  );
};

function DealsListCardGroup({ items, pageViewOrigin, cardStyle }) {
  function dealCards() {
    return items
      .map(deal =>
        <Card
          key={deal.id}
          as={Link} to={{ pathname: `/deals/${deal.id}`, state: { pageViewOrigin } }}
          style={cardStyle}>

          <DealCardImage dealName={deal.name} minHeight={140} fontSize={24}/>
          <Card.Content>
            <Card.Header>{deal.name}</Card.Header>
            <Card.Meta><Icon name='tag'/> {deal.category}</Card.Meta>
          </Card.Content>
        </Card>
      );
  };

  return (
    <Card.Group centered>
      {dealCards()}
    </Card.Group>
  );
};

DealsListCardGroup.propTypes = {
  items: PropTypes.array,
  pageViewOrigin: PropTypes.string,
  cardStyle: PropTypes.object
};

function DealsList() {
  const [deals, setDeals] = React.useState([]);

  React.useEffect(() => {
    async function fetchData () {
      const result = await API.graphql(graphqlOperation(queries.listDeals, { limit: 1000 }));
      const deals = result.data.listDeals.items;
      setDeals(deals);
    }
    fetchData();
  }, []);

  React.useEffect(() => {
    let dealSubscription;
    async function fetchData() {
      dealSubscription = await API.graphql(graphqlOperation(subscriptions.onCreateDeal)).subscribe({
        next: (dealData) => {
          const newDeal = dealData.value.data.onCreateDeal;
          setDeals([...deals, newDeal]);
        }
      });
    }
    fetchData();

    return () => {
      if (dealSubscription) {
        dealSubscription.unsubscribe();
      }
    };
  });

  document.title = 'Travel Deals';
  return (
    <Container style={{ marginTop: 70 }}>
      <DealsListCardGroup items={deals} pageViewOrigin='Browse'/>
    </Container>
  );
};

function DealDetails({ id, locationState }) {
  const [deal, setDeal] = React.useState({});
  const [loading, setLoading] = React.useState(true);

  React.useEffect(() => {
    async function loadDealInfo() {
      const dealResult = await API.graphql(graphqlOperation(queries.getDeal, { id }));
      const deal = dealResult.data.getDeal;
      setDeal(deal);
      setLoading(false);
      document.title = `${deal.name} - Travel Deals`;
    };
    loadDealInfo();

    return () => {
      setDeal({});
      setLoading(true);
    };
  }, [id, locationState]);

  return (
    <Container>
      <NavLink to='/'><Icon name='arrow left'/>Back to Deals list</NavLink>
      <Divider hidden/>
      <Card key={deal.id} style={{ width: '100%', maxWidth: 720, margin: 'auto' }}>
        {loading ? (
          <Placeholder fluid style={{minHeight: 320}}>
            <Placeholder.Image/>
          </Placeholder>
        ) : (
          <DealCardImage dealName={deal.name} minHeight={320} fontSize={48}/>
        )}
        {loading ? (
          <Placeholder>
            <Placeholder.Line/>
            <Placeholder.Line/>          
          </Placeholder>
        ) : (
          <Card.Content>
            <Card.Header>{deal.name}</Card.Header>
            <Card.Meta><Icon name='tag'/> {deal.category}</Card.Meta>
          </Card.Content>
        )}

      </Card>
      <Divider hidden/>
    </Container>
  );
};

DealDetails.propTypes = {
  id: PropTypes.string,
  locationState: PropTypes.object
};

function AuthStateApp() {  
  const [authState, setAuthState] = React.useState();
  const [user, setUser] = React.useState();

  React.useEffect(() => {
    onAuthUIStateChange((nextAuthState, authData) => {
      setAuthState(nextAuthState);
      setUser(authData);
    });
  }, []);

  document.title = 'Travel Deals';
  return authState === AuthState.SignedIn && user ? (
      <div className='App'>
        <Router>
          <Menu fixed='top' color='teal' inverted>
            <Menu.Menu>
              <Menu.Item header href='/'><Icon name='globe'/>Travel Deals</Menu.Item>
            </Menu.Menu>
            <Menu.Menu position='right'>
              <Menu.Item link><DealCreation/></Menu.Item>
              <Dropdown item simple text={user.username}>
                <Dropdown.Menu>
                  <Dropdown.Item onClick={() => Auth.signOut()}><Icon name='power off'/>Log Out</Dropdown.Item>
                </Dropdown.Menu>
              </Dropdown>
            </Menu.Menu>
          </Menu>
          <Container style={{ marginTop: 70 }}>
            <Route path='/' exact component={() => 
              <DealsList/>
            }/>
            <Route path='/deals/:dealId' render={props => 
              <DealDetails id={props.match.params.dealId} locationState={props.location.state}/>
            }/>
          </Container>
        </Router>
      </div>
  ) : (
      <AmplifyAuthenticator>
        <AmplifySignUp slot='sign-up' formFields={[
            { type: 'username' },
            { type: 'password' },
            { type: 'email' }
          ]}/>
      </AmplifyAuthenticator>
  );
};

export default AuthStateApp;

After the code is updated, publish client-side changes to the cloud:

amplify publish

Generating events

Functionality is available only after amplify push is complete.

Now you can:

  • Create some web application Users
  • Generate some sample Deals
  • Try login and logout operations.

All of these actions will be recorded, and you can later see them on Pinpoint Dashboards.

You can also use Web Developer Tools in your Browser to see all requests to record events.