Cloud Function برای خودکارسازی واردات داده CSV به Google Sheets

۱. مقدمه

هدف این آزمایشگاه کد این است که شما یاد بگیرید چگونه یک تابع ابری بنویسید که به آپلود فایل CSV در فضای ذخیره‌سازی ابری واکنش نشان دهد، محتوای آن را بخواند و از آن برای به‌روزرسانی یک Google Sheet با استفاده از API Sheets استفاده کند.

e9c78061022a6760.png

این را می‌توان به عنوان خودکارسازی مرحله‌ی «وارد کردن به صورت CSV» که در حالت عادی دستی بود، در نظر گرفت. این کار تضمین می‌کند که می‌توانید داده‌ها (که شاید توسط تیم دیگری تولید شده باشند) را به محض در دسترس بودن در یک صفحه گسترده تجزیه و تحلیل کنید.

این پیاده‌سازی به این شکل است:

52df703605ae4bd3.png

۲. تنظیمات و الزامات

تنظیم محیط خودتنظیم

  1. وارد Cloud Console شوید و یک پروژه جدید ایجاد کنید یا از یک پروژه موجود دوباره استفاده کنید. (اگر از قبل حساب Gmail یا G Suite ندارید، باید یکی ایجاد کنید .)

dMbN6g9RawQj_VXCSYpdYncY-DbaRzr2GbnwoV7jFf1u3avxJtmGPmKpMYgiaMH-qu80a_NJ9p2IIXFppYk8x3wyymZXavjglNLJJhuXieCem56H30hwXtd8PvXGpXJO9gEUDu3cZw

ci9Oe6PgnbNuSYlMyvbXF1JdQyiHoEgnhl4PlV_MFagm2ppzhueRkqX4eLjJllZco_2zCp0V0bpTupUSKji9KkQyWqj11pqit1K1faS1V6aTQz5GpG0T

8-tA_Lheyo8SscAVKrGii2coplQp2_D1Iosb2ViABY0UUO1A8cimXUu6Wf1R9zJIRExL5 OB2j946aIiFtyKTzxDcNnuznmR45vZ2HMoK3o67jxuoUJCAnqvEX6NgPGFjCVNgASc-lg

شناسه پروژه را به خاطر بسپارید، یک نام منحصر به فرد در تمام پروژه‌های Google Cloud (نام بالا قبلاً گرفته شده و برای شما کار نخواهد کرد، متاسفیم!). بعداً در این آزمایشگاه کد به آن PROJECT_ID گفته خواهد شد.

  1. در مرحله بعد، برای استفاده از منابع گوگل کلود، باید پرداخت را در Cloud Console فعال کنید .

اجرای این آزمایشگاه کد، اگر اصلاً هزینه‌ای نداشته باشد، نباید هزینه زیادی داشته باشد. حتماً دستورالعمل‌های بخش «پاکسازی» را که به شما نحوه خاموش کردن منابع را آموزش می‌دهد، دنبال کنید تا پس از این آموزش، متحمل هزینه نشوید. کاربران جدید Google Cloud واجد شرایط برنامه آزمایشی رایگان ۳۰۰ دلاری هستند.

۳. یک Google Sheet ایجاد و پیکربندی کنید و API را فعال کنید

ابتدا، یک سند Sheets جدید ایجاد می‌کنیم (این برگه می‌تواند متعلق به هر کاربری باشد). پس از ایجاد، شناسه آن را به خاطر بسپارید؛ از آن به عنوان یک متغیر محیطی برای تابعی که خواهیم نوشت استفاده خواهد شد:

dd77d5fc1364ad3e.png

از کنسول GCP ، با رفتن به بخش «APIs and Services» و سپس «API Library»، API گوگل شیت را در پروژه تازه ایجاد شده خود فعال کنید:

c64e2e98b8b55f16.png

در بخش «IAM & admin»، به «Service accounts» بروید و ایمیل مربوط به حساب سرویس پیش‌فرض App Engine را یادداشت کنید. این ایمیل باید به شکل your-project-id@appspot.gserviceaccount.com باشد. البته می‌توانید حساب سرویس خودتان را نیز برای این اقدام ایجاد کنید.

6e279d7e07d4febf.png

در نهایت، با استفاده از دکمه‌ی «اشتراک‌گذاری»، به این حساب کاربری سرویس، امتیاز ویرایش صفحه‌گسترده‌ی خود را اعطا کنید:

c334062465ddf928.png

