كيفية بناء أول إضافة (plugin) خاصة بك في OpenReplay

تعلّم كيفية إنشاء الإضافات الخاصة بك في OpenReplay

كيفية بناء أول إضافة (plugin) خاصة بك في OpenReplay

إذا كنت تستضيف نسختك الخاصة من OpenReplay ذاتياً (self-hosting)، فإنك تتمتع بميزة إضافية تتمثل في القدرة على تطوير وتثبيت إضافات مخصّصة. وهذا سيتيح لك إضافة وظائف وتوافق لا توفّره OpenReplay جاهزاً (out-of-the-box).

في الوقت الحالي، هناك بضع إضافات طوّرها وتصونها فريق OpenReplay، مثل إضافة Redux التي تتيح لك تتبّع تغييرات الحالة، أو إضافة Fetch التي تمنحك القدرة على تسجيل بيانات Request و Response. يمكنك الاطلاع على القائمة الكاملة للإضافات هنا إذا كنت ترغب في معرفة المزيد.

في هذا الدرس التعليمي، ستتعلّم كيفية بناء الإضافة الخاصة بك. وعلى وجه التحديد، سنبني إضافة تتتبّع طريقة GET الخاصة بـ JQuery.

بناء إضافة من الصفر

Section titled بناء إضافة من الصفر

نوصي بأن تبدأ بنسخ ولصق مجلد إضافة تقوم بشيء مشابه لما تسعى إلى القيام به، وبهذه الطريقة سيكون لديك كل المنطق وشيفرة الإعداد جاهزَين.

ومع ذلك، بما أن الإضافة التي سنبنيها هنا ستتصرف بشكل مشابه لطريقة عمل إضافة Fetch، فسنقوم بتكرار (duplicate) تلك الإضافة.

ومع ذلك، سيظل عليك القيام بأمور مثل:

  • تعديل الواجهة الخلفية (back-end).
  • إضافة رسائل جديدة إلى بروتوكول الرسائل.
  • بناء بعض خدمات الواجهة الخلفية من الشيفرة المصدرية.

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

أما إذا كنت قد أتقنت هذا الجزء بالفعل، فتابع القراءة!

ماذا تحتاج لبناء إضافة جديدة؟

Section titled ماذا تحتاج لبناء إضافة جديدة؟

هناك ثلاثة أجزاء لبناء إضافة جديدة:

  1. أولاً، عليك تعريف نوع الرسالة التي ستُرسلها إضافتك إلى المنصة. تحتاج هذه الرسالة إلى أن تحتوي على كل بيانات القياس عن بُعد (telemetry) التي تريد تتبّعها.
  2. بعد ذلك، عليك تحديث الواجهة الأمامية (front-end) لإنشاء مكوّن (component) قادر على عرض البيانات الواردة من الإضافة. في حالتنا، سنضيف تبويباً في المشغّل (replayer) يَسرد جميع الطلبات التي تم تنفيذها باستخدام JQuery.
  3. وأخيراً، سيتعيّن عليك أيضاً إنشاء الإضافة! تذكّر، فهذا هو سبب وجودنا هنا. الإضافة هي الجزء الوحيد من كل هذا الذي يمكنك تطويره خارج OpenReplay، وستقوم بتثبيته في مكان آخر (تطبيقك).

لاحظ أن بقية الدرس ستفترض أنك قمت بعمل fork لمستودعنا على Github وأنه جاهز ويعمل لديك في مكان ما. إذا لم تكن قد فعلت ذلك، يُرجى مراجعة قسم النشر (deployment) في وثائقنا أولاً.

الخطوة 1: بناء رسالتك الجديدة

Section titled الخطوة 1: بناء رسالتك الجديدة

أول ما يجب فعله هو فهم البيانات التي تريد التقاطها وعرضها بواسطة إضافتك. في حالتنا، وبما أننا نبني شيئاً لتتبّع طلبات GET، فعلينا التأكد من أننا نلتقط العناصر التالية:

  • URL: بطبيعة الحال، عنوان الـ URL الخاص بالطلب
  • Response: سنحاول أيضاً التقاط بعض الاستجابات
  • Status: رمز الحالة (status code) المُستلَم من الخادم

