כתיבת נתונים חזרה ל-BigQuery באמצעות פעולות Looker בפונקציות Cloud Run

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

באמצעות Action API, ‏ Looker תומך בתרחיש השימוש הזה לכל מחסן נתונים או יעד. בדף התיעוד הזה מוסבר ללקוחות שמשתמשים בתשתית Google Cloud איך לפרוס פתרון ב-Cloud Run functions כדי לכתוב בחזרה ל-BigQuery. בדף הזה מפורטים הנושאים הבאים:

שיקולים לגבי פתרונות

כדי לוודא שהפתרון הזה מתאים לצרכים שלכם, כדאי לעיין ברשימת השיקולים הבאה.

  • פונקציות Cloud Run
    • למה כדאי להשתמש בפונקציות Cloud Run? פונקציות Cloud Run הן פתרון מצוין לתפעול ותחזוקה קלים, כי הן חלק מהפתרון 'בלי שרת (serverless)' של Google. חשוב לזכור שההשהיה, במיוחד בהפעלות ראשוניות, עשויה להיות ארוכה יותר מאשר בפתרון שמסתמך על שרת ייעודי.
    • שפה וסביבת זמן ריצה פונקציות Cloud Run תומכות בכמה שפות ובסביבות זמן ריצה. בדף התיעוד הזה נתמקד בדוגמה ב-JavaScript וב-Node.js. עם זאת, אפשר לתרגם את המושגים ישירות לשפות ולסביבות זמן ריצה אחרות שנתמכות.
  • BigQuery
    • למה כדאי להשתמש ב-BigQuery? למרות שבדף התיעוד הזה מניחים שאתם כבר משתמשים ב-BigQuery, זוהי בחירה מצוינת למחסן נתונים באופן כללי. חשוב לזכור את הנקודות הבאות:
      • BigQuery Storage Write API: ב-BigQuery יש כמה ממשקים לעדכון נתונים במחסן הנתונים, כולל למשל הצהרות בשפת הטיפול בנתונים (DML) במשימות מבוססות-SQL. עם זאת, האפשרות הכי טובה לכתיבה של נפח גדול של נתונים היא BigQuery Storage Write API.
      • הוספה במקום עדכון: למרות שהפתרון הזה יוסיף שורות בלבד ולא יעדכן שורות, תמיד אפשר להפיק טבלאות של 'מצב נוכחי' בזמן השאילתה מיומן שמוסיף שורות בלבד, וכך לדמות עדכונים.
  • שירותים תומכים
    • Secret Manager: Secret Manager מחזיק ערכים סודיים כדי לוודא שהם לא מאוחסנים במקומות נגישים מדי, כמו ישירות בהגדרות של הפונקציה.
    • ניהול זהויות והרשאות גישה (IAM): IAM מאשר לפונקציה לגשת לסוד הנדרש בזמן הריצה ולכתוב לטבלה ב-BigQuery הרצויה.
    • Cloud Build: למרות שלא נדון ב-Cloud Build לעומק בדף הזה, פונקציות Cloud Run משתמשות בו ברקע, ואפשר להשתמש ב-Cloud Build כדי להפוך את הפריסה של עדכונים לפונקציות לאוטומטית באופן רציף משינויים בקוד המקור במאגר Git.
  • פעולה ואימות משתמשים
    • חשבון שירות של Cloud Run הדרך העיקרית והקלה ביותר להשתמש בפעולות של Looker לשילוב עם נכסים ומשאבים מאינטראקציה ישירה (First-Party) של הארגון היא לאמת בקשות כבקשות שמגיעות ממופע Looker באמצעות מנגנון האימות מבוסס-הטוקנים של Looker Action API, ולאחר מכן לאשר לפונקציה לעדכן נתונים ב-BigQuery באמצעות חשבון שירות.
    • OAuth: אפשרות נוספת שלא מוסברת בדף הזה היא להשתמש בתכונת ה-OAuth של Looker Action API. הגישה הזו מורכבת יותר ובדרך כלל לא נדרשת, אבל אפשר להשתמש בה אם צריך להגדיר את הגישה של משתמשי הקצה לכתיבה לטבלה באמצעות IAM, במקום להשתמש בגישה שלהם ב-Looker או בלוגיקה אד-הוק בתוך קוד הפונקציה.

הסבר על קוד ההדגמה

