أداة لوحة المعلومات مع واجهات برمجة تطبيقات Bird

زاكاري صامويلز

24‏/03‏/2022

البريد الإلكتروني

1 min read

أداة لوحة المعلومات مع واجهات برمجة تطبيقات Bird

زاكاري صامويلز

24‏/03‏/2022

البريد الإلكتروني

1 min read

أداة لوحة المعلومات مع واجهات برمجة تطبيقات Bird

هذا النص يلامس السطح فقط لما هو ممكن باستخدام Python, Plotly Dash, وواجهات برمجة التطبيقات الخاصة بنا.

قبل حوالي عام، كتب توم ميرز، مدير نجاح العملاء في Bird، أداة مراسلة باستخدام واجهات برمجة التطبيقات (APIs) الخاصة بـ Bird. في هذه المقالة، ألتقط الأمر من حيث تركه. تسمح أداته بالنقل الزمني للوظائف، ولكن ماذا لو أردنا إنشاء لوحات معلومات خاصة بنا وسجلات أحداث؟

ربما أرغب في إنشاء لوحة معلومات مخصصة لمجموعة أعمال أو لوحة معلومات مواجهة للعملاء، ولكن دون توفير وصول كامل للمستخدمين إلى واجهة مستخدم Bird. هذا النص البرمجي يلمس فقط السطح لما هو ممكن باستخدام Python، Plotly Dash، وAPIs الخاصة بنا. عند بناء لوحات معلومات تعالج بيانات API ذات حجم كبير، كن واعيًا بأن المكونات التحتية مثل DNS يمكن أن تصبح مراكز اختناق - لقد عانينا من تحديات توسيع نطاق DNS لـ AWS أثرت على قدرات معالجة البيانات لدينا. لمحبي سير العمل المرئي، يمكنك أيضًا استكشاف دمج Flow Builder مع وظائف Google Cloud وVision API لإضافة أتمتة تعتمد على الذكاء الاصطناعي لخطوط معالجة البيانات الخاصة بك.

عندما بدأت بحثي على الإنترنت، كنت أرغب في إيجاد المسار الأقل مقاومة. كان يمكنني إنشاء جميع لوحات المعلومات وواجهة المستخدم بنفسي في HTML وpython، ومع ذلك، بعد بعض البحث عبر Google، توصلت إلى Dash الخاص بـ Plotly، الذي يتكامل بسهولة مع python. اخترت Dash لسببين: 1) إنه مفتوح المصدر، و2) بعد قراءة الوثائق تبين أنه يمكن تخصيصه بسهولة لما كنت أحاول فعله. Dash هو مكتبة مفتوحة المصدر مثالية لبناء ونشر تطبيقات البيانات بواجهات مستخدم مخصصة. جعل ذلك إنشاء واجهة المستخدم أمرًا بسيطًا للغاية. ثم أصبحت المسألة كيف يمكنني جعل هذا التطبيق معقدًا؟ كلما قضيت وقتًا أكبر، زادت الميزات التي أردت إضافتها.

بالنسبة للمشروع الأولي، أردت التأكد من أن لدي لوحة معلومات بمقاييس قابلة للتخصيص وإطار زمني قابل للاختيار. في البداية، بدأت بلوحة معلومات حيث يمكنك اختيار مقياس واحد فقط من القائمة المسندلة. ثم، بعد تلقي تعليقات من الزملاء، قمت بصقل لوحة المعلومات قليلاً لإضافة خيارات متعددة وعناوين محاور. كما قررت إضافة علامة تبويب إضافية لسجل الأحداث. وصلت إلى نقطة شعرت فيها بالرضا عن ما كان لدي كنقطة انطلاق جيدة لأي شخص يرغب في بناء لوحات معلومات خاصة به. للمطورين الذين يريدون تغذية البيانات في الوقت الفعلي إلى لوحات المعلومات الخاصة بهم، تحقق من دليلنا على بناء مستهلكي webhook بـ Azure Functions. بالطبع، وضعت المشروع في Github لتتمكن من نسخه أو تقسيمه.

البدء

للوصول إلى هذا التطبيق، ستحتاج إلى التأكد من أنك تقوم بتشغيل python 3.10 أو أحدث وتثبيت المكتبات التالية:

  • requests

  • dash

  • pandas

