สร้างงานนำเสนอ Google สไลด์จาก Big Data ใน Node.js

1. ภาพรวม

ใน Codelab นี้ คุณจะได้เรียนรู้วิธีใช้ Google สไลด์เป็นเครื่องมืองานนำเสนอที่กำหนดเองสำหรับการวิเคราะห์ใบอนุญาตซอฟต์แวร์ที่พบบ่อยที่สุด คุณจะค้นหาโค้ดโอเพนซอร์สทั้งหมดใน GitHub โดยใช้ BigQuery API และสร้างสไลด์โดยใช้ Google สไลด์ API เพื่อนำเสนอผลลัพธ์ แอปพลิเคชันตัวอย่างสร้างขึ้นโดยใช้ Node.js แต่หลักการพื้นฐานเดียวกันนี้ใช้ได้กับสถาปัตยกรรมใดก็ได้

สิ่งที่คุณจะได้เรียนรู้

  • การสร้างงานนำเสนอโดยใช้ Slides API
  • การใช้ BigQuery เพื่อรับข้อมูลเชิงลึกในชุดข้อมูลขนาดใหญ่
  • การคัดลอกไฟล์โดยใช้ Google Drive API

สิ่งที่คุณต้องมี

  • ติดตั้ง Node.js แล้ว
  • สิทธิ์เข้าถึงอินเทอร์เน็ตและเว็บเบราว์เซอร์
  • บัญชี Google
  • โปรเจ็กต์ Google Cloud Platform

2. รับโค้ดตัวอย่าง

คุณจะดาวน์โหลดโค้ดตัวอย่างทั้งหมดลงในคอมพิวเตอร์...

...หรือโคลนที่เก็บ GitHub จากบรรทัดคำสั่ง

git clone https://github.com/googleworkspace/slides-api.git

ที่เก็บจะมีชุดไดเรกทอรีที่แสดงแต่ละขั้นตอนในกระบวนการ ในกรณีที่คุณต้องอ้างอิงเวอร์ชันที่ใช้งานได้

คุณจะทำงานกับสำเนาที่อยู่ในไดเรกทอรี start แต่สามารถอ้างอิงหรือคัดลอกไฟล์จากไดเรกทอรีอื่นๆ ได้ตามต้องการ

3. เรียกใช้แอปตัวอย่าง

ก่อนอื่น มาทำให้สคริปต์ Node ทำงานกัน เมื่อดาวน์โหลดโค้ดแล้ว ให้ทำตามวิธีการด้านล่างเพื่อติดตั้งและเริ่มแอปพลิเคชัน Node.js

  1. เปิดเทอร์มินัลบรรทัดคำสั่งในคอมพิวเตอร์ แล้วไปที่ไดเรกทอรี start ของโค้ดแล็บ
  2. ป้อนคำสั่งต่อไปนี้เพื่อติดตั้งทรัพยากร Dependency ของ Node.js
npm install
  1. ป้อนคำสั่งต่อไปนี้เพื่อเรียกใช้สคริปต์
node .
  1. ดูข้อความต้อนรับที่แสดงขั้นตอนสำหรับโปรเจ็กต์นี้
-- Start generating slides. --
TODO: Get Client Secrets
TODO: Authorize
TODO: Get Data from BigQuery
TODO: Create Slides
TODO: Open Slides
-- Finished generating slides. --

คุณดูรายการสิ่งที่ต้องทำได้ใน slides.js, license.js และ auth.js โปรดทราบว่าเราใช้ JavaScript Promises เพื่อเชื่อมโยงขั้นตอนที่จำเป็นในการสร้างแอปให้เสร็จสมบูรณ์ เนื่องจากแต่ละขั้นตอนขึ้นอยู่กับขั้นตอนก่อนหน้า

หากคุณไม่คุ้นเคยกับ Promise ก็ไม่ต้องกังวล เราจะให้โค้ดทั้งหมดที่คุณต้องการ กล่าวโดยย่อคือ Promise ช่วยให้เราจัดการการประมวลผลแบบอะซิงโครนัสในลักษณะที่ซิงโครนัสมากขึ้นได้

4. รับรหัสลับไคลเอ็นต์

หากต้องการใช้ API ของสไลด์, BigQuery และไดรฟ์ เราจะสร้างไคลเอ็นต์ OAuth และบัญชีบริการ

