Genera presentazioni Google da big data in Node.js

1. Panoramica

In questo codelab imparerai a utilizzare Presentazioni Google come strumento di presentazione personalizzato 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 Google Presentazioni per presentare i risultati. L'applicazione di esempio è creata utilizzando Node.js, ma gli stessi principi di base sono applicabili a qualsiasi architettura.

Obiettivi didattici

  • Creare presentazioni utilizzando l'API Slides
  • Utilizzare BigQuery per ottenere informazioni 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 di esempio sul computer…

...oppure 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.

Lavorerai sulla copia nella directory start, ma puoi fare riferimento alle altre o copiare file da queste, se necessario.

3. Esegui l'app di esempio

Per prima cosa, avvia lo script Node. Dopo aver 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 computer e vai alla directory start del codelab.
  2. Inserisci il seguente comando per installare le dipendenze Node.js.
npm install
  1. Inserisci il seguente comando per eseguire lo script:
node .
  1. Leggi il messaggio di 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 consultare il nostro elenco di elementi da fare in slides.js, license.js e auth.js. Tieni presente che utilizziamo le Promise di JavaScript per collegare i passaggi necessari per completare l'app, poiché ogni passaggio dipende dal completamento del passaggio precedente.

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

4. Ottenere i client secret

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

Configurare Google Developers Console

  1. Utilizza 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 le credenziali al progetto, fai clic sul pulsante Annulla.
  3. Nella parte superiore della pagina, seleziona la scheda Schermata di 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 Attiva API e abilita le tre API seguenti:
  2. API BigQuery
  3. API Google Drive
  4. API Google Slides

Scarica il 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 segreto in client_secret.json e copialo nelle directory start/ e finish/.

Scarica il segreto 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. Poi fai clic su Ruolo, 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 tuo computer. Fai clic su Chiudi per uscire dalla finestra di dialogo visualizzata.
  5. Rinomina il file segreto in service_account_secret.json e copialo nelle directory start/ e finish/.

Ottenere i 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 abbiamo caricato i client secret. Le credenziali verranno passate alla promessa successiva. Esegui il progetto con node . per assicurarti che non siano presenti errori.

5. Creare un client OAuth2

Per creare le diapositive, aggiungiamo l'autenticazione alle API Google aggiungendo il seguente codice al file auth.js. Questa autenticazione richiede l'accesso al tuo Account Google per leggere e scrivere file in 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. Configurare BigQuery

Esplora BigQuery (facoltativo)

BigQuery ci consente di eseguire query su set di dati di grandi dimensioni in pochi secondi. Utilizziamo l'interfaccia web prima di eseguire query in modo programmatico. Se non hai mai configurato BigQuery, segui i passaggi descritti in questa guida introduttiva.

Apri la console Cloud per sfogliare i dati di GitHub disponibili in BigQuery ed eseguire le tue query. Scopri le licenze software più utilizzate 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 scoperto le licenze più utilizzate. Interessante! Ora impostiamo l'esecuzione della stessa query, ma questa volta in modo programmatico.

Configurare BigQuery

Sostituisci il codice nel file license.js. La funzione bigquery.query restituisce 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 della nostra promessa per comprendere la struttura dei nostri oggetti e vedere il codice in azione.

7. Creazione slide

E ora la parte divertente. Creiamo delle diapositive chiamando i metodi create e batchUpdate dell'API Slides. Il nostro file deve essere sostituito con quanto segue:

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, apri 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 visualizzare il risultato finale.

9. Complimenti!

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

Possibili miglioramenti

Ecco alcune idee aggiuntive per creare un'integrazione ancora più efficace:

  • Aggiungere immagini a ogni diapositiva
  • Condividere le diapositive via email utilizzando l'API Gmail
  • Personalizzare la diapositiva del modello come argomento della riga di comando

Scopri di più