استخدام الأحداث المخصصة لتحسين التتبع لديك

التقط جميع أنواع الأحداث المخصصة عبر واجهة برمجة التطبيقات الخاصة بالأحداث المخصصة

استخدام الأحداث المخصصة لتحسين التتبع لديك

الأحداث المخصصة مفهوم بسيط لكنه قوي يوفره الـ Tracker لدينا دون الحاجة إلى إضافة أي شيء إضافي. يمكنها توسيع البيانات المتتبَّعة بأي شيء آخر تحتاجه، سواء كانت أخطاءً مخصصة مرتبطة بمنطق عملك أو حتى أحداثًا بسيطة، لتكون على دراية بما يفعله مستخدموك.

سيتابع الـ tracker لدينا، افتراضيًا، العديد من الأمور المختلفة، بما في ذلك بعض الأخطاء المفيدة، لكنها قد لا تكون كافية لك، ولهذا السبب لدينا الأحداث المخصصة.

إضافة الأحداث المخصصة

Section titled إضافة الأحداث المخصصة

في هذا المثال، لنأخذ موقعًا عامًّا للتجارة الإلكترونية ونضيف بعض الأحداث لفهم متى يضيف مستخدمنا منتجًا إلى عربة التسوق.

افتراضيًا، لن يتتبع OpenReplay تلك المعلومات. ومع ذلك، من خلال الأحداث المخصصة، يمكنك بسهولة تتبع هذا الأمر.

في هذا الدرس، سنستخدم مشروع Next.js مُعدًّا وفق البنية نفسها المتبعة في درس Next.js، لذا لا تتردد في الاطلاع عليه إن لم تكن قد فعلت بعد.

إنشاء مزوّد الـ Tracker

Section titled إنشاء مزوّد الـ Tracker

المنطق داخل هذا الملف موضّح بالكامل في هذا الدرس.

كل ما تحتاج إلى معرفته الآن هو أن هذا مزوّد سياق (context provider) نقوم بإنشائه، وسيتيح لك التفاعل مع الـ tracker عبر دوال متعددة.

على وجه الخصوص، سنهتم بـ logIssue وlogEvent، اللذين يتيحان لك إرسال مشكلة أو حدث مخصص إلى المنصة.

  • الأحداث (Events) مخصصة لتسجيل الإجراءات الخاصة بالمستخدم. ففي حالتنا، على سبيل المثال، سنسجل إضافة منتج إلى العربة.
  • المشكلات (Issues)، من ناحية أخرى، مخصصة لتسجيل الأخطاء التي لا يلتقطها الـ tracker لدينا تلقائيًا. في حالتنا، سنحاكي خطأ شبكة يمنعنا من الوصول إلى واجهة برمجة تطبيقات تابعة لجهة خارجية. وعندما يحدث ذلك، سنسجّل مشكلة على المنصة.
import { createContext, useCallback } from 'react'
import Tracker from '@openreplay/tracker'
import { v4 as uuidV4 } from 'uuid'
import { useReducer } from 'react'

export const TrackerContext = createContext()
function defaultGetUserId() {
  return uuidV4()
}
function newTracker(config) {
  const getUserId =
    config?.userIdEnabled && config?.getUserId
      ? config.getUserId
      : defaultGetUserId
  let userId = null
  const trackerConfig = {
    projectKey:
      config?.projectKey || process.env.NEXT_PUBLIC_OPENREPLAY_PROJECT_KEY,

  }
  if (config?.ingestPoint || process.env.NEXT_PUBLIC_OPENREPLAY_INGEST_POINT) {
    trackerConfig.ingestPoint =
      config?.ingestPoint || process.env.NEXT_PUBLIC_OPENREPLAY_INGEST_POINT
  }

  console.log('Tracker configuration: ')
  console.log(trackerConfig)
  const tracker = new Tracker(trackerConfig)
  if (config?.userIdEnabled) {
    userId = getUserId()
    tracker.setUserID(userId)
  }
  return tracker
}
function reducer(state, action) {
  switch (action.type) {
    case 'init': {
      if (!state.tracker) {
        console.log('Instantiaing the tracker for the first time...')
        let t = newTracker(state.config)
        let pluginsReturnedValue = {}
        if (state.config.plugins) {
          state.config.plugins.forEach((p) => {
            console.log('Using plugin...')
            pluginsReturnedValue[p.name] = t.use(p.fn(p.config))
          })
        }
        return {
          ...state,
          pluginsReturnedValue: pluginsReturnedValue,
          tracker: t,
        }
      }
      return state
    }
    case 'start': {
      console.log('Starting tracker...')
      state.tracker.start()
      return state
    }
    case 'logEvent': {
      console.log('Logging event')
      state.tracker?.event(action.payload?.name, action.payload?.data)
      return state
    }
    case 'logIssue': {
      console.log('Logging issue')
      state.tracker?.issue(action.payload?.name, action.payload?.data)
      return state
    }
  }
}
export default function TrackerProvider({ children, config = {} }) {
  let [state, dispatch] = useReducer(reducer, {
    tracker: null,
    pluginsReturnedValue: {},
    config,
  })
  let value = {
    startTracking: () => dispatch({ type: 'start' }),
    initTracker: () => dispatch({ type: 'init' }),
    logEvent: (evnt) => dispatch({ type: 'logEvent', payload: evnt }),
    logIssue: (evnt) => dispatch({ type: 'logIssue', payload: evnt }),
    pluginsReturnedValues: { ...state.pluginsReturnedValue },
  }
  return (
    <TrackerContext.Provider value={value}>{children}</TrackerContext.Provider>
  )
}

