Tạo bản trình bày trên Google Trang trình bày từ Dữ liệu lớn trong Node.js

1. Tổng quan

Trong lớp học lập trình này, bạn sẽ tìm hiểu cách sử dụng Google Trang trình bày làm công cụ trình bày tuỳ chỉnh để phân tích các giấy phép phần mềm phổ biến nhất. Bạn sẽ truy vấn tất cả mã nguồn mở trên GitHub bằng API BigQuery và tạo một bộ trang trình bày bằng API Google Trang trình bày để trình bày kết quả. Ứng dụng mẫu này được xây dựng bằng Node.js, nhưng mọi cấu trúc đều có thể áp dụng các nguyên tắc cơ bản tương tự.

Kiến thức bạn sẽ học được

  • Tạo bản trình bày bằng API Trang trình bày
  • Sử dụng BigQuery để thu thập thông tin chi tiết về một tập dữ liệu lớn
  • Sao chép tệp bằng API Google Drive

Bạn cần có

  • Đã cài đặt Node.js
  • Quyền truy cập vào Internet và trình duyệt web
  • Tài khoản Google
  • Một dự án trên Google Cloud Platform

2. Nhận mã mẫu

Bạn có thể tải tất cả mã mẫu xuống máy tính...

...hoặc sao chép kho lưu trữ GitHub từ dòng lệnh.

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

Kho lưu trữ chứa một tập hợp các thư mục đại diện cho từng bước trong quy trình, trong trường hợp bạn cần tham khảo một phiên bản đang hoạt động.

Bạn sẽ xử lý bản sao nằm trong thư mục start, nhưng bạn có thể tham khảo hoặc sao chép các tệp từ những tệp khác nếu cần.

3. Chạy ứng dụng mẫu

Trước tiên, hãy thiết lập và chạy tập lệnh Nút. Khi đoạn mã được tải xuống, hãy làm theo hướng dẫn bên dưới để cài đặt và khởi động ứng dụng Node.js:

  1. Mở một thiết bị đầu cuối của dòng lệnh trên máy tính rồi chuyển đến thư mục start của lớp học lập trình.
  2. Nhập lệnh sau đây để cài đặt các phần phụ thuộc Node.js.
npm install
  1. Nhập lệnh sau để chạy tập lệnh:
node .
  1. Quan sát lời chào cho thấy các bước dành cho dự án này.
-- Start generating slides. --
TODO: Get Client Secrets
TODO: Authorize
TODO: Get Data from BigQuery
TODO: Create Slides
TODO: Open Slides
-- Finished generating slides. --

Bạn có thể xem danh sách VIỆC CẦN LÀM trong slides.js, license.jsauth.js. Xin lưu ý rằng chúng ta sử dụng Lời hứa về JavaScript để tạo chuỗi các bước cần thiết để hoàn tất ứng dụng vì mỗi bước đều phụ thuộc vào bước đã hoàn thành trước đó.

Nếu bạn không quen với lời hứa, đừng lo, chúng tôi sẽ cung cấp tất cả các mã bạn cần. Tóm lại, hứa hẹn sẽ cho chúng ta một cách để xử lý quá trình xử lý không đồng bộ theo cách đồng bộ hơn.

4. Lấy mật khẩu ứng dụng khách

Để sử dụng API Trang trình bày, BigQuery và Drive, chúng ta sẽ tạo một ứng dụng OAuth và một Tài khoản dịch vụ.

Thiết lập Google Developers Console

  1. Sử dụng trình hướng dẫn này để tạo hoặc chọn dự án trong Google Developers Console và tự động bật API. Nhấp vào Tiếp tục, sau đó nhấp vào Chuyển đến phần thông tin đăng nhập.
  2. Trên trang Add credentials to your project (Thêm thông tin xác thực vào dự án), hãy nhấp vào nút Cancel (Huỷ).
  3. Ở đầu trang, hãy chọn thẻ Màn hình xin phép bằng OAuth. Chọn Địa chỉ email, nhập Tên sản phẩm Slides API Codelab rồi nhấp vào nút Lưu.

Bật API BigQuery, Drive và Trang trình bày

  1. Chọn thẻ Trang tổng quan, nhấp vào nút Bật API và bật 3 API sau đây:
  2. API BigQuery
  3. API Google Drive
  4. API Google Trang trình bày

Tải mật khẩu ứng dụng OAuth xuống (dành cho Trang trình bày và Drive)

  1. Chọn thẻ Thông tin xác thực, nhấp vào nút Tạo thông tin xác thực rồi chọn Mã ứng dụng khách OAuth.
  2. Chọn loại ứng dụng Other (Khác), nhập tên Google Slides API Codelab rồi nhấp vào nút Create (Tạo). Nhấp vào OK để đóng hộp thoại hiện ra.
  3. Nhấp vào nút file_download (Tải tệp JSON xuống) ở bên phải của mã ứng dụng khách.
  4. Đổi tên tệp bí mật thành client_secret.json rồi sao chép tệp đó vào cả hai thư mục start/finish/.