ثم، أدخل مفتاح API الخاص بك في App.py وشغل التطبيق. سوف يعمل على http://localhost:8050. لمزيد من المعلومات حول نشر هذا على خادم عام (مثل AWS)، انظر الموارد التالية:

إنشاء صفحة لوحة التحكم

أولاً، قم بتهيئة إطار البيانات ولوحة المعلومات. بدون تهيئة لوحة المعلومات، لن تظهر أي لوحة في واجهة المستخدم.

df = pd.DataFrame({

"Count": [0,0],

"Time": [0,0]

})

fig = px.line(df,x="Time",y="Count")

ثم قم ببناء التاب الأول. هذا هو مزيج من استدعاء تطبيق (للتحقق من أي تاب يتم استخدامه) جنبًا إلى جنب مع دالة شرطية للتحقق من التاب الذي تم اختياره حاليًا. الكود أدناه يبني فقط لوحة المعلومات الفارغة وعناصر واجهة المستخدم (سنتطرق إلى تاب الأحداث لاحقًا). عناصر dcc هي مكونات Dash Core ومكونات HTML التي تسمح بسهولة لاستخدام HTML في واجهة المستخدم.

html.H2('Analytics Dashboard'),

#قائمة منسدلة متعددة الاختيارات

dcc.Dropdown(['Count Accepted','Count Admin Bounce','Count Block Bounce','Count Bounce','Count Clicked','Count Delayed', 'Count Delayed First','Count Delivered','Count Delivered First','Count Delivered Subsequent','Count Generation Failed', 'Count Generation Rejection','Count Hard Bounce','Count Inband Bounce','Count Initial Rendered','Count Injected', 'Count Out of Band Bounce', 'Count Policy Rejection','Count Rejected','Count Rendered','Count Sent','Count Soft Bounce', 'Count Spam Complaint','Count Targeted','Count Undetermined Bounce','Count Unique Clicked','Count Unique Confirmed Opened', 'Count Unique Initial Rendered','Count Unique Rendered','Count Unsubscribe','Total Delivery Time First','Total Delivery Time Subsequent', 'Total Message Volume'], id="y-axis", multi=True, searchable=True, placeholder="اختر المقياس/المقاييس"),

#محدد التاريخ (يتم تعيين أقصى تاريخ مسموح به إلى تاريخ اليوم) dcc.DatePickerRange( id='date-picker-range', start_date=date(2022,1,1), end_date=date(2022, 2, 1), max_date_allowed=date(datetime.today().year,datetime.today().month,datetime.today().day), ),

#جسم الرسم البياني dcc.Graph( id='Emails', figure=fig )

لاحظ بالاعتماد على Dash كيف يكون إنشاء واجهة لوحة المعلومات بقائمة منسدلة متعددة الاختيارات وقابلة للبحث سهلاً. لتعطيل تعدد الاختيارات أو البحث، يمكن تعديل المعايير لإنشاء قائمة منسدلة بسهولة. وجدت أن الجزء الأكثر تعقيدًا من هذا المشروع كان بناء إطار البيانات الفعلي من المدخلات، وكذلك جعل HTML + CSS يعملان بشكل صحيح في واجهة المستخدم.

يسمح واجهة برمجة تطبيقات مقاييس السلاسل الزمنية بجلب 33 مقياسًا فرديًا بناءً على نطاق تاريخ/وقت. يمكنك التصفية بعمق أكثر حسب المجالات، الحملات، مجموعات عناوين IP، مجالات الإرسال، الحسابات الفرعية، وتحديد دقة بيانات السلاسل الزمنية. يمكن أن تكون هذه التصفية الإضافية جنبًا إلى جنب مع تحليلات الإرسال تحسينًا مستقبليًا لهذا المشروع (سيحتاج إلى تنفيذ الإمساك بالأخطاء من أجل العملاء الذين ليس لديهم حق الوصول إلى تحليلات الإرسال).

باستخدام واستدعاء واجهة برمجة تطبيقات المقاييس، قمت ببناء لوحة معلومات بمعايير وختيارات المستخدم المحددة والإطار الزمني المحدد. ثم يتم تحديث لوحة المعلومات التي تم تهيئتها.

