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 BigQuery API và tạo một bản trình bày bằng Google Slides API để trình bày kết quả. Ứng dụng mẫu được xây dựng bằng Node.js, nhưng các nguyên tắc cơ bản tương tự cũng áp dụng cho mọi cấu trúc.
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 để biết 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
- Có quyền truy cập 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ữ này 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 chiếu một phiên bản đang hoạt động.
Bạn sẽ làm việc trên bản sao nằm trong thư mục start
, nhưng bạn có thể tham khảo hoặc sao chép tệp từ các thư mục khác nếu cần.
3. Chạy ứng dụng mẫu
Trước tiên, hãy chạy tập lệnh Node. Sau khi tải mã 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:
- Mở một cửa sổ 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. - Nhập lệnh sau để cài đặt các phần phụ thuộc Node.js.
npm install
- Nhập lệnh sau để chạy tập lệnh:
node .
- Quan sát lời chào cho biết các bước của 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.js
và auth.js
. Xin lưu ý rằng chúng ta sử dụng Promises JavaScript để liên kết các bước cần thiết để hoàn tất ứng dụng vì mỗi bước phụ thuộc vào bước trước đó đã hoàn tất.
Nếu bạn chưa quen với lời hứa, đừng lo, chúng tôi sẽ cung cấp tất cả mã bạn cần. Tóm lại, lời hứa giúp chúng ta xử lý quá trình xử lý không đồng bộ theo cách đồng bộ hơn.
4. Nhận khoá bí mật của ứng dụng khách
Để sử dụng các 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
- Sử dụng trình hướng dẫn này để tạo hoặc chọn một dự án trong Google Developers Console và tự động bật API. Nhấp vào Tiếp tục, rồi nhấp vào Chuyển đến thông tin xác thực.
- Trên trang Thêm thông tin xác thực vào dự án, hãy nhấp vào nút Huỷ.
- Ở đầ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 BigQuery, Drive và API Trang trình bày
- Chọn thẻ Trang tổng quan, nhấp vào nút Bật API rồi bật 3 API sau:
- BigQuery API
- API Google Drive
- API Google Trang trình bày
Tải bí mật ứng dụng khách OAuth xuống (dành cho Trang trình bày và Drive)
- 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.
- 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. - Nhấp vào nút file_download (Tải JSON xuống) ở bên phải mã ứng dụng.
- Đổi tên tệp bí mật thành
client_secret.json
rồi sao chép tệp đó vào cả thư mục start/ và finish/.
Tải khoá bí mật của tài khoản dịch vụ xuống (dành cho BigQuery)
- 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 Khoá tài khoản dịch vụ.
- 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 di chuyển đến BigQuery và chọn cả Người xem dữ liệu BigQuery và Người dùng công việc BigQuery. - Đối với Loại khoá, hãy chọn JSON.
- Nhấp vào Tạo. Tệp khoá sẽ tự động tải xuống máy tính của bạn. Nhấp vào Close (Đóng) để thoát khỏi hộp thoại xuất hiện.
- Đổi tên tệp bí mật thành
service_account_secret.json
rồi sao chép tệp đó vào cả thư mục start/ và finish/.
Nhận khoá bí mật của ứng dụng khách
Trong start/auth.js
, hãy điền vào 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));
});
});
}
Bây giờ, chúng ta đã tải các khoá bí mật của ứng dụng khách. Thông tin xác thực sẽ được chuyển đến 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 ứng dụng khách OAuth2
Để tạo trang trình bày, hãy thêm tính năng xác thực vào API của Google bằng cách thêm mã sau vào tệp auth.js. Quy trình 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 thi các truy vấn chỉ có thể đọc từ Google BigQuery. (Lưu ý: Chúng ta 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 ta 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 nhanh này.
Mở Cloud Console để duyệt qua dữ liệu GitHub có trong BigQuery và chạy các 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 và 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 các 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 là 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 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 của Promise để hiểu cấu trúc của các đối tượng và xem mã hoạt động như thế nào.
7. Tạo trang trình bày
Giờ là phần thú vị! Hãy tạo trang trình bày bằng cách gọi các phương thức create
và batchUpdate
của API Trang trình bày. Tệp của chúng ta sẽ được thay thế bằng nội dung sau:
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);
}
Chạy dự án của bạn lần cuối để xem 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 sẽ tạo một bản trình bày bằng cách sử dụng API Google Trang trình bày và BigQuery để báo cáo kết quả 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 khác để tích hợp một cách hấp dẫn hơn:
- Thêm hình ảnh vào từng 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 dưới dạng đối số dòng lệnh
Tìm hiểu thêm
- Đọc tài liệu dành cho nhà phát triển về API Google Trang trình bày.
- Đăng câu hỏi và tìm câu trả lời trên Stack Overflow trong thẻ google-slides-api.