1. 개요
이 Codelab에서는 가장 일반적인 소프트웨어 라이선스를 분석하기 위한 맞춤 프레젠테이션 도구로 Google Slides를 사용하는 방법을 알아봅니다. 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.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 Promises를 사용하여 앱을 완료하는 데 필요한 단계를 연쇄합니다.
약속을 잘 모르더라도 걱정하지 마세요. 필요한 모든 코드를 제공해 드리겠습니다. 요약하자면 약속을 사용하면 비동기 처리를 더 동기식 방식으로 처리할 수 있습니다.
4. 클라이언트 보안 비밀번호 가져오기
Slides, Bigquery, Drive API를 사용하려면 OAuth 클라이언트와 서비스 계정을 만듭니다.
Google Developers Console 설정
- 이 마법사를 사용하여 Google Developers Console에서 프로젝트를 만들거나 선택하고 API를 자동으로 사용 설정합니다. 계속을 클릭한 다음 사용자 인증 정보로 이동을 클릭합니다.
- 프로젝트에 사용자 인증 정보 추가 페이지에서 취소 버튼을 클릭합니다.
- 페이지 상단에서 OAuth 동의 화면 탭을 선택합니다. 이메일 주소를 선택하고 제품 이름
Slides API Codelab
을 입력한 다음 저장 버튼을 클릭합니다.
BigQuery, Drive, Slides API 사용 설정
- 대시보드 탭을 선택하고 API 사용 설정 버튼을 클릭한 다음 다음 3가지 API를 사용 설정합니다.
- BigQuery API
- Google Drive API
- Google Slides API
OAuth 클라이언트 보안 비밀번호 다운로드 (슬라이드 및 Drive용)
- 사용자 인증 정보 탭을 선택하고 사용자 인증 정보 만들기 버튼을 클릭한 다음 OAuth 클라이언트 ID를 선택합니다.
- 애플리케이션 유형 기타를 선택하고 이름
Google Slides API Codelab
을 입력한 다음 만들기 버튼을 클릭합니다.확인을 클릭하여 대화상자를 닫습니다. - 클라이언트 ID 오른쪽에 있는 file_download (JSON 다운로드) 버튼을 클릭합니다.
- 비밀 파일의 이름을
client_secret.json
로 변경하고 start/ 및 finish/ 디렉터리 모두에 복사합니다.
서비스 계정 비밀번호 다운로드 (BigQuery용)
- 사용자 인증 정보 탭을 선택하고 사용자 인증 정보 만들기 버튼을 클릭한 다음 서비스 계정 키를 선택합니다.
- 드롭다운에서 새 서비스 계정을 선택합니다. 서비스의 이름으로
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 Drive에서 파일을 읽고 쓰고, Google Slides에서 프레젠테이션을 만들고, Google BigQuery에서 읽기 전용 쿼리를 실행하기 위해 Google 계정에 대한 액세스를 요청합니다. (참고: 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에서 가장 인기 있는 소프트웨어 라이선스를 알아보겠습니다.
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
});
});
});
}
Promise 콜백 내의 일부 데이터를 console.log
하여 객체의 구조를 파악하고 코드가 작동하는 모습을 확인해 보세요.
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 열기
마지막으로 브라우저에서 프레젠테이션을 엽니다. 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 Slides를 생성했습니다. 스크립트는 Google Slides API와 BigQuery를 사용하여 프레젠테이션을 만들어 가장 일반적인 소프트웨어 라이선스에 대한 분석을 보고합니다.
가능한 개선사항
다음은 더욱 매력적인 통합을 위한 몇 가지 추가 아이디어입니다.
- 각 슬라이드에 이미지 추가
- Gmail API를 사용하여 이메일을 통해 슬라이드 공유
- 템플릿 슬라이드를 명령줄 인수로 맞춤설정
자세히 알아보기
- Google Slides API 개발자 문서를 읽어보세요.
- google-slides-api 태그를 사용해 Stack Overflow에 대한 질문을 게시하고 답변을 확인하세요.