ตั้งค่า Google Developers Console

  1. ใช้วิซาร์ดนี้เพื่อสร้างหรือเลือกโปรเจ็กต์ใน Google Developers Console และเปิด API โดยอัตโนมัติ คลิกต่อไป แล้วไปที่ข้อมูลเข้าสู่ระบบ
  2. ในหน้าเพิ่มข้อมูลเข้าสู่ระบบลงในโปรเจ็กต์ ให้คลิกปุ่มยกเลิก
  3. ที่ด้านบนของหน้า ให้เลือกแท็บหน้าจอความยินยอมของ OAuth เลือกอีเมล ป้อนชื่อผลิตภัณฑ์ Slides API Codelab แล้วคลิกปุ่มบันทึก

เปิดใช้ BigQuery, ไดรฟ์ และ Slides API

  1. เลือกแท็บแดชบอร์ด คลิกปุ่มเปิดใช้ API แล้วเปิดใช้ API 3 รายการต่อไปนี้
  2. BigQuery API
  3. Google Drive API
  4. Google Slides API

ดาวน์โหลดรหัสลับไคลเอ็นต์ OAuth (สำหรับสไลด์และไดรฟ์)

  1. เลือกแท็บข้อมูลเข้าสู่ระบบ คลิกปุ่มสร้างข้อมูลเข้าสู่ระบบ แล้วเลือกรหัสไคลเอ็นต์ OAuth
  2. เลือกประเภทแอปพลิเคชันอื่นๆ ป้อนชื่อ Google Slides API Codelab แล้วคลิกปุ่มสร้าง คลิกตกลงเพื่อปิดกล่องโต้ตอบที่ปรากฏ
  3. คลิกปุ่ม file_download (ดาวน์โหลด JSON) ทางด้านขวาของรหัสไคลเอ็นต์
  4. เปลี่ยนชื่อไฟล์ลับเป็น client_secret.json แล้วคัดลอกลงในทั้งไดเรกทอรี start/ และ finish/

ดาวน์โหลดข้อมูลลับของบัญชีบริการ (สำหรับ BigQuery)

  1. เลือกแท็บข้อมูลเข้าสู่ระบบ คลิกปุ่มสร้างข้อมูลเข้าสู่ระบบ แล้วเลือกคีย์ของบัญชีบริการ
  2. เลือกบัญชีบริการใหม่ในเมนูแบบเลื่อนลง เลือกชื่อ Slides API Codelab Service สำหรับบริการ จากนั้นคลิกบทบาท แล้วเลื่อนไปที่ BigQuery แล้วเลือกทั้งผู้ดูข้อมูล BigQuery และผู้ใช้งาน BigQuery
  3. สําหรับประเภทคีย์ ให้เลือก JSON
  4. คลิกสร้าง ระบบจะดาวน์โหลดไฟล์คีย์ลงในคอมพิวเตอร์โดยอัตโนมัติ คลิกปิดเพื่อออกจากกล่องโต้ตอบที่ปรากฏขึ้น
  5. เปลี่ยนชื่อไฟล์ลับเป็น service_account_secret.json แล้วคัดลอกลงในทั้งไดเรกทอรี start/ และ finish/

รับรหัสลับไคลเอ็นต์

ใน start/auth.js ให้กรอกข้อมูลวิธีการ getClientSecrets

auth.js

const fs = require('fs');

/**
 * Loads client secrets from a local file.
 * @return {Promise} A promise to return the secrets.
 */
module.exports.getClientSecrets = () => {
  return new Promise((resolve, reject) => {
    fs.readFile('client_secret.json', (err, content) => {
      if (err) return reject('Error loading client secret file: ' + err);
      console.log('loaded secrets...');
      resolve(JSON.parse(content));
    });
  });
}

ตอนนี้เราได้โหลดรหัสลับไคลเอ็นต์แล้ว ระบบจะส่งต่อข้อมูลเข้าสู่ระบบไปยัง Promise ถัดไป เรียกใช้โปรเจ็กต์ด้วย node . เพื่อให้แน่ใจว่าไม่มีข้อผิดพลาด

5. สร้างไคลเอ็นต์ OAuth2

หากต้องการสร้างสไลด์ ให้เพิ่มการตรวจสอบสิทธิ์ลงใน Google API โดยเพิ่มโค้ดต่อไปนี้ลงในไฟล์ auth.js การตรวจสอบสิทธิ์นี้จะขอสิทธิ์เข้าถึงบัญชี Google เพื่ออ่านและเขียนไฟล์ใน Google ไดรฟ์ สร้างงานนำเสนอใน Google สไลด์ และเรียกใช้การค้นหาแบบอ่านอย่างเดียวจาก Google BigQuery (หมายเหตุ: เราไม่ได้เปลี่ยน getClientSecrets)

