Giter Site home page Giter Site logo

andreweastwood / enhanced-analytics Goto Github PK

View Code? Open in Web Editor NEW
4.0 3.0 0.0 274 KB

Here are a couple of handy tools to populate dataLayer ecommerce event data

Home Page: https://www.npmjs.com/package/enhanced-analytics

License: MIT License

TypeScript 99.81% Shell 0.14% JavaScript 0.05%
facebook-feed googleanalytics4 klaviyo metapixel product-fees google-products fullstory

enhanced-analytics's Introduction

enhanced-analytics

A couple of convenient tools for populating dataLayer ecommerce event data or even more.

You can use them either on the Frontend or the Backend sides. The Faacebook Pixel events can be configured on both sides, just to increase pixel's performance.

integrated services:
  ✅ Google Analytics & GA4 / Tag Manager (Browser Only)
  ✅ Klaviyo (Server+Browser) https://www.klaviyo.com/

  • Install npm i -S [email protected] when this lib is being used at NodeJs
  • This will auto-install js when running it in a broswer.

  ✅ Facebook (Server+Browser)

  ✅ FullStory (Broswer Only) https://www.fullstory.com/

  • Install npm i @fullstory/browser when this lib is being used at NodeJs

1 Configure (UI Side)

import { useEffect } from 'react';
import { configureAnalytics } from 'enhanced-analytics';
import * as EATypes from 'enhanced-analytics';

const MyApp = () => {
  // store configuration
  const activeStore = {
    name: 'My Store',
    homepage: 'www.my-store.com',
    localization: {
      currency: 'USD',
    },
  };

  useEffect(() => {
    configureAnalytics({
      affiliation: activeStore.name,
      description: 'Your store description. This will appear in product feeds too',
      absoluteURL: activeStore.homepage,
      currency: activeStore.localization.currency,
      debug: /* set true when localhost or dev env */
      integrations: {
        // Klaviyo
        klaviyo: {
          enabled: true,
          siteId: 'YOUR-SITE-ID',
        },
        // Google Analytics (TagManager)
        ga: {
          enabled: true,
          trackId: 'GTM-XXXXXXX',
          ga4: true, // <-- publish GA4 events data
          // more default params:
          // defaultCatalogName: `${activeStore.name} Landing Products`,
          // defaultBasketName: 'Shopping Cart',
          // dataLayerName: 'dataLayer'
        },
        // FullStory
        fullstory: {
          enabled: true,
          orgId: 'YOUR-ORG-ID',
          // @ts-ignore
          sdk: FullStory, // <-- this requires: npm i @fullstory/browser
        },
        // Facebook Pixel
        fb: {
          enabled: true,
          pixelId: 'YOUR-PIXEL-ID',
          testCode: 'TEST61709', // <---- test code, when testing Pixel data (server side only)
        },
      },
      // you may have your own data structure
      // therefore we need it converted for the lib here
      // This is just real use-case.
      resolvers: {
        // custom data transformation configuration
        // prettier-ignore
        page(input) {//  <================================|
          //   ^^ this would be 'test'                    |
          return {                          //            |
            id: '',                         //            |
            name: document.title,           //            |
            path: window.location.pathname, //            |
            url: window.location.href,      //            |
            title: document.title,          //            |
          };                                //            |
        }, //                               //            |
        //                                                |
        // ^^ here, If you call useAnalytics().withPage('test').integrations.klaviyo.trackPageView();
        //    and the same approach for the other scopes: withUser, withBasket... etc.
        profile(input) {
          const currUser = input || /* get session user */;
          return currUser?.userName && currUser?.email === 12
            ? {
                email: currUser.email,
                firstName: currUser.userName,
              }
            : null;
        },
        product: (p: any) => {
          const res: EATypes.T_EA_DataProduct = {
            id: p.id,
            brand: p.seller,
            category: p.category,
            description: p.description,
            isSale: !!p.promo,
            price: p.price,
            salePrice: p.price,
            title: p.title,
            sku: p.sku,
            viewOrder: p.viewOrder,
          };
          return res;
        },
        basket: () => {
          const diff = CartBuilderStore.getLastDiff();
          const res: EATypes.TDataBasket = {
            coupon: CartBuilderStore.getCouponCode(),
            total: CartBuilderStore.getCartTotal(),
            quantity: CartBuilderStore.getItemsCount(),
            lastAdded: diff?.lastAddedItems.map(mapCartItemToAnalytics) || [],
            lastRemoved:
              diff?.lastRemovedItems.map(mapCartItemToAnalytics) || [],
            products: CartBuilderStore.getItems().map(mapCartItemToAnalytics),
          };
          return res;
        },
        order: (o: any) => {
          const res: EATypes.T_EA_DataOrder = {
            id: o.id,
            coupon: o.coupon,
            dateCreated: o.dateCreated,
            revenue: o.costsDetails.net,
            status: o.status,
            tax: o.taxValue,
            payment: {
              type: o.paymentType,
            },
            products: o.orderProducts,
            quantity: o.orderTotal
            customer: {
              email: o.customerEmail,
              firstName: o.customerFullName,
              lastName: o.customerLastName,
              phone: o.customerPhone,
              address: {
                street: o.customerAddress,
              },
            },
            shipping: {
              cost: o.costsDetails.feeValue,
              name: o.deliveryMethod,
              address: {
                street: o.customerAddress,
              },
            },
          };
          return res;
        },
      },
    });
  }, []);

  return <div>my app</div>;
};

