1. 概览
在此 Codelab 中,您将学习如何使用 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 应用:
- 在计算机上打开命令行终端,然后导航到此 Codelab 的
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 来串联完成应用所需的步骤。
如果您不熟悉 Promise,也不用担心,我们会提供您需要的所有代码。简而言之,Promise 让我们能够以更同步的方式处理异步处理。
4. 获取客户端密钥
如需使用 Google 幻灯片、BigQuery 和云端硬盘 API,我们将创建一个 OAuth 客户端和一个服务账号。
设置 Google 管理中心
- 使用此向导在 Google Developers Console 中创建或选择项目,并自动启用该 API。点击继续,然后点击转到凭据。
- 在向项目添加凭据页面上,点击取消按钮。
- 在页面顶部,选择 OAuth 权限请求屏幕标签页。选择电子邮件地址,输入商品名
Slides API Codelab
,然后点击保存按钮。
启用 BigQuery、云端硬盘和幻灯片 API
- 选择信息中心标签页,点击启用 API 按钮,然后启用以下 3 个 API:
- BigQuery API
- Google Drive API
- Google Slides API
下载 OAuth 客户端密钥(适用于幻灯片和云端硬盘)
- 选择凭据标签页,点击创建凭据按钮,然后选择 OAuth 客户端 ID。
- 选择应用类型 Other,输入名称
Google Slides API Codelab
,然后点击 Create 按钮。点击 OK 关闭随即显示的对话框。 - 点击客户端 ID 右侧的 file_download(下载 JSON)按钮。
- 将密钥文件重命名为
client_secret.json
,并将其复制到 start/ 和 finish/ 目录中。
下载服务账号密钥(适用于 BigQuery)
- 选择凭据标签页,点击创建凭据按钮,然后选择服务账号密钥。
- 在下拉菜单中,选择新建服务账号。为您的服务选择名称
Slides API Codelab Service
。然后,点击角色,滚动到 BigQuery,然后同时选择 BigQuery Data Viewer 和 BigQuery Job User。 - 在密钥类型部分,选择 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));
});
});
}
现在,我们已加载客户端密钥。凭据将传递给下一个 promise。使用 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,我们可以在几秒钟内查询庞大的数据集。在以编程方式进行查询之前,我们先使用 Web 界面。如果您之前从未设置过 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. 打开 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 Slides API 和 BigQuery 创建演示文稿,以报告对最常见软件许可的分析。
可能的改进
以下是一些其他建议,可帮助您打造更具吸引力的集成:
- 向每张幻灯片添加图片
- 使用 Gmail API 通过电子邮件分享幻灯片
- 将模板幻灯片自定义为命令行参数
了解详情
- 阅读 Google Slides API 开发者文档。
- 在 Stack Overflow 上,您可以通过 google-slides-api 标记发布问题和查找答案。