#بناء استدعاء واجهة برمجة التطبيقات باستخدام المعايير المقدمة params = { "from" : start_date + "T00:00", "to" : end_date + "T00:00", "delimiter" : ",", "precision" : "day", "metrics" : joined_values } api_url = BASE_URL + "/metrics/deliverability/time-series" response_API = requests.get(api_url, headers = {"Authorization" : API_KEY}, params=params) response_info = json.loads(response_API.text) new_df = pd.json_normalize(response_info, record_path=['results']) value_array = joined_values.split(",") #بناء لوحة معلومات جديدة باستخدام المقاييس والتواريخ الجديدة من استدعاء واجهة برمجة التطبيقات المحدّث fig = px.line(new_df, x=new_df['ts'], y=value_array, labels={"value": "Count", "variable": "Metric","ts":"Date"}) fig.update_xaxes(title_text="Time") fig.update_yaxes(title_text="Count") return fig

الآتي هو مثال لعدة مقاييس تم اختيارها وإطار زمني موسع.


Sparkpost analytics dashboard

ملاحظة: هناك العديد من العناصر المدمجة في رسم dash تلقائيًا (التحويم، التكبير، التغيير التلقائي للمقياس).

أولاً، قم بتهيئة إطار البيانات ولوحة المعلومات. بدون تهيئة لوحة المعلومات، لن تظهر أي لوحة في واجهة المستخدم.

df = pd.DataFrame({

"Count": [0,0],

"Time": [0,0]

})

fig = px.line(df,x="Time",y="Count")

ثم قم ببناء التاب الأول. هذا هو مزيج من استدعاء تطبيق (للتحقق من أي تاب يتم استخدامه) جنبًا إلى جنب مع دالة شرطية للتحقق من التاب الذي تم اختياره حاليًا. الكود أدناه يبني فقط لوحة المعلومات الفارغة وعناصر واجهة المستخدم (سنتطرق إلى تاب الأحداث لاحقًا). عناصر dcc هي مكونات Dash Core ومكونات HTML التي تسمح بسهولة لاستخدام HTML في واجهة المستخدم.

html.H2('Analytics Dashboard'),

#قائمة منسدلة متعددة الاختيارات

dcc.Dropdown(['Count Accepted','Count Admin Bounce','Count Block Bounce','Count Bounce','Count Clicked','Count Delayed', 'Count Delayed First','Count Delivered','Count Delivered First','Count Delivered Subsequent','Count Generation Failed', 'Count Generation Rejection','Count Hard Bounce','Count Inband Bounce','Count Initial Rendered','Count Injected', 'Count Out of Band Bounce', 'Count Policy Rejection','Count Rejected','Count Rendered','Count Sent','Count Soft Bounce', 'Count Spam Complaint','Count Targeted','Count Undetermined Bounce','Count Unique Clicked','Count Unique Confirmed Opened', 'Count Unique Initial Rendered','Count Unique Rendered','Count Unsubscribe','Total Delivery Time First','Total Delivery Time Subsequent', 'Total Message Volume'], id="y-axis", multi=True, searchable=True, placeholder="اختر المقياس/المقاييس"),

#محدد التاريخ (يتم تعيين أقصى تاريخ مسموح به إلى تاريخ اليوم) dcc.DatePickerRange( id='date-picker-range', start_date=date(2022,1,1), end_date=date(2022, 2, 1), max_date_allowed=date(datetime.today().year,datetime.today().month,datetime.today().day), ),

#جسم الرسم البياني dcc.Graph( id='Emails', figure=fig )

لاحظ بالاعتماد على Dash كيف يكون إنشاء واجهة لوحة المعلومات بقائمة منسدلة متعددة الاختيارات وقابلة للبحث سهلاً. لتعطيل تعدد الاختيارات أو البحث، يمكن تعديل المعايير لإنشاء قائمة منسدلة بسهولة. وجدت أن الجزء الأكثر تعقيدًا من هذا المشروع كان بناء إطار البيانات الفعلي من المدخلات، وكذلك جعل HTML + CSS يعملان بشكل صحيح في واجهة المستخدم.

