2016年5月8日、今年最初のkintoneビッグアップデートがやってきました(2016/05/08 の定期メンテナンスにおけるkintone API、User API更新情報)。例年夏季にビッグアップデートが来ますが、今年は早めの仕掛けでした。どれも目玉であろうアップデート内容ですが、まずは「レコード内コメント」を試してみたいと思います。
レコードバックアップアプリ
既にcybozu.com developer networkのTipsで「レコードのコメント情報をCSVでダウンロードする方法」が公開されており、やや似通った内容にはなりますが、ここではアプリIDを指定してレコード単位でコメントをサブテーブルにバックアップするシナリオを想定しました。次のような「コメントバックアップアプリ」を作成します。
| フィールド名 | フィールドコード(フィールドタイプ) | 利用目的 |
|---|---|---|
| アプリID | アプリID(数値) | バックアップ元のアプリID |
| コメントレコードID | コメントレコードID(数値) | バックアップ元のレコードID |
| コメント投稿者 | レコード内コメント一覧/コメント投稿者(サブテーブル/ユーザー選択) | コメントの投稿者 |
| コメント日時 | レコード内コメント一覧/コメント日時(サブテーブル/日時) | コメントの投稿日時 |
| コメント | レコード内コメント一覧/コメント(サブテーブル/文字列複数行) | コメント |
アプリIDを指定して一括バックアップ
アプリの動作
レコード一覧画面に設置した「コメントバックアップ」ボタンをクリックで、対象アプリのコメントをレコード単位でサブテーブルにバックアップします。

