Genera presentazioni Google da big data in Node.js

1. Panoramica

In questo codelab, imparerai a utilizzare Presentazioni Google come strumento di presentazione personalizzata per un'analisi delle licenze software più comuni. Eseguirai query su tutto il codice open source su GitHub utilizzando l'API BigQuery e creerai una presentazione utilizzando l'API Presentazioni Google per presentare i tuoi risultati. L'applicazione di esempio è stata creata utilizzando Node.js, ma gli stessi principi di base sono applicabili a qualsiasi architettura.

Obiettivi didattici

  • Creazione di presentazioni utilizzando l'API Presentazioni
  • Utilizzo di BigQuery per ottenere insight su un set di dati di grandi dimensioni
  • Copiare un file utilizzando l'API Google Drive

Che cosa ti serve

  • Node.js installato
  • Accesso a internet e a un browser web
  • Un Account Google
  • Un progetto Google Cloud

2. recupera il codice campione

Puoi scaricare tutto il codice campione sul tuo computer...

...o clona il repository GitHub dalla riga di comando.

git clone https://github.com/googleworkspace/slides-api.git

Il repository contiene un insieme di directory che rappresentano ogni passaggio del processo, nel caso in cui tu debba fare riferimento a una versione funzionante.

Utilizzerai la copia che si trova nella directory start, ma puoi fare riferimento o copiare file da queste altre risorse in base alle tue esigenze.

3. Esegui l'app di esempio

Innanzitutto, configuriamo lo script Node. Una volta scaricato il codice, segui le istruzioni riportate di seguito per installare e avviare l'applicazione Node.js:

  1. Apri un terminale a riga di comando sul tuo computer e vai alla directory start del codelab.
  2. Inserisci il comando seguente per installare le dipendenze Node.js.
npm install
  1. Inserisci il seguente comando per eseguire lo script:
node .
  1. Osserva il saluto che mostra i passaggi per questo progetto.
-- Start generating slides. --
TODO: Get Client Secrets
TODO: Authorize
TODO: Get Data from BigQuery
TODO: Create Slides
TODO: Open Slides
-- Finished generating slides. --

Puoi vedere il nostro elenco di cose da fare in slides.js, license.js e auth.js. Tieni presente che utilizziamo le promesse di JavaScript per collegare i passaggi necessari per completare l'app, poiché ogni passaggio dipende dal passaggio precedente completato.

Se non hai dimestichezza con le promesse, non preoccuparti: ti forniremo tutto il codice necessario. In breve, le promesse ci permettono di gestire l'elaborazione asincrona in modo più sincrono.

4. Ottieni client secret

Per utilizzare le API Presentazioni, BigQuery e Drive, creeremo un client OAuth e un account di servizio.

Configura Google Developers Console

  1. Usa questa procedura guidata per creare o selezionare un progetto in Google Developers Console e attivare automaticamente l'API. Fai clic su Continua, quindi su Vai alle credenziali.
  2. Nella pagina Aggiungi credenziali al progetto, fai clic sul pulsante Annulla.
  3. Nella parte superiore della pagina, seleziona la scheda Schermata consenso OAuth. Seleziona un Indirizzo email, inserisci il nome del prodotto Slides API Codelab e fai clic sul pulsante Salva.

Abilita le API BigQuery, Drive e Presentazioni

  1. Seleziona la scheda Dashboard, fai clic sul pulsante Abilita API e abilita le seguenti tre API:
  2. API BigQuery
  3. API Google Drive
  4. API Presentazioni Google

Scarica client secret OAuth (per Presentazioni e Drive)

  1. Seleziona la scheda Credenziali, fai clic sul pulsante Crea credenziali e seleziona ID client OAuth.
  2. Seleziona il tipo di applicazione Altro, inserisci il nome Google Slides API Codelab e fai clic sul pulsante Crea.Fai clic su OK per chiudere la finestra di dialogo visualizzata.
  3. Fai clic sul pulsante file_download (Scarica JSON) a destra dell'ID client.
  4. Rinomina il file secret in client_secret.json e copialo in entrambe le directory start/ e finish/.

Scarica il secret dell'account di servizio (per BigQuery)

  1. Seleziona la scheda Credenziali, fai clic sul pulsante Crea credenziali e seleziona Chiave account di servizio.
  2. Nel menu a discesa, seleziona Nuovo account di servizio. Scegli il nome Slides API Codelab Service per il servizio. Quindi, fai clic su Ruolo e scorri fino a BigQuery e seleziona sia Visualizzatore dati BigQuery sia Utente job BigQuery.
  3. In Tipo di chiave, seleziona JSON.
  4. Fai clic su Crea. Il file della chiave verrà scaricato automaticamente sul computer. Fai clic su Chiudi per uscire dalla finestra di dialogo visualizzata.
  5. Rinomina il file secret in service_account_secret.json e copialo in entrambe le directory start/ e finish/.

Ottieni client secret

In start/auth.js, compiliamo il metodo 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));
    });
  });
}

Ora i client secret sono stati caricati. Le credenziali verranno passate alla promessa successiva. Esegui il progetto con node . per assicurarti che non ci siano errori.

5. Crea un client OAuth2

Per creare slide, aggiungiamo l'autenticazione alle API di Google aggiungendo il codice seguente al file auth.js. Questa autenticazione richiederà l'accesso al tuo Account Google per leggere e scrivere file su Google Drive, creare presentazioni in Presentazioni Google ed eseguire query di sola lettura da Google BigQuery. (Nota: non abbiamo modificato 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. Configura BigQuery

(Facoltativo) Esplora BigQuery

BigQuery ci consente di eseguire query su enormi set di dati in pochi secondi. Prima di eseguire query in modo programmatico, utilizziamo l'interfaccia web. Se non hai mai configurato BigQuery, segui i passaggi descritti in questa guida rapida.

Apri la console Cloud per sfogliare i dati GitHub disponibili in BigQuery ed eseguire le tue query. Scopriamo le licenze software più popolari su GitHub scrivendo questa query e premendo il pulsante Esegui.

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

Abbiamo appena analizzato milioni di repository pubblici su GitHub e abbiamo individuato le licenze più popolari. Interessante! Ora configuriamo l'esecuzione della stessa query, ma questa volta in modo programmatico.

Configura BigQuery

Sostituisci il codice nel file license.js. La funzione bigquery.query restituirà una promessa.

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
      });
    });
  });
}

Prova a console.log alcuni dei dati all'interno del callback di Promise per comprendere la struttura degli oggetti e vedere il funzionamento del codice in azione.

7. Creazione slide

E ora la parte divertente. Creiamo slide chiamando i metodi create e batchUpdate dell'API Presentazioni. Il nostro file dovrebbe essere sostituito con il seguente:

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. Apri Presentazioni

Infine, apriamo la presentazione nel browser. Aggiorna il seguente metodo in 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);
}

Esegui il progetto un'ultima volta per mostrare il risultato finale.

9. Complimenti!

Hai generato Presentazioni Google a partire dai dati analizzati utilizzando BigQuery. Lo script crea una presentazione utilizzando l'API Presentazioni Google e BigQuery per segnalare un'analisi delle licenze software più comuni.

Possibili miglioramenti

Di seguito sono riportate alcune idee aggiuntive per realizzare un'integrazione ancora più convincente:

  • Aggiungi immagini a ogni slide
  • Condividi le tue slide via email utilizzando l'API Gmail
  • Personalizza la slide del modello come argomento della riga di comando

Scopri di più