تخطي إلى المحتوى
PollsLive

June 18, 2026 · 10 min read

كيف وضعنا اجتماع الاستعراض الأسبوعي على الطيار الآلي باستخدام واجهة PollsLive البرمجية

شرح عملي من مطوّر: أنشئ استطلاع استعراض، وافتح جلسة مباشرة، ثم اسحب النتائج وملف CSV، كل ذلك من سكربت Node صغير يعمل على cron. نقاط نهاية حقيقية من ‎/api/v1‎ وحمولات حقيقية.

By the PollsLive team·DevelopersHow-toTeams & meetings

أنا مهندس منصّات، وكل يوم جمعة كنت أقضي عشر دقائق في بناء نفس عرض الاستعراض يدويًا. نفس الأسئلة الخمسة، أسبوع مختلف. شيء كلاسيكي من المفترض أن يقوم به سكربت. لذلك نقلته إلى واجهة PollsLive البرمجية ومهمة cron: أصبح الاستطلاع ينشئ نفسه، وتُفتح جلسة مباشرة تلقائيًا، وبعد الوقوف اليومي يصل ملف CSV إلى Slack لدينا. إليك الطريقة بالضبط، مع نقاط النهاية والحمولات الحقيقية.

المصادقة: رمز Bearer واحد

أنشأت مفتاح واجهة برمجية لمساحة العمل في Studio ← Developers (تبدو مثل `plv_live_…`) ووضعته في بيئة السكربت. كل طلب هو مجرد رمز Bearer، لا حاجة لرقصة OAuth في الاستخدام بين الخوادم.

# Base URL for all calls
export POLLSLIVE_API=https://pollslive.com/api/v1
export POLLSLIVE_KEY=plv_live_xxxxxxxxxxxxxxxxxxxx

# Every request carries the key as a Bearer token:
#   Authorization: Bearer $POLLSLIVE_KEY
# Limits and HTTP 429 handling: https://pollslive.com/docs/developers/rate-limits

الخطوة 1: إنشاء استطلاع الاستعراض

الاستطلاع هو عرض شرائح مُصنّف: `content.questions` عبارة عن مصفوفة من الشرائح، لكل منها مُميّز `kind`. للاستعراض أستخدم scale و open_ended يُعرض كسحابة و multiple_choice للتصويت على الإصلاح. يعيد لي `POST /polls` مع `renderingMode: "LIVE"` مسوّدة.

curl -sS -X POST "$POLLSLIVE_API/polls" \
  -H "Authorization: Bearer $POLLSLIVE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Weekly retro - week of Jun 16",
    "renderingMode": "LIVE",
    "content": {
      "questions": [
        {
          "id": "health",
          "kind": "scale",
          "text": "How healthy did this sprint feel?",
          "min": 1, "max": 5,
          "minLabel": "Rough", "maxLabel": "Great"
        },
        {
          "id": "blockers",
          "kind": "open_ended",
          "text": "What slowed us down this sprint?",
          "render": "cloud",
          "maxLength": 120
        },
        {
          "id": "fix",
          "kind": "multiple_choice",
          "type": "single",
          "text": "What should we fix first next sprint?",
          "options": [
            { "id": "ci",       "label": "Stabilise the CI pipeline" },
            { "id": "tickets",  "label": "Tighter ticket acceptance criteria" },
            { "id": "wip",      "label": "Cap WIP and reviews SLA" },
            { "id": "meetings", "label": "Trim the meeting load" }
          ]
        }
      ]
    }
  }'

تغلّف استجابة `201` الاستطلاع في مظروف `data`. أحتفظ بـ `data.id` لبقية التدفّق، وبـ `data.accessPin` / `data.shareUrl` لتفاصيل الانضمام:

{
  "data": {
    "id": "poll_9aZ2kP",
    "slug": "weekly-retro-jun-16",
    "title": "Weekly retro - week of Jun 16",
    "renderingMode": "LIVE",
    "isPublished": false,
    "accessPin": "6098",
    "shareUrl": "https://pollslive.com/p/weekly-retro-jun-16",
    "createdAt": "2026-06-16T08:00:11.204Z"
  }
}

الخطوة 2: انشر، ثم افتح جلسة مباشرة

النشر يلتقط لقطة من المحتوى ليصبح آمنًا للعرض. يقوم بذلك `PATCH /polls/{id}` مع `publish: true`؛ ثم يُشغّل `POST /sessions` جلسة المُقدّم ويعيد رمز الانضمام إضافة إلى `hostToken` يُستخدم مرة واحدة (سرّ سلطة المُضيف، يُعاد فقط عند الإنشاء، لذا أحفظه فورًا).

# Publish the draft
curl -sS -X PATCH "$POLLSLIVE_API/polls/poll_9aZ2kP" \
  -H "Authorization: Bearer $POLLSLIVE_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "publish": true }'

# Open a presenter-paced live session
curl -sS -X POST "$POLLSLIVE_API/sessions" \
  -H "Authorization: Bearer $POLLSLIVE_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "pollId": "poll_9aZ2kP", "pace": "PRESENTER" }'
# → { "data": { "id": "ses_4dF1", "pin": "6098",
#       "joinUrl": "https://pollslive.com/play/6098",
#       "hostUrl": "https://pollslive.com/host/ses_4dF1",
#       "hostToken": "hst_…", "status": "LOBBY" } }

ينشر cron الخاص بي رابط `joinUrl` ورمز PIN في قناة الفريق قبل الوقوف اليومي بدقيقة. يمسح الفريق الرمز، وتظهر لهم الشرائح التي بنيتها في الكود. وإليك النتيجة المباشرة لشريحة `health` بعد أن يجيب الجميع:

