こんにちは!
今回は、他のアプリにたくさん準備したレコードを、1つのアプリに全部登録するということをやってみたいと思います。
kintone REST API では一度に一括登録できるレコード数の上限が100件です。
なので、何回も100件登録を繰り返すことになるかなぁ・・・。
今回作ってみるのは
アプリA:レコードたくさん
アプリB:レコードたくさん
↓
アプリCに登録(たくさんのレコードを100件ずつ切り取りながら・・・)
という感じ。
ちょっと説明足らずになるかも知れませんが、やっていきたいと思います!
アプリの準備
(1)エクセルなどで2000件くらいレコードを作ります。
こんなデータを作りまして、
アップロードしてアプリA,アプリBを作成します。
(2)アプリAを再利用してレコードが空っぽのアプリCを作成します。
↓こんな感じ
アプリA | アプリB | アプリC |
---|---|---|
ちなみにどのアプリもフィールドはこんなかんじ
フィールド名 | フィールドコード | フィールド種類 |
---|---|---|
数字 | 数値 | 数値 |
名前 | 文字列__1行_ | 文字列(1行) |
JavaScriptを書く
kintone UI Componentを使う
こちらから、
https://github.com/kintone/kintone-ui-component/tree/master/dist
kintone-ui-component.min.css
kintone-ui-component.min.js
をダウンロードしてアプリCにアップロードしておきます。
(spinnerという機能を使いたくて)
カーソルAPIを使って取ってくる
アプリA,アプリBからは公式のコードを使って取ってくるようにしましょう。
レコード一括取得の JavaScript コーディング例 - カーソル API を利用する方法
※イマドキなlet、constで書くES6なコードは、
kintone Advent Calendar 2020 8日目
@tarimo34 さんの記事カーソルAPIを使ってレコードを一括取得したい(シンプル風味のソースを添えて)
を是非ご覧くださいっ!
第1段階の準備としてはこんな感じ。
//ココに一覧表示後イベントのコードを書いていく
と
//ココにレコード登録用コードを書いていく
のところにコードを書いていきます。
(()=>{
'use strict';
kintone.events.on(['app.record.index.show'], event => {
//ココに一覧表示後イベントのコードを書いていく
});
const putRecords = async (_params) =>{
//ココにレコード登録用コードを書いていく
}
//以下、公式サイト通りのソース
// カーソルを作成する
var postCursor = function(_params) {
var MAX_READ_LIMIT = 500;
var params = _params || {};
var app = params.app || kintone.app.getId();
var filterCond = params.filterCond;
var sortConds = params.sortConds;
var fields = params.fields;
var conditions = [];
if (filterCond) {
conditions.push(filterCond);
}
var sortCondsAndLimit =
(sortConds && sortConds.length > 0 ? ' order by ' + sortConds.join(', ') : '');
var query = conditions.join(' and ') + sortCondsAndLimit;
var body = {
app: app,
query: query,
size: MAX_READ_LIMIT
};
if (fields && fields.length > 0) {
body.fields = fields;
}
return kintone.api(kintone.api.url('/k/v1/records/cursor', true), 'POST', body).then(function(r) {
return r.id;
});
};
// 作成したカーソルからレコードを取得する
var getRecordsByCursorId = function(_params) {
var params = _params || {};
var id = params.id;
var data = params.data;
if (!data) {
data = {
records: []
};
}
var body = {
id: id
};
return kintone.api(kintone.api.url('/k/v1/records/cursor', true), 'GET', body).then(function(r) {
data.records = data.records.concat(r.records);
if (r.next) {
return getRecordsByCursorId({ id: id, data: data });
}
return data;
});
};
/*
* @param {Object} params
* - app {String}: アプリID(省略時は表示中アプリ)
* - filterCond {String}: 絞り込み条件
* - sortConds {Array}: ソート条件の配列
* - fields {Array}: 取得対象フィールドの配列
* @return {Object} response
* - records {Array}: 取得レコードの配列
*/
var getRecords = function(_params) {
return postCursor(_params).then(function(id) {
return getRecordsByCursorId({ id: id });
});
};
})();
一覧表示イベント
一覧表示はこんな感じにします。
↓spinner(待ってる間にグルグル表示するやつ)についてはこちらをご参考に
「kintone UI Component」を使って簡単にkintoneライクなUIを設置する
//一覧表示後イベント
kintone.events.on(['app.record.index.show'], event => {
//メニュー横の
const sp = kintone.app.getHeaderMenuSpaceElement()
//ボタン追加
const btn = document.createElement('button');
btn.textContent='ボタン';
sp.appendChild(btn);
//sppiner追加
const spinner = new kintoneUIComponent.Spinner();
const spn = spinner.render();
sp.appendChild(spn);
//ボタンクリック
btn.onclick = async () => {
//sppiner(待ってる間グルグル表示するやつ)表示する
spinner.show();
//アプリA、アプリBからレコード全件取ってくる(公式のコード内の関数を使用)
const appA = await getRecords({app:アプリAのID, fields:['文字列__1行_','数値']});
const appB = await getRecords({app:アプリBのID, fields:['文字列__1行_','数値']});
//アプリAとアプリBのレコードをつなげる
const appAB = appA.records.concat(appB.records);
//アプリCにレコードを登録する(次の節でつくる関数)
await postRecords({app:アプリCのID, records:appAB, next:appAB.length>0});
//sppiner隠す(表示更新するから要らないと思うけど)
spinner.hide();
//表示更新
location.reload();
}
});
アプリAとアプリBからレコードを全件取ってきて、くっつけて、アプリCに登録する・・・という流れです。
fields:['文字列__1行_','数値']
とすることで、余計な「レコード番号」等を取ってこなくて済みます。
//アプリA、アプリBからレコード全件取ってくる(公式のコード内の関数を使用)
const appA = await getRecords({app:アプリAのID, fields:['文字列__1行_','数値']});
const appB = await getRecords({app:アプリBのID, fields:['文字列__1行_','数値']});
//アプリAとアプリBのレコードをつなげる
const appAB = appA.records.concat(appB.records);
//アプリCにレコードを登録する(次の節でつくる関数)
await postRecords({app:アプリCのID, records:appAB, next:appAB.length>0});
レコード登録(今回のメイン)
たくさんのレコードをどうやって登録しましょうかね?
というわけで、
postRecords
関数として↓を考えてみました。
const postRecords = async (_params) =>{
//_params.nextがfalseだったら終了
if (!_params.next ){ return; }
//今回POSTする100件のbody
const body = {
app: _params.app,
records: _params.records.splice(0,100), //POSTする最大100件分だけ取り出す。
};
//次回以降分のパラメータ
const next_params = {
app: _params.app,
records: _params.records, //残りのレコード
next:(_params.records.length == 0) ? false: true //残りのレコードが0件ならfalse
}
return kintone.api(kintone.api.url('/k/v1/records', true), 'POST', body).then(function(r) {
//next_params.nextがfalseだったらreturn;でいいやんと、投稿後に気づいた。
return postRecords(next_params);
});
}
引数の_params
ですが、
ドコから何を渡してやってきたかと言うと、この部分です。
//アプリCにレコードを登録する(次の節でつくる関数)
await postRecords({app:アプリCのID, records:appAB, next:appAB.length>0});
引数の中身はこんな感じ。
_params.app:アプリCのID
_params.records:登録するレコード(AとBあわせた4000件くらいのレコード)
_params.next:true or false(続きのレコードがあればtrue, 全部登録し終わったらfalse)
↓つづいてこちらbody
はレコードPOSTのためのパラメータ。
//今回POSTする100件のbody
const body = {
app: _params.app,
records: _params.records.splice(0,100), //POSTする最大100件分だけ取り出す。
};
kintone REST APIのPOSTではレコードは100件ずつまでしか一括登録できないので、
100件ずつ登録して、続きがあれば繰り返すようにしています。
_params.records.splice(0,100)
ここアプリABのレコードである_params.records
の4000件の100件を取り出してbody
のrecords
に渡しています。
※_params.records
自体は3900件に減ります。
続きまして、次回用のパラメータ(postRecords関数を再帰的に呼び出す際のパラメータ)です。
//次回以降分のパラメータ
const next_params = {
app: _params.app,
records: _params.records, //残りのレコード
next:(_params.records.length == 0) ? false: true //残りのレコードが0件ならfalse、0件じゃなければtrue
}
そして最後の「登録!」の部分
return kintone.api(kintone.api.url('/k/v1/records', true), 'POST', body).then(function(r) {
return postRecords(next_params);
});
1行目で100件に切り取ったレコード分のPOSTをして、メソッドチェーン。
もう一度自分(postRecords)を呼びます。
何だか不思議ですが、自分(postRecords)を次々に呼ぶことにより、
最初4000件ほどあったレコードが100件ずつ切り取られていって、最後0件になったときにはもうPOSTせずに終わる・・・。
というような処理を実現できます。(再帰的な処理といいます)
※処理を終わりたいときは自分を呼ぶ時のパラメータで、next_params.nextをfalse
にしておけば、次のpostRecords
関数内の最初でreturn;
して、処理を終了!とすることができるのです。
//_params.nextがfalseだったら終了
if (!_params.next ){ return; }
動作確認
アプリAに2201件、アプリBにも2201件レコードがある状態で、アプリCで動かしてみます。
ノーカット版なのでちょっとspinner表示(グルグル)が長いよ!
まとめ
たくさんのレコードから、どうやって100件ずつ登録したら良いかなぁと思いましたが、なんとなくうまくいきました!splice(0,100)でいい感じに取ってこれそうです。
※kintone JavaScript ClientのaddAllRecordsというメソッドを使うという方法もありますが、別の機会に書けたら良いなぁと思います。
また、いつものことながら、行数節約のため、エラー処理などは入れていません(><)
もっと良い方法があったり、この書き方だとこんなデメリットがあるよ~というのがあったら教えて下さいm(_ _)m
というわけで、今日はこのへんで~(´・ω・`)ノシノシ
追記
冷静にコードを読んでいたら・・・、nextなフラグなんていらなかったので、修正したものを貼り付けて終わりますm(_ _)m
kintone.events.on(['app.record.index.show'], event => {
const sp = kintone.app.getHeaderMenuSpaceElement()
//ボタン追加
const btn = document.createElement('button');
btn.textContent='ボタン';
sp.appendChild(btn);
//sppiner追加
const spinner = new kintoneUIComponent.Spinner();
const spn = spinner.render();
sp.appendChild(spn);
btn.onclick = async () => {
//sppiner表示
spinner.show();
const appA = await getRecords({app:アプリAのID, fields:['文字列__1行_','数値']});
const appB = await getRecords({app:アプリBのID, fields:['文字列__1行_','数値']});
const appAB = appA.records.concat(appB.records);
//登録!
await postRecords({app:アプリCのID, records:appAB});
//sppiner隠す(表示更新するから要らないと思う)
spinner.hide();
//表示更新
location.reload();
}
});
const postRecords = async (_params) =>{
//今回POSTする100件のbody
const body = {
app: _params.app,
records: _params.records.splice(0,100), //POSTする最大100件分だけ取り出す。
};
//次回以降分
const next_params = {
app: _params.app,
records: _params.records, //残りのレコード
}
return kintone.api(kintone.api.url('/k/v1/records', true), 'POST', body).then(function(r) {
return _params.records.length > 0 && postRecords(next_params);
});
}