{"id":21670592,"url":"https://github.com/andreweastwood/enhanced-analytics","last_synced_at":"2025-08-21T17:19:16.931Z","repository":{"id":49556896,"uuid":"515476393","full_name":"AndrewEastwood/enhanced-analytics","owner":"AndrewEastwood","description":"Here are a couple of handy tools to populate dataLayer ecommerce event data","archived":false,"fork":false,"pushed_at":"2024-05-04T21:11:23.000Z","size":420,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-10T13:17:18.528Z","etag":null,"topics":["facebook-feed","fullstory","google-products","googleanalytics4","klaviyo","metapixel","product-fees"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/enhanced-analytics","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AndrewEastwood.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2022-07-19T07:15:00.000Z","updated_at":"2024-05-04T21:11:26.000Z","dependencies_parsed_at":"2024-04-16T18:42:01.805Z","dependency_job_id":"d41d3cf7-3797-4abb-ab01-35e6c0660e4f","html_url":"https://github.com/AndrewEastwood/enhanced-analytics","commit_stats":{"total_commits":15,"total_committers":3,"mean_commits":5.0,"dds":0.5333333333333333,"last_synced_commit":"bc48d9b98f8aa268e7b15d60b211c9fee0f26eb7"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndrewEastwood%2Fenhanced-analytics","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndrewEastwood%2Fenhanced-analytics/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndrewEastwood%2Fenhanced-analytics/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndrewEastwood%2Fenhanced-analytics/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AndrewEastwood","download_url":"https://codeload.github.com/AndrewEastwood/enhanced-analytics/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248507688,"owners_count":21115654,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["facebook-feed","fullstory","google-products","googleanalytics4","klaviyo","metapixel","product-fees"],"created_at":"2024-11-25T12:33:36.212Z","updated_at":"2025-04-12T02:43:15.287Z","avatar_url":"https://github.com/AndrewEastwood.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# enhanced-analytics\n\nA couple of convenient tools for populating dataLayer ecommerce event data or even more.\n\nYou can use them either on the Frontend or the Backend sides.\nThe Faacebook Pixel events can be configured on both sides, just to increase pixel's performance.\n\n**integrated services**:\\\n\u0026nbsp;\u0026nbsp;✅ Google Analytics \u0026 GA4 / Tag Manager (Browser Only)\\\n\u0026nbsp;\u0026nbsp;✅ Klaviyo (Server+Browser) https://www.klaviyo.com/\n\n- Install `npm i -S klaviyo-api@2.11` when this lib is being used at NodeJs\n- This will auto-install js when running it in a broswer.\n\n\u0026nbsp;\u0026nbsp;✅ Facebook (Server+Browser)\n\n- install `npm i -S facebook-nodejs-business-sdk@13.0.0` when running in NodeJs env\n\n\u0026nbsp;\u0026nbsp;✅ FullStory (Broswer Only) https://www.fullstory.com/\n\n- Install `npm i @fullstory/browser` when this lib is being used at NodeJs\n\n---\n\n## 1 Configure (UI Side)\n\n```tsx\nimport { useEffect } from 'react';\nimport { configureAnalytics } from 'enhanced-analytics';\nimport * as EATypes from 'enhanced-analytics';\n\nconst MyApp = () =\u003e {\n  // store configuration\n  const activeStore = {\n    name: 'My Store',\n    homepage: 'www.my-store.com',\n    localization: {\n      currency: 'USD',\n    },\n  };\n\n  useEffect(() =\u003e {\n    configureAnalytics({\n      affiliation: activeStore.name,\n      description: 'Your store description. This will appear in product feeds too',\n      absoluteURL: activeStore.homepage,\n      currency: activeStore.localization.currency,\n      debug: /* set true when localhost or dev env */\n      integrations: {\n        // Klaviyo\n        klaviyo: {\n          enabled: true,\n          siteId: 'YOUR-SITE-ID',\n        },\n        // Google Analytics (TagManager)\n        ga: {\n          enabled: true,\n          trackId: 'GTM-XXXXXXX',\n          ga4: true, // \u003c-- publish GA4 events data\n          // more default params:\n          // defaultCatalogName: `${activeStore.name} Landing Products`,\n          // defaultBasketName: 'Shopping Cart',\n          // dataLayerName: 'dataLayer'\n        },\n        // FullStory\n        fullstory: {\n          enabled: true,\n          orgId: 'YOUR-ORG-ID',\n          // @ts-ignore\n          sdk: FullStory, // \u003c-- this requires: npm i @fullstory/browser\n        },\n        // Facebook Pixel\n        fb: {\n          enabled: true,\n          pixelId: 'YOUR-PIXEL-ID',\n          testCode: 'TEST61709', // \u003c---- test code, when testing Pixel data (server side only)\n        },\n      },\n      // you may have your own data structure\n      // therefore we need it converted for the lib here\n      // This is just real use-case.\n      resolvers: {\n        // custom data transformation configuration\n        // prettier-ignore\n        page(input) {//  \u003c================================|\n          //   ^^ this would be 'test'                    |\n          return {                          //            |\n            id: '',                         //            |\n            name: document.title,           //            |\n            path: window.location.pathname, //            |\n            url: window.location.href,      //            |\n            title: document.title,          //            |\n          };                                //            |\n        }, //                               //            |\n        //                                                |\n        // ^^ here, If you call useAnalytics().withPage('test').integrations.klaviyo.trackPageView();\n        //    and the same approach for the other scopes: withUser, withBasket... etc.\n        profile(input) {\n          const currUser = input || /* get session user */;\n          return currUser?.userName \u0026\u0026 currUser?.email === 12\n            ? {\n                email: currUser.email,\n                firstName: currUser.userName,\n              }\n            : null;\n        },\n        product: (p: any) =\u003e {\n          const res: EATypes.T_EA_DataProduct = {\n            id: p.id,\n            brand: p.seller,\n            category: p.category,\n            description: p.description,\n            isSale: !!p.promo,\n            price: p.price,\n            salePrice: p.price,\n            title: p.title,\n            sku: p.sku,\n            viewOrder: p.viewOrder,\n          };\n          return res;\n        },\n        basket: () =\u003e {\n          const diff = CartBuilderStore.getLastDiff();\n          const res: EATypes.TDataBasket = {\n            coupon: CartBuilderStore.getCouponCode(),\n            total: CartBuilderStore.getCartTotal(),\n            quantity: CartBuilderStore.getItemsCount(),\n            lastAdded: diff?.lastAddedItems.map(mapCartItemToAnalytics) || [],\n            lastRemoved:\n              diff?.lastRemovedItems.map(mapCartItemToAnalytics) || [],\n            products: CartBuilderStore.getItems().map(mapCartItemToAnalytics),\n          };\n          return res;\n        },\n        order: (o: any) =\u003e {\n          const res: EATypes.T_EA_DataOrder = {\n            id: o.id,\n            coupon: o.coupon,\n            dateCreated: o.dateCreated,\n            revenue: o.costsDetails.net,\n            status: o.status,\n            tax: o.taxValue,\n            payment: {\n              type: o.paymentType,\n            },\n            products: o.orderProducts,\n            quantity: o.orderTotal\n            customer: {\n              email: o.customerEmail,\n              firstName: o.customerFullName,\n              lastName: o.customerLastName,\n              phone: o.customerPhone,\n              address: {\n                street: o.customerAddress,\n              },\n            },\n            shipping: {\n              cost: o.costsDetails.feeValue,\n              name: o.deliveryMethod,\n              address: {\n                street: o.customerAddress,\n              },\n            },\n          };\n          return res;\n        },\n      },\n    });\n  }, []);\n\n  return \u003cdiv\u003emy app\u003c/div\u003e;\n};\n```\n\n## 1.2 Google Analytics\n\n... somewhere in components:\n\n```tsx\nimport useAnalytics from 'enhanced-analytics';\n\nconst MyComponent = () =\u003e {\n  const analytics = useAnalytics();\n\n  useEffect(() =\u003e {\n    const myProductItems = [];\n\n    //\n    // Google Analytics: track basket add/remove items\n    //\n    analytics\n      .withBasket(/* TDataBasket|Record\u003cany\u003e|null */) // \u003c- this can be empty or TDataBasket AND resolver.basket is being invoked as well\n      .events.ga()\n      .getEECCheckoutList()\n      .when(() =\u003e true /* or your condition */) // \u003c----- or omit this call, if there is no any conditions\n      .push(); // \u003c- inject event into the dataLayer (config dataLayerName default is 'dataLayer');\n\n    //\n    // Google Analytics: track search/on-page product items\n    //\n    analytics\n      .withCatalog(/* your array of goods; any custom data[] or T_EA_DataProduct[] */)\n      // ^ the resolver.product is being invoked over the each item in the given collection\n      .events.ga()\n      .getEECProductsList()\n      .push();\n\n    //\n    // Google Analytics: track product details\n    //\n    analytics\n      .withCatalog(/* array with just one product data [T_EA_DataProduct] */)\n      // ^ the resolver.product is being invoked over the each item in the given collection\n      .events.ga()\n      .getEECProductDetails()\n      .when(() =\u003e /* producItem is loaded */)\n      .push();\n\n    //\n    // Google Analytics: track order creation\n    //\n    analytics\n      .withOrder(/* TDataOrder or any custom object */)\n      // ^ invokes resolver.order\n      .events.ga()\n      .getEECPurchased()\n      .when(() =\u003e !thisOrderWasSeen) // why not, implement your logic, that prevents duplicated events\n      .push();\n  }, []);\n\n  return \u003c\u003e\u003c/\u003e;\n};\n```\n\n### 1.2.Klaviyo UI / FullStory UI / Facebook Pixel\n\n```tsx\nimport useAnalytics from 'enhanced-analytics';\n\nconst MyComponent = () =\u003e {\n  const analytics = useAnalytics();\n  const order = useOrder();\n\n  // Track PageView\n  useEffect(() =\u003e {\n    // page tracking\n    const evtPageView = analytics.withPage().events;\n\n    evtPageView.fullstory().trackPageView();\n    evtPageView.klaviyo().trackPageView();\n  }, []);\n\n  // Track User Indetify\n  useEffect(() =\u003e {\n    // page tracking\n    const evtPageView = analytics.withPage().events;\n    evtProfile.fullstory().trackIdentify();\n    evtProfile.klaviyo().trackIdentify();\n    evtPageView.fb().trackPageView();\n  }, []);\n\n  // Track Order Complete + Custom event \"OrderSeen\"\n  useEffect(() =\u003e {\n    const evtOrder = analytics.withOrder(order).events;\n    const evtProfile = analytics.withProfile({\n      userName: order.customerFullName,\n      phone: order.customerPhone,\n    }).events;\n    // the custom event. This is going to track 'orderSeen' event\n    const evtCustom = analytics.withMisc('orderSeen', {\n      orderId: order.id,\n      orderDate: moment(order.dateCreated).format(),\n      orderTotal: order.total,\n    }).events;\n\n    // push the 'eec.purchase' evet along with order details\n    evtOrder.ga().getEECPurchased().push();\n\n    // indetify current session (this will link anonymous events to this user by email)\n    evtProfile.fullstory().trackIdentify();\n    evtProfile.klaviyo().trackIdentify();\n    evtPageView.fb().trackIdentify();\n\n    // any other custom events\n    evtCustom.fullstory().trackCustom();\n    evtCustom.klaviyo().trackCustom();\n    evtPageView.fb().trackCustom();\n  }, []);\n};\n```\n\n## 2 Configure (Backend Side)\n\nSimple snippet with Express.Js as middleware:\n\n```typescript\napp.use(\n  analyticsMiddleware({\n    absoluteURL: 'https://www.your-domain.lviv.ua/',\n    serverAnalytics: {\n      testing: false,\n      klaviyo: {\n        enabled: true,\n        token: 'pk_token-goes-here',\n        sdk: require('klaviyo-api'), // npm i klaviyo-api@2.1.1\n      },\n      userIdentification: {\n        // this field is used this way: incoming request has body and we\n        // check if this field contains req.body, if so we store the whole req.body\n        // into app.locals.customer = req.body\n        reqBodyKey: 'customerPhone',\n      },\n    },\n    resolvers: (req) =\u003e ({\n      order(evtPayload: any) {\n        // in this case { order, products } (see order tracking at 2.2.KalviyoAPI below)\n        const order = evtPayload.order;\n        const orderProducts = evtPayload.products;\n        return {\n          id: order.id,\n          revenue: order.total,\n          tax: 0,\n          quantity: order.features.length,\n          coupon: order.coupon,\n          products: [],\n          dateCreated: order.dateCreated,\n          status: order.status,\n          shipping: {\n            cost: order.deliveryFee,\n            name: order.deliveryMethod,\n            address: {\n              street: order.customerAddress,\n            },\n          },\n          customer: {\n            firstName: order.customerFullName,\n            email: `john.smith@test.com`,\n          },\n          payment: {\n            type: order.paymentType,\n          },\n          url: `${req.protocol}://${req.hostname}/order/success/${order.externalId}`,\n        };\n      },\n      profile() {\n        return app.locals.customer.customerPhone\n          ? {\n              email: `john.smith@test.com`,\n              firstName: app.locals.customer.customerFullName,\n              phoneNumber: '5551234567',\n              address: {\n                country: 'United States',\n                city: 'Boston',\n                postcode: '02110',\n                region: 'MA',\n                countryCode: 'UA',\n                street: app.locals.customer.customerAddress,\n              },\n            }\n          : null;\n      },\n      eventUUID() {\n        return app.locals.evtUuid;\n      },\n      product(input: TDataList\u003cIDataProduct\u003e) {\n        const l = input.items.map((prodItem) =\u003e {\n          return {\n            id: prodItem.id,\n            title: prodItem.title,\n            description: prodItem.description,\n            price: prodItem.price,\n            salePrice: prodItem.price,\n            isSale: false,\n            brand: prodItem.seller,\n            category: prodItem.categoryName,\n            sku: prodItem.sku,\n            list: 'main',\n            url: `${req.protocol}://${req.hostname}/product/${prodItem.id}/${prodItem.sku}`,\n            imageUrl: prodItem.imageUrl,\n          };\n        });\n        return l;\n      },\n      page() {\n        return {\n          id: req.baseUrl,\n          name: req.path.split('/')[0],\n          path: req.path,\n          title: 'Main Page',\n          url: `${req.protocol}://${req.hostname}${req.originalUrl}`,\n        };\n      },\n      session() {\n        return {\n          // agent, fbp and ip are optional when using fb tracking\n          agent: req.headers['user-agent'],\n          fbp: req.cookies['_fbp'],\n          ip: req.ip,\n        };\n      },\n    }),\n  })\n);\n```\n\nAnother configuration for NextJs:\n\n```typescript\n\n// src/utils/ea.ts\n// Server Side EA Configuration for NextJs\nconst getServerEA = (req: GetServerSidePropsContext['req']) =\u003e {\n  const ea = useAnalytics({\n    absoluteURL: \"https://my-store.com/\",\n    affiliation: \"My Store\",\n    currency: \"USD\",\n    integrations: {\n      testing: true,\n      fb: {\n        enabled: true,\n        pixelId: \"PIXEL-ID\",\n        token: \"PIXEL-TOKEN\",\n        sdk: bizSdk,\n        testCode: \"TESTxxxxx\",\n      },\n    },\n    resolvers: {\n      eventUUID() {\n        return Date.now().toString(32);\n      },\n      session() {\n          // agent, fbp and ip are optional when using fb tracking\n        return {\n          agent: req.headers[\"user-agent\"],\n          fbp: req.cookies[\"_fbp\"],\n          ip: req.socket.remoteAddress,\n        };\n      },\n      profile() {\n        const user = /* get session user info */\n        return user\n          ? {\n              email: user.email,\n              firstName: user.username,\n            }\n          : null;\n      },\n      basket() {\n        return {\n          total: 0,\n          coupon: null,\n          quantity: 0,\n          lastAdded: [],\n          lastRemoved: [],\n          products: [],\n        };\n      },\n    },\n  });\n\n  return ea;\n}\n\n// See usage below at 2.3.FB Pixel\n```\n\n## 2.1 Use It\n\n### 2.2.Klaviyo API\n\nTrack new order:\n\n```typescript\nimport useAnalytics from 'enhanced-analytics';\n\nconst evtPayload = { order, products };\n// you can define your own payload\n// and handle it at your resolvers.order function\nawait useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackTransaction();\n```\n\nBegin checkout:\n\n```typescript\nimport useAnalytics from 'enhanced-analytics';\n\nconst evtPayload = { order, products };\n// you can define your own payload\n// and handle it at your resolvers.order function\nawait useAnalytics()\n  .withOrder(evtPayload)\n  .s2s.klaviyo()\n  .trackInitiateCheckout();\n```\n\nAll methods:\n\n```ts\n// track Identify\nawait useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackIdentify();\n\n// track Transaction\nawait useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackTransaction();\n\n// track ProductAddToCart\nawait useAnalytics()\n  .withOrder(evtPayload)\n  .s2s.klaviyo()\n  .trackProductAddToCart();\n\n// track ProductRemoveFromCart\nawait useAnalytics()\n  .withOrder(evtPayload)\n  .s2s.klaviyo()\n  .trackProductRemoveFromCart();\n\n// track ProductItemView\nawait useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackProductItemView();\n\n// track ProductsItemView\nawait useAnalytics()\n  .withOrder(evtPayload)\n  .s2s.klaviyo()\n  .trackProductsItemView();\n\n// track Search\nawait useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackSearch();\n\n// track PageView\nawait useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackPageView();\n\n// track InitiateCheckout\nawait useAnalytics()\n  .withOrder(evtPayload)\n  .s2s.klaviyo()\n  .trackInitiateCheckout();\n\n// track NewProfile\nawait useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackNewProfile();\n\n// track ProfileResetPassword\nawait useAnalytics()\n  .withOrder(evtPayload)\n  .s2s.klaviyo()\n  .trackProfileResetPassword();\n\n// track ProfileLogIn\nawait useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackProfileLogIn();\n\n// track ProfileLogOut\nawait useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackProfileLogOut();\n\n// track ProfileSubscribeNL\nawait useAnalytics()\n  .withOrder(evtPayload)\n  .s2s.klaviyo()\n  .trackProfileSubscribeNL();\n\n// track TransactionRefund\nawait useAnalytics()\n  .withOrder(evtPayload)\n  .s2s.klaviyo()\n  .trackTransactionRefund();\n\n// track TransactionCancel\nawait useAnalytics()\n  .withOrder(evtPayload)\n  .s2s.klaviyo()\n  .trackTransactionCancel();\n\n// track TransactionFulfill\nawait useAnalytics()\n  .withOrder(evtPayload)\n  .s2s.klaviyo()\n  .trackTransactionFulfill();\n\n// track Custom\nawait useAnalytics().withOrder(evtPayload).s2s.klaviyo().trackCustom();\n```\n\n### 2.2.FB Pixel (Server+UI)\n\nThis examples shows how to send server-side (NextJs) fb events and then re-process them from the UI.\n\n```tsx\nimport { GetServerSideProps } from \"next\";\nimport { useEffect } from \"react\";\nimport {\n  EA_FB_Server_RePublish_Events,\n  TFbNormalizedEventPayload,\n} from \"enhanced-analytics/apiTracker/facebook\";\nimport useAnalytics, { configureAnalytics } from \"enhanced-analytics\";\nimport * as bizSdk from \"facebook-nodejs-business-sdk\";\nimport { GetServerSidePropsContext } from \"next\";\n\ninterface IShopProductResponse {\n  eaFbEvents: any;\n}\n\nexport default function AnyProductPage({\n  eaFbEvents,\n}: IShopProductResponse) {\n    useEffect(() =\u003e {\n      analytics\n        .withPage({\n          name: props.title,\n          path: window.location.pathname,\n        })\n        .events.fb()\n        .trackPageView();\n    }, []);\n  return (\n    \u003cdiv\u003e\n      \u003cspan\u003etesting fb events\u003c/span\u003e\n      {* The component from EA, which is digesting handed server response *}\n      \u003cEA_FB_Server_RePublish_Events serverPayloads={props.eaFbEvents} /\u003e\n    \u003c/div\u003e\n  );\n}\n\nexport const getServerSideProps: GetServerSideProps = async ({ req, }) =\u003e {\n  // analytics\n  const product = fetch(/* your api that fetches product data */);\n  const ea = getServerEA(req); /* see NextJs configuration */\n  const fbResp = await ea\n    .withCatalog([\n      {\n        id: product.id,\n        title: product.title,\n        description: product.description,\n        salePrice: roundToTwo(product.salePrice),\n        price: product.salePrice || product.price,\n        isSale: roundToTwo(product.salePrice) \u003e 0,\n        brand: product.brand,\n        category: product.category,\n        color: product.color,\n        sku: product.sku,\n        imageUrl: product.images.length \u003e 0 ? product.images[0] : void 0,\n        url: `https://my-store.com/any-product/${product.shortId}/${product.slug}`,\n      },\n    ])\n    .s2s.fb()\n    .trackProductItemView(); // server to server event\n\n  return {\n    props: {\n      // @ts-ignore\n      eaFbEvents: fbResp[0].value.payload\n    },\n  };\n};\n\n```\n\n## 3 useAnalytics top methods\n\n```ts\nconst ea = useAnalytics();\n\n// set runtime user\nea.identify(user: T_EA_DataProfile);\n\n// custom events\nea.withMisc(name: string, attributes?: Record\u003cstring, any\u003e);\n\n// ...TBD\nea.withPage(payload: T_EA_DataPage | Record\u003cstring, any\u003e | null = null);\nea.withProfile(payload: T_EA_DataProfile | Record\u003cstring, any\u003e | null = null);\nea.withCatalog(payload: (T_EA_DataProduct | Record\u003cstring, any\u003e)[] | null = null);\nea.withBasket(payload: T_EA_DataBasket | Record\u003cstring, any\u003e | null = null);\nea.withOrder(payload: T_EA_DataOrder | Record\u003cstring, any\u003e | null = null);\n\n// TBD\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandreweastwood%2Fenhanced-analytics","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandreweastwood%2Fenhanced-analytics","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandreweastwood%2Fenhanced-analytics/lists"}