Generowanie prezentacji Google na podstawie big data w Node.js

1. Omówienie

Z tego Codelab dowiesz się, jak używać Prezentacji Google jako niestandardowego narzędzia do prezentacji na potrzeby analizy najpopularniejszych licencji na oprogramowanie. Za pomocą interfejsu BigQuery API będziesz wysyłać zapytania do całego kodu open source na GitHubie, a następnie tworzyć prezentację za pomocą interfejsu Google Slides API, aby prezentować wyniki. Przykładowa aplikacja została utworzona w Node.js, ale te same podstawowe zasady można zastosować w przypadku dowolnej architektury.

Czego się nauczysz

  • Tworzenie prezentacji za pomocą interfejsu Slides API
  • Korzystanie z BigQuery do uzyskiwania statystyk na podstawie dużego zbioru danych
  • Kopiowanie pliku za pomocą interfejsu Google Drive API

Czego potrzebujesz

  • Node.js jest zainstalowany.
  • dostęp do internetu i przeglądarki internetowej;
  • konto Google,
  • Projekt Google Cloud Platform

2. Pobieranie przykładowego kodu

Możesz pobrać cały przykładowy kod na komputer...

…lub sklonuj repozytorium GitHub z poziomu wiersza poleceń.

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

Repozytorium zawiera zestaw katalogów reprezentujących poszczególne etapy procesu, na wypadek gdyby trzeba było odwołać się do wersji roboczej.

Będziesz pracować na kopii znajdującej się w katalogu start, ale w razie potrzeby możesz też odwoływać się do plików z innych katalogów lub kopiować je.

3. Uruchamianie przykładowej aplikacji

Najpierw uruchom skrypt Node. Po pobraniu kodu wykonaj podane niżej instrukcje, aby zainstalować i uruchomić aplikację Node.js:

  1. Otwórz terminal na komputerze i przejdź do katalogu start.
  2. Aby zainstalować zależności Node.js, wpisz to polecenie:
npm install
  1. Aby uruchomić skrypt, wpisz to polecenie:
node .
  1. Obserwuj powitanie, które zawiera instrukcje dotyczące tego projektu.
-- Start generating slides. --
TODO: Get Client Secrets
TODO: Authorize
TODO: Get Data from BigQuery
TODO: Create Slides
TODO: Open Slides
-- Finished generating slides. --

Naszą listę zadań do wykonania znajdziesz na stronach slides.js, license.js i auth.js. Pamiętaj, że do łańcuchowania kroków potrzebnych do ukończenia aplikacji używamy obietnic JavaScriptu, ponieważ każdy krok zależy od poprzedniego.

Jeśli nie znasz obietnic, nie martw się. Udostępnimy Ci cały potrzebny kod. Krótko mówiąc, obietnice umożliwiają nam przetwarzanie asynchroniczne w sposób bardziej synchroniczny.

4. Pobieranie tajnych kluczy klienta

Aby korzystać z interfejsów API Prezentacje, BigQuery i Dysk, utworzymy klienta OAuth i konto usługi.

Konfigurowanie konsoli Google Developers

  1. Użyj tego kreatora, aby utworzyć lub wybrać projekt w Konsoli Google Developers i automatycznie włączyć interfejs API. Kliknij kolejno DalejPrzejdź do danych logowania.
  2. Na stronie Dodaj dane logowania do projektu kliknij przycisk Anuluj.
  3. U góry strony wybierz kartę Ekran zgody OAuth. Wybierz adres e-mail, wpisz nazwę produktu Slides API Codelab i kliknij przycisk Zapisz.

Włącz interfejsy BigQuery, Dysk i Prezentacje Google

  1. Kliknij kartę Panel, kliknij przycisk Włącz interfejs API i włącz te 3 interfejsy API:
  2. BigQuery API
  3. Interfejs API Dysku Google
  4. Interfejs API Google Slides

Pobieranie tajnego klucza klienta OAuth (w przypadku aplikacji Prezentacje i Dysk)

  1. Kliknij kartę Dane logowania, kliknij przycisk Utwórz dane logowania i wybierz Identyfikator klienta OAuth.
  2. Wybierz typ aplikacji Inne, wpisz nazwę Google Slides API Codelab i kliknij przycisk Utwórz.Kliknij OK, aby zamknąć okno.
  3. Kliknij przycisk file_download (Pobierz JSON) po prawej stronie identyfikatora klienta.
  4. Zmień nazwę pliku z danymi na client_secret.json i skopiuj go do katalogów start/finish/.

