8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

GMOペパボAdvent Calendar 2019

Day 12

コミュニティを支える技術

Last updated at Posted at 2019-12-12

この記事は GMOペパボ Advent Calendar 2019 12日目の記事です。

皆さん、コミュニティ活動していますか?
僕はやっています。

思い返せば僕は今年、

などなど、いろいろなコミュニティ活動に参加してきました。
今回はそんなコミュニティに運営・参加する中でこういう取り組みをしたよ!みたいなことをアウトプットできればなぁと思って記事をしたためました。
よろしければご覧ください。

ランチマップ

大きめのイベントを開催する場合、県外の方が来られることもあるかと思います。
せっかく来ていただいたからには、地元が誇る美味しいものをたくさん食べてもらいたい!そう思いますよね!

しかしながらランチマップを作るにも、色々と手がかかることは間違いありません。
マップの情報を保持しておくデータベースを用意し、APIのエンドポイントを準備し、js側でAPIのレスポンスを元にマップをレンダリングする…ちょっとマップを公開したいだけなのに、構築がとても大変ですよね。

誰でも簡単にメンテできて、お店の追加が簡単で、なおかつ使い回せる…そんなランチマップがあればいいのに!ということで作りました。

スクリーンショット

image.png
https://jasst-kyushu-lunch.tosite.work/

概略

image.png

  1. あらかじめスプレッドシートに店情報(店名・タグ・店舗位置情報)を入力しておく
  2. GASを用いてシート内のレコードをJSON形式に変換する
  3. スプレッドシートをAPIサーバ化する
    4. https://qiita.com/howdy39/items/22068b3f768f0f9a757d
    4. https://qiita.com/takatama/items/7aa1097aac453fff1d53
  4. js側で受け取ったJSONを元にマップをレンダリングする
    5. https://www.tam-tam.co.jp/tipsnote/javascript/post12851.html

🤪注意:Google側で 100秒間に500リクエスト というレートリミットが設けられています🤪

実装

GAS

function getSheetData(sheetName) {
  const sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
  const rows = sheet.getDataRange().getValues();

  var ary = [];
  rows.map(function(row, index) {
    const obj = {};
    obj["name"] = row[0];
    obj["tags"] = row[1].split(',');
    obj['url'] = row[2];
    obj['lat'] = row[3];
    obj['lng'] = row[4];
    ary.push(obj);
  });
  return ary;
}

function doGet(e) {
  const data = getSheetData('publish');
  return ContentService.createTextOutput()
    .setMimeType(ContentService.MimeType.JAVASCRIPT)
    .setContent(JSON.stringify(data));
}

GitHub
https://github.com/tosite0345/jasst18kyushu-lunch

所感

特にjs側の実装がかなり前に作ったものなので、やり変えたいなあ、と思っています。
レートリミットのこともあるのでゆくゆくはサーバ立てるほうがいいのかなぁ、という感じですね。
とは言えGASって強力だなぁ…と改めて痛感しました。
スプレッドシートに権限さえ与えておけば、コードをいじらずに誰でもランチマップの修正ができるようになったのは大きいですね。

アンケートフォーム自動生成

イベントを開催する上で大事なものは参加された皆様からのフィードバック。
生の声に耳を傾け、次回はもっといいイベントにしていきたい!誰もがそう思うことでしょう。

経験則でお話しすると、アンケートは紙のほうが回収率が高いような気がします。そのため、紙フォームは必須でした。
しかしながらGoogleフォームも使いたいと思いました。
アンケート項目は1つですが、回答フォームを別々に作るのは効率が悪いですよね。
人間なので入力間違いやレイアウト修正の手間などもありますし。

そんな課題を解決するべく、設定をスプレッドシートに書くことで紙・フォームどちらも自動で生成してくれるくんを作りました。

スクリーンショット

生成ボタン
image.png

設定シート
image.png

アンケート
image.png

フォーム
image.png

概略

image.png

実装

GAS

var folderId = 'folderId'; // e.g. https://drive.google.com/drive/folders/folderId

function getFolder(id) {
  return DriveApp.getFolderById(id);
}

// ドキュメント生成
function createDoc(body) {
  var document = DocumentApp.create("アンケート");
  var lines = "";
  var cnt = 1;
  body.forEach(function (line) {
    var text = "";
    var prefix = "";
    if (line.prefix.length !== 0) {
      prefix = line.prefix + cnt + '.';
      cnt++;
    }
    lines += "## " + prefix + line.title;
    if (line.required === true) {
      lines += " *必須";
    }
    lines += "\n";
    if (line.description.length !== 0) {
      lines += line.description + "\n";
    }
    lines += "<" + line.type + ">\n";
    if (line.option.length > 0) {
      if (line.type === 'radio') {
        line.option.forEach(function (o) {
          lines += o + " - ";
        });
        lines.split(0, -4) + "\n";
      }
      if (line.type === 'checkbox') {
        line.option.forEach(function (o) {
          lines += "" + o + " ";
        });
        lines += "□その他(\t\t";
      }
    }
    lines += "\n";
  })
  document.getBody().setText(lines);
  targetFolder = getFolder(folderId);
  
  //指定したフォルダに移動させる
  var docFile = DriveApp.getFileById(document.getId());
  targetFolder.addFile(docFile);
  DriveApp.getRootFolder().removeFile(docFile);
}