auth.js

const fs = require('fs');
const readline = require('readline');
const openurl = require('openurl');
const googleAuth = require('google-auth-library');
const TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH ||
      process.env.USERPROFILE) + '/.credentials/';
const TOKEN_PATH = TOKEN_DIR + 'slides.googleapis.com-nodejs-quickstart.json';

// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/slides.googleapis.com-nodejs-quickstart.json
const SCOPES = [
  'https://www.googleapis.com/auth/presentations', // needed to create slides
  'https://www.googleapis.com/auth/drive', // read and write files
  'https://www.googleapis.com/auth/bigquery.readonly' // needed for bigquery
];

/**
 * Loads client secrets from a local file.
 * @return {Promise} A promise to return the secrets.
 */
module.exports.getClientSecrets = () => {
  return new Promise((resolve, reject) => {
    fs.readFile('client_secret.json', (err, content) => {
      if (err) return reject('Error loading client secret file: ' + err);
      console.log('loaded secrets...');
      resolve(JSON.parse(content));
    });
  });
}

/**
 * Create an OAuth2 client promise with the given credentials.
 * @param {Object} credentials The authorization client credentials.
 * @param {function} callback The callback for the authorized client.
 * @return {Promise} A promise to return the OAuth client.
 */
module.exports.authorize = (credentials) => {
  return new Promise((resolve, reject) => {
    console.log('authorizing...');
    const clientSecret = credentials.installed.client_secret;
    const clientId = credentials.installed.client_id;
    const redirectUrl = credentials.installed.redirect_uris[0];
    const auth = new googleAuth();
    const oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl);

    // Check if we have previously stored a token.
    fs.readFile(TOKEN_PATH, (err, token) => {
      if (err) {
        getNewToken(oauth2Client).then(() => {
          resolve(oauth2Client);
        });
      } else {
        oauth2Client.credentials = JSON.parse(token);
        resolve(oauth2Client);
      }
    });
  });
}

/**
 * Get and store new token after prompting for user authorization, and then
 * fulfills the promise. Modifies the `oauth2Client` object.
 * @param {google.auth.OAuth2} oauth2Client The OAuth2 client to get token for.
 * @return {Promise} A promise to modify the oauth2Client credentials.
 */
function getNewToken(oauth2Client) {
  console.log('getting new auth token...');
  openurl.open(oauth2Client.generateAuthUrl({
    access_type: 'offline',
    scope: SCOPES
  }));

  console.log(''); // \n
  return new Promise((resolve, reject) => {
    const rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout
    });
    rl.question('Enter the code from that page here: ', (code) => {
      rl.close();
      oauth2Client.getToken(code, (err, token) => {
        if (err) return reject(err);
        oauth2Client.credentials = token;
        let storeTokenErr = storeToken(token);
        if (storeTokenErr) return reject(storeTokenErr);
        resolve();
      });
    });
  });
}

/**
 * Store token to disk be used in later program executions.
 * @param {Object} token The token to store to disk.
 * @return {Error?} Returns an error or undefined if there is no error.
 */
function storeToken(token) {
  try {
    fs.mkdirSync(TOKEN_DIR);
    fs.writeFileSync(TOKEN_PATH, JSON.stringify(token));
  } catch (err) {
    if (err.code != 'EEXIST') return err;
  }
  console.log('Token stored to ' + TOKEN_PATH);
}

6. ตั้งค่า BigQuery

สำรวจ BigQuery (ไม่บังคับ)

BigQuery ช่วยให้เราค้นหาชุดข้อมูลขนาดใหญ่ได้ในไม่กี่วินาที มาใช้อินเทอร์เฟซเว็บก่อนที่จะทำการค้นหาแบบเป็นโปรแกรมกัน หากไม่เคยตั้งค่า BigQuery มาก่อน ให้ทำตามขั้นตอนในคู่มือเริ่มต้นฉบับย่อนี้

เปิด Cloud Console เพื่อเรียกดูข้อมูล GitHub ที่มีใน BigQuery และเรียกใช้การค้นหาของคุณเอง มาดูกันว่าใบอนุญาตซอฟต์แวร์ที่ได้รับความนิยมมากที่สุดใน GitHub คืออะไร โดยเขียนคำค้นหานี้แล้วกดปุ่มเรียกใช้

bigquery.sql

