Hi Guys, I'm having trouble getting everyone assi...
# ask-questions
p
Hi Guys, I'm having trouble getting everyone assiged to a test. (or at least getting a tracking callback for everyone.) We are using heap as the analytics platform, and would like to use the heapID to assign variations. But sometimes heap is not initiated when Growthbook starts. So I am setting up growthbook, and then calling
setAttributes()
when the heap ID is avalible. Is this the wrong approach? Should I just wait until we have all the attributes first and then setup the growthbook object? The documentation seemed to indicate that assignment would be reevaluated when setAttributes is gets called. I have started tracking the set Attributes call and only like 1% of the users that should have an expirement assigned are getting a tracking callback. Here is my full growthbook setup script:
Copy code
import { GrowthBook } from '@growthbook/growthbook';
import { BrowserCookieStickyBucketService } from '@growthbook/growthbook';
import Cookies from 'js-cookie';
import * as features from '@/scripts/store/features';
import { head } from 'underscore';

const growth_book = new GrowthBook({
  apiHost: '<https://cdn.growthbook.io>',
  clientKey: 'sdk-bFMPGDFC9sMwhjsE',
  enableDevMode: process.env.NODE_ENV === 'development',
  subscribeToChanges: true,
  stickyBucketService: new BrowserCookieStickyBucketService({
    jsCookie: Cookies,
  }),
  trackingCallback: (experiment, result) => {
    console.log('viewed experiment', experiment.key, result.key);
    window.heap?.track('Viewed Experiment', {
      experimentId: experiment.key,
      variationId: result.key,
    });
    window.dataLayer = window.dataLayer || [];
    window.dataLayer.push({
      event: 'viewed_experiment',
      experimentId: experiment.key,
      variationId: result.key,
    });
  },
});

window.gb = growth_book; // Expose for debugging
window.growth_book = growth_book; // Expose for debugging

//===========================
// Collect user attributes
//===========================

//should match tailwind.config.js
const screens = {
  sm: '700px',
  md: '1000px',
  lg: '1150px',
  xl: '1400px',
  '2xl': '1600px',
};

const screenSize = Object.keys(screens)
  .reverse()
  .find(key => {
    return window.innerWidth > parseInt(screens[key]);
  });

const getHeapIdFromStorage = function () {
  try {
    const appId = window.heap?.appid;
    const key = appId ? `_hp5_meta.${appId}` : Object.keys(localStorage).find(k => k.startsWith('_hp5_meta.'));
    if (key) {
      const meta = JSON.parse(localStorage.getItem(key));
      return meta?.value?.userId || null;
    }
  } catch (e) {}
  return null;
};

const waitForHeapId = function () {
  const storedId = getHeapIdFromStorage();
  if (storedId) {
    setGbAttributes(storedId);
    return Promise.resolve();
  }

  return new Promise(resolve => {
    const timeout = setTimeout(() => {
      console.warn('heapReady timeout — proceeding without Heap ID');
      setGbAttributes(getHeapIdFromStorage());
      resolve();
    }, 5000);

    window.addEventListener('heapReady', function () {
      clearTimeout(timeout);
      const heapId = getHeapIdFromStorage();
      if (heapId) {
        setGbAttributes(heapId);
        resolve();
      } else {
        // heapReady can fire before userId is written — wait briefly for new visitors
        setTimeout(() => {
          setGbAttributes(window.heap?.getUserId() || getHeapIdFromStorage());
          resolve();
        }, 500);
      }
    });
  });
};

const setGbAttributes = function (id) {
  const attributes = {
    id: id,
    heapId: id || 'unknown',
    email: window?.userAttributes?.email,
    customerId: window?.userAttributes?.customerId,
    totalSpent: window?.userAttributes?.totalSpent,
    ordersCount: window?.userAttributes?.ordersCount,
    tags: window?.userAttributes?.tags,
    country: Shopify.country,
    locale: Shopify?.locale,
    currency: Shopify?.currency?.active,
    url: window.location.href,
    userAgent: navigator.userAgent,
    screenWidth: window.innerWidth,
    screenSize: screenSize,
    themeId: Shopify.theme.id,
    themeName: Shopify.theme.name,
    themeRole: Shopify.theme.role,
    params: paramsToObject(new URL(document.location).searchParams),
  };

  growth_book.setAttributes(attributes);

  try {
    window.heap?.track('growthbook:attributes', attributes);
  } catch (e) {
    console.log('heap not ready?');
  }
};

//===========================
// setup growthbook
//===========================

function paramsToObject(entries) {
  const result = {};
  for (const [key, value] of entries) {
    // each 'entry' is a [key, value] tupple
    result[key] = value;
  }
  return result;
}

export const setupGrowthbook = async function () {
  window.dispatchEvent(new Event('growthbook:setup'));

  try {
    window.heap?.track('growthbook:setup');
  } catch (e) {
    console.log('heap not ready?');
  }

  console.log('setup growthbook');
  await waitForHeapId();
  console.log('heap up');

  // Wait for features to be available
  await growth_book.init({ timeout: 1000 });

  for (const key in features) {
    const feature = features[key];
    const value = growth_book.getFeatureValue(feature.key);
    if (typeof value != 'undefined') {
      feature.init(value);
    }
  }

  growth_book.setRenderer(() => {
    for (const key in features) {
      const feature = features[key];
      const value = growth_book.getFeatureValue(feature.key);
      if (typeof value != 'undefined') {
        feature.init(value);
      }
    }
  });

  console.log('growthbook ready');

  //publish event that growthbook is ready
  window.dispatchEvent(new Event('growthbook:ready'));

  try {
    window.heap?.track('growthbook:ready');
  } catch (e) {
    console.log('heap not ready?');
  }
};
Ok maybe this is a misunderstanding of how the tracking callback is getting called. It seems that the tracking callback is getting called only if the feature is looked for? is that correct? We don't check the value of this feature until after somebody adds to cart, and then it seems that the "expirement viewed" is run. When is the actual assignment done? at the same time?
a
What error message are you getting, if any?