با این تنظیمات، اکنون می‌توانیم تابع ابری خود را بنویسیم و آن را برای استفاده از این حساب سرویس پیکربندی کنیم. این تابع قادر خواهد بود در این سند صفحه‌گسترده‌ای که اخیراً ایجاد کرده‌ایم، بنویسد.

۴. یک سطل ذخیره‌سازی ایجاد کنید

بیایید باکتی ایجاد کنیم که تابع ابری ما فایل‌های CSV جدید را رصد کند.

در کنسول، از منوی سمت چپ برای رفتن به «ذخیره‌سازی» استفاده کنید...:

2ddcb54423979d25.png

... و یک باکت جدید به نام csv2sheet-POSTFIX ایجاد کنید (POSTFIX را با چیزی منحصر به فرد جایگزین کنید) و سایر تنظیمات را روی مقادیر پیش‌فرض خود تنظیم کنید:

dd637080ade62e81.png

۵. تابع ابری را ایجاد کنید

اکنون می‌توانیم یک تابع ابری به نام csv2sheet ایجاد کنیم که هنگام آپلود فایل‌ها به یک مخزن ذخیره‌سازی ابری خاص فعال می‌شود. کد در Node.js 8 با توابع async و با استفاده از ویرایشگر درون‌خطی مستقیماً در Cloud Console نوشته خواهد شد:

6ee1a5ce63174ae8.png

مطمئن شوید که Trigger را روی «Cloud Storage» تنظیم کرده و نام باکت را همان نامی که در مرحله قبل ایجاد کرده‌اید، تنظیم کنید.

همچنین نقطه ورودی تابعی که قرار است در csv2sheet بنویسیم را به‌روزرسانی کنید:

446e7c7c992c2d8a.png

حالا بدنه تابع را به صورت زیر تغییر دهید:

  1. از APIهای Cloud Storage و Sheets استفاده کنید
  2. تابع csv2sheet را به عنوان async علامت گذاری کنید
  3. fileName را از ابرداده رویداد Cloud Storage دریافت کنید و نامی برای برگه جدیدی که ایجاد خواهیم کرد، استخراج کنید:
const {google} = require("googleapis");
const {Storage} = require("@google-cloud/storage")

exports.csv2sheet = async (data, context) => {
  var fileName = data.name;
  // basic check that this is a *.csv file, etc...
  if (!fileName.endsWith(".csv")) {
    console.log("Not a .csv file, ignoring.");
    return;
  }
  // define name of new sheet  
  const sheetName = fileName.slice(0, -4);

  // TODO!
};

همانطور که در ادامه خواهیم دید، استفاده از async در اینجا برای استفاده از await الزامی است.

چند گزینه مهم هنگام ایجاد این تابع عبارتند از (روی پیوند «بیشتر» در پایین صفحه کلیک کنید):

  • از منوی کشویی برای انتخاب حساب سرویس مورد بحث در بالا استفاده کنید.
  • یک متغیر محیطی به نام SPREADSHEET_ID تعریف کنید که باید با سند برگه‌ای که قبلاً ایجاد کرده‌اید مطابقت داشته باشد:

fd22d1873bcb8c66.png

به عنوان آخرین مرحله راه‌اندازی، در اینجا محتوای package.json به همراه APIهای Cloud Storage و Google Sheet به عنوان دو وابستگی که استفاده خواهیم کرد، آمده است (از تب PACKAGE.JSON ویرایشگر درون‌خطی کنسول استفاده کنید):

{
    "name": "csv2sheet",
    "version": "0.0.42",
    "dependencies": {
        "googleapis": "^51.0.0",
        "@google-cloud/storage": "^5.0.1"
    }
}

وقتی همه چیز را طبق توضیحات پیکربندی کردید، روی «ایجاد» کلیک کنید! پس از یک دقیقه کوتاه، تابع شما باید ایجاد و مستقر شود.

۶. راه‌اندازی auth و Sheets API

قبل از اینکه کد بیشتری را در تابع Cloud خود با استفاده از ویرایشگر درون‌خطی بنویسیم، باید ایجاد یک API کلاینت گوگل با محدوده‌های ذخیره‌سازی و برگه مناسب را مسدود کنیم (به یاد داشته باشید، این بخشی از یک تابع async است).

در ویرایشگر تابع کنسول، روی «ویرایش» کلیک کنید و کد زیر را به بدنه تابع csv2sheet خود اضافه کنید:

// block on auth + getting the sheets API object
const auth = await google.auth.getClient({
  scopes: [
    "https://www.googleapis.com/auth/spreadsheets",
    "https://www.googleapis.com/auth/devstorage.read_only"
  ]
});

