استخدام إضافة GraphQL
إذا كنت تستخدم GraphQL كلغة استعلام لواجهات برمجة التطبيقات (APIs) الخاصة بك من داخل تطبيقك، فإن إضافة GraphQL يمكن أن تساعدك في تتبّع كل من التغييرات (mutations) والاستعلامات (queries) التي تُنفَّذ على خادم GraphQL الخاص بك.
في هذا الدرس، سنستخدم عميل Apollo Boost، ولكن ما دام العميل الذي تستخدمه يتيح لك إعداد بعض البرمجيات الوسيطة (middleware)، فستتمكن من استخدام الإضافة في شيفرتك.
إذا كنت ترغب في المتابعة معنا، يمكنك الاطلاع على هذا المستودع الذي يحتوي على كل من خادم GraphQL وتطبيق العميل.
إعداد المتتبّع (Tracker) أولاً
Section titled إعداد المتتبّع (Tracker) أولاًقبل تثبيت إضافة GraphQL، ستحتاج إلى أن يكون المتتبّع (Tracker) مثبّتًا لديك. إذا كنت تعرف بالفعل كيفية القيام بذلك، فانتقل إلى القسم التالي، وإلا فتابع القراءة.
سنحفظ هذه الشيفرة داخل وحدة (module) منفصلة ستصدّر دالتين: init و start.
الدالة الأولى ستنشئ نسخة من المتتبّع وتعدّ جميع الإضافات؛ أما الثانية فستستدعي فقط دالة start.
import { tracker } from '@openreplay/tracker';
export function init({plugins}) {
tracker.configure({
projectKey: process.env.OPENREPLAY_PROJECT_KEY
});
let pluginResults = {}
if(plugins) {
Object.keys(plugins).forEach( pk => {
pluginResults[pk] = tracker.use(plugins[pk]())
})
}
return pluginResults
}
export function start() {
return tracker.start()
}
الجانب المثير للاهتمام في دالة init هو أنها تُرجع كائنًا يتكوّن من جميع القيم التي تُرجعها الإضافات. بعض إضافاتنا ستُرجع دالة سيتعيّن عليك استخدامها لاحقًا (كما في حالة إضافة GraphQL). يتيح لك هذا الأسلوب تهيئة المتتبّع بجميع الإضافات دفعة واحدة، ثم استخدام القيم المُرجَعة متى شئت.
استخدام الإضافة
Section titled استخدام الإضافةبعد تثبيت الإضافة باستخدام npm i @openreplay/tracker-graphql، استخدم الشيفرة التالية لاستدعاء دالة init التي عرّفناها للتو:
import trackerGraphQL from '@openreplay/tracker-graphql';
import {init} from './tracker/index'
const {graphqlTracker} = init({
plugins: {
graphqlTracker: trackerGraphQL
}
})
المفتاح graphqlTracker المستخدَم هنا يمكن أن يكون أي شيء تريده. ما دام المفتاح المستخدَم داخل قسم plugins هو نفسه الذي تفكّكه (destructuring) من نتائج دالة init، فلن تواجه أي مشكلة.
إعداد الإضافة مع عميل Apollo
Section titled إعداد الإضافة مع عميل Apolloفي هذا الدرس، سنستخدم مكتبة Apollo Boost، التي تتيح لك تعديل تدفّق بيانات كل طلب من خلال ما يسمّونه “links” (الروابط).
هذه الروابط تشبه دوال البرمجيات الوسيطة (middleware) التي يمكنك استخدامها لاعتراض تدفّق بيانات الطلب، وفي حالتنا، تسجيله.
ستنشئ الشيفرة التالية رابطًا جديدًا باستخدام دالة ApolloLink. سيلتقط هذا الرابط بيانات العملية ونتائجها، ويستدعي دالة graphqlTracker الخاصة بنا (تلك التي تُرجَع من استدعاء init أعلاه).
const trackerApolloLink = new ApolloLink((operation, forward) => {
const operationDefinition = operation.query.definitions[0];
let {operationName, variables} = operation
const {kind, operation: op} = operationDefinition
const opKind = kind === 'OperationDefinition' ? op : 'unknown?'
let results = forward(operation).map((result) => {
return graphqlTracker(opKind, operationName, variables, result);
});
if(results.length === 0) { //if there are no results, then we've not tracked anything so far...
graphqlTracker(opKind, operationName, variables, {});
}
return results
});
بعد الانتهاء من ذلك، يمكننا استخدام الرابط الذي أنشأناه للتوّ على النحو التالي:
import {ApolloClient, HttpLink } from 'apollo-boost';
import { ApolloProvider } from '@apollo/react-hooks';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloLink, from } from '@apollo/client';
const link = from([
trackerApolloLink,
new HttpLink({uri: () => 'http://localhost:4000/graphql'}),
]);
const client = new ApolloClient({
link,
cache: new InMemoryCache()
});
ReactDOM.render(<ApolloProvider client={client}>
<App />
</ApolloProvider>, document.getElementById('root'));
الشيفرة أعلاه مأخوذة من وثائق Apollo، وعند هذه النقطة يكون المتتبّع والإضافة قد أُعِدّا بالفعل، لذا لا داعي حقًا للقلق بشأن أي شيء آخر.
بمجرد الانتهاء، ستعرض عمليات إعادة التشغيل (replays) الخاصة بك قسمًا جديدًا يسرد جميع عمليات GraphQL.