تمتلك الدالتان logEvent وlogIssue التوقيع نفسه؛ إذ سنمرّر كائنًا يحتوي على الخاصيتين name وdata. سيُستخدم name لتعريف سجلّنا في واجهة OpenReplay، بينما سيحتوي data على المعلومات المسجَّلة.

تذكّر: يجب أن تحتوي الخاصية data على كائن قابل للتسلسل (serializable).

بعد ذلك يمكننا إعداد هذا المزوّد في ملف _app.tsx لدينا على النحو التالي:


//imports here...

export default function MyApp({ Component, pageProps }: AppProps) {
  const Layout = (Component as any).Layout || Noop

  useEffect(() => {
    document.body.classList?.remove('loading')
  }, [])

  return (
    <TrackerProvider config={{}}>
      <Head />
      <ManagedUIContext>
        <Layout pageProps={pageProps}>
          <Component {...pageProps} />
        </Layout>
      </ManagedUIContext>
    </TrackerProvider>
  )
}

وبعد الانتهاء من ذلك، يمكننا الآن الانتقال إلى تشغيل الأحداث.

تسجيل المشكلات والأحداث المخصصة

Section titled تسجيل المشكلات والأحداث المخصصة

لهذا الغرض، سنستفيد من واجهة المستخدم لدينا:

صفحة المنتج

سنسجّل حدثًا جديدًا في كل مرة يضيف فيها المستخدم منتجًا إلى عربتنا (أي أساسًا عند الضغط على زر “ADD TO CART”).

وسنسجّل مشكلة إذا فعل ذلك دون تحديد مقاس أولًا.

يمكنك الاطلاع على الشيفرة المصدرية الكاملة لهذا المكوّن من هنا مباشرة، لكن دعنا نركّز على المنطق الذي سنضيفه.

في بداية مكوّننا، سنستخدم الـ hook المسمّى useContext:

//outside the component
import { TrackerContext } from '../../../context/trackerProvider'

//inside the component
const { logEvent, logIssue } = useContext(TrackerContext)

داخل الدالة addToCart، سنضيف المنطق التالي للتحقق مما إذا لم يكن هناك مقاس صالح محدّد:

const validSizes = product.options
      .filter((o) => o.id == 'option-size')
      .map((o) => o.values)[0]

    let pickedSized = validSizes.find((s) => {
      return selectedOptions.size == s.label.toLowerCase()
    })

    if (!pickedSized) {
      logIssue({
        name: 'Product added without a size',
        data: {
          product_id: product.id,
          added_date: new Date(),
          available_options: validSizes,
        },
      })
    }

الجزء الأساسي من الشيفرة هو جملة IF الأخيرة؛ فعندما ندرك أنه لا يوجد مقاس صالح محدّد، نستدعي الدالة logIssue، التي حصلنا عليها من استدعاء useContext السابق.

أما بالنسبة إلى الحدث، فسنتعمّق أكثر في الدالة نفسها وسنضيف:

logEvent({
        name: 'product_added',
        data: {
          id: product.id,
          date_added: new Date(),
        },
      })

هذا كل ما نحتاجه؛ بعد ذلك يمكننا الانتقال إلى OpenReplay، والعثور على تسجيل الجلسة الخاص بنا، وفحص قسم الأحداث (Events):

لوحة الأحداث المخصصة

وإذا أردنا الاطلاع على التفاصيل، يمكننا النقر على رابط “DETAILS” الذي يظهر عند تمرير المؤشر فوق أحد الصفوف:

تفاصيل حدث مخصص

تلك هي التفاصيل التي سجّلناها عند إضافة منتج.

تذكّر أن تطّلع على درس Next.js الكامل إذا كانت هذه أول مرة تستخدم فيها OpenReplay مع هذا الإطار.

وإذا أردت مراجعة الشيفرة المصدرية الكاملة لهذا المثال، يمكنك العثور عليها هنا.

إذا واجهت أي مشكلات تتعلق بالأحداث المخصصة في مشروعك، يُرجى التواصل معنا عبر مجتمعنا على Slack وطرح أسئلتك على مطوّرينا مباشرةً!