از آنجا می‌توانیم یک کلاینت Sheets API ایجاد کنیم:

const sheetsAPI = google.sheets({version: 'v4', auth});

۷. از API صفحات برای ایجاد یک برگه خالی استفاده کنید

با یک کلاینت Sheets API می‌توانیم یک برگه جدید ساده در سند خود ایجاد کنیم، اما قبل از اینکه ادامه دهیم، در اینجا یک نکته سریع در مورد واژگان آورده شده است:

  • یک صفحه گسترده، سند واقعی است و توسط شناسه‌اش (که در بالا مورد بحث قرار گرفت و در URL سند قابل مشاهده است) ارجاع داده می‌شود.
  • یک برگه (sheet) یکی از برگه‌های (tab) موجود در سند است و می‌توان آن را با نامش (نام برگه) یا شناسه‌ای که هنگام ایجاد برگه ایجاد می‌شود، ارجاع داد.

با توجه به این نکته، در اینجا تابعی با استفاده از کلاینت Sheets API برای ایجاد یک برگه خالی در موقعیت ۲ (معمولاً بعد از "Sheet1" پیش‌فرض)، با ۲۶ ستون و ۲۰۰۰ ردیف، با ردیف اول ثابت (freeze) ارائه شده است (آن را با استفاده از ویرایشگر درون‌خطی به تابع خود اضافه کنید):

function addEmptySheet(sheetsAPI, sheetName) {
  return new Promise((resolve, reject) => {
    const emptySheetParams = {
      spreadsheetId: process.env.SPREADSHEET_ID,
      resource: {
        requests: [
          {
            addSheet: {
              properties: {
                title: sheetName,
                index: 1,
                gridProperties: {
                  rowCount: 2000,
                  columnCount: 26,
                  frozenRowCount: 1
                }
              }
            }
          }
        ]
      }
    };
    sheetsAPI.spreadsheets.batchUpdate( emptySheetParams, function(err, response) {
        if (err) {
          reject("The Sheets API returned an error: " + err);
        } else {
          const sheetId = response.data.replies[0].addSheet.properties.sheetId;
          console.log("Created empty sheet: " + sheetId);
          resolve(sheetId);
        }
      }
    );
  });
}

توجه داشته باشید که چگونه به جای کدگذاری مستقیم ارجاع به صفحه گسترده، به متغیر محیطی SPREADSHEET_ID که قبلاً ایجاد شده است، تکیه می‌کنیم.

ما باید sheetId برای درخواست‌های بعدی که به این برگه خاص ارسال می‌شوند، به خاطر بسپاریم. همچنین، نام برگه باید منحصر به فرد باشد و اگر از قبل برگه‌ای به نام sheetName وجود داشته باشد، ایجاد با شکست مواجه خواهد شد.

تابع batchUpdate در Sheets API یک روش رایج برای تعامل با اسناد است و در اینجا توضیح داده شده است.

۸. خواندن داده‌ها از یک فایل CSV ذخیره‌سازی

حالا که جایی برای تخلیه داده‌هایمان داریم، بیایید تابع ابری خود را در ویرایشگر درون‌خطی بیشتر توسعه دهیم و از API ذخیره‌سازی ابری برای گرفتن داده‌های واقعی از فایلی که تازه آپلود شده است و ذخیره آنها در یک رشته استفاده کنیم:

function readCSVContent(sheetsAPI, file, sheetName) {
  return new Promise((resolve, reject) => {
    const storage = new Storage();
    let fileContents = new Buffer('');
    storage.bucket(file.bucket).file(file.name).createReadStream()
    .on('error', function(err) {
      reject('The Storage API returned an error: ' + err);
    })
    .on('data', function(chunk) {
      fileContents = Buffer.concat([fileContents, chunk]);
    })  
    .on('end', function() {
      let content = fileContents.toString('utf8');
      console.log("CSV content read as string : " + content );
      resolve(content);
    });
  });
}

۹. صفحه تازه ایجاد شده را پر کنید

اکنون زمان آن رسیده است که برگه‌ای را که ایجاد کرده‌ایم با استفاده از همان API کلاینت Sheet و داده‌هایی که جمع‌آوری کرده‌ایم، پر کنیم. از این فرصت استفاده خواهیم کرد تا کمی استایل به ستون‌های برگه اضافه کنیم (اندازه فونت ردیف بالا را تغییر داده و آن را پررنگ کنیم):