WITH AllLicenses AS (
  SELECT * FROM `bigquery-public-data.github_repos.licenses`
)
SELECT
  license,
  COUNT(*) AS count,
  ROUND((COUNT(*) / (SELECT COUNT(*) FROM AllLicenses)) * 100, 2) AS percent
FROM `bigquery-public-data.github_repos.licenses`
GROUP BY license
ORDER BY count DESC
LIMIT 10

เราเพิ่งวิเคราะห์ที่เก็บข้อมูลสาธารณะนับล้านรายการใน GitHub และพบว่าใบอนุญาตที่ได้รับความนิยมมากที่สุดคือ เยี่ยมเลย ตอนนี้มาตั้งค่าการเรียกใช้คําค้นหาเดียวกัน แต่คราวนี้จะทําแบบเป็นโปรแกรม

ตั้งค่า BigQuery

แทนที่โค้ดในไฟล์ license.js ฟังก์ชัน bigquery.query จะแสดงผล Promise

license**.js**

const google = require('googleapis');
const read = require('read-file');
const BigQuery = require('@google-cloud/bigquery');
const bigquery = BigQuery({
  credentials: require('./service_account_secret.json')
});

// See codelab for other queries.
const query = `
WITH AllLicenses AS (
  SELECT * FROM \`bigquery-public-data.github_repos.licenses\`
)
SELECT
  license,
  COUNT(*) AS count,
  ROUND((COUNT(*) / (SELECT COUNT(*) FROM AllLicenses)) * 100, 2) AS percent
FROM \`bigquery-public-data.github_repos.licenses\`
GROUP BY license
ORDER BY count DESC
LIMIT 10
`;

/**
 * Get the license data from BigQuery and our license data.
 * @return {Promise} A promise to return an object of licenses keyed by name.
 */
module.exports.getLicenseData = (auth) => {
  console.log('querying BigQuery...');
  return bigquery.query({
    query,
    useLegacySql: false,
    useQueryCache: true,
  }).then(bqData => Promise.all(bqData[0].map(getLicenseText)))
    .then(licenseData => new Promise((resolve, reject) => {
      resolve([auth, licenseData]);
    }))
    .catch((err) => console.error('BigQuery error:', err));
}

/**
 * Gets a promise to get the license text about a license
 * @param {object} licenseDatum An object with the license's
 *   `license`, `count`, and `percent`
 * @return {Promise} A promise to return license data with license text.
 */
function getLicenseText(licenseDatum) {
  const licenseName = licenseDatum.license;
  return new Promise((resolve, reject) => {
    read(`licenses/${licenseName}.txt`, 'utf8', (err, buffer) => {
      if (err) return reject(err);
      resolve({
        licenseName,
        count: licenseDatum.count,
        percent: licenseDatum.percent,
        license: buffer.substring(0, 1200) // first 1200 characters
      });
    });
  });
}

ลอง console.log ข้อมูลบางอย่างภายในแฮนเดิลการเรียกกลับของ Promise เพื่อทำความเข้าใจโครงสร้างของออบเจ็กต์และดูโค้ดที่ทำงานจริง

7. สร้างสไลด์

มาถึงช่วงสนุกๆ กันแล้ว มาสร้างสไลด์โดยเรียกใช้เมธอด create และ batchUpdate ของ Slides API กัน โดยควรแทนที่ไฟล์ของเราด้วยไฟล์ต่อไปนี้

slides.js

const google = require('googleapis');
const slides = google.slides('v1');
const drive = google.drive('v3');
const openurl = require('openurl');
const commaNumber = require('comma-number');

const SLIDE_TITLE_TEXT = 'Open Source Licenses Analysis';

/**
 * Get a single slide json request
 * @param {object} licenseData data about the license
 * @param {object} index the slide index
 * @return {object} The json for the Slides API
 * @example licenseData: {
 *            "licenseName": "mit",
 *            "percent": "12.5",
 *            "count": "1667029"
 *            license:"<body>"
 *          }
 * @example index: 3
 */
