הגדרה וקבלה של התראות

אפשר להשתמש בשיטות באוסף Watches כדי לקבל התראות על שינויים בטפסים. בדף הזה מפורטת סקירה כללית מושגים והוראות להגדרה ולקבלה של התראות.

סקירה כללית

תכונת ההתראות ב-Google Forms API מאפשרת לאפליקציות להירשם לקבלת התראות כשנתונים משתנים בטפסים. ההתראות נשלחות לנושא ב-Cloud Pub/Sub, בדרך כלל תוך כמה דקות מרגע השינוי.

כדי לקבל התראות, צריך להגדיר נושא ב-Cloud Pub/Sub ולציין את שם הנושא כשיוצרים שעון לסוג האירוע המתאים.

בהמשך מפורטות הגדרות למושגים מרכזיים המופיעים בתיעוד הזה:

  • יעד הוא מקום שאליו נשלחות הודעות. היעד היחיד שנתמך הוא נושא Cloud Pub/Sub.
  • סוג אירוע הוא קטגוריית ההתראות שאפליקציית צד שלישי יכולה להירשם אליהן.
  • שעון הוא הוראה ל-Forms API לשלוח התראות לגבי סוג אירוע מסוים בטופס מסוים ליעד.

אחרי שיוצרים שעון לסוג אירוע בטופס מסוים, קהל היעד של השעון (שהוא נושא ב-Cloud Pub/Sub) מקבל התראות מהאירועים שבטופס הזה עד שתוקף השעון יפוג. השעון פועל למשך שבוע, אבל אפשר להאריך אותו בכל שלב לפני שהתוקף שלו פג. לשם כך צריך לשלוח בקשה אל watches.renew().

לנושא ב-Cloud Pub/Sub מתקבלות התראות רק לגבי טפסים שאפשר להציג באמצעות פרטי הכניסה שאתם מספקים. לדוגמה, אם המשתמש מבטל את ההרשאה מהאפליקציה או מאבד את גישת העריכה לטופס שנצפה, לא יישלחו יותר התראות.

סוגי אירועים זמינים

בשלב זה, יש שתי קטגוריות של אירועים ב-Google Forms API:

  • EventType.SCHEMA, שמודיעה על שינויים בתוכן ובהגדרות של הטופס.
  • EventType.RESPONSES, שמודיעה כשנשלחות תשובות לטופס (חדשות ומעודכנות).

תגובות להתראות

ההתראות מקודדות באמצעות JSON ומכילות:

  • המזהה של הטופס שגורם להקפצה
  • המזהה של השעון המפעיל
  • סוג האירוע שהקפיץ את ההתראה
  • שדות אחרים שהוגדרו על ידי Cloud Pub/Sub, כמו messageId ו-publishTime

ההתראות לא מכילות נתונים מפורטים של טפסים או תגובות. אחרי שמתקבלת כל התראה, נדרשת קריאה נפרדת ל-API כדי לאחזר נתונים עדכניים. בקטע הצעות לשימוש מוסבר איך לעשות זאת.

קטע הקוד הבא מציג התראה לדוגמה על שינוי סכימה:

{
  "attributes": {
    "eventType": "SCHEMA",
    "formId": "18Xgmr4XQb-l0ypfCNGQoHAw2o82foMr8J0HPHdagS6g",
    "watchId": "892515d1-a902-444f-a2fe-42b718fe8159"
  },
  "messageId": "767437830649",
  "publishTime": "2021-03-31T01:34:08.053Z"
}

בקטע הקוד הבא מוצגת התראה לדוגמה על תשובה חדשה:

{
  "attributes": {
    "eventType": "RESPONSES",
    "formId": "18Xgmr4XQb-l0ypfCNGQoHAw2o82foMr8J0HPHdagS6g",
    "watchId": "5d7e5690-b1ff-41ce-8afb-b469912efd7d"
  },
  "messageId": "767467004397",
  "publishTime": "2021-03-31T01:43:57.285Z"
}