function populateAndStyle(sheetsAPI, theData, sheetId) {
  return new Promise((resolve, reject) => {
    // Using 'batchUpdate' allows for multiple 'requests' to be sent in a single batch.
    // Populate the sheet referenced by its ID with the data received (a CSV string)
    // Style: set first row font size to 11 and to Bold. Exercise left for the reader: resize columns
    const dataAndStyle = {
      spreadsheetId: process.env.SPREADSHEET_ID,
      resource: {
        requests: [
          {
            pasteData: {
              coordinate: {
                sheetId: sheetId,
                rowIndex: 0,
                columnIndex: 0
              },
              data: theData,
              delimiter: ","
            }
          },
          {
            repeatCell: {
              range: {
                sheetId: sheetId,
                startRowIndex: 0,
                endRowIndex: 1
              },
              cell: {
                userEnteredFormat: {
                  textFormat: {
                    fontSize: 11,
                    bold: true
                  }
                }
              },
              fields: "userEnteredFormat(textFormat)"
            }
          }       
        ]
      }
    };
        
    sheetsAPI.spreadsheets.batchUpdate(dataAndStyle, function(err, response) {
      if (err) {
        reject("The Sheets API returned an error: " + err);
      } else {
        console.log(sheetId + " sheet populated with " + theData.length + " rows and column style set.");
        resolve();
      }
    });    
  });
}

این کد باید به تابع Cloud ما اضافه شود که اکنون ۹۹٪ تکمیل شده است!

توجه داشته باشید که چگونه داده‌ها و استایل‌بندی به عنوان چندین requests در یک فراخوانی batchUpdate از API Sheets ترکیب می‌شوند. این امر باعث می‌شود به‌روزرسانی کارآمدتر و اتمیک‌تر باشد.

همچنین توجه داشته باشید که ما یک محدوده ویرایش تعریف می‌کنیم که با اندازه برگه‌ای که ایجاد کرده‌ایم مطابقت دارد. این بدان معناست که محتوایی که از ۲۶ ستون (مقدار columnCount که هنگام ایجاد برگه استفاده شده است) تجاوز کند، با این کد خاص با شکست مواجه خواهد شد.

اگر همه چیز خوب پیش برود، در این مرحله می‌توانید:

  1. تابع به‌روزرسانی‌شده را ذخیره کنید
  2. یک فایل CSV را داخل سطل بیندازید
  3. ببینید که داده‌های مربوطه در صفحه گسترده شما ظاهر می‌شوند!

۱۰. کنار هم قرار دادن همه چیز و آزمایش جریان

فراخوانی‌های توابعی که در موردشان صحبت کردیم می‌توانند به صورت فراخوانی‌های مسدودکننده‌ی متوالی در تابع اصلی csv2sheet انجام شوند:

  const sheetId = await addEmptySheet(sheetsAPI, sheetName);
  const theData = await readCSVContent(sheetsAPI, data, sheetName);
  await populateAndStyle(sheetsAPI, theData, sheetId);

اگر به کد منبع کامل تابع نیاز دارید، اینجا موجود است (احتمالاً تهیه همه آن در یک مجموعه آسان‌تر است).

وقتی همه چیز سر جای خودش قرار گرفت، کافیست یک فایل CSV را در پوشه‌ی مناسب آپلود کنید و ببینید که صفحه‌گسترده‌تان با یک برگه‌ی جدید حاوی محتوای فایل به‌روزرسانی می‌شود. اگر فایل CSV ندارید، در اینجا یک نمونه فایل CSV آورده شده است.

1efae021942e64fa.png

سعی کنید چندین فایل را در سطل بارگذاری کنید تا ببینید چه اتفاقی می‌افتد!

۱۱. همین! وقت تخریب زیرساخت‌هاست

شوخی کردم، هیچ زیرساختی برای تخریب وجود ندارد، همه این کارها بدون سرور انجام شده است!

در صورت تمایل می‌توانید تابع ابری و باکتی که ایجاد کرده‌اید یا حتی کل پروژه را حذف کنید.

۱۲. قدم بعدی چیست؟

این پایان این کدلب است که شما را در مراحل گوش دادن به آپلودها در یک باکت ذخیره‌سازی ابری در یک تابع ابری برای به‌روزرسانی یک گوگل شیت با استفاده از API مناسب، راهنمایی می‌کند.

در اینجا چند مرحله پیگیری آمده است:

اگر با این آزمایشگاه کدنویسی مشکلی داشتید، می‌توانید با استفاده از لینک گوشه پایین سمت چپ، مشکل خود را گزارش دهید.

نظرات شما قابل تقدیر است!