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 de proposer une analyse des licences logicielles les plus courantes. Vous allez exécuter des requêtes sur la totalité du code Open Source sur GitHub à l'aide de l'API BigQuery, puis créer une présentation au moyen de l'API Google Slides pour communiquer vos résultats. L'exemple d'application fourni dans cet atelier a été créé avec Node.js, mais les principes de base sont les mêmes pour 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'intégralité de l'exemple de code sur votre ordinateur…

Vous pouvez également cloner le dépôt GitHub à partir de 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 vous reporter à une version de travail.

Vous travaillerez à partir de la copie située dans le répertoire start, mais vous pourrez faire référence aux fichiers des autres répertoires ou copier ces fichiers selon vos besoins.

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 de 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 tâches à faire dans slides.js, license.js et auth.js. Notez que nous utilisons des promesses JavaScript pour chaîner les étapes nécessaires à l'exécution de l'application, car chaque étape dépend de l'achèvement de celle qui la précède.

Si vous ne connaissez pas les promesses, ne vous inquiétez pas. Nous vous fournirons tout le code dont vous aurez 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 allons 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 Continue (Continuer), puis sur Go to credentials (Accéder à 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 Credentials (Identifiants), cliquez sur le bouton Create credentials (Créer des identifiants) et sélectionnez OAuth client ID (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 JSON) à droite de l'ID client.
  4. Renommez votre fichier de secret en client_secret.json, puis copiez-le dans les répertoires start/ et finish/.

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

  1. Sélectionnez l'onglet Credentials (Identifiants), cliquez sur le bouton Create credentials (Créer des identifiants) et sélectionnez Service account key (Clé de compte de service).
  2. Dans la liste déroulante, sélectionnez New Service Account (Nouveau compte de service). Choisissez le nom Slides API Codelab Service pour votre service. Cliquez ensuite sur Rôle, faites défiler la page jusqu'à BigQuery, puis sélectionnez Lecteur de données BigQuery et Utilisateur de tâche BigQuery.
  3. Pour 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 de secret en service_account_secret.json, puis 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 maintenant chargé les codes secrets du client. Les identifiants seront transmis à la promesse suivante. Exécutez le projet avec node . pour vous assurer qu'il ne comporte aucune erreur.

5. Créer un client OAuth2

Pour créer des diapositives, ajoutons une authentification aux API Google en insérant le code suivant dans le fichier auth.js. Cette authentification demande 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 permet d'interroger des ensembles de données volumineux en quelques secondes. Utilisons l'interface Web avant d'exécuter des requêtes de manière automatisée. Si vous n'avez jamais configuré BigQuery, 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 rédigeant 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 du 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 d'console.log certaines données figurant dans le rappel de la promesse afin de comprendre la structure des objets et d'examiner le 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 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. Modifiez 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