سنضيف أيضاً خاصية «duration»، لتتبّع المدة التي يستغرقها تنفيذ الطلب والحصول على بعض البيانات، وسنحتاج لذلك إلى «timestamp» الطلب.

للقيام بذلك، سيتعيّن علينا تعديل الملف mob/messages.rb. هذا ملف بلغة Ruby، لكن لا تقلق إذا كنت لا تعرف أي شيء عن Ruby، فكل ما عليك فعله هو إضافة سجلّ جديد يصف رسالتك الجديدة، شيء كهذا:

message 112, 'JQueryGET' do
  string 'Method'
  string 'url'
  string 'response'
  string 'status'
  string 'duration'
  int  'timestamp'
end

لاحظ أنني عرّفتُ أيضاً:

  • معرّف رسالة (112)، وهو رقم عشوائي غير مُستخدَم بالفعل (يمكنك النظر في الرسائل الأخرى في الملف) وأصغر من 200.
  • اسم رسالة، وهذا متروك لك تماماً، فقط احرص على أن يصف نوع رسالتك.
  • حقل «method»، وبهذه الطريقة قد نتمكّن من إعادة استخدام الرسالة لطرق أخرى في المستقبل.
  • أنواع الخصائص بصيغة “string” و “int”، وذلك لأن المُرمِّز (encoder) لدينا سيحوّل المصفوفات والكائنات إلى سلاسل نصية (strings).

بعد الانتهاء من هذا، عليك التأكد من أن الرسالة يمكن الوصول إليها من كل مكان، لذا ستُشغّل سكربتَين (scripts) داخل هذا المجلد:

$ ruby run.rb
$ sh format.sh

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

لننتقل الآن إلى الواجهة الأمامية ولننظر في ما نحتاج إلى تغييره.

الخطوة 2: تحديث الواجهة الأمامية

Section titled الخطوة 2: تحديث الواجهة الأمامية

بعد أن أصبحت الرسالة جاهزة، ستحتاج إلى تحديث الدالة messageDistributor (داخل الملف frontend/app/player/MessageDistributor/MessageDistributor.ts)، التي تستقبل جميع الرسائل وتقرّر ما يجب فعله بها.

داخل الدالة، سترى عبارة switch كبيرة و case جديدة بداخلها مخصّصة لرسالتك بالفعل (ستلاحظ أن الاسم المُستخدَم مكتوب بحالة أحرف مختلفة عمّا استخدمتَه). في حالة هذا المثال، بالنسبة لنوع الرسالة «JQueryGET»، ستحصل على نوع «j_query_get».

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

ينبغي أن تبدو الـ case هكذا:

case 'j_query_get': 
    listAppend("jquery", Resource({
      method: msg.method,
      url: msg.url,
      payload: {},
      response: msg.response,
      status: msg.status,
      type: TYPES.JQUERY,
      time: msg.timestamp - this.sessionStart,
      duration: msg.duration,
      index
    }))
  break;

الآن، هناك بضعة أمور يتعيّن علينا القيام بها لجعل هذا يعمل:

  • علينا إضافة قائمة «jquery»، وللقيام بذلك سنعدّل الملف frontend/app/player/lists/index.js ونضيف «jquery» إلى المصفوفة entityNamesWithRed.
  • علينا إضافة TYPES.JQUERY، ويمكنك القيام بذلك عن طريق تعديل هذا الملف: frontend/app/types/session/resource.js

بعد الانتهاء من ذلك، سننشئ المكوّن الفعلي الذي يعرض البيانات داخل المشغّل. شيء كهذا:

النتيجة النهائية

للقيام بذلك، سنعتمد على مكوّن موجود مسبقاً، وسننسخ ونلصق مكوّن Fetch. ولفعل ذلك، سنكرّر المجلد frontend/app/components/Session_/Fetch ونسمّيه «JQuery».