function createSlideJSON(licenseData, index) {
  // Then update the slides.
  const ID_TITLE_SLIDE = 'id_title_slide';
  const ID_TITLE_SLIDE_TITLE = 'id_title_slide_title';
  const ID_TITLE_SLIDE_BODY = 'id_title_slide_body';

  return [{
    // Creates a "TITLE_AND_BODY" slide with objectId references
    createSlide: {
      objectId: `${ID_TITLE_SLIDE}_${index}`,
      slideLayoutReference: {
        predefinedLayout: 'TITLE_AND_BODY'
      },
      placeholderIdMappings: [{
        layoutPlaceholder: {
          type: 'TITLE'
        },
        objectId: `${ID_TITLE_SLIDE_TITLE}_${index}`
      }, {
        layoutPlaceholder: {
          type: 'BODY'
        },
        objectId: `${ID_TITLE_SLIDE_BODY}_${index}`
      }]
    }
  }, {
    // Inserts the license name, percent, and count in the title
    insertText: {
      objectId: `${ID_TITLE_SLIDE_TITLE}_${index}`,
      text: `#${index + 1} ${licenseData.licenseName}  — ~${licenseData.percent}% (${commaNumber(licenseData.count)} repos)`
    }
  }, {
    // Inserts the license in the text body paragraph
    insertText: {
      objectId: `${ID_TITLE_SLIDE_BODY}_${index}`,
      text: licenseData.license
    }
  }, {
    // Formats the slide paragraph's font
    updateParagraphStyle: {
      objectId: `${ID_TITLE_SLIDE_BODY}_${index}`,
      fields: '*',
      style: {
        lineSpacing: 10,
        spaceAbove: {magnitude: 0, unit: 'PT'},
        spaceBelow: {magnitude: 0, unit: 'PT'},
      }
    }
  }, {
    // Formats the slide text style
    updateTextStyle: {
      objectId: `${ID_TITLE_SLIDE_BODY}_${index}`,
      style: {
        bold: true,
        italic: true,
        fontSize: {
          magnitude: 10,
          unit: 'PT'
        }
      },
      fields: '*',
    }
  }];
}

/**
 * Creates slides for our presentation.
 * @param {authAndGHData} An array with our Auth object and the GitHub data.
 * @return {Promise} A promise to return a new presentation.
 * @see https://developers.google.com/apis-explorer/#p/slides/v1/
 */
module.exports.createSlides = (authAndGHData) => new Promise((resolve, reject) => {
  console.log('creating slides...');
  const [auth, ghData] = authAndGHData;

  // First copy the template slide from drive.
  drive.files.copy({
    auth: auth,
    fileId: '1toV2zL0PrXJOfFJU-NYDKbPx9W0C4I-I8iT85TS0fik',
    fields: 'id,name,webViewLink',
    resource: {
      name: SLIDE_TITLE_TEXT
    }
  }, (err, presentation) => {
    if (err) return reject(err);

    const allSlides = ghData.map((data, index) => createSlideJSON(data, index));
    slideRequests = [].concat.apply([], allSlides); // flatten the slide requests
    slideRequests.push({
      replaceAllText: {
        replaceText: SLIDE_TITLE_TEXT,
        containsText: { text: '{{TITLE}}' }
      }
    })

    // Execute the requests
    slides.presentations.batchUpdate({
      auth: auth,
      presentationId: presentation.id,
      resource: {
        requests: slideRequests
      }
    }, (err, res) => {
      if (err) {
        reject(err);
      } else {
        resolve(presentation);
      }
    });
  });
});

8. เปิดสไลด์

สุดท้าย ให้เปิดงานนำเสนอในเบราว์เซอร์ อัปเดตวิธีการต่อไปนี้ใน slides.js

slides.js

/**
 * Opens a presentation in a browser.
 * @param {String} presentation The presentation object.
 */
module.exports.openSlidesInBrowser = (presentation) => {
  console.log('Presentation URL:', presentation.webViewLink);
  openurl.open(presentation.webViewLink);
}

เรียกใช้โปรเจ็กต์อีกครั้งเพื่อแสดงผลลัพธ์สุดท้าย

9. ยินดีด้วย

คุณสร้าง Google สไลด์จากข้อมูลที่วิเคราะห์โดยใช้ BigQuery ได้สำเร็จแล้ว สคริปต์จะสร้างงานนำเสนอโดยใช้ Google Slides API และ BigQuery เพื่อรายงานการวิเคราะห์ใบอนุญาตซอฟต์แวร์ที่พบบ่อยที่สุด

การปรับปรุงที่เป็นไปได้

ต่อไปนี้เป็นไอเดียเพิ่มเติมที่จะช่วยให้การผสานรวมน่าสนใจยิ่งขึ้น

  • เพิ่มรูปภาพลงในแต่ละสไลด์
  • แชร์สไลด์ทางอีเมลโดยใช้ Gmail API
  • ปรับแต่งสไลด์เทมเพลตเป็นอาร์กิวเมนต์บรรทัดคำสั่ง

ดูข้อมูลเพิ่มเติม