Консолидация данных из гугл форм

Недавно мы семьей погрузились в мир игротек и настольных игр. С детьми играем, в целом, давно, а вот взрослыми — как-то не приходилось. Ну и плюс, появилось свободное время — было принято решение: почему не попробовать DnD.

Как следствие, через VK нашел группу в телеграме одной известной московской компании, которая эти настолки продает, а так же проводит игротеки.

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

Таких игротек в день проводят от одной до трех и они повторяются плюс-минус на еженедельной основе.

Итого максимум 15 форм.

Добрая половина вопросов в телеграм группе — есть ли еще места на игротеку и зарегистрирован ли тот или иной человек.

На вопросы отвечают админы группы — допускаю, что происходит открытие формы, или связанной с ней гугл таблицей с последующим поиском/просмотром того или иного контакта.

И вот тут я подумал — можно ли автоматизировать это всё? Ну например на какой-то периодической основе собирать все ответы из форм, где-то их консолидировать, а потом выводить в веб списком или чем-то подобным.

Предварительное ТЗ

Есть 15 форм, из них надо собирать данные, при условии, что данные могут (и будут) удаляться из формы — кто-то не может приехать, кто был ранее зарегистрирован или по факту окончания недели — надо очищать всю форму ради новой регистрации.

Собранные данные надо где-то хранить и обновлять.

Для вывода в веб данные надо изменять — например выводить не весь номер телефона, а только последние 4 цифры

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

Формы не должны быть связаны с таблицами, а если и да — то данные по игрокам должны забираться напрямую из формы.

Разработка типовой формы

Форма будет иметь заголовок в котором отражается дата и название игротеки.

Форма будет в себе иметь поля: Имя, email, телефон, согласие на обработку персональных данных.

google forms template

Так как таких форм может быть сколь угодно, формы будут храниться в специальной папке:

папка с формами

Для тестового примера была взята одна игра и размножена 16 раз. Был изменен заголовок в форме, чтобы в дальнейшем отличать из какой именно формы приходит ответ.

Изначально была идея, что в форме будет скрипт, который будет запускаться при отправке формы, но потом пришлось отказаться от этой идеи — лучше отслеживать работу одного скрипта, чем, пусть и однотипных, но 15+.

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

Скрипт — получение всех id форм

Чтобы выиграть в лотерею сначала надо купить лотерейный билетик. Чтобы собирать данных из форм, надо знать сколько таких форм вообще есть.

const formType = "application/vnd.google-apps.form" // искомый тип файла, если в папке с формами будут и другие файлы, брать только этот

const consoTableId = "GSheetID" // Id таблицы где хранятся все данные
const folderId = "GFolderID" // Id папки, где лежат все формы

function getAllFormsIDInFolder() {

  const currentFolder = DriveApp.getFolderById(folderId)
  const allFilesInFolderArr = []

  const allFiles = currentFolder.getFiles();

  while (allFiles.hasNext()) {
    let file = allFiles.next();
    if (file.getMimeType() == formType) {
      allFilesInFolderArr.push(file.getId())
    }
  }

  return allFilesInFolderArr;
}

Так как теоретически в папке с формами может лежать что угодно еще — нужно получать id только форм. Понять что за тип файла помогает file.getMimeType(), где у гул форм возвращается — application/vnd.google-apps.form.

Именно с ним идет сравнение и если MimeType совпал — берется Id файла и заносится в массив:

все файлы с id гугл форм

Скрипт — получение всех ответов из всех форм

function getFormResponses() {
  const allForResponsesArr = []

  const formsID = getAllFormsIDInFolder()

  for (let formID of formsID) {

    let form = FormApp.openById(formID);
    let formResponses = form.getResponses();
    let formTitle = form.getTitle()

    let responseArr = []
    let itemsArr = []

    for (let formResponse of formResponses) {
      itemsArr.push(formTitle)

      const itemResponses = formResponse.getItemResponses();
      for (let itemResponse of itemResponses) {
        itemsArr.push(itemResponse.getResponse())
      }
      responseArr.push(itemsArr)
      itemsArr = []
    }
    allForResponsesArr.push(responseArr)
  }
  
  return (allForResponsesArr.flat())
}

let form = FormApp.openById(formID); — открывается каждая форма

const itemResponses = formResponse.getItemResponses(); — получаю все актуальные на момент сбора данных ответы из формы для дальнейшего занесения данных в массив.

Чтобы отличать формы используется название в ФОРМЕ let formTitle = form.getTitle()

Результат:

В среднем каждая форма обрабатывается до 1 секунды, общее время обработки всех форм колеблется от 8 до 15 секунд в зависимости от воли гугл серверов.

Скрипт — текущая БД

Для хранения собранных выше данных используется гугл таблица

// Передача всех ответов в файл гугл таблицы
function setCurrentDB() {
  const table = SpreadsheetApp.openById(consoTableId);
  const ws = table.getSheets()[0]
  const allPeople = getFormResponses();

  if (allPeople.length > 0) {
    ws.clear()
    ws.getRange(1, 1, allPeople.length, allPeople[0].length).setValues(allPeople)
  }
}

Со следующим результатом:

Где первый столбец — это название игротеки в форме

Второй — email зарегистрированного

Третий -Имя

Четвертый — Телефон

Пятый — согласие на обработку данных.

Ну, что, в целом все пользователи есть, можно их выводить в интернет, слегка причесав в целях сохранности персональных данных, а именно телефона.

doGet() — куда же без него

// doGet на API с обрезанными данными телефона
function doGet() {
  const allPeople = SpreadsheetApp.openById(consoTableId).getSheets()[0].getDataRange().getValues()
  const webArr = []

  for (let elem of allPeople) {
    let lastFourDigits = elem[3].toString().replace(/\s/g, '')
    webArr.push([elem[0].toString(), elem[2].toString(), lastFourDigits.substring(lastFourDigits.length - 4)])
  }

  const contService = ContentService.createTextOutput((JSON.stringify(webArr)))
  contService.setMimeType(ContentService.MimeType.JSON)

  return contService
}

let lastFourDigits = elem[3].toString().replace(/\s/g, '') — убираются возможные пробелы из телефона

lastFourDigits.substring(lastFourDigits.length - 4) — оставляются последние 4 цифры телефона

Для постоянных читателей этого блога ничего нового не будет. При развернутом webapp наружу выдается массив «причесанных» данных:

json api

Фронтенд

Как быстро и просто решить проблему предоставления данных пользователю?

Конечно через Vue для обработки данных и Bootstrap — для внешнего лоска.

Полный код html-css-js можно посмотреть здесь — https://codepen.io/DmitriiBeattle/full/dyQKoVj

игротека 1
игротека 2

При этом список выпадающего меню зависит от активных игротек и в случае изменения названия или дополнения новыми формами — так же будет меняться (именно для этого и используется title в форме):

список игротек

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

Ну и напоследок: для обновления данных в таблице необходимо использовать триггеры по времени, например запускать каждые 10 минут.

В результате получаем информационный сайт на котором видны все игротеки текущей недели с актуальными регистрациями.


Ваше мнение важно и может улучшить блог

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

У этой записи 2 комментариев

  1. Андрей

    Добрый день! Подскажите, возможно управлять данными в гугл форме при ее заполнении. Например, при выборе в поле ФИО вывести в следующее поле список данных относящийся только к этому ФИО?

    1. Андрей, добрый день.
      Непосредственно с гугл формами не сталкивался с такой задачей, делал подобное на чистом вебе (веб апп) через vue.js

Добавить комментарий