קובץ יחיד שמכיל את כל הלוגיקה של פעולת ההדגמה שלנו זמין ב-GitHub. בקטע הזה נסביר את הרכיבים העיקריים של הקוד.

קוד הגדרה

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

/*** Demo constants */
const projectId = "your-project-id"
const datasetId = "demo_dataset"
const tableId = "demo_table"

בקטע הבא מצהירים על כמה תלות בקוד שהפעולה תשתמש בהן ומאתחלים אותן. אנחנו מספקים דוגמה לגישה ל-Secret Manager 'בתוך הקוד' באמצעות מודול Secret Manager Node.js. עם זאת, אפשר גם לבטל את התלות הזו בקוד באמצעות התכונה המובנית של פונקציות Cloud Run לאחזור סוד במהלך האתחול.

/*** Code Dependencies ***/
const crypto = require("crypto")
const {SecretManagerServiceClient} = require('@google-cloud/secret-manager')
const secrets = new SecretManagerServiceClient()
const BigqueryStorage = require('@google-cloud/bigquery-storage')
const BQSManagedWriter = BigqueryStorage.managedwriter

שימו לב שהתלויות @google-cloud שמוזכרות גם מוצהרות בקובץ package.json שלנו כדי לאפשר טעינה מראש של התלויות וזמינות שלהן בזמן הריצה של Node.js. ‫crypto הוא מודול מובנה של Node.js ולא מוצהר ב-package.json.

טיפול בבקשות HTTP וניתוב שלהן

הממשק הראשי שהקוד חושף לסביבת זמן הריצה של פונקציות Cloud Run הוא פונקציית JavaScript מיוצאת שפועלת לפי המוסכמות של שרת האינטרנט Node.js Express. בפרט, הפונקציה מקבלת שני ארגומנטים: הראשון מייצג את בקשת ה-HTTP, שממנה אפשר לקרוא פרמטרים וערכים שונים של הבקשה, והשני מייצג אובייקט תגובה, שאליו מוציאים את נתוני התגובה. אפשר לתת לפונקציה כל שם שרוצים, אבל תצטרכו לספק את השם לפונקציות של Cloud Run בהמשך, כמו שמתואר בקטע מדריך הפריסה.

/*** Entry-point for requests ***/
exports.httpHandler = async function httpHandler(req,res) {

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

בדוגמאות מסוימות של פעולות + פונקציות Cloud Run, כל מסלול פריסה כזה מפריס פונקציה נפרדת כדי להתאים אחד לאחד לניתוב ברירת המחדל של פונקציות Cloud Run. עם זאת, הפונקציות יכולות להחיל 'ניתוב משנה' נוסף בתוך הקוד שלהן, כפי שמוצג כאן. בסופו של דבר, זה עניין של העדפה, אבל ביצוע הניתוב הנוסף הזה בקוד מצמצם את מספר הפונקציות שאנחנו צריכים לפרוס ועוזר לנו לשמור על מצב קוד עקבי יחיד בכל נקודות הקצה של הפעולות.

    const routes = {
        "/": [hubListing],
        "/status": [hubStatus], // Debugging endpoint. Not required.
        "/action-0/form": [
            requireInstanceAuth,
            action0Form
            ],
        "/action-0/execute": [
            requireInstanceAuth,
            processRequestBody,
            action0Execute
            ]
        }

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

    try {
        const routeHandlerSequence = routes[req.path] || [routeNotFound]
        for(let handler of routeHandlerSequence) {
            let handlerResponse = await handler(req)
            if (!handlerResponse) continue
            return res
                .status(handlerResponse.status || 200)
                .json(handlerResponse.body || handlerResponse)
            }
        }
    catch(err) {
        console.error(err)
        res.status(500).json("Unhandled error. See logs for details.")
        }
    }

אחרי שהגדרנו את ה-handler של HTTP ואת הצהרות הנתיב, נתעמק בשלושת נקודות הקצה העיקריות של הפעולות שאנחנו צריכים להטמיע:

נקודת הקצה של רשימת הפעולות

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

בהצהרות המסלול שהצגנו קודם, הפכנו את נקודת הקצה הזו לזמינה בנתיב הבסיס (/) בכתובת ה-URL של הפונקציה שלנו, וציינו שהיא תטופל על ידי הפונקציה hubListing.