הגדרת נושא ב-Cloud Pub/Sub

ההתראות נשלחות לנושאים של Cloud Pub/Sub. באמצעות Cloud Pub/Sub תוכלו לקבל התראות על תגובה לפעולה מאתר אחר (webhook) או באמצעות סקר של נקודת קצה (endpoint) של מינוי.

כדי להגדיר נושאים ב-Cloud Pub/Sub, מבצעים את הפעולות הבאות:

  1. להשלים את הדרישות המוקדמות ל-Cloud Pub/Sub.
  2. הגדרת לקוח Cloud Pub/Sub
  3. בודקים את המחירון של Cloud Pub/Sub ומפעילים את החיוב בפרויקט ב-Developer Console.
  4. אפשר ליצור נושאים ב-Cloud Pub/Sub באחת משלוש דרכים:

  5. יצירת מינוי ב-Cloud Pub/Sub כדי להנחות את Cloud Pub/Sub לשלוח את ההתראות שלכם.

  6. לסיום, לפני שיוצרים שעונים שמטרגטים את הנושא, צריך להעניק הרשאה לחשבון השירות של ההתראות ב-Forms (forms-notifications@system.gserviceaccount.com) לפרסום בנושא שלכם.

יצירת שעון

אחרי שיוצרים נושא מחשבון שירות ההתראות של Forms API, אפשר ליצור בו התראות באמצעות method watches.create(). השיטה הזו מאמתת שאפשר לגשת לנושא Cloud Pub/Sub שסופק באמצעות חשבון שירות ההתראות, והיא לא מצליחה להגיע לנושא, למשל אם הנושא לא קיים או שלא נתתם לו הרשאה לפרסם בנושא הזה.

Python

forms/snippets/create_watch.py
from apiclient import discovery
from httplib2 import Http
from oauth2client import client, file, tools

SCOPES = "https://www.googleapis.com/auth/drive"
DISCOVERY_DOC = "https://forms.googleapis.com/$discovery/rest?version=v1"

store = file.Storage("token.json")
creds = None
if not creds or creds.invalid:
  flow = client.flow_from_clientsecrets("client_secret.json", SCOPES)
  creds = tools.run_flow(flow, store)

service = discovery.build(
    "forms",
    "v1",
    http=creds.authorize(Http()),
    discoveryServiceUrl=DISCOVERY_DOC,
    static_discovery=False,
)

watch = {
    "watch": {
        "target": {"topic": {"topicName": "<YOUR_TOPIC_PATH>"}},
        "eventType": "RESPONSES",
    }
}

form_id = "<YOUR_FORM_ID>"

# Print JSON response after form watch creation
result = service.forms().watches().create(formId=form_id, body=watch).execute()
print(result)

Node.js

forms/snippets/create_watch.js
'use strict';

const path = require('path');
const google = require('@googleapis/forms');
const {authenticate} = require('@google-cloud/local-auth');

const formID = '<YOUR_FORM_ID>';

async function runSample(query) {
  const authClient = await authenticate({
    keyfilePath: path.join(__dirname, 'credentials.json'),
    scopes: 'https://www.googleapis.com/auth/drive',
  });
  const forms = google.forms({
    version: 'v1',
    auth: authClient,
  });
  const watchRequest = {
    watch: {
      target: {
        topic: {
          topicName: 'projects/<YOUR_TOPIC_PATH>',
        },
      },
      eventType: 'RESPONSES',
    },
  };
  const res = await forms.forms.watches.create({
    formId: formID,
    requestBody: watchRequest,
  });
  console.log(res.data);
  return res.data;
}

if (module === require.main) {
  runSample().catch(console.error);
}
module.exports = runSample;

מחיקת שעון

Python

forms/snippets/delete_watch.py
from apiclient import discovery
from httplib2 import Http
from oauth2client import client, file, tools

SCOPES = "https://www.googleapis.com/auth/drive"
DISCOVERY_DOC = "https://forms.googleapis.com/$discovery/rest?version=v1"