يسمح واجهة برمجة تطبيقات مقاييس السلاسل الزمنية بجلب 33 مقياسًا فرديًا بناءً على نطاق تاريخ/وقت. يمكنك التصفية بعمق أكثر حسب المجالات، الحملات، مجموعات عناوين IP، مجالات الإرسال، الحسابات الفرعية، وتحديد دقة بيانات السلاسل الزمنية. يمكن أن تكون هذه التصفية الإضافية جنبًا إلى جنب مع تحليلات الإرسال تحسينًا مستقبليًا لهذا المشروع (سيحتاج إلى تنفيذ الإمساك بالأخطاء من أجل العملاء الذين ليس لديهم حق الوصول إلى تحليلات الإرسال).

باستخدام واستدعاء واجهة برمجة تطبيقات المقاييس، قمت ببناء لوحة معلومات بمعايير وختيارات المستخدم المحددة والإطار الزمني المحدد. ثم يتم تحديث لوحة المعلومات التي تم تهيئتها.

#بناء استدعاء واجهة برمجة التطبيقات باستخدام المعايير المقدمة params = { "from" : start_date + "T00:00", "to" : end_date + "T00:00", "delimiter" : ",", "precision" : "day", "metrics" : joined_values } api_url = BASE_URL + "/metrics/deliverability/time-series" response_API = requests.get(api_url, headers = {"Authorization" : API_KEY}, params=params) response_info = json.loads(response_API.text) new_df = pd.json_normalize(response_info, record_path=['results']) value_array = joined_values.split(",") #بناء لوحة معلومات جديدة باستخدام المقاييس والتواريخ الجديدة من استدعاء واجهة برمجة التطبيقات المحدّث fig = px.line(new_df, x=new_df['ts'], y=value_array, labels={"value": "Count", "variable": "Metric","ts":"Date"}) fig.update_xaxes(title_text="Time") fig.update_yaxes(title_text="Count") return fig

الآتي هو مثال لعدة مقاييس تم اختيارها وإطار زمني موسع.


Sparkpost analytics dashboard

ملاحظة: هناك العديد من العناصر المدمجة في رسم dash تلقائيًا (التحويم، التكبير، التغيير التلقائي للمقياس).

أولاً، قم بتهيئة إطار البيانات ولوحة المعلومات. بدون تهيئة لوحة المعلومات، لن تظهر أي لوحة في واجهة المستخدم.

df = pd.DataFrame({

"Count": [0,0],

"Time": [0,0]

})

fig = px.line(df,x="Time",y="Count")

ثم قم ببناء التاب الأول. هذا هو مزيج من استدعاء تطبيق (للتحقق من أي تاب يتم استخدامه) جنبًا إلى جنب مع دالة شرطية للتحقق من التاب الذي تم اختياره حاليًا. الكود أدناه يبني فقط لوحة المعلومات الفارغة وعناصر واجهة المستخدم (سنتطرق إلى تاب الأحداث لاحقًا). عناصر dcc هي مكونات Dash Core ومكونات HTML التي تسمح بسهولة لاستخدام HTML في واجهة المستخدم.

html.H2('Analytics Dashboard'),

#قائمة منسدلة متعددة الاختيارات

dcc.Dropdown(['Count Accepted','Count Admin Bounce','Count Block Bounce','Count Bounce','Count Clicked','Count Delayed', 'Count Delayed First','Count Delivered','Count Delivered First','Count Delivered Subsequent','Count Generation Failed', 'Count Generation Rejection','Count Hard Bounce','Count Inband Bounce','Count Initial Rendered','Count Injected', 'Count Out of Band Bounce', 'Count Policy Rejection','Count Rejected','Count Rendered','Count Sent','Count Soft Bounce', 'Count Spam Complaint','Count Targeted','Count Undetermined Bounce','Count Unique Clicked','Count Unique Confirmed Opened', 'Count Unique Initial Rendered','Count Unique Rendered','Count Unsubscribe','Total Delivery Time First','Total Delivery Time Subsequent', 'Total Message Volume'], id="y-axis", multi=True, searchable=True, placeholder="اختر المقياس/المقاييس"),

#محدد التاريخ (يتم تعيين أقصى تاريخ مسموح به إلى تاريخ اليوم) dcc.DatePickerRange( id='date-picker-range', start_date=date(2022,1,1), end_date=date(2022, 2, 1), max_date_allowed=date(datetime.today().year,datetime.today().month,datetime.today().day), ),

#جسم الرسم البياني dcc.Graph( id='Emails', figure=fig )