1.2 Google Analytics

... somewhere in components:

import useAnalytics from 'enhanced-analytics';

const MyComponent = () => {
  const analytics = useAnalytics();

  useEffect(() => {
    const myProductItems = [];

    //
    // Google Analytics: track basket add/remove items
    //
    analytics
      .withBasket(/* TDataBasket|Record<any>|null */) // <- this can be empty or TDataBasket AND resolver.basket is being invoked as well
      .events.ga()
      .getEECCheckoutList()
      .when(() => true /* or your condition */) // <----- or omit this call, if there is no any conditions
      .push(); // <- inject event into the dataLayer (config dataLayerName default is 'dataLayer');

    //
    // Google Analytics: track search/on-page product items
    //
    analytics
      .withCatalog(/* your array of goods; any custom data[] or T_EA_DataProduct[] */)
      // ^ the resolver.product is being invoked over the each item in the given collection
      .events.ga()
      .getEECProductsList()
      .push();

    //
    // Google Analytics: track product details
    //
    analytics
      .withCatalog(/* array with just one product data [T_EA_DataProduct] */)
      // ^ the resolver.product is being invoked over the each item in the given collection
      .events.ga()
      .getEECProductDetails()
      .when(() => /* producItem is loaded */)
      .push();

    //
    // Google Analytics: track order creation
    //
    analytics
      .withOrder(/* TDataOrder or any custom object */)
      // ^ invokes resolver.order
      .events.ga()
      .getEECPurchased()
      .when(() => !thisOrderWasSeen) // why not, implement your logic, that prevents duplicated events
      .push();
  }, []);

  return <></>;
};

1.2.Klaviyo UI / FullStory UI / Facebook Pixel

import useAnalytics from 'enhanced-analytics';

const MyComponent = () => {
  const analytics = useAnalytics();
  const order = useOrder();

  // Track PageView
  useEffect(() => {
    // page tracking
    const evtPageView = analytics.withPage().events;

    evtPageView.fullstory().trackPageView();
    evtPageView.klaviyo().trackPageView();
  }, []);

  // Track User Indetify
  useEffect(() => {
    // page tracking
    const evtPageView = analytics.withPage().events;
    evtProfile.fullstory().trackIdentify();
    evtProfile.klaviyo().trackIdentify();
    evtPageView.fb().trackPageView();
  }, []);

  // Track Order Complete + Custom event "OrderSeen"
  useEffect(() => {
    const evtOrder = analytics.withOrder(order).events;
    const evtProfile = analytics.withProfile({
      userName: order.customerFullName,
      phone: order.customerPhone,
    }).events;
    // the custom event. This is going to track 'orderSeen' event
    const evtCustom = analytics.withMisc('orderSeen', {
      orderId: order.id,
      orderDate: moment(order.dateCreated).format(),
      orderTotal: order.total,
    }).events;

    // push the 'eec.purchase' evet along with order details
    evtOrder.ga().getEECPurchased().push();

    // indetify current session (this will link anonymous events to this user by email)
    evtProfile.fullstory().trackIdentify();
    evtProfile.klaviyo().trackIdentify();
    evtPageView.fb().trackIdentify();

    // any other custom events
    evtCustom.fullstory().trackCustom();
    evtCustom.klaviyo().trackCustom();
    evtPageView.fb().trackCustom();
  }, []);
};

2 Configure (Backend Side)

Simple snippet with Express.Js as middleware:

