1. 總覽
在本程式碼研究室中,您將瞭解如何使用 Google 簡報做為自訂簡報工具,分析最常見的軟體授權。您將使用 BigQuery API 查詢 GitHub 上的所有開放原始碼程式碼,並使用 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 應用程式:
- 在電腦上開啟指令列終端機,然後前往程式碼研究室的
start
目錄。 - 輸入下列指令,安裝 Node.js 依附元件。
npm install
- 請輸入下列指令來執行指令碼:
node .
- 查看問候語中有關這項專案步驟的問候語。
-- 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
中查看我們的 TODO 清單。請注意,我們之所以使用 JavaScript Promise,來鏈結完成應用程式所需的步驟,因為每個步驟都取決於上一個步驟。
如果您不熟悉承諾內容,也請別擔心,我們會提供所有您需要的程式碼。簡單來說,保證讓我們能以更同步的方式處理非同步處理作業。
4. 取得用戶端密鑰
如要使用 Slides、BigQuery 和 Drive API,我們會建立 OAuth 用戶端和服務帳戶。
設定 Google Developers Console
- 使用這個精靈在 Google Developers Console 中建立或選取專案,並自動啟用 API。依序點選「繼續」和「前往憑證」。
- 在「Add credentials to your project」(新增憑證至專案) 頁面,按一下「Cancel」(取消) 按鈕。
- 選取頁面頂端的「OAuth 同意畫面」分頁標籤。選取「電子郵件地址」並輸入產品名稱
Slides API Codelab
,然後按一下「儲存」按鈕。
啟用 BigQuery、Drive 和 Slides API
- 選取「Dashboard」分頁標籤,然後按一下「Enable API」按鈕,並啟用下列 3 個 API:
- BigQuery API
- Google Drive API
- Google Slides API
下載 OAuth 用戶端密鑰 (適用於 Google 簡報和雲端硬碟)
- 選取「Credentials」(憑證) 分頁標籤,按一下「Create credentials」(建立憑證) 按鈕,然後選取「OAuth client ID」(OAuth 用戶端 ID)。
- 選取「Other」應用程式類型,輸入名稱
Google Slides API Codelab
,然後按一下「Create」按鈕。按一下「OK」,關閉顯示的對話方塊。 - 按一下用戶端 ID 右側的 file_download (Download JSON) 按鈕。
- 將密鑰檔案重新命名為
client_secret.json
,然後複製到 start/ 和 finish/ 目錄中。
下載服務帳戶密鑰 (適用於 BigQuery)
- 選取「憑證」分頁標籤,按一下「建立憑證」按鈕,然後選取「服務帳戶金鑰」。
- 在下拉式選單中,選取「New Service Account」。為服務選擇名稱「
Slides API Codelab Service
」。接著,按一下「Role」(角色) 並捲動至「BigQuery」,然後選取「BigQuery Data Viewer」(BigQuery 資料檢視者) 和「BigQuery Job User」(BigQuery 工作使用者)。 - 在「Key type」(金鑰類型) 部分,選取「JSON」。
- 點選「建立」。金鑰檔案會自動下載至電腦。按一下「關閉」,結束顯示的對話方塊。
- 將密鑰檔案重新命名為
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 用戶端
為了製作投影片,我們將下列程式碼新增至 auth.js 檔案,以新增驗證至 Google API。這項驗證程序會要求存取您的 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 控制台,瀏覽 BigQuery 中的 GitHub 資料並執行自己的查詢。我們會編寫這項查詢並按下「Run」按鈕,找出 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. 建立簡報
現在來到有趣的部分吧!現在呼叫 Slides API 的 create
和 batchUpdate
方法來建立投影片。檔案應替換為下列內容:
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. 恭喜!
您已成功使用 BigQuery 分析的資料產生 Google 簡報。指令碼會使用 Google Slides API 和 BigQuery 建立簡報,分析最常見的軟體授權分析。
可能的改善項目
以下提供幾個額外建議,可以讓您進行更吸引人的整合:
- 在每張投影片中新增圖片
- 使用 Gmail API 透過電子郵件分享簡報
- 將範本投影片自訂為指令列引數
瞭解詳情
- 請參閱 Google Slides API 開發人員說明文件。
- 在 Stack Overflow 上提問及尋找解答,並置於 google-slides-api 標記之下。