Создание презентаций Google Slides на основе больших данных в Node.js

1. Обзор

В этом практическом занятии вы узнаете, как использовать Google Slides в качестве инструмента для создания презентаций, позволяющего анализировать наиболее распространенные лицензии на программное обеспечение. Вы будете запрашивать весь открытый исходный код на GitHub с помощью API BigQuery и создавать слайд-презентацию, используя API Google Slides, для представления результатов. Пример приложения создан на Node.js, но те же основные принципы применимы к любой архитектуре.

Что вы узнаете

  • Создание презентаций с помощью API Slides
  • Использование BigQuery для получения аналитических данных из большого набора данных.
  • Копирование файла с помощью API Google Drive

Что вам понадобится

  • Node.js установлен.
  • Доступ к интернету и веб-браузеру.
  • Аккаунт Google
  • Проект Google Cloud Platform

2. Получите пример кода.

Вы можете либо загрузить весь пример кода на свой компьютер...

...или клонируйте репозиторий GitHub из командной строки.

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

В репозитории содержится набор каталогов, представляющих каждый этап процесса, на случай, если вам потребуется сослаться на рабочую версию.

Вы будете работать с копией, расположенной в start директории, но при необходимости можете ссылаться на другие папки или копировать из них файлы.

3. Запустите демонстрационное приложение.

Для начала давайте запустим скрипт Node.js. После загрузки кода следуйте приведенным ниже инструкциям, чтобы установить и запустить приложение Node.js:

  1. Откройте командную строку на своем компьютере и перейдите в start каталог проекта.
  2. Введите следующую команду для установки зависимостей Node.js.
npm install
  1. Для запуска скрипта введите следующую команду:
node .
  1. Обратите внимание на приветствие, в котором показаны этапы выполнения этого проекта.
-- Start generating slides. --
TODO: Get Client Secrets
TODO: Authorize
TODO: Get Data from BigQuery
TODO: Create Slides
TODO: Open Slides
-- Finished generating slides. --

Список задач (TODO) можно найти в slides.js , license.js и auth.js Обратите внимание, что мы используем JavaScript-промисы для цепочки шагов, необходимых для завершения работы приложения, поскольку каждый шаг зависит от выполнения предыдущего.

Если вы не знакомы с промисами, не волнуйтесь, мы предоставим весь необходимый код. Вкратце, промисы позволяют обрабатывать асинхронные операции более синхронным способом.

4. Получите секреты клиентов.

Для использования API Slides, Bigquery и Drive нам потребуется создать клиент OAuth и учетную запись службы.

Настройка консоли разработчиков Google

  1. Воспользуйтесь этим мастером , чтобы создать или выбрать проект в консоли разработчиков Google и автоматически включить API. Нажмите «Продолжить» , затем «Перейти к учетным данным» .
  2. На странице «Добавить учетные данные в проект» нажмите кнопку «Отмена» .
  3. В верхней части страницы выберите вкладку «Экран согласия OAuth» . Выберите адрес электронной почты , введите название продукта Slides API Codelab и нажмите кнопку «Сохранить» .

Включите API BigQuery, Google Drive и Slides.

  1. Выберите вкладку «Панель управления» , нажмите кнопку «Включить API» и активируйте следующие 3 API:
  2. API BigQuery
  3. API Google Drive
  4. API Google Slides

Скачать секретный ключ клиента OAuth (для Slides и Drive)

  1. Выберите вкладку «Учетные данные» , нажмите кнопку «Создать учетные данные» и выберите «Идентификатор клиента OAuth» .
  2. Выберите тип приложения «Другое» , введите название Google Slides API Codelab и нажмите кнопку «Создать ». Нажмите «ОК» , чтобы закрыть появившееся диалоговое окно.
  3. Нажмите кнопку file_download (Загрузить JSON) справа от идентификатора клиента.
  4. Переименуйте файл с секретным ключом в client_secret.json и скопируйте его в каталоги start/ и finish/ .

Скачать секретный ключ учетной записи службы (для BigQuery)

  1. Выберите вкладку «Учетные данные» , нажмите кнопку «Создать учетные данные» и выберите «Ключ учетной записи службы» .
  2. В раскрывающемся списке выберите «Новая учетная запись службы» . Выберите имя Slides API Codelab Service для вашей службы. Затем нажмите «Роль» , прокрутите до раздела «BigQuery» и выберите как «BigQuery Data Viewer» , так и «BigQuery Job User» .
  3. В поле « Тип ключа» выберите JSON .
  4. Нажмите «Создать» . Файл ключа будет автоматически загружен на ваш компьютер. Нажмите «Закрыть» , чтобы закрыть появившееся диалоговое окно.
  5. Переименуйте файл с секретными данными в service_account_secret.json и скопируйте его в каталоги start/ и finish/ .

Получите секреты клиента

В start/auth.js давайте заполним метод 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));
    });
  });
}

Теперь секреты клиента загружены. Учетные данные будут переданы следующему промису. Запустите проект с помощью node . чтобы убедиться в отсутствии ошибок.

5. Создайте клиент OAuth2.

Для создания слайдов добавим аутентификацию к API Google, добавив следующий код в файл auth.js. Эта аутентификация запросит доступ к вашей учетной записи Google для чтения и записи файлов в Google Drive, создания презентаций в Google Slides и выполнения запросов только для чтения из Google BigQuery. (Примечание: мы не изменили 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. Настройка BigQuery

Изучите BigQuery (необязательно)

BigQuery позволяет за считанные секунды выполнять запросы к огромным массивам данных. Давайте воспользуемся веб-интерфейсом, прежде чем выполнять запросы программно. Если вы никогда раньше не настраивали BigQuery, следуйте инструкциям в этом кратком руководстве .

Откройте Cloud Console , чтобы просмотреть данные GitHub, доступные в BigQuery, и выполнить собственные запросы. Давайте узнаем о самых популярных лицензиях на программное обеспечение на 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

Мы только что проанализировали миллионы общедоступных репозиториев на GitHub и выяснили самые популярные лицензии. Отлично! Теперь давайте настроим выполнение того же запроса, но на этот раз программно.

Настройка BigQuery

Замените код в файле license.js . Функция bigquery.query будет возвращать промис .

лицензия **.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
      });
    });
  });
}

Попробуйте вывести в console.log некоторые данные из функции обратного вызова нашего промиса, чтобы понять структуру наших объектов и увидеть, как работает код.

7. Создайте слайды

А теперь самое интересное! Давайте создадим слайды, вызвав методы create и batchUpdate из API Slides. Наш файл должен быть заменен следующим:

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. Открыть слайды

Наконец, откроем презентацию в браузере. Обновим следующий метод в 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);
}

Запустите проект ещё раз, чтобы показать окончательный результат.

9. Поздравляем!

Вы успешно создали презентацию Google Slides на основе данных, проанализированных с помощью BigQuery. Ваш скрипт создает презентацию, используя API Google Slides и BigQuery, для анализа наиболее распространенных лицензий на программное обеспечение.

Возможные улучшения

Вот еще несколько идей для создания еще более привлекательной интеграции:

  • Добавьте изображения на каждый слайд
  • Отправляйте свои слайды по электронной почте, используя API Gmail.
  • Настройте шаблон слайда в качестве аргумента командной строки.

Узнать больше