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

1. ภาพรวม

ใน Codelab นี้ คุณจะได้เรียนรู้วิธีใช้ Google สไลด์เป็นเครื่องมือนำเสนอที่กําหนดเองสําหรับการวิเคราะห์ใบอนุญาตซอฟต์แวร์ที่ใช้กันมากที่สุด คุณจะค้นหาโค้ดโอเพนซอร์สทั้งหมดใน GitHub โดยใช้ BigQuery API และสร้างชุดสไลด์โดยใช้ Google Slides 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. ป้อนคำสั่งต่อไปนี้เพื่อติดตั้งการอ้างอิง 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 ก็ไม่ต้องกังวล เราจะจัดเตรียมโค้ดทั้งหมดที่จำเป็นให้คุณ กล่าวโดยย่อคือ Promises ช่วยให้เราจัดการการประมวลผลแบบไม่พร้อมกันในลักษณะแบบพร้อมกันได้มากขึ้น

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

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

ตั้งค่า Google Developers Console

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

เปิดใช้ BigQuery, ไดรฟ์ และ 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 เพื่อเรียกดูข้อมูล 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 สไลด์ API และ BigQuery เพื่อรายงานการวิเคราะห์ใบอนุญาตซอฟต์แวร์ที่ใช้กันมากที่สุด

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

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

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

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