app.use(
  analyticsMiddleware({
    absoluteURL: 'https://www.your-domain.lviv.ua/',
    serverAnalytics: {
      testing: false,
      klaviyo: {
        enabled: true,
        token: 'pk_token-goes-here',
        sdk: require('klaviyo-api'), // npm i [email protected]
      },
      userIdentification: {
        // this field is used this way: incoming request has body and we
        // check if this field contains req.body, if so we store the whole req.body
        // into app.locals.customer = req.body
        reqBodyKey: 'customerPhone',
      },
    },
    resolvers: (req) => ({
      order(evtPayload: any) {
        // in this case { order, products } (see order tracking at 2.2.KalviyoAPI below)
        const order = evtPayload.order;
        const orderProducts = evtPayload.products;
        return {
          id: order.id,
          revenue: order.total,
          tax: 0,
          quantity: order.features.length,
          coupon: order.coupon,
          products: [],
          dateCreated: order.dateCreated,
          status: order.status,
          shipping: {
            cost: order.deliveryFee,
            name: order.deliveryMethod,
            address: {
              street: order.customerAddress,
            },
          },
          customer: {
            firstName: order.customerFullName,
            email: `[email protected]`,
          },
          payment: {
            type: order.paymentType,
          },
          url: `${req.protocol}://${req.hostname}/order/success/${order.externalId}`,
        };
      },
      profile() {
        return app.locals.customer.customerPhone
          ? {
              email: `[email protected]`,
              firstName: app.locals.customer.customerFullName,
              phoneNumber: '5551234567',
              address: {
                country: 'United States',
                city: 'Boston',
                postcode: '02110',
                region: 'MA',
                countryCode: 'UA',
                street: app.locals.customer.customerAddress,
              },
            }
          : null;
      },
      eventUUID() {
        return app.locals.evtUuid;
      },
      product(input: TDataList<IDataProduct>) {
        const l = input.items.map((prodItem) => {
          return {
            id: prodItem.id,
            title: prodItem.title,
            description: prodItem.description,
            price: prodItem.price,
            salePrice: prodItem.price,
            isSale: false,
            brand: prodItem.seller,
            category: prodItem.categoryName,
            sku: prodItem.sku,
            list: 'main',
            url: `${req.protocol}://${req.hostname}/product/${prodItem.id}/${prodItem.sku}`,
            imageUrl: prodItem.imageUrl,
          };
        });
        return l;
      },
      page() {
        return {
          id: req.baseUrl,
          name: req.path.split('/')[0],
          path: req.path,
          title: 'Main Page',
          url: `${req.protocol}://${req.hostname}${req.originalUrl}`,
        };
      },
      session() {
        return {
          // agent, fbp and ip are optional when using fb tracking
          agent: req.headers['user-agent'],
          fbp: req.cookies['_fbp'],
          ip: req.ip,
        };
      },
    }),
  })
);

Another configuration for NextJs:

// src/utils/ea.ts
// Server Side EA Configuration for NextJs
const getServerEA = (req: GetServerSidePropsContext['req']) => {
  const ea = useAnalytics({
    absoluteURL: "https://my-store.com/",
    affiliation: "My Store",
    currency: "USD",
    integrations: {
      testing: true,
      fb: {
        enabled: true,
        pixelId: "PIXEL-ID",
        token: "PIXEL-TOKEN",
        sdk: bizSdk,
        testCode: "TESTxxxxx",
      },
    },
    resolvers: {
      eventUUID() {
        return Date.now().toString(32);
      },
      session() {
          // agent, fbp and ip are optional when using fb tracking
        return {
          agent: req.headers["user-agent"],
          fbp: req.cookies["_fbp"],
          ip: req.socket.remoteAddress,
        };
      },
      profile() {
        const user = /* get session user info */
        return user
          ? {
              email: user.email,
              firstName: user.username,
            }
          : null;
      },
      basket() {
        return {
          total: 0,
          coupon: null,
          quantity: 0,
          lastAdded: [],
          lastRemoved: [],
          products: [],
        };
      },
    },
  });

  return ea;
}

// See usage below at 2.3.FB Pixel

2.1 Use It

2.2.Klaviyo API

Track new order:

import useAnalytics from 'enhanced-analytics';

const evtPayload = { order, products };
// you can define your own payload
// and handle it at your resolvers.order function
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackTransaction();

Begin checkout:

import useAnalytics from 'enhanced-analytics';

const evtPayload = { order, products };
// you can define your own payload
// and handle it at your resolvers.order function
await useAnalytics()
  .withOrder(evtPayload)
  .s2s.klaviyo()
  .trackInitiateCheckout();

All methods:

// track Identify
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackIdentify();

