Générer des présentations Google Slides à partir de big data dans Node.js

1. Présentation

Dans cet atelier de programmation, vous allez apprendre à utiliser Google Slides en tant qu'outil de présentation personnalisé afin d'analyser les licences logicielles les plus courantes. Vous allez interroger la totalité du code Open Source sur GitHub à l'aide de l'API BigQuery et créer une présentation à l'aide de l'API Google Slides pour présenter vos résultats. L'exemple d'application a été créé à l'aide de Node.js, mais les mêmes principes de base s'appliquent à toutes les architectures.

Points abordés

  • Création de présentations à l'aide de l'API Slides
  • Utilisation de BigQuery pour dégager des informations à partir d'un ensemble de données volumineux
  • Copie d'un fichier à l'aide de l'API Google Drive

Prérequis

  • Node.js installé
  • Accès à Internet et à un navigateur Web
  • Un compte Google
  • Un projet Google Cloud Platform

2. Obtenir l'exemple de code

Vous pouvez télécharger l'exemple de code complet sur votre ordinateur...

...ou cloner le dépôt GitHub depuis la ligne de commande.

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

Le dépôt contient un ensemble de répertoires représentant chaque étape du processus, au cas où vous auriez besoin de référencer une version fonctionnelle.

Vous allez travailler à partir de la copie située dans le répertoire start, mais vous pouvez vous référer aux autres fichiers ou les copier à partir de ces autres fichiers si nécessaire.

3. Exécuter l'application exemple

Commençons par activer le script Node. Une fois le code téléchargé, suivez les instructions ci-dessous pour installer et démarrer l'application Node.js:

  1. Ouvrez un terminal de ligne de commande sur votre ordinateur et accédez au répertoire start de l'atelier de programmation.
  2. Saisissez la commande suivante pour installer les dépendances Node.js.
npm install
  1. Saisissez la commande suivante pour exécuter le script:
node .
  1. Observez le message d'accueil qui indique les étapes à suivre pour ce projet.
-- Start generating slides. --
TODO: Get Client Secrets
TODO: Authorize
TODO: Get Data from BigQuery
TODO: Create Slides
TODO: Open Slides
-- Finished generating slides. --

Vous pouvez consulter notre liste de TODO dans slides.js, license.js et auth.js. Notez que nous utilisons des promesses JavaScript pour chaîner les étapes nécessaires à la réalisation de l'application, car chaque étape dépend de l'accomplissement de l'étape précédente.

Si vous ne connaissez pas les promesses, ne vous inquiétez pas : nous vous fournirons tout le code dont vous avez besoin. En bref, les promesses nous permettent de gérer le traitement asynchrone de manière plus synchrone.

4. Obtenir les codes secrets du client

Pour utiliser les API Slides, BigQuery et Drive, nous devons créer un client OAuth et un compte de service.

Configurer la Google Developers Console

  1. Utilisez cet assistant pour créer ou sélectionner un projet dans la Google Developers Console et activer automatiquement l'API. Cliquez sur Continuer, puis sur Accéder aux identifiants.
  2. Sur la page Add credentials to your project (Ajouter des identifiants au projet), cliquez sur le bouton Cancel (Annuler).
  3. En haut de la page, sélectionnez l'onglet OAuth consent screen (Écran d'autorisation OAuth). Sélectionnez une adresse e-mail, saisissez le nom du produit Slides API Codelab, puis cliquez sur le bouton Enregistrer.

Activer les API BigQuery, Drive et Slides

  1. Sélectionnez l'onglet Dashboard (Tableau de bord), cliquez sur le bouton Enable API (Activer l'API), puis activez les trois API suivantes:
  2. API BigQuery
  3. API Google Drive
  4. API Google Slides