כפי שאפשר לראות מהגדרת הפונקציה הבאה, אין בה הרבה 'קוד' – היא פשוט מחזירה את אותם נתוני JSON בכל פעם. חשוב לשים לב שהיא כוללת באופן דינמי את כתובת ה-URL שלה בחלק מהשדות, וכך מאפשרת למכונת Looker לשלוח בקשות מאוחרות יותר בחזרה לאותה פונקציה.

async function hubListing(req){
    return {
        integrations: [
            {
                name: "demo-bq-insert",
                label: "Demo BigQuery Insert",
                supported_action_types: ["cell", "query", "dashboard"],
                form_url:`${process.env.CALLBACK_URL_PREFIX}/action-0/form`,
                url: `${process.env.CALLBACK_URL_PREFIX}/action-0/execute`,
                icon_data_uri: "data:image/png;base64,...",
                supported_formats:["inline_json"],
                supported_formattings:["unformatted"],
                required_fields:[
                    // You can use this to make your action available
                    // for specific queries/fields
                    // {tag:"user_id"}
                    ],
                params: [
                    // You can use this to require parameters, either
                    // from the Action's administrative configuration,
                    // or from the invoking user's user attributes.
                    // A common use case might be to have the Looker
                    // instance pass along the user's identification to
                    // allow you to conditionally authorize the action:
                    {name: "email", label: "Email", user_attribute_name: "email", required: true}
                    ]
                }
            ]
        }
    }

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

חשוב גם לציין שפונקציית Cloud Run שלנו יכולה לחשוף ולטפל בכמה פעולות, וזה מסביר את מוסכמת הנתיב שלנו /action-X/.... עם זאת, פונקציית Cloud Run של ההדגמה שלנו תטמיע רק פעולה אחת.

נקודת קצה של טופס פעולה

לא בכל תרחישי השימוש נדרש טופס, אבל הוא מתאים מאוד לתרחיש השימוש של כתיבת נתונים בחזרה למסד נתונים, כי המשתמשים יכולים לבדוק את הנתונים ב-Looker ואז לספק ערכים להוספה למסד הנתונים. מכיוון שרשימת הפעולות שלנו סיפקה פרמטר form_url, ‏ Looker יפעיל את נקודת הקצה של טופס הפעולה הזו כשמשתמש יתחיל אינטראקציה עם הפעולה, כדי לקבוע אילו נתונים נוספים צריך לאסוף מהמשתמש.

בהצהרות על המסלולים, הגדרנו את נקודת הקצה הזו כזמינה בנתיב /action-0/form, ושייכנו לה שני handlers: ‏ requireInstanceAuth ו-action0Form.

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

לדוגמה, אנחנו יכולים לראות שrequireInstanceAuth משמש למספר מסלולים. אנחנו משתמשים ב-handler הזה בכל מקום שבו אנחנו רוצים לדרוש שהבקשה תגיע ממכונת Looker שלנו. ה-handler מאחזר את ערך הטוקן הסודי הצפוי מ-Secret Manager ודוחה בקשות שלא כוללות את ערך הטוקן הצפוי הזה.

async function requireInstanceAuth(req) {
    const lookerSecret = await getLookerSecret()
    if(!lookerSecret){return}
    const expectedAuthHeader = `Token token="${lookerSecret}"`
    if(!timingSafeEqual(req.headers.authorization,expectedAuthHeader)){
        return {
            status:401,
            body: {error: "Looker instance authentication is required"}
            }
        }
    return

    function timingSafeEqual(a, b) {
        if(typeof a !== "string"){return}
        if(typeof b !== "string"){return}
        var aLen = Buffer.byteLength(a)
        var bLen = Buffer.byteLength(b)
        const bufA = Buffer.allocUnsafe(aLen)
        bufA.write(a)
        const bufB = Buffer.allocUnsafe(aLen) //Yes, aLen
        bufB.write(b)

        return crypto.timingSafeEqual(bufA, bufB) && aLen === bLen;
        }
    }

שימו לב שאנחנו משתמשים בהטמעה של timingSafeEqual ולא בבדיקת השוויון הרגילה (==), כדי למנוע דליפה של מידע על תזמון ערוצי צד, שתאפשר לתוקף להבין במהירות את הערך של הסוד שלנו.

בהנחה שבקשה עוברת את בדיקת האימות של המופע, הבקשה מטופלת על ידי ה-handler‏ action0Form.

