Generar presentaciones de Presentaciones de Google a partir de macrodatos en Node.js

1. Descripción general

En este codelab, aprenderás a usar Presentaciones de Google como una herramienta de presentaciones personalizadas para realizar un análisis de las licencias de software más comunes. Consultarás todo el código abierto en GitHub con la API de BigQuery y crearás una presentación de diapositivas con la API de Presentaciones de Google para presentar tus resultados. La aplicación de ejemplo se compila con Node.js, pero los mismos principios básicos se aplican a cualquier arquitectura.

Qué aprenderás

  • Cómo crear presentaciones con la API de Slides
  • Cómo usar BigQuery para obtener estadísticas sobre un gran conjunto de datos
  • Cómo copiar un archivo mediante la API de Google Drive

Requisitos

  • Se instaló Node.js
  • Acceso a Internet y un navegador web
  • Una Cuenta de Google
  • Un proyecto de Google Cloud Platform

2. Obtén el código de muestra

Puedes descargar el código de muestra completo en tu computadora...

Descargar ZIP

...o clonar el repositorio de GitHub desde la línea de comandos.

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

El repositorio contiene un conjunto de directorios que representan cada paso del proceso, en caso de que necesites hacer referencia a una versión en funcionamiento.

Trabajarás con la copia ubicada en el directorio start, pero puedes consultar o copiar archivos de los otros archivos según sea necesario.

3. Ejecuta la app de muestra

Primero, ejecutemos la secuencia de comandos de Node. Una vez que hayas descargado el código, sigue las instrucciones que se indican a continuación para instalar y, luego, iniciar la aplicación de Node.js:

  1. Abre una terminal de línea de comandos en tu computadora y navega al directorio start del codelab.
  2. Ingresa el siguiente comando para instalar las dependencias de Node.js.
npm install
  1. Ingresa el siguiente comando para ejecutar la secuencia de comandos:
node .
  1. Observa el saludo que muestra los pasos de este proyecto.
-- Start generating slides. --
TODO: Get Client Secrets
TODO: Authorize
TODO: Get Data from BigQuery
TODO: Create Slides
TODO: Open Slides
-- Finished generating slides. --

Puedes ver nuestra lista de tareas pendientes en slides.js, license.js y auth.js. Ten en cuenta que usamos promesas de JavaScript para encadenar los pasos necesarios para completar la app, ya que cada paso depende de que se complete el anterior.

Si no conoces las promesas, no te preocupes: te proporcionaremos todo el código que necesitarás. En resumen, las promesas nos brindan una forma de controlar el procesamiento asíncrono de forma más síncrona.

4. Obtenga los secretos del cliente

Para usar las APIs de Presentaciones, BigQuery y Drive, crearemos un cliente de OAuth y una cuenta de servicio.

Configure Google Developers Console

  1. Usa este asistente para crear o seleccionar un proyecto en Google Developers Console y activar automáticamente la API. Haz clic en Continuar y, luego, en Ir a Credenciales.
  2. En la página Add credentials to your project, haz clic en el botón Cancel.
  3. En la parte superior de la página, seleccione la pestaña OAuth consent screen. Selecciona una Dirección de correo electrónico, ingresa el nombre del producto Slides API Codelab y haz clic en el botón Guardar.

Habilite las API de BigQuery, Drive y Slides

  1. Selecciona la pestaña Panel, haz clic en el botón Habilitar API y habilita las siguientes 3 APIs:
  2. API de BigQuery
  3. API de Google Drive
  4. API de Presentaciones de Google

Descargar el secreto del cliente de OAuth (para Presentaciones y Drive)

  1. Selecciona la pestaña Credenciales, haz clic en el botón Crear credenciales y selecciona ID de cliente de OAuth.
  2. Selecciona el tipo de aplicación Other, ingresa el nombre Google Slides API Codelab y haz clic en el botón Crear.Luego, haz clic en Aceptar para descartar el cuadro de diálogo resultante.
  3. Haz clic en el botón file_download (Descargar JSON) a la derecha del ID de cliente.
  4. Cambia el nombre de tu archivo secreto a client_secret.json y cópialo en los directorios start/ y finish/.

Descargar el secreto de la cuenta de servicio (para BigQuery)

  1. Selecciona la pestaña Credenciales, haz clic en el botón Crear credenciales y selecciona Clave de cuenta de servicio.
  2. En el menú desplegable, seleccione Nueva cuenta de servicio. Elige el nombre Slides API Codelab Service para tu servicio. Luego, haz clic en Rol, desplázate hasta BigQuery y selecciona Visualizador de datos de BigQuery y Usuario de trabajo de BigQuery.
  3. En Tipo de clave, selecciona JSON.
  4. Haz clic en Crear. El archivo de claves se descargará automáticamente en tu computadora. Haga clic en Cerrar para salir del diálogo que se muestra.
  5. Cambia el nombre de tu archivo secreto a service_account_secret.json y cópialo en los directorios start/ y finish/.

Obtenga los secretos del cliente

En start/auth.js, completaremos el método 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));
    });
  });
}

Ya cargamos los secretos del cliente. Las credenciales se pasarán a la siguiente promesa. Ejecuta el proyecto con node . para asegurarte de que no haya errores.

5. Cree un cliente de OAuth2

Para crear diapositivas, agreguemos la autenticación a las APIs de Google con el siguiente código a nuestro archivo auth.js. Esta autenticación solicitará acceso a tu Cuenta de Google para leer y escribir archivos en Google Drive, crear presentaciones en Presentaciones de Google y ejecutar consultas de solo lectura desde Google BigQuery. (Nota: No cambiamos 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. Configure BigQuery

Explora BigQuery (opcional)

BigQuery nos permite consultar enormes conjuntos de datos en segundos. Usemos la interfaz web antes de realizar consultas de manera programática. Si nunca configuraste BigQuery, sigue los pasos de esta guía de inicio rápido.

Abre la consola de Cloud para explorar los datos de GitHub disponibles en BigQuery y ejecutar tus propias consultas. Vamos a escribir esta consulta y presionar el botón Ejecutar para descubrir las licencias de software más populares en 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

Acabamos de analizar millones de repositorios públicos en GitHub y descubrimos las licencias más populares. Genial. Ahora configuremos la ejecución de la misma consulta, pero esta vez de manera programática.

Configure BigQuery

Reemplaza el código en el archivo license.js. La función bigquery.query mostrará una promesa.

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

Intenta console.log algunos de los datos dentro de la devolución de llamada de nuestra promesa para comprender la estructura de nuestros objetos y ver cómo funciona el código en acción.

7. Crear diapositivas

Aquí comienza la diversión. Para crear diapositivas, llamemos a los métodos create y batchUpdate de la API de Presentaciones. El archivo debería reemplazarse por lo siguiente:

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. Abrir Presentaciones

Por último, abramos la presentación en el navegador. Actualiza el siguiente método en 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);
}

Ejecuta tu proyecto por última vez para mostrar el resultado final.

9. ¡Felicitaciones!

Generó correctamente Presentaciones de Google a partir de los datos analizados mediante BigQuery. Su secuencia de comandos creó una presentación con la API de Google Slides y BigQuery para realizar un análisis de las licencias de software más comunes.

Posibles mejoras

Estas son algunas ideas adicionales para lograr una integración todavía más atractiva:

  • Agrega imágenes a cada diapositiva
  • Comparta sus diapositivas por correo electrónico con la API de Gmail
  • Personaliza la diapositiva de la plantilla como un argumento de la línea de comandos

Más información