🍊

Pleasanter x Google 連携 : Google Form を Pleasanter で集計する

2022/12/18に公開

2022 個人アドベントカレンダー の記事です。

やること

前回 Form 作ったんで、取り込みます。

まあ基本的に列構造が合致してるはずなので CSV でインポートしたほうが早いとは思います。
そうはいっても自動化したいとかってなるんだろうなーと。

あと Form の送信イベントで発動させるので Form にスクリプトを書かないといけないです。
(ここも、あまり汎用的でないところ)

作り

  • フォーム送信時イベントで Pleasanter API にリクエストします。
    • 一括でいいなら CSV 取り込みのほうがマシなので。

Google Apps Script のコード

  • OnFormSubmit をフォーム送信のトリガーとして設定してください。
const onFormSubmit = (e) => {
  const formResponse = e.response;
  const formName = e.source.getTitle();
  const answer = formResponse
    .getItemResponses()
    .reduce(
      (a, c) => a.concat([[c.getItem().getTitle(), c.getResponse()]]),
      [],
    );
  const { siteId: id, body: dic } = getInfo(formName);
  const data = answer.reduce((a, c) => ({ ...a, [dic[c[0]]]: c[1] }), {});
  insertTo(id, data);
};

const apikey = 'Api キー';
const host = 'https://demo.pleasanter.org/api/items/';
const post = (host, id, action, body = {}) => {
  const url = `${host}/${id}/${action}`;
  const param = {
    contentType: 'application/json',
    method: 'post',
    payload: JSON.stringify({ ...body, ApiVersion: 1.1, ApiKey: apikey }),
    muteHttpExceptions: true,
  };
  const res = UrlFetchApp.fetch(url, param);
  if (res.getResponseCode() !== 200) return undefined;
  return JSON.parse(res.getContentText());
};
const getInfo = (title) => {
  const id = 7167230;
  const filter = {
    View: {
      ColumnFilterHash: {
        ClassA: title,
      },
    },
  };
  const dat = post(host, id, 'get', filter).Response?.Data;
  if (dat.length !== 1) {
    throw new Error('could not find valid site information');
  }
  const record = dat[0];
  return {
    siteId: record.NumHash.NumA,
    body: JSON.parse(record.Body).reduce(
      (a, c) => ({ ...a, [c.LabelText]: c.ColumName }),
      {},
    ),
  };
};
const insertTo = (id, data) => {
  console.log(buildData(data));
  const res = post(host, id, 'create', buildData(data));
  if (res.StatusCode !== 200) {
    throw new Error(JSON.stringify(res));
  }
  console.log(res);
};

const buildData = (data) =>
  Object.entries(data).reduce((a, [k, v]) => assignTo(a, k, v), {});
const needsHash = (str) =>
  /^(Class|Num|Description|Date|Check)[A-Z0-9]+$/.test(str);
const matchHash = (str) =>
  /^(Class|Num|Description|Date|Check)[A-Z0-9]+$/.exec(str);
const assignTo = (obj, key, value) => {
  if (!needsHash(key)) return { ...obj, [key]: value };
  const varType = matchHash(key)[1];
  if (varType === 'Date' && value === '') return { ...obj };
  if (varType === 'Class' && Array.isArray(value))
    value = JSON.stringify(value);
  const sub = { [key]: value };
  const bundleKey = `${varType}Hash`;
  return { ...obj, [bundleKey]: { ...obj[bundleKey], ...sub } };
};

詰まったところ

  • Google Form のチェックボックス

チェックボックスは Pleasanter で言うところの、「複数選択」を有効にした「分類型」だった。
→ 前回記事で手直しが必要になった。

  • Pleasanter API の動きとして、不正な JSON になるとデバッグが厳しい

Google Form での日付型は、ハイフンで区切られた表記("2022-12-18")だが、これは Pleasanter でも正しくパースできた。
一方、未入力の場合 '' になり、これは Pleasanter では受け入れられない値となる(不正な JSON として扱われる)。ので、空配列のときスキップする手当を場当たりに実施。

また、前記のとおり Google Form のチェックボックス型は、複数選択可能なので文字列の配列でデータ保持される。
これをこのまま Pleasanter に送ると不正な文字列になるので、一旦 JSON.stringify して ClassX のキーに文字列の値として引き渡さないといけない。ので、stringify を場当たりに追加。

おわりに

  • 前回とセットで Form 連携はひととおりできた。
  • 連携機能をアプリ的に作ったので ID 依存する箇所が、アプリとして使うテーブルのサイト ID のみに限定されているのが良いと考えている。
    • Form 連携したテーブルを何度も作ると、それらテーブルのサイト ID は変動してしまう。もし、これら各テーブルのスクリプトに機能を作り込んでいると Google Apps Script には、都度対応するサイト ID を指定しないといけなくなる。
  • 細かいところでは Pleasanter のタイトルが Google Form の場合、うまく生かせてないなーという印象。
    • 項目として無効化すればいいんだけれども。
GitHubで編集を提案

Discussion