async function action0Form(req){
    return [
        {name: "choice",  label: "Choose", type:"select", options:[
            {name:"Yes", label:"Yes"},
            {name:"No", label:"No"},
            {name:"Maybe", label:"Maybe"}
            ]},
        {name: "note", label: "Note", type: "textarea"}
        ]
    }

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

הפעולה Execute Endpoint

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

בהצהרות על המסלולים שלנו, הפכנו את נקודת הקצה הזו לזמינה בנתיב /action-0/execute, ושייכנו לה שלושה רכיבי handler: ‏ requireInstanceAuth,‏ processRequestBody ו-action0Execute.

כבר הסברנו על requireInstanceAuth, וה-handler של processRequestBody מספק עיבוד מקדים לא מעניין במיוחד כדי להפוך שדות מסוימים בגוף הבקשה של Looker לפורמט נוח יותר, אבל אפשר לעיין בו בקובץ הקוד המלא.

הפונקציה action0Execute מתחילה בהצגת דוגמאות לחילוץ מידע מכמה חלקים בבקשת הפעולה שעשויים להיות שימושיים. בפועל, שימו לב שרכיבי הבקשה שהקוד שלנו מתייחס אליהם כאל formParams ו-actionParams יכולים להכיל שדות שונים, בהתאם למה שאתם מציינים בנקודות הקצה של כרטיס המוצר והטופס.