Live scale
Join at the PIN 6098

كيف كان شعورك بصحة هذا السبرنت؟ (1 = صعب، 5 = رائع)

5 - رائع11% · 1
4 - جيد22% · 2
3 - مقبول22% · 2
2 - صعب33% · 3
1 - سيئ11% · 1
الشريحة التي عرّفتها بصيغة JSON، تُعرض مباشرة، دون بناء عرض يدوي.

الخطوة 3: اسحب النتائج وملف CSV بعد الوقوف اليومي

عند انتهاء الجلسة، يعيد `GET /polls/{id}/results` الإجماليات في مصفوفة `slides[]`، عنصر لكل شريحة، مع تجميعات لكل نوع (`optionCounts` لشرائح الاختيار، متوسطات المقياس، النصوص المفتوحة، إلخ). هذا هو الجزء الذي تقرؤه بعد الجلسة المباشرة: لا يعدّ `totalVotes` و`voteCounts` في المستوى الأعلى إلا الأصوات غير المتزامنة، لذا يبقيان صفرًا وفارغًا لاستطلاع لم يُشغَّل إلا مباشرة.

curl -sS "$POLLSLIVE_API/polls/poll_9aZ2kP/results" \
  -H "Authorization: Bearer $POLLSLIVE_KEY"

{
  "data": {
    "pollId": "poll_9aZ2kP",
    "slug": "weekly-retro-jun-16",
    "title": "Weekly retro - week of Jun 16",
    "isPublished": true,
    "closed": false,
    "totalVotes": 0,
    "voteCounts": {},
    "slides": [
      { "slideId": "health", "kind": "scale", "totalResponses": 9,
        "average": 2.888888888888889,
        "distribution": { "1": 1, "2": 3, "3": 2, "4": 2, "5": 1 } },
      { "slideId": "blockers", "kind": "open_ended", "totalResponses": 2,
        "responses": [
          { "text": "flaky CI", "displayName": null,
            "createdAt": "2026-06-19T09:04:12.000Z" },
          { "text": "late reviews", "displayName": null,
            "createdAt": "2026-06-19T09:04:31.000Z" }
        ] },
      { "slideId": "fix", "kind": "multiple_choice", "totalResponses": 9,
        "optionCounts": { "ci": 4, "tickets": 3, "wip": 2, "meetings": 0 } }
    ]
  }
}

للأرشيف، يبثّ `GET /polls/{id}/export` ملف CSV بصيغة RFC-4180 (رقم الشريحة، السؤال، النوع، تسمية الإجابة، العدد/القيمة). أحفظه وأرفقه برسالة Slack. خطوة ما بعد الوقوف بأكملها هي دالة صغيرة واحدة:

const API = process.env.POLLSLIVE_API;
const KEY = process.env.POLLSLIVE_KEY;
const auth = { Authorization: `Bearer ${KEY}` };

export async function summariseRetro(pollId) {
  const res = await fetch(`${API}/polls/${pollId}/results`, { headers: auth });
  if (res.status === 429) throw new Error("rate_limited"); // back off + retry
  if (!res.ok) throw new Error(`results ${res.status}`);
  const { data } = await res.json();

  // Live answers are in slides[], not in the top-level voteCounts
  const slide = (id) => data.slides.find((s) => s.slideId === id);
  const fix = slide("fix");
  const winner = Object.entries(fix?.optionCounts ?? {})
    .filter(([, count]) => count > 0)
    .sort((a, b) => b[1] - a[1])[0];

  // Grab the CSV for the archive
  const csv = await fetch(`${API}/polls/${pollId}/export`, { headers: auth })
    .then((r) => r.text());

  return {
    health: slide("health")?.average,
    topFix: winner?.[0],
    voters: fix?.totalResponses ?? 0,
    csv,
  };
}

هذا هو الأمر كله. عرض الاستعراض يبني نفسه، والجلسة تُفتح في موعدها، والملخّص مع ملف CSV يُنشران تلقائيًا. استعدت عشر دقائق من يوم جمعتي، والصيغة متطابقة كل أسبوع، وهو بالضبط ما تريده من اجتماع استعراض.

أجمل جزء: الشرائح التي أصفها بصيغة JSON هي نفس الشرائح التي يراها الفريق ويصوّت عليها. لا انحراف بين ما صنعه السكربت وما شغّلناه.

المرجع الكامل لنقاط النهاية والمخططات موجود في مرجع الواجهة البرمجية التفاعلي، والبداية السريعة السردية في دليل المطوّرين. التالي على قائمتي: الـ webhooks، كي يُطلَق ملخّص Slack في اللحظة التي تنتهي فيها الجلسة بدلًا من مؤقّت، وهو بالضبط ما يغطّيه مقال الـ webhooks.

Tip

استخدم cron job لاستنساخ عرض رترو الأسبوع الماضي ونشر رابط جديد تلقائيًا.

بديل دون برمجة: استطلاعات متكررة دون إعادة بناء.

ابنِ على PollsLive: أنشئ استطلاعات، وشغّل جلسات مباشرة، واسحب النتائج، واستقبل الـ webhooks من كودك الخاص.

اقرأ وثائق المطوّرين

نستخدم ملفات تعريف ارتباط ضرورية لإبقائك مسجّل الدخول ولمنع التصويت المكرر. وبإذنك، نستخدم أيضًا Cloudflare Web Analytics لإحصاء مشاهدات الصفحات وWhop Pixel لمعرفة الزيارات التي تؤدي إلى عملية شراء. سياسة ملفات تعريف الارتباط