store = file.Storage("token.json")
creds = None
if not creds or creds.invalid:
  flow = client.flow_from_clientsecrets("client_secret.json", SCOPES)
  creds = tools.run_flow(flow, store)
service = discovery.build(
    "forms",
    "v1",
    http=creds.authorize(Http()),
    discoveryServiceUrl=DISCOVERY_DOC,
    static_discovery=False,
)

form_id = "<YOUR_FORM_ID>"
watch_id = "<YOUR_WATCH_ID>"

# Print JSON response after deleting a form watch
result = (
    service.forms().watches().delete(formId=form_id, watchId=watch_id).execute()
)
print(result)

Node.js

forms/snippets/delete_watch.js
'use strict';

const path = require('path');
const google = require('@googleapis/forms');
const {authenticate} = require('@google-cloud/local-auth');

const formID = '<YOUR_FORM_ID>';
const watchID = '<YOUR_FORMS_WATCH_ID>';

async function runSample(query) {
  const authClient = await authenticate({
    keyfilePath: path.join(__dirname, 'credentials.json'),
    scopes: 'https://www.googleapis.com/auth/drive',
  });
  const forms = google.forms({
    version: 'v1',
    auth: authClient,
  });
  const res = await forms.forms.watches.delete({
    formId: formID,
    watchId: watchID,
  });
  console.log(res.data);
  return res.data;
}

if (module === require.main) {
  runSample().catch(console.error);
}
module.exports = runSample;

הרשאה

כמו כל הקריאות ל-Forms API, גם הקריאות ל-watches.create() חייבות להיות מאושרות באמצעות אסימון הרשאה. האסימון חייב לכלול היקף שמעניק גישת קריאה לנתונים לגבי ההתראות שנשלחות.

כדי שההתראות יישלחו, האפליקציה צריכה לקבל מענק OAuth של המשתמש המורשה עם ההיקפים הנדרשים. אם המשתמש ינתק את האפליקציה, ההתראות ייפסקו וייתכן שהשעון יושעה עם הודעת שגיאה. חידוש השעון תוכלו לקרוא שוב את ההתראות אחרי קבלת ההרשאה מחדש.

הצגת רשימה של שעונים של טופס

Python

Forms/snippets/list_watches.py
from apiclient import discovery
from httplib2 import Http
from oauth2client import client, file, tools

SCOPES = "https://www.googleapis.com/auth/drive"
DISCOVERY_DOC = "https://forms.googleapis.com/$discovery/rest?version=v1"

store = file.Storage("token.json")
creds = None
if not creds or creds.invalid:
  flow = client.flow_from_clientsecrets("client_secrets.json", SCOPES)
  creds = tools.run_flow(flow, store)
service = discovery.build(
    "forms",
    "v1",
    http=creds.authorize(Http()),
    discoveryServiceUrl=DISCOVERY_DOC,
    static_discovery=False,
)

form_id = "<YOUR_FORM_ID>"

# Print JSON list of form watches
result = service.forms().watches().list(formId=form_id).execute()
print(result)

Node.js

Forms/snippets/list_watches.js
'use strict';

const path = require('path');
const google = require('@googleapis/forms');
const {authenticate} = require('@google-cloud/local-auth');

const formID = '<YOUR_FORM_ID>';

async function runSample(query) {
  const auth = await authenticate({
    keyfilePath: path.join(__dirname, 'credentials.json'),
    scopes: 'https://www.googleapis.com/auth/forms.responses.readonly',
  });
  const forms = google.forms({
    version: 'v1',
    auth: auth,
  });
  const res = await forms.forms.watches.list({formId: formID});
  console.log(res.data);
  return res.data;
}

if (module === require.main) {
  runSample().catch(console.error);
}
module.exports = runSample;

חידוש של שעון

Python

forms/snippets/renew_watch.py
from apiclient import discovery
from httplib2 import Http
from oauth2client import client, file, tools

SCOPES = "https://www.googleapis.com/auth/drive"
DISCOVERY_DOC = "https://forms.googleapis.com/$discovery/rest?version=v1"