// フォーム生成
function createForm(body) {
  var form = FormApp.create("フォームアンケート");
  var targetFolder = getFolder(folderId);

  body.forEach(function(line) {
    if(line.type === 'text') {
      item = form.addTextItem();
    }
    if (line.type === 'header') {
      item = form.addSectionHeaderItem();
    }
    if(line.type === 'textarea') {
      item = form.addParagraphTextItem();
    }
    if(line.type === 'radio') {
      item = form.addMultipleChoiceItem().setChoiceValues(line.option);
    }
    if(line.type === 'checkbox') {
      item = form.addCheckboxItem().setChoiceValues(line.option).showOtherOption(true);
    }
    if (line.required === true && line.type !== 'header') {
      item.setRequired(true);
    }
    if (line.description.length !== 0) {
      item.setHelpText(line.description);
    }
    item.setTitle(line.title);
  })
 
  var formFile = DriveApp.getFileById(form.getId());
  targetFolder.addFile(formFile);  
  DriveApp.getRootFolder().removeFile(formFile);
}

function makeDocuments() {
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = spreadsheet.getSheetByName('publish');
  var count = sheet.getLastRow();
  var body = [];
  for(i = 2; i <= count; i++){    
    var prefix = sheet.getRange(i, 1).getValue();
    var title = sheet.getRange(i, 2).getValue();
    var description = sheet.getRange(i, 3).getValue();
    var type = sheet.getRange(i, 4).getValue();
    var required = sheet.getRange(i, 5).getValue();

    var lastCol = sheet.getRange(i, sheet.getMaxColumns()).getNextDataCell(SpreadsheetApp.Direction.PREVIOUS).getColumn();
    var option = [];
    if (lastCol > 5) {
      for (j = 6; j <= lastCol; j++) {
        option.push(sheet.getRange(i, j).getValue());
      }
    }
    body.push({prefix: prefix, title: title, description: description, type: type, required: required, option: option});
  }
  createDoc(body);
  createForm(body);
  Browser.msgBox("処理が完了しました。");
}

参考

所感

突貫で実装したのでまだまだ回収の余地はありそうです。
特に紙のアンケート出力を全てマークダウンにしてCSSでスタイル当てるようにすればもっと手間を削減できそうなので、暇を見つけて改修する予定です。
フォームの自動生成、用途は限られそうだけど使い勝手はいいなぁという印象でした。

アンケート結果通知

せっかく答えてもらったアンケート、すぐにでも見たい!と思うのが人情だと思います。
そこで、回答があったタイミングでSlackに通知するような仕組みを作りました。

概略

image.png

実装

var postUrl = 'https://hooks.slack.com/services/xxxx/xxxx/xxxx';

function sendToSlack(message, channel)
{
  var username = 'IKENBAKO';
  var icon = ':smirk_cat:';
  var jsonData = {
    "channel" : channel,
    "username" : username,
    "icon_emoji": icon,
    "text" : message
  };
  
  var payload = JSON.stringify(jsonData);
  var options = {
    "method" : "post",
    "contentType" : "application/json",
    "payload" : payload
  };
  UrlFetchApp.fetch(postUrl, options);
}

function onFormSubmit(e)
{
  var body = "<!here> 意見が届きましたよ〜 \n\n";
  var itemResponse = e.response.getItemResponses();
  
  for (var j = 0; j < itemResponse.length; j++){
    var formData = itemResponse[j];
    var title = formData.getItem().getTitle();
    body += `*${title}:* ` + formData.getResponse();
  }
  sendToSlack(body, "#channel");
}

所感

この辺はググれば他にもいっぱい記事が出てくるのでそちらを参考にしていただけるとよさそうです。
https://qiita.com/pchan52/items/574e930a3cc42cf7f8b9
通知が来るとテンション上がりますね。

その他構想など

  • 定期通知くん
    • GASで動くcronみたいなものを実装しようと思っています
    • リマインダーや定期通知をスプレッドシートに書くだけで実行するようにしたいと思っています
  • 議事録自動生成くん
    • 前回の議事録を自動コピーしつつ、過去の議事録をアーカイブするような何かを作ろうと思っています

終わりに

いかがでしたでしょうか?
GASやSlackなどを活用して、少ない工数でコミュニティ活動をもっとよいものにしていけるといいですね。
他にもこんな工夫をしているよ!こうすればもっといいよ!など、ご意見ありましたらお待ちしております☺️

8
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?