Tải thông tin bí mật về tài khoản dịch vụ xuống (dành cho BigQuery)

  1. Chọn thẻ Credentials (Thông tin xác thực), nhấp vào nút Create credentials (Tạo thông tin xác thực) rồi chọn Service account key (Khoá tài khoản dịch vụ).
  2. Trong trình đơn thả xuống, hãy chọn Tài khoản dịch vụ mới. Chọn tên Slides API Codelab Service cho dịch vụ của bạn. Sau đó, nhấp vào Vai trò rồi cuộn đến BigQuery rồi chọn cả Người xem dữ liệu BigQueryNgười dùng công việc BigQuery.
  3. Đối với Loại khoá, hãy chọn JSON.
  4. Nhấp vào Tạo. Tệp khoá sẽ tự động được tải xuống máy tính của bạn. Nhấp vào Đóng để thoát khỏi hộp thoại hiện ra.
  5. Đổi tên tệp bí mật thành service_account_secret.json rồi sao chép tệp đó vào cả hai thư mục start/finish/.

Lấy mật khẩu ứng dụng khách

Trong start/auth.js, hãy điền phương thức 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));
    });
  });
}

Chúng ta hiện đã tải xong khoá bí mật của ứng dụng khách. Thông tin đăng nhập sẽ được chuyển sang lời hứa tiếp theo. Chạy dự án bằng node . để đảm bảo không có lỗi.

5. Tạo một ứng dụng OAuth2

Để tạo trang trình bày, hãy thêm phương thức xác thực vào Google API bằng cách thêm đoạn mã sau vào tệp auth.js. Phương thức xác thực này sẽ yêu cầu quyền truy cập vào Tài khoản Google của bạn để đọc và ghi tệp trong Google Drive, tạo bản trình bày trong Google Trang trình bày và thực hiện các truy vấn chỉ đọc từ Google BigQuery. (Lưu ý: Chúng tôi không thay đổi 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. Thiết lập BigQuery

Khám phá BigQuery (Không bắt buộc)

BigQuery cho phép chúng tôi truy vấn các tập dữ liệu khổng lồ chỉ trong vài giây. Hãy sử dụng giao diện web trước khi truy vấn theo phương thức lập trình. Nếu bạn chưa từng thiết lập BigQuery, hãy làm theo các bước trong hướng dẫn bắt đầu nhanh này.

Mở Cloud Console để duyệt xem dữ liệu GitHub có trong BigQuery và chạy truy vấn của riêng bạn. Hãy tìm hiểu các giấy phép phần mềm phổ biến nhất trên GitHub bằng cách viết truy vấn này rồi nhấn nút Run (Chạy).

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

Chúng tôi vừa phân tích hàng triệu kho lưu trữ công khai trên GitHub và tìm ra những giấy phép phổ biến nhất. Tuyệt! Bây giờ, hãy thiết lập để chạy cùng một truy vấn, nhưng lần này theo phương thức lập trình.

Thiết lập BigQuery

Thay thế mã trong tệp license.js. Hàm bigquery.query sẽ trả về một promise (lời hứa).

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
      });
    });
  });
}

Hãy thử console.log một số dữ liệu bên trong lệnh gọi lại Promise để hiểu cấu trúc của các đối tượng cũng như xem mã hoạt động như thế nào.

7. Tạo trang trình bày

Đây là phần thú vị! Hãy tạo trang trình bày bằng cách gọi phương thức createbatchUpdate của API Trang trình bày. Tệp của chúng ta sẽ được thay thế bằng:

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. Mở Trang trình bày

Cuối cùng, hãy mở bản trình bày trong trình duyệt. Cập nhật phương thức sau trong 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);
}

Hãy chạy dự án của bạn một lần cuối để hiển thị kết quả cuối cùng.

9. Xin chúc mừng!

Bạn đã tạo thành công Google Trang trình bày từ dữ liệu được phân tích bằng BigQuery. Tập lệnh của bạn tạo một bản trình bày bằng API Google Trang trình bày và BigQuery để báo cáo bản phân tích về các giấy phép phần mềm phổ biến nhất.

Những điểm có thể cải thiện

Dưới đây là một số ý tưởng bổ sung để tích hợp hấp dẫn hơn nữa:

  • Thêm hình ảnh vào mỗi trang trình bày
  • Chia sẻ trang trình bày qua email bằng API Gmail
  • Tuỳ chỉnh trang trình bày mẫu làm đối số dòng lệnh

Tìm hiểu thêm