سنحدّث كل إشارة إلى fetch داخل المجلد المنشأ حديثاً للتأكد من أنها تقرأ “jquery” (من المنطقي على الأرجح استخدام عملية «بحث واستبدال» هنا).

سيؤدي ذلك إلى إنشاء مكوّن التصوّر (visualization) الذي سيتعيّن عليك بعد ذلك إضافته يدوياً. أولاً، عليك تحرير الملف frontend/app/components/Session_/Player/Controls/Controls.js لإضافة زر «JQuery» في الزاوية السفلية اليمنى من الشاشة، حيث تُعرض جميع عناصر التحكّم.

ابحث عن شيفرة JSX وأضف كتلة (block) كهذه:

{showJQuery && (
  <ControlButton
    disabled={disabled && !inspectorMode}
    onClick={() => toggleBottomTools(JQUERY)}
    active={bottomBlock === JQUERY && !inspectorMode}
    hasErrors={jqueryRedCount > 0}
    count={jqueryCount}
    label="JQUERY"
    noIcon
    labelClassName="!text-base font-semibold"
    containerClassName="mx-2"
  />
)}

وبينما أنت في ذلك، احرص على تعريف جميع المتغيّرات ذات الصلة، مثل showJQuery و jqueryCount و JQUERY و jqueryRedCount. إذا لم تكن متأكداً من كيفية القيام بذلك، فابحث عن نظائرها الخاصة بـ fetch، وستجدها بسرعة.

بعد إضافة الزر، يمكنك الآن تحرير الملف frontend/app/components/Session_/Player/Player.js وإضافة المكوّن الفعلي.

للقيام بذلك، وبعد استيراده، احرص على إضافة كتلة كالتالية داخل قسم JSX:

{ bottomBlock === JQUERY &&
  <JQuery />
}

وكجزء من هذا، سيتعيّن عليك أيضاً إضافة الثابت JQUERY ليتم تصديره من الملف frontend/app/duck/components/player.js.

سيتكفّل ذلك بكل شيء.

أصبحت الواجهة الأمامية لديك الآن جاهزة لعرض المعلومات، ونحتاج إلى إيجاد طريقة لتوليدها. فلننظر إذن إلى الشيفرة الفعلية للإضافة!

الخطوة 3: بناء إضافتك

Section titled الخطوة 3: بناء إضافتك

يمكنك إنشاء هذا المشروع في أي مكان تريده. المغزى هو توفير حزمة (package) قابلة للتثبيت لاستخدامها في مشاريع أخرى.

لذا احرص على أن يبدو الملف package.json هكذا:

{
  "name": "tracker-jquery",
  "version": "1.0.0",
  "description": "jquery openreplay tracker",
  "main": "src/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "peerDependencies": {
    "@openreplay/tracker": ">=3.6.0"
  },
  "devDependencies": {
    "@openreplay/tracker": "<latest version of the tracker>",
    "prettier": "^1.18.2",
    "replace-in-files-cli": "^1.0.0"
  }
}

وسنضيف ملف index.js داخل مجلد src الخاص بالمشروع. أنت حرّ في هيكلة هذا المشروع كما تراه مناسباً ما دامت الدوال المُصدَّرة لديك تتّبع معايير معيّنة محدّدة مسبقاً.

بنية الإضافة بسيطة: تعرّف دالة، عند استدعائها، تُعيد دالة جديدة تتوقّع أن يُمرَّر إليها سمة (attribute) app. تحتوي سمة app هذه على نسخة (instance) الـ tracker. داخل الدالة المُعادة، أنت حرّ في فعل كل ما تريد ما دمت ترسل رسالة عبر الكائن app. ستستقبل المنصة تلك الرسالة وتعالجها وفقاً لمجموعة من القواعد.

سيكون تنسيق الرسالة هو ذلك الذي عرّفناه في بداية هذا الدرس.

دعني أريك كيف تبدو شيفرة إضافة JQuery الخاصة بنا:

import { App, Messages } from '@openreplay/tracker';