لاحظ بالاعتماد على Dash كيف يكون إنشاء واجهة لوحة المعلومات بقائمة منسدلة متعددة الاختيارات وقابلة للبحث سهلاً. لتعطيل تعدد الاختيارات أو البحث، يمكن تعديل المعايير لإنشاء قائمة منسدلة بسهولة. وجدت أن الجزء الأكثر تعقيدًا من هذا المشروع كان بناء إطار البيانات الفعلي من المدخلات، وكذلك جعل HTML + CSS يعملان بشكل صحيح في واجهة المستخدم.

يسمح واجهة برمجة تطبيقات مقاييس السلاسل الزمنية بجلب 33 مقياسًا فرديًا بناءً على نطاق تاريخ/وقت. يمكنك التصفية بعمق أكثر حسب المجالات، الحملات، مجموعات عناوين IP، مجالات الإرسال، الحسابات الفرعية، وتحديد دقة بيانات السلاسل الزمنية. يمكن أن تكون هذه التصفية الإضافية جنبًا إلى جنب مع تحليلات الإرسال تحسينًا مستقبليًا لهذا المشروع (سيحتاج إلى تنفيذ الإمساك بالأخطاء من أجل العملاء الذين ليس لديهم حق الوصول إلى تحليلات الإرسال).

باستخدام واستدعاء واجهة برمجة تطبيقات المقاييس، قمت ببناء لوحة معلومات بمعايير وختيارات المستخدم المحددة والإطار الزمني المحدد. ثم يتم تحديث لوحة المعلومات التي تم تهيئتها.

#بناء استدعاء واجهة برمجة التطبيقات باستخدام المعايير المقدمة params = { "from" : start_date + "T00:00", "to" : end_date + "T00:00", "delimiter" : ",", "precision" : "day", "metrics" : joined_values } api_url = BASE_URL + "/metrics/deliverability/time-series" response_API = requests.get(api_url, headers = {"Authorization" : API_KEY}, params=params) response_info = json.loads(response_API.text) new_df = pd.json_normalize(response_info, record_path=['results']) value_array = joined_values.split(",") #بناء لوحة معلومات جديدة باستخدام المقاييس والتواريخ الجديدة من استدعاء واجهة برمجة التطبيقات المحدّث fig = px.line(new_df, x=new_df['ts'], y=value_array, labels={"value": "Count", "variable": "Metric","ts":"Date"}) fig.update_xaxes(title_text="Time") fig.update_yaxes(title_text="Count") return fig

الآتي هو مثال لعدة مقاييس تم اختيارها وإطار زمني موسع.


Sparkpost analytics dashboard

ملاحظة: هناك العديد من العناصر المدمجة في رسم dash تلقائيًا (التحويم، التكبير، التغيير التلقائي للمقياس).

إنشاء صفحة تفاصيل الحدث

كانت صفحة تفاصيل الحدث أكثر صعوبة قليلاً لأنني لم أكن أعرف أفضل طريقة لتقديم جميع مقاييس الأحداث بطريقة سهلة القراءة. فكرت في إضافة معلمات تصفية إلى هذه الصفحة، لكني قررت أن ذلك سيضيف قدراً كبيراً من الوقت إلى هذا المشروع حيث سيصبح الجدول ديناميكياً (إلى جانب إضافة المعلمات واسترجاعات البيانات، وما إلى ذلك). استقرت على عرض جميع الأحداث ووضع الطابع الزمني أولاً (لأنه بدون وضع الطابع الزمني أولاً، لم يكن المخطط سهلاً للقراءة). في البداية، وجدت أنه مع مجرد HTML الخام، كان الجدول صعبًا للغاية بالنسبة للعينين. لم تكن هناك حدود ولم تكن هناك اختلافات في الألوان بين الرأس والصفوف. لجعل الجدول أسهل في القراءة، كنت قادرًا على استخدام CSS داخل Dash.

الفكرة لتفاصيل الحدث تشبه تقريبًا لوحة التحكم، باستثناء هذه المرة، أنني أدعو Events API وجلب جميع الأحداث. لاحظ أن تفاصيل الحدث تعرض فقط أحدث 10 أحداث (باستخدام معلمة max_rows وتصفية API). يمكن زيادتها، لكن استقرت على عرض أحدث 10 أحداث لأن المزيد من الأحداث تم عرضها، كلما استغرق وقت استدعاء API أطول. قد يكون التحسين الكبير الذي يمكن إجراؤه هو القدرة على تقسيم الصفحات وتضمين صفحة التالية / صفحة السابقة في واجهة المستخدم.

