สร้างงานนำเสนอ 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.js

  1. เปิดเทอร์มินัลบรรทัดคำสั่งในคอมพิวเตอร์และไปที่ไดเรกทอรี start ของ Codelab
  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 เพื่อเชื่อมโยงขั้นตอนที่จำเป็นในการดำเนินการแอปให้เสร็จสมบูรณ์ เนื่องจากแต่ละขั้นตอนจะขึ้นอยู่กับขั้นตอนก่อนหน้าที่ดำเนินการจนเสร็จสิ้น

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

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

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

ตั้งค่า Google Developers Console

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

เปิดใช้ API ของ BigQuery, ไดรฟ์ และสไลด์

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

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

  1. เลือกแท็บข้อมูลเข้าสู่ระบบ แล้วคลิกปุ่มสร้างข้อมูลเข้าสู่ระบบ แล้วเลือกรหัสไคลเอ็นต์ OAuth
  2. เลือกประเภทแอปพลิเคชัน Other ป้อนชื่อ Google Slides API Codelab และคลิกปุ่ม สร้าง คลิก OK เพื่อปิดกล่องโต้ตอบที่ปรากฏขึ้น
  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));
    });
  });
}

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

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

ในการสร้างสไลด์ ให้เพิ่มการตรวจสอบสิทธิ์ไปยัง Google APIs โดยการเพิ่มโค้ดต่อไปนี้ในไฟล์ 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 จะแสดงผลคำสัญญา

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 ข้อมูลบางส่วนภายใน Callback ของ 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
  • ปรับแต่งสไลด์เทมเพลตเป็นอาร์กิวเมนต์บรรทัดคำสั่ง

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