// track Transaction
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackTransaction();

// track ProductAddToCart
await useAnalytics()
  .withOrder(evtPayload)
  .s2s.klaviyo()
  .trackProductAddToCart();

// track ProductRemoveFromCart
await useAnalytics()
  .withOrder(evtPayload)
  .s2s.klaviyo()
  .trackProductRemoveFromCart();

// track ProductItemView
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackProductItemView();

// track ProductsItemView
await useAnalytics()
  .withOrder(evtPayload)
  .s2s.klaviyo()
  .trackProductsItemView();

// track Search
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackSearch();

// track PageView
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackPageView();

// track InitiateCheckout
await useAnalytics()
  .withOrder(evtPayload)
  .s2s.klaviyo()
  .trackInitiateCheckout();

// track NewProfile
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackNewProfile();

// track ProfileResetPassword
await useAnalytics()
  .withOrder(evtPayload)
  .s2s.klaviyo()
  .trackProfileResetPassword();

// track ProfileLogIn
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackProfileLogIn();

// track ProfileLogOut
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackProfileLogOut();

// track ProfileSubscribeNL
await useAnalytics()
  .withOrder(evtPayload)
  .s2s.klaviyo()
  .trackProfileSubscribeNL();

// track TransactionRefund
await useAnalytics()
  .withOrder(evtPayload)
  .s2s.klaviyo()
  .trackTransactionRefund();

// track TransactionCancel
await useAnalytics()
  .withOrder(evtPayload)
  .s2s.klaviyo()
  .trackTransactionCancel();

// track TransactionFulfill
await useAnalytics()
  .withOrder(evtPayload)
  .s2s.klaviyo()
  .trackTransactionFulfill();

// track Custom
await useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackCustom();

2.2.FB Pixel (Server+UI)

This examples shows how to send server-side (NextJs) fb events and then re-process them from the UI.

import { GetServerSideProps } from "next";
import { useEffect } from "react";
import {
  EA_FB_Server_RePublish_Events,
  TFbNormalizedEventPayload,
} from "enhanced-analytics/apiTracker/facebook";
import useAnalytics, { configureAnalytics } from "enhanced-analytics";
import * as bizSdk from "facebook-nodejs-business-sdk";
import { GetServerSidePropsContext } from "next";

interface IShopProductResponse {
  eaFbEvents: any;
}

export default function AnyProductPage({
  eaFbEvents,
}: IShopProductResponse) {
    useEffect(() => {
      analytics
        .withPage({
          name: props.title,
          path: window.location.pathname,
        })
        .events.fb()
        .trackPageView();
    }, []);
  return (
    <div>
      <span>testing fb events</span>
      {* The component from EA, which is digesting handed server response *}
      <EA_FB_Server_RePublish_Events serverPayloads={props.eaFbEvents} />
    </div>
  );
}

export const getServerSideProps: GetServerSideProps = async ({ req, }) => {
  // analytics
  const product = fetch(/* your api that fetches product data */);
  const ea = getServerEA(req); /* see NextJs configuration */
  const fbResp = await ea
    .withCatalog([
      {
        id: product.id,
        title: product.title,
        description: product.description,
        salePrice: roundToTwo(product.salePrice),
        price: product.salePrice || product.price,
        isSale: roundToTwo(product.salePrice) > 0,
        brand: product.brand,
        category: product.category,
        color: product.color,
        sku: product.sku,
        imageUrl: product.images.length > 0 ? product.images[0] : void 0,
        url: `https://my-store.com/any-product/${product.shortId}/${product.slug}`,
      },
    ])
    .s2s.fb()
    .trackProductItemView(); // server to server event

  return {
    props: {
      // @ts-ignore
      eaFbEvents: fbResp[0].value.payload
    },
  };
};

3 useAnalytics top methods

const ea = useAnalytics();

// set runtime user
ea.identify(user: T_EA_DataProfile);

// custom events
ea.withMisc(name: string, attributes?: Record<string, any>);

// ...TBD
ea.withPage(payload: T_EA_DataPage | Record<string, any> | null = null);
ea.withProfile(payload: T_EA_DataProfile | Record<string, any> | null = null);
ea.withCatalog(payload: (T_EA_DataProduct | Record<string, any>)[] | null = null);
ea.withBasket(payload: T_EA_DataBasket | Record<string, any> | null = null);
ea.withOrder(payload: T_EA_DataOrder | Record<string, any> | null = null);

// TBD

enhanced-analytics's People

Contributors

andreweastwood avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.