لبناء علامة التبويب الأحداث (الصفحة)، أولاً أدعو Events API وأقوم بتحليل استجابة JSON إلى إطار بيانات. ثم أقوم بفرز وإعادة ترتيب إطار البيانات لوضع الطابع الزمني كأول عمود. وأخيرًا، أقوم ببناء جدول HTML عن طريق تشغيل البيانات عبر إطار البيانات.

#بناء واستدعاء معلمات API الأحداث params = { "events" : "التسليم، لحقن، ارتداد، تأخير، رفض السياسات، خارج النطاق، فتح، نقر، فشل التكوين، رفض التكوين، شكوى البريد العشوائي، إلغاء الاشتراك في القائمة، إلغاء الاشتراك في الرابط", "delimiter" : ",", "page" : "1", "per_page" : "10" } api_url = BASE_URL + "/events/message" response_API = requests.get(api_url, headers = {"Authorization" : API_KEY}, params=params) response_info = json.loads(response_API.text) new_df = pd.json_normalize(response_info, record_path=['results']) max_rows=10 #الحد الأقصى لعدد النتائج المعروضة في جدول الأحداث #ضع الطابع الزمني كعمود أول في الجدول new_df = new_df.reindex(sorted(new_df.columns), axis=1) cols = ['timestamp'] new_df = new_df[cols + [c for c in new_df.columns if c not in cols]] #عرض HTML الجديد مع جدول الأحداث (لاحظ، أن هذا الجدول يشير أيضًا إلى table.css) return html.Div([ html.H2("تفاصيل الأحداث"), html.Table([ html.Thead( html.Tr([html.Th(col) for col in new_df.columns],className="table_css") ), html.Tbody([ html.Tr([ html.Td(new_df.iloc[i][col],className="table_css") for col in new_df.columns ]) for i in range(min(len(new_df), max_rows)) ]) ]) ])

الذي يبدو هكذا في واجهة المستخدم.

Sparkpost event details

الخطوات التالية

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

كما نوقش أعلاه، بعض التحسينات المستقبلية التي يمكن إجراؤها تشمل:

  • إضافة تحليلات القدرة على التسليم إلى لوحة المعلومات

  • إضافة المزيد من الفلاتر إلى لوحة المعلومات

  • خيارات التخزين المؤقت الممكنة بحيث لا يتم استدعاء API في كل مرة لعرض الصفحات

  • تحسينات واجهة المستخدم

  • إضافة التصفية والتقسيم إلى صفحة تفاصيل الأحداث

أود سماع أي تعليقات أو اقتراحات لتوسيع هذا المشروع.

~ زاك سامويلز، مهندس حلول أول في Bird

دعنا نوصلك بخبير من Bird.
رؤية القوة الكاملة لـ Bird في 30 دقيقة.

بتقديمك طلبًا، فإنك توافق على أن تقوم Bird بالاتصال بك بشأن منتجاتنا وخدماتنا.

يمكنك إلغاء الاشتراك في أي وقت. انظر بيان الخصوصية الخاص بـ Bird للتفاصيل حول معالجة البيانات.

دعنا نوصلك بخبير من Bird.
رؤية القوة الكاملة لـ Bird في 30 دقيقة.

بتقديمك طلبًا، فإنك توافق على أن تقوم Bird بالاتصال بك بشأن منتجاتنا وخدماتنا.

يمكنك إلغاء الاشتراك في أي وقت. انظر بيان الخصوصية الخاص بـ Bird للتفاصيل حول معالجة البيانات.

دعنا نوصلك بخبير من Bird.
رؤية القوة الكاملة لـ Bird في 30 دقيقة.

بتقديمك طلبًا، فإنك توافق على أن تقوم Bird بالاتصال بك بشأن منتجاتنا وخدماتنا.

يمكنك إلغاء الاشتراك في أي وقت. انظر بيان الخصوصية الخاص بـ Bird للتفاصيل حول معالجة البيانات.

R

وصول

G

نمو

م

إدارة

A

أتمتة

النشرة الإخبارية

ابقَ على اطلاع مع Bird من خلال التحديثات الأسبوعية إلى بريدك الوارد.