store = file.Storage("token.json")
creds = None
if not creds or creds.invalid:
  flow = client.flow_from_clientsecrets("client_secrets.json", SCOPES)
  creds = tools.run_flow(flow, store)
service = discovery.build(
    "forms",
    "v1",
    http=creds.authorize(Http()),
    discoveryServiceUrl=DISCOVERY_DOC,
    static_discovery=False,
)

form_id = "<YOUR_FORM_ID>"
watch_id = "<YOUR_WATCH_ID>"

# Print JSON response after renewing a form watch
result = (
    service.forms().watches().renew(formId=form_id, watchId=watch_id).execute()
)
print(result)

Node.js

Forms/snippets/renew_watch.js
'use strict';

const path = require('path');
const google = require('@googleapis/forms');
const {authenticate} = require('@google-cloud/local-auth');

const formID = '<YOUR_FORM_ID>';
const watchID = '<YOUR_FORMS_WATCH_ID>';

async function runSample(query) {
  const authClient = await authenticate({
    keyfilePath: path.join(__dirname, 'credentials.json'),
    scopes: 'https://www.googleapis.com/auth/drive',
  });
  const forms = google.forms({
    version: 'v1',
    auth: authClient,
  });
  const res = await forms.forms.watches.renew({
    formId: formID,
    watchId: watchID,
  });
  console.log(res.data);
  return res.data;
}

if (module === require.main) {
  runSample().catch(console.error);
}
module.exports = runSample;

ויסות נתונים

ההתראות מווסתות – כל שעון יכול לקבל התראה אחת לכל היותר בכל 30 שניות. סף התדירות הזה עשוי להשתנות.

עקב הגבלת רוחב הפס, התראה יחידה עשויה להתייחס למספר אירועים. במילים אחרות, התראה מציינת אירוע אחד או יותר שהתרחשו מאז ההתראה האחרונה.

מגבלות

בכל שלב, עבור טופס וסוג אירוע מסוימים, כל פרויקט במסוף Cloud יכול לכלול:

  • עד 20 צפיות בסך הכול
  • עד שעון אחד לכל משתמש קצה

בנוסף, בכל שלב, כל טופס מוגבל ל-50 צפיות לכל סוג אירוע בסך הכול בכל הפרויקטים במסוף Cloud.

שעון משויך למשתמש קצה כשהוא נוצר או מתחדש עם פרטי הכניסה עבור אותו משתמש. שעון מושעה אם משתמש הקצה המשויך מאבד את הגישה לטופס או מבטל את הגישה של האפליקציה לטופס.

אמינות

כל צפייה מקבלת הודעה לפחות פעם אחת לאחר כל אירוע בכל הנסיבות מלבד החריגות. ברוב המקרים, ההתראה נשלחת תוך דקות ספורות לאחר האירוע.

שגיאות

אם ההתראות מהשעון לא מתקבלות כל הזמן, מצב השעון הופך ל-SUSPENDED והשדה errorType של השעון מוגדר. כדי לאפס את מצב השעון ל-ACTIVE ולהמשיך לקבל התראות, קראו את המאמר חידוש השעון.

שימוש מוצע

  • להשתמש בנושא אחד ב-Cloud Pub/Sub כיעד של שעונים רבים.
  • כשמקבלים התראה בנושא מסוים, מזהה הטופס נכלל במטען הייעודי (payload) של ההתראות. כדאי להשתמש בה בשילוב עם סוג האירוע כדי לדעת אילו נתונים לאחזר ומאיזה טופס לאחזר אותם.
  • כדי לאחזר נתונים מעודכנים אחרי התראה באמצעות EventType.RESPONSES, צריך לקרוא ל-forms.responses.list().
    • מגדירים את המסנן של הבקשה לערך timestamp > timestamp_of_the_last_response_you_fetched.
  • כדי לאחזר נתונים מעודכנים אחרי התראה באמצעות EventType.SCHEMA, צריך לקרוא ל-forms.get().