ومع ذلك، فإن المعلومات الحساسة التي يقوم المتتبّع بتنقيتها تلقائيًا (مثل عناوين البريد الإلكتروني) لن تُنقَّى بواسطة الإضافة. لذلك ستواجه حالات كالتالية، حيث يحتوي الـ DOM على البيانات المُنقّاة، بينما تُظهِر تفاصيل العملية البيانات الفعلية.

في حين أن الإضافة نفسها لا توفّر أي دالة تنقية، لا يزال بإمكاننا إضافة شيفرة تخفي المعلومات الشخصية والخاصة من إعادة التشغيل للمساعدة في الحفاظ على خصوصية المستخدمين لديك.
تنقية البيانات المسجَّلة
Section titled تنقية البيانات المسجَّلةإذا نظرت إلى عيّنة الشيفرة التي أنشئ فيها كائن trackerApolloLink، فسترى أن كل ما أفعله هو استدعاء دالة المتتبّع التي تحفظ المعلومات على المتتبّع.
إذا لم أُغيّر البيانات، فسيُحفظ كل شيء دون تغيير. لذا، لتنقية البيانات في إعادة التشغيل وإبقاء العملية دون تغيير، نحتاج إلى استنساخ المتغيّرات الأساسية قبل استدعاء المتتبّع. وهذا يعني استنساخ المتغيّرات والنتائج من العملية، وهذا كل ما نريده.
إليك إذن مقتطف شيفرة سينشئ ApolloLink ويُبقي البيانات سرّية ضمن بيانات إعادة التشغيل:
/**
* Sanitize the result from a GraphQL operation
* @returns Returns the result object but with the sanitized fields changed.
*/
function sanitizeResult(res) {
//deep clonning needs to happen to make sure this only affects the new object and not
//the original object.
let sanitized = JSON.parse(JSON.stringify(res))
let ops = Object.keys(sanitized.data)
ops.forEach( o => {
if(Array.isArray(sanitized.data[o])) { //mutations don't really return arrays
sanitized.data[o] = sanitized.data[o].map( sanitizeData )
}
})
return sanitized
}
// We only want to hide the content of othe "email" field for now.
function sanitizeData(vars) {
let newVars = {...vars}
if(newVars.email) {
newVars.email = "****@***.***"
}
return newVars
}
const trackerApolloLink = new ApolloLink((operation, forward) => {
const operationDefinition = operation.query.definitions[0];
let {operationName, variables} = operation
const {kind, operation: op} = operationDefinition
const opKind = kind === 'OperationDefinition' ? op : 'unknown?'
let trackedVariables = sanitizeData({...variables})
let results = forward(operation).map((result) => {
let trackeresults = sanitizeResult(result)
graphqlTracker(opKind, operationName, trackedVariables, trackeresults);
return result //we have to return the original "result" object here, not the sanitized one
});
if(results.length === 0) { //if there are no results, then we've not tracked anything so far...
graphqlTracker(opKind, operationName, trackedVariables, {});
}
return results
});
الجوانب الأساسية في هذه الشيفرة هي:
- أضفنا دالتين، إحداهما لتنقية
emailمن كائن، والأخرى لتنقية نتائج عملية GraphQL. - داخل دالة الاستدعاء
map(من دالة الرابط)، لم نعد الآن نُرجع مخرجات graphqlTracker، لأن تلك الدالة ستُرجع قيمة النتيجة التي تلقّتها دون تغيير. 1. لكن تلك النتيجة ستُرجَع إلى تطبيق العميل، وإذا كنا نقوم بتنقية النتيجة، فسيرى المستخدم النسخة المُنقّاة من مجموعة البيانات. بدلًا من ذلك، نحتاج إلى استنساخ النتيجة لتعديل النسخة التي يجري تتبّعها وإرجاع النسخة الأصلية. - تقوم دالة
sanitizeResultباستنساخ الكائن استنساخًا عميقًا لأن تعديله بطريقة أخرى سيُغيّر النتيجة نفسها.

هل لديك أسئلة؟
Section titled هل لديك أسئلة؟يمكنك الاطلاع على هذا المستودع للحصول على الشيفرة المصدرية الكاملة لتطبيق يعمل قائم على GraphQL مع المتتبّع (Tracker).
إذا واجهت أي مشكلات في إعداد المتتبّع في مشروع GraphQL الخاص بك، فيرجى التواصل معنا عبر مجتمع Slack الخاص بنا وطرح أسئلتك على مطوّرينا مباشرةً!