Télécharger le code secret du client OAuth (pour Slides et Drive)

  1. Sélectionnez l'onglet Identifiants, cliquez sur le bouton Créer des identifiants, puis sélectionnez ID client OAuth.
  2. Sélectionnez le type d'application Autre, saisissez le nom Google Slides API Codelab, puis cliquez sur le bouton Créer.Cliquez sur OK pour fermer la boîte de dialogue qui s'affiche.
  3. Cliquez sur le bouton file_download (Télécharger au format JSON) à droite de l'ID client.
  4. Renommez votre fichier secret client_secret.json et copiez-le dans les répertoires start/ et finish/.

Télécharger le code secret du compte de service (pour BigQuery)

  1. Sélectionnez l'onglet Identifiants, cliquez sur le bouton Créer des identifiants, puis sélectionnez Clé de compte de service.
  2. Dans la liste déroulante, sélectionnez New Service Account (Nouveau compte de service). Attribuez un nom à votre service : Slides API Codelab Service. Cliquez ensuite sur Rôle et faites défiler la page jusqu'à BigQuery, puis sélectionnez Lecteur de données BigQuery et Utilisateur de tâche BigQuery.
  3. Dans le champ Type de clé, sélectionnez JSON.
  4. Cliquez sur Créer. Le fichier de clé est automatiquement téléchargé sur votre ordinateur. Cliquez sur Close (Fermer) pour quitter la boîte de dialogue qui s'affiche.
  5. Renommez votre fichier secret service_account_secret.json et copiez-le dans les répertoires start/ et finish/.

Obtenir les codes secrets du client

Dans start/auth.js, remplissons la méthode 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));
    });
  });
}

Nous avons chargé les codes secrets du client. Les identifiants seront transmis à la promesse suivante. Exécutez le projet avec node . pour vous assurer qu'il n'y a pas d'erreurs.

5. Créer un client OAuth2

Pour créer des diapositives, ajoutons l'authentification aux API Google en insérant le code suivant dans le fichier auth.js. Cette authentification demandera l'accès à votre compte Google pour lire et écrire des fichiers dans Google Drive, créer des présentations dans Google Slides et exécuter des requêtes en lecture seule à partir de Google BigQuery. (Remarque: nous n'avons pas modifié 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. Configurer BigQuery

Explorer BigQuery (facultatif)

BigQuery nous permet d'interroger des ensembles de données volumineux en quelques secondes. Utilisons l'interface Web avant d'interroger les données par programmation. Si vous n'avez jamais configuré BigQuery auparavant, suivez les étapes de ce guide de démarrage rapide.

Ouvrez la console Cloud pour parcourir les données GitHub disponibles dans BigQuery et exécuter vos propres requêtes. Découvrons les licences logicielles les plus populaires sur GitHub en écrivant cette requête et en appuyant sur le bouton Run (Exécuter).

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

Nous venons d'analyser des millions de dépôts publics sur GitHub et de découvrir les licences les plus populaires. C'est parfait ! Maintenant, configurons la même requête pour qu'elle s'exécute cette fois de manière automatisée.

Configurer BigQuery

Remplacez le code dans le fichier license.js. La fonction bigquery.query renvoie une promesse.

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

Essayez de console.log certaines des données contenues dans le rappel de notre promesse pour comprendre la structure de nos objets et voir le fonctionnement du code en action.

7. Créer des diapositives

Passons maintenant à la partie la plus intéressante : Créons des diapositives en appelant les méthodes create et batchUpdate de l'API Slides. Notre fichier doit être remplacé par le code suivant:

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. Ouvrir la présentation

Enfin, ouvrons la présentation dans le navigateur. Mettez à jour la méthode suivante dans 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);
}

Exécutez votre projet une dernière fois pour afficher le résultat final.

9. Félicitations !

Vous avez généré une présentation Google Slides à partir de données analysées à l'aide de BigQuery. Votre script crée une présentation en utilisant l'API Google Slides et BigQuery pour fournir une analyse des licences logicielles les plus courantes.

Améliorations possibles

Voici quelques idées supplémentaires pour réaliser une intégration encore plus réussie :

  • Ajouter des images à chaque diapositive
  • Partager vos diapositives par e-mail à l'aide de l'API Gmail
  • Personnaliser la diapositive du modèle en tant qu'argument de ligne de commande

En savoir plus