export default function($) {

    const oldGET = $.get;

    return (app) => {

        const newGET = async (settingsObj) => {

            const startTime = performance.now();
            let resp = await fetch(settingsObj.url, {
                method: 'GET'
            })
            const duration = performance.now() - startTime;

            let valueResp = null;

            if(settingsObj.json) {
                valueResp = await resp.json()
            } else {
                valueResp = await resp.text()
            }
            const getStj = (res) => {
                let r = {...res}
                if (r && typeof r.body !== 'string') {
	                try {
	                    r.body = JSON.stringify(r.body)
	                } catch {
	                    r.body = "<unable to stringify>"
	                }
                }
                return JSON.stringify(r)
            }

            const msg = Messages.JQueryGET(
                    'GET',
                    String(settingsObj.url),
                    getStj(resp),
                    resp.status,
                    duration,
                    (new Date()).getTime()
                )
            console.log("Sending a message to the tracker....", msg)

            app.send(msg, true)   
            return valueResp;

        }

        $.get = newGET;

    }
}

في جوهر الأمر، كل ما أفعله هو استبدال طريقة .get الخاصة بـ JQuery بطريقة مخصّصة تستخدم في الواقع fetch خلف الكواليس (هذا اختياري تماماً، إذ يمكنك استخدام طريقة GET الخاصة بـ JQuery أيضاً). وبعد تنفيذ جميع الطلبات، تُرسل الشيفرة رسالة باستخدام طريقة app.send. تُنشأ الرسالة باستخدام طريقة Messages.JQueryGET التي أنشأها لنا سكربت Ruby السابق.

السمات التي تستقبلها هذه الرسالة هي، بطبيعة الحال، تلك التي عرّفناها على أنها هيكل الرسالة نفسها خلال الخطوة 1. والأهم من ذلك أنها جميعاً إلزامية؛ فإذا كان عليك التعامل مع قيمة فارغة، فمرّر المصفوفة/الكائن/السلسلة/أياً كان فارغاً، لكن لا تتجاوز المفتاح (key) أبداً.

بمجرد الانتهاء من اختباراتك المحلية وتأكُّدك من أن الإضافة جاهزة، ستحتاج إلى نشرها على NPM.

اتّبع هذا الدليل لفهم كيفية القيام بذلك: https://docs.npmjs.com/creating-and-publishing-unscoped-public-packages

بمجرد النشر، ستريد استخدام إضافتك في كل مكان. آليّة استخدام الإضافات مع tracker الخاص بـ OpenReplay هي دائماً نفسها.

  1. عليك أولاً إنشاء نسخة (instance) من الـ tracker.
  2. ثم عليك استدعاء طريقة use وتمرير الدالة الناتجة عن استدعاء إضافتك إليها.
  3. وأخيراً، شغّل الـ tracker باستخدام طريقة start.

إليك نموذجاً للشيفرة يوضّح كيف يبدو استخدام هذه الإضافة داخل تطبيق React:

import logo from './logo.svg';
import './App.css';
import tracker from 'openreplay-tracker'
import jqueryTracker from 'tracker-jquery'
import { useEffect } from 'react';
import $ from 'jquery'

const t = new tracker({
  __DISABLE_SECURE_MODE: true, //only if you're testing locally
  ingestPoint: "openreplay.<your custom domain>/ingest",
  projectKey: "<your project key>",
})

function App() {
  useEffect(()=> {
    t.use(jqueryTracker($))
    t.start()
    async function doGet() {
      let resp = await $.get({
        url: "http://localhost:3000"
      })
      console.log(resp)
    }

    doGet()
  }, [])
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

تقوم الشيفرة بتنفيذ طلب GET باستخدام طريقة get الخاصة بـ Jquery بمجرد تحميل الصفحة، وبما أننا أعددنا الـ tracker الصغير البارع الخاص بنا، فإننا قادرون فعلاً على تتبّع ذلك الطلب.

إليك كيف ينبغي أن تبدو النتيجة النهائية لديك:

عرض طلبات JQuery


يمكنك الاطلاع على هذا المستودع للحصول على الشيفرة المصدرية الكاملة لهذا الدرس.

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