Pobieranie klucza tajnego konta usługi (w przypadku BigQuery)

  1. Kliknij kartę Dane logowania, kliknij przycisk Utwórz dane logowania i wybierz Klucz konta usługi.
  2. W menu kliknij Nowe konto usługi. Wybierz nazwę Slides API Codelab Service dla usługi. Następnie kliknij Rola, przewiń do sekcji BigQuery i zaznacz obie opcje: Przeglądający dane BigQueryUżytkownik zadań BigQuery.
  3. W polu Typ klucza wybierz JSON.
  4. Kliknij Utwórz. Plik klucza zostanie automatycznie pobrany na Twój komputer. Kliknij Zamknij, aby zamknąć okno.
  5. Zmień nazwę pliku z danymi na service_account_secret.json i skopiuj go do katalogów start/finish/.

Pobieranie tajnych kluczy klienta

W start/auth.js wypełnij metodę 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));
    });
  });
}

Tajne klucze klienta zostały załadowane. Dane logowania zostaną przekazane do następnego obietnica. Uruchom projekt za pomocą node ., aby sprawdzić, czy nie ma błędów.

5. Tworzenie klienta OAuth2

Aby utworzyć slajdy, dodajmy uwierzytelnianie do interfejsów Google API, dodając ten kod do pliku auth.js. Ta metoda uwierzytelniania będzie wymagać dostępu do Twojego konta Google w celu odczytu i zapisu plików na Dysku Google, tworzenia prezentacji w Prezentacjach Google oraz wykonywania zapytań tylko do odczytu z Google BigQuery. (Uwaga: nie zmieniliśmy wartości 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. Konfigurowanie BigQuery

Poznawanie BigQuery (opcjonalnie)

BigQuery umożliwia nam wysyłanie zapytań dotyczących ogromnych zbiorów danych w ciągu kilku sekund. Zanim zaczniesz wysyłać zapytania programowo, użyj interfejsu internetowego. Jeśli nie masz jeszcze skonfigurowanego BigQuery, wykonaj te czynności.

Otwórz Cloud Console, aby przeglądać dane z GitHuba dostępne w BigQuery i wykonywać własne zapytania. Aby sprawdzić najpopularniejsze licencje na oprogramowanie w GitHub, wpisz to zapytanie i kliknij przycisk Uruchom.

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

Przeanalizowaliśmy miliony publicznych repozytoriów na GitHubie i wybraliśmy najpopularniejsze licencje. Super! Skonfigurujmy teraz uruchomienie tego samego zapytania, ale tym razem programowo.

Konfigurowanie BigQuery

Zastąp kod w pliku license.js. Funkcja bigquery.query zwraca obiecankę.

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

Spróbuj console.log niektórych danych w obiekcie wywołania funkcji obietnicy, aby poznać strukturę naszych obiektów i zobaczyć, jak działa kod.

7. Tworzenie prezentacji

Teraz czas na zabawę. Utwórz slajdy, wywołując metody create i batchUpdate interfejsu Slides API. Nasz plik powinien zostać zastąpiony tym:

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. Otwórz Prezentacje

Na koniec otwórz prezentację w przeglądarce. Zaktualizuj w pliku slides.js tę metodę:

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

Uruchom projekt jeszcze raz, aby wyświetlić ostateczny wynik.

9. Gratulacje!

Udało Ci się wygenerować prezentację Google Slides na podstawie danych przeanalizowanych za pomocą BigQuery. Skrypt tworzy prezentację za pomocą interfejsu API Prezentacji Google i BigQuery, aby przekazać analizę najczęściej stosowanych licencji na oprogramowanie.

Możliwe ulepszenia

Oto kilka dodatkowych pomysłów na jeszcze bardziej atrakcyjną integrację:

  • Dodawanie obrazów do każdego slajdu
  • Udostępnianie slajdów e-mailem za pomocą interfejsu Gmail API
  • Dostosowywanie slajdu szablonu jako argumentu wiersza poleceń

Więcej informacji