🍊
Pleasanter x Google 連携:スプレッドシートからデータを書き込む(削除編)
- 2022 個人アドベントカレンダー の記事です。
- 当日中に公開できませんでした
ゴール
-
Google SpreadSheet で編集した内容を Pleasanter に適用する
-
力及ばず、今回は削除のみ対応
- チェックボックスを上手くだせなかったので、削除列には
1
などを入れる想定
- チェックボックスを上手くだせなかったので、削除列には
-
読み取りについての前の記事を下敷にしています(コード.gs の部分)
コード
- 使いかたとして
main
を実行して、一旦シートに取り込まれている状態で、削除列に1
とかくとデータが消されます。 - スプレッドシートのデータは消していません。
- ここでは、最新のデータは、都度取り出す想定のためです。
コード.gs
読み取りの記事からの変更点として、
- 削除列を追加
- post 関数で、インプット用パラメータを受けられるようにした(今後の新規・編集を見据えての対応)
const apikey = ''; //apikey を記入します
const host = 'https://demo.pleasanter.org';
const siteId = 123456; //サイト ID を記入します
const main = () => {
writeToSheet(post(host, 'records', 'get', siteId));
};
/**
* エンドポイントを決定するためのヘルパ
* @param control:string // records データレコード, site サイト, users ユーザ, depts 組織, groups グループ
* @param act:string // get delete update create
* @param id:number // (レコードおよびサイトを操作するとき)親の id
* */
const controlDefiner = (control, act, id) => {
const controlName = normalizeControl(control, act, id);
const actionName = normalizeAction(control, act);
if (controlName === '' || actionName === '')
throw new Error(`${control} ${act} is not implemented`);
return { controller: controlName, action: actionName };
};
const normalizeControl = (control, act, id) => {
switch (control) {
case 'records':
case 'record':
case 'site':
case 'table':
if (act[0] === 'u' || act[0] === 'd') {
return 'items';
}
return `items/${id}`;
case 'users':
case 'user':
return 'users';
case 'depts':
case 'dept':
return 'depts';
case 'groups':
case 'group':
return 'groups;';
default:
return '';
}
};
const normalizeAction = (target, action) => {
switch (target) {
case 'site':
case 'table':
return `${action}site`;
case 'records':
case 'record':
case 'users':
case 'user':
case 'depts':
case 'dept':
case 'groups':
case 'group':
return action;
default:
return '';
}
};
const urlBuilder = (host, control, act, id) => {
const { controller, action } = controlDefiner(control, act, id);
switch (action[0]) {
case 'u':
case 'd':
return `${host}/api/${controller}/${id}/${action}`;
case 'c':
case 'g':
default:
return `${host}/api/${controller}/${action}`;
}
};
//
// データを取得。ページンングなし
const post = (host, control, act, id, body = {}) => {
const param = {
contentType: 'application/json',
method: 'post',
payload: JSON.stringify({ ...body, ApiVersion: 1.1, ApiKey: apikey }),
muteHttpExceptions: true,
};
const url = urlBuilder(host, control, act, id);
const res = UrlFetchApp.fetch(url, param);
if (res.getResponseCode() !== 200) return [];
return JSON.parse(res.getContentText()).Response?.Data;
};
// 階層化しているデータ項目を展開する。添付は除外
const flatten = (data, parent = {}) =>
Object.entries(data).reduce(
(a, c) =>
c[0].startsWith('Attachments')
? a
: c[0].endsWith('Hash')
? flatten(c[1], a)
: { ...a, [c[0]]: c[1] },
parent,
);
// データ項目からヘッダ行を作る
const headerFromFirst = (data) => Object.keys(data[0]);
const objectToArray = (data, appendCheck = '削除') => {
const header =
appendCheck === undefined
? headerFromFirst(data)
: [appendCheck].concat(headerFromFirst(data));
return [header].concat(data.map((r) => header.map((c) => r[c])));
};
// データをシートに書き込む
const writeToSheet = (data, sheetName = 'シート1') => {
const sheet =
sheetName === undefined
? SpreadsheetApp.getActiveSheet()
: SpreadsheetApp.getActive().getSheetByName(sheetName);
if (sheet === undefined || sheet === null) {
console.error(`no sheet ${sheetName}`);
return;
}
if (data.length < 1) return;
sheet.clearContents();
const rows = data.map(flatten);
const values = objectToArray(rows);
sheet.getRange(1, 1, values.length, values[0].length).setValues(values);
};
OnEdit.gs
- OnEdit をトリガーとしてインストールします
- シンプルトリガーだと fetch ができないためです
const onEdit = (e) => {
if (e.authMode.toString() === 'LIMITED') return;
const sheet = e.range.getSheet();
const data = sheet.getDataRange().getValues();
const header = data[0];
const idColName = header.includes('ResultId') ? 'ResultId' : 'IssueId';
const idCol = header.findIndex((e) => e === idColName);
const colNums = range(e.range.getColumn() - 1, e.range.getWidth());
if (!colNums.includes(idCol)) {
colNums.push(idCol);
}
const rowNums = range(e.range.getRow() - 1, e.range.getHeight());
if (!rowNums.includes(0)) {
rowNums.push(0);
}
const edited = data
.filter((_, i) => rowNums.includes(i))
.map((r) => r.filter((_, i) => colNums.includes(i)));
handler(edited, idColName);
};
const handler = (data, idColName) => {
const idCol = data[0].findIndex((e) => e === idColName);
handleDelete(filterDelete(data, idCol));
};
const filterDelete = (data, idCol) => {
const idxDel = data[0].findIndex((e) => e === '削除');
if (idxDel === -1) return [];
return data.slice(1).map((e) => e[idCol]);
};
const handleDelete = (ids) => {
ids.forEach((e) => post(host, 'records', 'delete', e));
};
const range = (start, len) => Array.from({ length: len }, (_, i) => start + i);
さいごに
ひとまず、今回はこれまで。
引き続き、更新と新規作成を対応予定です。
Discussion