JavaScriptコーディング
/*
*
* comment.js
*
* Dependencies:
* Dependencies:
* jQuery( https://js.cybozu.com/jquery/1.11.3/jquery.min.js )
* SweetAlert( https://js.cybozu.com/sweetalert/v1.1.0/sweetalert.min.js / https://js.cybozu.com/sweetalert/v1.1.0/sweetalert.css)
* bluebird.js
* 51-us-default.css
*/
jQuery.noConflict();
(function($) {
"use strict";
kintone.events.on(['app.record.index.show'], function(event) {
// ボタン設置
$(kintone.app.getHeaderMenuSpaceElement()).append(
$('<button>').addClass('kintoneplugin-button-normal')
.prop({
id: 'joyzo-button'
})
.text('コメントバックアップ')
);
// ボタンクリックイベント
$('#joyzo-button').click(function(){
// バックアップ対象のアプリIDを入力してコメントをバックアップ
swal({
title: 'アプリID入力',
text: 'バックアップ対象アプリIDを入力してください',
type: 'input',
showCancelButton: true,
closeOnConfirm: false,
showLoaderOnConfirm: true,
}, function(inputValue){
copyComments(inputValue).then(function(resp){
swal({
title: 'バックアップ完了',
text: '',
type: 'success'
}, function() {
location.reload(true);
});
}).catch(function(e){
swal({
title: 'バックアップ失敗',
text: JSON.stringify(e),
type: 'error'
}, function() {
return;
});
console.log(e);
});
});
});
return event;
});
// レコードID毎にレコード内コメントをレコードとして登録する関数
function copyComments(src_app_id){
return fetchRecords(src_app_id, '', 500, 0, ['$id']).then(function(get_records){
return Promise.mapSeries(get_records.records, function(rec) {
return _copyComments(src_app_id, rec['$id'].value);
});
});
}
// バックアップ元アプリのレコード内コメントをこのアプリのレコードとして登録する関数
function _copyComments(src_app_id, rec_id){
return fetchComments(src_app_id, rec_id, 0).then(function(comments){
if(comments.length === 0){ // コメント0件のレコードはスルー
return kintone.Promise.resolve({});
}
var subtable = []; // レコード内コメント用サブテーブル
for(var i = 0; i < comments.length; i++){
var comment = comments[i];
var row = {
'value': {
'コメント投稿者':{
'value': [{
code: comment.creator.code
}]
},
'コメント日時':{
'value': comment.createdAt
},
'コメント':{
'value': comment.text
}
}
};
subtable.push(row);
}
// 登録レコード
var post_record = {
'アプリID': {
'value': src_app_id
},
'コメントレコードID': {
'value': rec_id
},
'レコード内コメント一覧': {
'value': subtable
}
};
// レコード登録
return kintone.api(kintone.api.url('/k/v1/record', true), 'POST', {
app: kintone.app.getId(),
record: post_record
});
});
}
// レコード内コメント全件取得する関数
function fetchComments(app_id, rec_id, opt_offset, opt_comments) {
var offset = opt_offset || 0;
var body = {
"app": app_id,
"record": rec_id,
"offset": offset
};
var comments = opt_comments || [];
return kintone.api(kintone.api.url('/k/v1/record/comments', true), 'GET', body).then(function(resp) {
comments = comments.concat(resp.comments);
if (resp.older === true) {
return fetchComments(app_id, rec_id, offset + 10, comments);
}
return comments;
});
}
// レコード全件取得する関数
function fetchRecords(appId, condition, lmt, ofs, fields, data) {
var limit_num = (lmt === undefined) ? 500 : lmt;
var limit = ' limit ' + limit_num;
var offset = (ofs === undefined) ? '' : ' offset ' + ofs;
if (!Array.isArray(fields)) {
fields = [];
data = callback;
callback = fields;
}
var query = condition + limit + offset;
if (!data) {
var data = {
records: []
};
}
var params = {
app: appId,
query: query
};
if (fields.length > 0) {
params.fields = fields;
}
return kintone.api(kintone.api.url('/k/v1/records', true), 'GET', params).then(function(resp) {
data.records = data.records.concat(resp.records);
if (resp.records.length === limit_num) {
ofs = parseInt(ofs) + resp.records.length;
return fetchRecords(appId, condition, limit_num, ofs, fields, data);
}
return data;
});
}
})(jQuery);
ボタンクリック後の主な動きとしては、
①対象アプリのレコードIDの全件取得
②レコードID毎に、コメントを全件取得して(コメントをサブテーブルに格納して)レコード登録
といった感じです。
PromiseのPolyfillで結構有名なbluebirdのmapSeriesを利用しているちょっとしたポイントがありますが、今回は本題ではないためその説明は公式ドキュメントにお任せしたいと思います。
そして、このカスタマイズファイルを「comment.js」として、次のように設定すれば、先に述べたようなレコード内コメントバックアップアプリの完成です。

まとめ
今回はレコード内コメントをレコードとしてバックアップするというものでした。これが出来れば、所謂アプリのデータ移行もコメントを含めて出来るようになると思います。今回は、レコード(フィールド値)自体の移行時からレコードIDの取扱をフォローする必要があり、そこまではやりませんでしたが・・・。あと、今回やって気付いたことでしたが、GET/recordsのリクエストでコメントの有無を知らせるプロパティがあると、取扱情報量を減らしたいシナリオで役立てられそうに思いました。
しかし、何よりkintone3大要素の中でやや出遅れ感があった「コミュニケーション」に関連するAPIが遂に走りだした!という感じで嬉しく思います!!
↑もう「弱い」とは書けなくなってきました。今回追加されたレコード内コメントは(取得、投稿、削除)が一通りサポートされました。日報アプリ等であれば、日報の内容を見てPepper君が「今日は沢山頑張りましたね」と労ってくれるAI連携等、これまで以上にkintoneへのIOの多様性が広がりを見せてきそうです。また、投稿のAPIは外部サービスから連携のシナリオが描きやすそうですが、kintone.events.on()でコメント投稿時イベントがサポートされたりすると、kintoneから外部サービスへの連携する用途で使いやすそうだと感じました。
次はスレッドコメントや(ちょっと色合いは違いますが)変更履歴のAPIがサポートされるのかなぁと期待も膨らみます^^



