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

אפשר להשתמש בשיטות שבאוסף 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 תוכלו לקבל התראות ב-web hook או על ידי סקירה של נקודת קצה של מינוי.

כדי להגדיר נושא ב-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) לפרסם בנושא.

יצירת שעון

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

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)
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;

מחיקת שעון

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)
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() חייבות להיות מאושרות באמצעות אסימון הרשאה. האסימון חייב לכלול היקף שמעניק גישה לקריאה לנתונים שלגביהם נשלחות ההתראות.

  • בשינויים בסכימה, המשמעות היא כל היקף שמעניק גישת קריאה ל-forms באמצעות forms.get().
  • לגבי תגובות, המשמעות היא כל היקף שמעניק הרשאת קריאה לתגובות לטופס, למשל באמצעות forms.responses.list().

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

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

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)
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;

חידוש של שעון

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)
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;

ויסות נתונים (throttle)

ההתראות מוגבלות – כל שעון יכול לקבל לכל היותר התראה אחת בכל 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().