1. 總覽
在本程式碼研究室中,您將瞭解如何使用 Google 簡報做為自訂簡報工具,分析最常見的軟體授權。您將使用 BigQuery API 查詢 GitHub 上的所有開放原始碼,並使用 Google 簡報 API 建立簡報,以呈現結果。本範例應用程式是使用 Node.js 建構,但相同的基本原則適用於任何架構。
課程內容
- 使用 Google 簡報 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 Promises 來鏈結完成應用程式所需的步驟。
如果您不熟悉承諾,請放心,我們會提供您所需的所有程式碼。簡而言之,承諾可讓我們以更同步的方式處理非同步處理作業。
4. 取得用戶端密鑰
如要使用簡報、BigQuery 和 Drive API,我們會建立 OAuth 用戶端和服務帳戶。
設定 Google Developers Console
- 使用這個精靈在 Google Developers Console 中建立或選取專案,並自動啟用 API。按一下「繼續」,然後點選「前往憑證」。
- 在「在專案中新增憑證」頁面,按一下「取消」按鈕。
- 選取頁面頂端的「OAuth 同意畫面」分頁標籤。選取「電子郵件地址」,輸入產品名稱
Slides API Codelab
,然後按一下「儲存」按鈕。
啟用 BigQuery、Drive 和簡報 API
- 選取「Dashboard」分頁標籤,按一下「Enable API」按鈕,然後啟用下列 3 個 API:
- BigQuery API
- Google Drive API
- Google Slides API
下載 OAuth 用戶端密鑰 (適用於簡報和雲端硬碟)
- 選取「Credentials」分頁,按一下「Create credentials」按鈕,然後選取「OAuth client ID」。
- 選取應用程式類型「Other」,輸入名稱
Google Slides API Codelab
,然後按一下「Create」按鈕。按一下「OK」關閉對話方塊。 - 按一下用戶端 ID 右側的「file_download」(下載 JSON) 按鈕。
- 將機密檔案重新命名為
client_secret.json
,並複製到 start/ 和 finish/ 目錄中。
下載服務帳戶密鑰 (適用於 BigQuery)
- 選取「憑證」分頁,按一下「建立憑證」按鈕,然後選取「服務帳戶金鑰」。
- 在下拉式選單中,選取「New Service Account」(新增服務帳戶)。為服務選擇名稱
Slides API Codelab Service
。接著,按一下「角色」,然後捲動至「BigQuery」,並選取「BigQuery 資料檢視者」和「BigQuery 工作使用者」。 - 在「金鑰類型」中,選取「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 資料,並執行自己的查詢。我們來找出 GitHub 上最熱門的軟體授權,方法是編寫這項查詢並按下「Run」按鈕。
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
在 Promise 的回呼中使用部分資料,瞭解物件的結構,並查看程式碼的實際運作情形。
7. 建立投影片
接下來是好玩的部分!讓我們呼叫 Google 簡報 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. 開啟 Google 簡報
最後,請在瀏覽器中開啟簡報。請在 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 簡報 API 和 BigQuery 建立簡報,以便回報對最常見軟體授權的分析結果。
可能的改善做法
以下提供一些其他想法,協助您打造更吸引人的整合體驗:
- 在每個投影片中加入圖片
- 使用 Gmail API 透過電子郵件分享簡報
- 將範本投影片自訂為指令列引數
瞭解詳情
- 參閱 Google Slides API 開發人員說明文件。
- 在 Stack Overflow 上發布問題並尋找解答,標記為 google-slides-api。