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 con 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

  • Node.js instalado
  • 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

Presiona el siguiente botón para 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 consultar una versión en funcionamiento.

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

3. Ejecuta la app de muestra

Primero, ejecutemos la secuencia de comandos de Node. Con el código descargado, sigue las instrucciones a continuación para instalar y dar inicio a la aplicación 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 estás familiarizado con las promesas, no te preocupes, te proporcionaremos todo el código que necesites. En resumen, las promesas nos dan una manera de manejar el procesamiento asíncrono de una manera más síncrona.

4. Obtenga los secretos del cliente

Para usar las APIs de Slides, 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 Play Console y activar automáticamente la API. Haz clic en Continuar y, luego, en Ir a Credenciales.
  2. En la página Agregar credenciales a tu proyecto, haz clic en el botón Cancelar.
  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 Dashboard, haz clic en el botón Enable API y habilita las siguientes 3 APIs:
  2. API de BigQuery
  3. API de Google Drive
  4. API de Presentaciones de Google

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

  1. Selecciona la pestaña Credentials, haz clic en el botón Create credentials y selecciona OAuth client ID.
  2. Selecciona el tipo de aplicación Otro, ingresa el nombre Google Slides API Codelab y haz clic en el botón Crear.Haz clic en Aceptar para descartar el diálogo resultante.
  3. Haz clic en el botón file_download (Download 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/.

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

  1. Selecciona la pestaña Credentials, haz clic en el botón Create credentials y selecciona Service account key.
  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 clave 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, completemos 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. Para esto, ingrese el siguiente código en 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 consultar de forma 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. Escribe esta consulta y presiona el botón Run para averiguar cuáles son 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 encontramos 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 del 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 el trabajo del código en acción.

7. Crear diapositivas

Aquí comienza la diversión. Para crear diapositivas, llama a los métodos create y batchUpdate de la API de Presentaciones. Nuestro archivo debe 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
  • Cómo compartir tus diapositivas por correo electrónico con la API de Gmail
  • Personaliza la diapositiva de la plantilla como un argumento de línea de comandos

Más información