async function action0Execute (req){
    try{
        // Prepare some data that we will insert
        const scheduledPlanId = req.body.scheduled_plan && req.body.scheduled_plan.scheduled_plan_id
        const formParams = req.body.form_params || {}
        const actionParams = req.body.data || {}
        const queryData = req.body.attachment.data //If using a standard "push" action

        /*In case any fields require datatype-specific preparation, check this example:
        https://github.com/googleapis/nodejs-bigquery-storage/blob/main/samples/append_rows_proto2.js
        */

        const newRow = {
            invoked_at: new Date(),
            invoked_by: actionParams.email,
            scheduled_plan_id: scheduledPlanId || null,
            query_result_size: queryData.length,
            choice: formParams.choice,
            note: formParams.note,
            }

לאחר מכן הקוד עובר לקוד BigQuery רגיל כדי להכניס את הנתונים. שימו לב: ממשקי BigQuery Storage Write API מציעים וריאציות אחרות ומורכבות יותר שמתאימות יותר לחיבור סטרימינג מתמשך או להוספה בכמות גדולה של רשומות רבות. אבל כדי להגיב לאינטראקציות של משתמשים ספציפיים בהקשר של פונקציית Cloud Run, זו הווריאציה הכי ישירה.

await bigqueryConnectAndAppend(newRow)

...

async function bigqueryConnectAndAppend(row){
    let writerClient
    try{
        const destinationTablePath = `projects/${projectId}/datasets/${datasetId}/tables/${tableId}`
        const streamId = `${destinationTablePath}/streams/_default`
        writerClient = new BQSManagedWriter.WriterClient({projectId})
        const writeMetadata = await writerClient.getWriteStream({
            streamId,
            view: 'FULL',
            })
        const protoDescriptor = BigqueryStorage.adapt.convertStorageSchemaToProto2Descriptor(
            writeMetadata.tableSchema,
            'root'
            )
        const connection = await writerClient.createStreamConnection({
            streamId,
            destinationTablePath,
            })
        const writer = new BQSManagedWriter.JSONWriter({
            streamId,
            connection,
            protoDescriptor,
            })

        let result
        if(row){
            // The API expects an array of rows, so wrap the single row in an array
            const rowsToAppend = [row]
            result = await writer.appendRows(rowsToAppend).getResult()
            }
        return {
            streamId: connection.getStreamId(),
            protoDescriptor,
            result
            }
        }
    catch (e) {throw e}
    finally{
        if(writerClient){writerClient.close()}
        }
    }

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

מדריך פריסה

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

דרישות מוקדמות לגבי פרויקטים ושירותים

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

  1. פרויקט חדש: תצטרכו פרויקט חדש כדי לאחסן את המשאבים מהדוגמה שלנו.
  2. שירותים: כשמשתמשים בפעם הראשונה בפונקציות של BigQuery ו-Cloud Run בממשק המשתמש של מסוף Cloud, מוצגת בקשה להפעיל את ממשקי ה-API הנדרשים לשירותים הדרושים, כולל BigQuery,‏ Artifact Registry,‏ Cloud Build,‏ Cloud Functions,‏ Cloud Logging,‏ Pub/Sub,‏ Cloud Run Admin ו-Secret Manager.
  3. מדיניות לגבי הפעלות לא מאומתות: בתרחיש השימוש הזה אנחנו צריכים לפרוס פונקציות Cloud Run שמאפשרות גישה ציבורית, כי אנחנו נטפל באימות של בקשות נכנסות בקוד שלנו בהתאם ל-Action API, במקום להשתמש ב-IAM. השימוש הזה מותר כברירת מחדל, אבל מדיניות הארגון מגבילה אותו בדרך כלל. במיוחד, מדיניות constraints/iam.allowedPolicyMemberDomains מגבילה את מי שיכול לקבל הרשאות IAM, ויכול להיות שתצטרכו לשנות אותה כדי לאפשר לחשבון המשתמש allUsers גישה לא מאומתת. אם אתם לא מצליחים לאפשר גישה ציבורית, תוכלו לקרוא מידע נוסף במדריך איך ליצור שירותים ציבוריים ב-Cloud Run כששיתוף מוגבל לדומיין נאכף.
  4. כללי מדיניות אחרים: חשוב לזכור שגם Google Cloud הגבלות אחרות של מדיניות הארגון יכולות למנוע פריסה של שירותים שאחרת מותרת כברירת מחדל.

פריסת הפונקציה של Cloud Run

אחרי שיוצרים פרויקט חדש, פועלים לפי השלבים הבאים כדי לפרוס את פונקציית Cloud Run

  1. בקטע Cloud Run functions, לוחצים על Create Function (יצירת פונקציה).
  2. בוחרים שם לפונקציה (לדוגמה, demo-bq-insert-action).
  3. בהגדרות Trigger (טריגר):
    1. סוג הטריגר צריך להיות כבר 'HTTPS'.
    2. מגדירים את האימות לאפשרות הפעלות לא מאומתות.
    3. מעתיקים את הערך של כתובת ה-URL ללוח.
  4. בהגדרות Runtime > Runtime environment variables:
    1. לוחצים על הוספת משתנה.
    2. מגדירים את שם המשתנה כ-CALLBACK_URL_PREFIX.
    3. מדביקים את כתובת ה-URL מהשלב הקודם כערך.
  5. לוחצים על הבא.
  6. לוחצים על הקובץ package.json ומדביקים את התוכן.
  7. לוחצים על הקובץ index.js ומדביקים את התוכן.
  8. מקצים את המשתנה projectId בחלק העליון של הקובץ למזהה הפרויקט שלכם.
  9. מגדירים את Entry Point ל-httpHandler.
  10. לוחצים על פריסה.
  11. נותנים לחשבון השירות של ה-build את ההרשאות הנדרשות (אם יש כאלה).
  12. מחכים שהפריסה תסתיים.
  13. אם בשלבים הבאים תופיע שגיאה שתפנה אותך לבדיקת Google Cloud היומנים, חשוב לדעת שאפשר לגשת ליומנים של הפונקציה הזו מהכרטיסייה Logs בדף הזה.
  14. לפני שעוברים מדף הפונקציה של Cloud Run, בכרטיסייה פרטים, מאתרים את חשבון השירות של הפונקציה ורושמים אותו. נשתמש בזה בשלבים הבאים כדי לוודא שלפונקציה יש את ההרשאות שהיא צריכה.
  15. כדי לבדוק את הפריסה של הפונקציה ישירות בדפדפן, נכנסים לכתובת ה-URL. אמורה להתקבל תגובת JSON שמכילה את רשימת האינטגרציות שלכם.
  16. אם מופיעה שגיאת 403, יכול להיות שהניסיון שלכם להגדיר את האפשרות Allow unauthenticated invocations נכשל בשקט כתוצאה ממדיניות הארגון. צריך לבדוק אם הפונקציה מאפשרת הפעלות לא מאומתות, לבדוק את הגדרת מדיניות הארגון ולנסות לעדכן את ההגדרה.

גישה לטבלת היעד ב-BigQuery

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

  1. עוברים אל מסוף BigQuery.
  2. יוצרים את טבלת ההדגמה:

    1. בסרגל Explorer, לוחצים על סמל האפשרויות הנוספות לצד הפרויקט ובוחרים באפשרות Create dataset (יצירת מערך נתונים).
    2. נותנים למערך הנתונים את המזהה demo_dataset ולוחצים על יצירת מערך נתונים.
    3. בתפריט האפשרויות הנוספות (סמל האליפסות) של מערך הנתונים החדש שיצרתם, בוחרים באפשרות יצירת טבלה.
    4. נותנים לטבלה את השם demo_table.
    5. בקטע סכימה, בוחרים באפשרות עריכה כטקסט, משתמשים בסכימה הבאה ואז לוחצים על יצירת טבלה.

      [
       {"name":"invoked_at","type":"TIMESTAMP"},
       {"name":"invoked_by","type":"STRING"},
       {"name":"scheduled_plan_id","type":"STRING"},
       {"name":"query_result_size","type":"INTEGER"},
       {"name":"choice","type":"STRING"},
       {"name":"note","type":"STRING"}
      ]
      
  3. הקצאת הרשאות:

    1. בסרגל Explorer, לוחצים על מערך הנתונים.
    2. בדף dataset (מערך נתונים), לוחצים על שיתוף > הרשאות.
    3. לוחצים על Add Principal.
    4. מגדירים את New Principal (חשבון משתמש חדש) לחשבון השירות של הפונקציה, שצוין קודם בדף הזה.
    5. מקצים את התפקיד BigQuery Data Editor.
    6. לוחצים על Save.

התחברות ל-Looker

עכשיו, אחרי שהפונקציה נפרסה, נחבר את Looker אליה.

  1. כדי לאמת שהבקשות מגיעות ממופע Looker שלכם, נצטרך סוד משותף לפעולה שלכם. יוצרים מחרוזת ארוכה של תווים אקראיים ושומרים אותה במקום בטוח. נשתמש בו בשלבים הבאים כערך של הסוד של Looker.
  2. במסוף Cloud, עוברים אל Secret Manager.
    1. לוחצים על Create Secret (יצירת סוד).
    2. מגדירים את Name לערך LOOKER_SECRET. (השם הזה מוטמע בקוד של ההדגמה הזו, אבל כשעובדים עם קוד משלכם אפשר לבחור כל שם).
    3. מגדירים את ערך הסוד לערך הסוד שיצרתם.
    4. לוחצים על Create Secret (יצירת סוד).
    5. בדף Secret, לוחצים על הכרטיסייה Permissions.
    6. לוחצים על הענקת גישה.
    7. מגדירים את New Principals (חשבונות משתמשים חדשים) לחשבון השירות של הפונקציה, שצוין קודם.
    8. מקצים את התפקיד Secret Manager Secret Accessor.
    9. לוחצים על Save.
    10. כדי לוודא שהפונקציה ניגשת לסוד בהצלחה, אפשר להיכנס לנתיב /status שצורף לכתובת ה-URL של הפונקציה.
  3. במופע שלכם ב-Looker:
    1. עוברים אל 'ניהול' > 'פלטפורמה' > 'פעולות'.
    2. בתחתית הדף, לוחצים על הוספת מרכז פעולות.
    3. מזינים את כתובת ה-URL של הפונקציה (לדוגמה, https://your-region-your-project.cloudfunctions.net/demo-bq-insert-action) ומאשרים בלחיצה על הוספת מרכז פעולות.
    4. עכשיו אמורה להופיע רשומה חדשה במרכז הפעולות עם פעולה אחת בשם Demo BigQuery Insert.
    5. בכרטיס של מרכז הפעולות, לוחצים על הגדרת הרשאה.
    6. מזינים את Looker Secret שנוצר בשדה Authorization Token ולוחצים על Update Token.
    7. בפעולה Demo BigQuery Insert (הדגמה של הכנסה ל-BigQuery), לוחצים על Enable (הפעלה).
    8. מעבירים את המתג מופעל למצב מופעל.
    9. בדיקה של הפעולה אמורה לפעול באופן אוטומטי, כדי לוודא שהפונקציה מקבלת את הבקשה של Looker ומגיבה בצורה נכונה לנקודת הקצה של הטופס.
    10. לוחצים על Save.

בדיקה מקצה לקצה

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

צילום מסך של תיבת הדו-שיח 'שליחה' ב-Looker עם הפעולה החדשה שלנו שנבחרה

אחרי שלוחצים על שליחה, אמורה להתווסף שורה חדשה לטבלה ב-BigQuery (וכתובת האימייל של חשבון המשתמש ב-Looker תופיע בעמודה invoked_by).