kintone: 選択肢データの一括取得 (kintone: Download Choice Data)
kintone アプリから指定した2つのフィールドに入っている選択肢データを取得します。
2020-10-15 (C) Questetra, Inc. (MIT License)
Configs
  • C1: API トークンを設定した認証設定 *
  • C2: ドメイン(xxxxx.kintone.com または xxxxx.cybozu.com) *
  • C3: ゲストスペース ID(ゲストスペース内のアプリの場合のみ)
  • C4: アプリ ID *
  • C5: 選択肢 ID のフィールドコード *
  • C6: 選択肢ラベルのフィールドコード *
  • C7: 検索クエリ #{EL}
  • C8: 選択肢 ID の一覧を保存する文字型データ項目 *
  • C9: 選択肢ラベルの一覧を保存する文字型データ項目 *
Script
main();
function main(){
//// == 工程コンフィグの参照 / Config Retrieving ==
const auth = configs.get("conf_auth");
const domain = configs.get("conf_domain");
const guestSpaceId = configs.get("conf_guestSpaceId");
const appId = configs.get("conf_appId");
const idField = configs.get("conf_idField");
const labelField = configs.get("conf_labelField");
const query = configs.get("conf_query");
const idsDef = configs.getObject("conf_ids");
const labelsDef = configs.getObject("conf_labels");

//// == ワークフローデータの参照 / Data Retrieving ==
const apiToken = httpClient.getOAuth2Token( auth );

//// == 演算 / Calculating ==
const apiUri = determineApiUri( domain, guestSpaceId );
checkAppId( appId );

checkFields( apiUri, apiToken, appId, idField, labelField );

const initialParams = prepareInitialParams( appId, idField, labelField, query );
const records = []; // レコードオブジェクトを格納する配列
const requestNum = 1; // 現時点での HTTP リクエスト回数
getRecords( apiUri, apiToken, initialParams, records, requestNum );

const idList = records.map( record => record[idField].value );
const labelList = records.map( record => record[labelField].value );
checkValues( idList, labelList );

//// == ワークフローデータへの代入 / Data Updating ==
setData( idsDef, idList );
setData( labelsDef, labelList );
}

/**
* kintone REST API のレコード取得の URI を決定する
* ドメインが空、または kintone のドメインとして不正な文字列であればエラーとする
* @param {String} domain ドメイン
* @param {String} guestSpaceId ゲストスペース ID
* @return {String} apiUri API の URI
*/
function determineApiUri( domain, guestSpaceId ) {
checkDomain( domain );
let apiUri;
if ( guestSpaceId === "" || guestSpaceId === null ) {
apiUri = `https://${domain}/k/v1/`;
} else {
if ( !isValidId(guestSpaceId) ) {
throw "Invalid Guest Space ID.";
}
apiUri = `https://${domain}/k/guest/${guestSpaceId}/v1/`;
}
return apiUri;
}

/**
* ドメインが空または不正な文字列であればエラーとする
* @param {String} domain ドメイン
*/
function checkDomain( domain ) {
if ( domain === "" || domain === null ) {
throw "Domain is empty.";
}
const reg = new RegExp( '^[0-9a-zA-Z-]{3,32}.(?:kintone.com|cybozu.com)$' );
if ( !reg.test(domain) ) {
throw "Invalid Kintone domain.";
}
}

/**
* アプリ ID が空または不正な文字列であればエラーとする
* @param {String} appId アプリ ID
*/
function checkAppId( appId ) {
if ( appId === "" || appId === null ) {
throw "App ID is empty.";
}
if ( !isValidId(appId) ) {
throw "Invalid App ID.";
}
}

/**
* ID が有効か(自然数か)を判定する
* @param {String} idString ID の文字列
* @return {Boolean} 有効な ID かどうか
*/
function isValidId( idString ) {
const idReg = new RegExp( '^[1-9][0-9]*$' );
return idReg.test( idString );
}

/**
* kintone REST API にフィールド一覧取得の GET リクエストを送信し、
* 指定したフィールドの存在とフィールド型をチェックする
* サポートするフィールド型: レコードID, レコード番号, 文字列(1行), リンク, 数値, 日付, 時刻, 日時, 計算
* @param {String} apiUri API の URI
* @param {String} apiToken API トークン
* @param {String} appId アプリ ID
* @param {String} idField 選択肢 ID のフィールドコード
* @param {String} labelField 選択肢ラベルのフィールドコード
*/
function checkFields( apiUri, apiToken, appId, idField, labelField ) {
const getFieldsUri = `${apiUri}app/form/fields.json?app=${appId}`;
engine.log(`API URI: ${getFieldsUri}`);
const response = httpClient.begin()
.header( "X-Cybozu-API-Token", apiToken )
.get( getFieldsUri );
//when error thrown
const responseJson = response.getResponseAsString();
const status = response.getStatusCode();
const accessLog = `---GET request--- ${status}\n${responseJson}\n`;
engine.log(accessLog);
if (status >= 300) {
throw `Failed to get form fields. status: ${status}`;
}
const json = JSON.parse(responseJson);

// フィールドコードの存在とフィールド型をチェック(フィールドコードが'$id'であればチェック不要)
const supportedFieldTypes = new Set( [ "RECORD_NUMBER", "SINGLE_LINE_TEXT", "LINK", "NUMBER", "DATE", "TIME", "DATETIME", "CALC" ] );
if ( idField !== '$id' ) {
if ( json.properties[idField] === undefined ) {
throw `${idField} does not exist in the app.`;
}
if ( !supportedFieldTypes.has( json.properties[idField].type ) ) { // idField のフィールド型がサポート外であればエラー
throw `Unable to use ${idField} as Choice ID. Field Type ${json.properties[idField].type} is not supported.`;
}
}
if ( labelField !== '$id' ) {
if ( json.properties[labelField] === undefined ) {
throw `${labelField} does not exist in the app.`;
}
if ( !supportedFieldTypes.has( json.properties[labelField].type ) ) { // labelField のフィールド型がサポート外であればエラー
throw `Unable to use ${labelField} as Choice Label. Field Type ${json.properties[labelField].type} is not supported.`;
}
}
}

/**
* 初回の GET リクエストのパラメータに使用する情報を準備する
* @param {String} appId アプリ ID
* @param {String} idField 選択肢 ID のフィールドコード
* @param {String} labelField 選択肢ラベルのフィールドコード
* @param {String} query 検索クエリ
* @return {Object} initialParams リクエストのパラメータに使用する情報を格納した JSON オブジェクト
* プロパティ: {String} app アプリ ID
* {Set<String>} fields フィールドコードの集合
* {String} query 検索クエリ
* {Number} lastRecordId 検索済みの最後のレコード番号
*/
function prepareInitialParams( appId, idField, labelField, query ) {
const fields = new Set([ idField, labelField ]);
fields.add( '$id' ); // $id で並べ替えを行うため、取得フィールドに $id を追加する
const initialParams = {
app: appId,
fields: fields,
query: query,
lastRecordId: 0
};
return initialParams;
}

/**
* kintone REST API にレコード取得の GET リクエストを送信する
* 未取得のレコードがなくなるまで再帰的に実行される
* @param {String} apiUri API の URI
* @param {String} apiToken API トークン
* @param {Object} params GET リクエストのパラメータに使用する情報が格納されたオブジェクト
* プロパティ: {String} app アプリ ID
* {Set<String>} fields フィールドコードの集合
* {String} query 検索クエリ
* {Number} lastRecordId 検索済みの最後のレコード番号
* @param {Array<Object>} records レコードオブジェクトを格納する配列
* @param {Number} requestNum HTTP リクエスト回数
*/
function getRecords( apiUri, apiToken, { app, fields, query, lastRecordId }, records, requestNum ) {
// リクエスト回数の上限チェック
if ( requestNum + 1 > httpClient.getRequestingLimit() ) {
throw "HTTP requests exceed the limit.";
}

const LIMIT = 500; // 1回の GET リクエストで取得できるレコードの上限件数
const getRecordsUri = `${apiUri}records.json`;
let queryString;
if ( query === "" || query === null ) {
queryString = `query=$id > ${lastRecordId} order by $id asc limit ${LIMIT}`;
} else {
queryString = `query=( ${query} ) and $id > ${lastRecordId} order by $id asc limit ${LIMIT}`;
}
const fieldsString = Array.from( fields, (fieldCode, i) => `fields[${i}]=${fieldCode}` ).join('&');
const paramsString = `app=${app}&${queryString}&${fieldsString}`;
engine.log(`API URI: ${getRecordsUri}`);
engine.log(`Params: ${paramsString}`);
const uriWithParams = encodeURI( `${getRecordsUri}?${paramsString}` );
const response = httpClient.begin()
.header( "X-Cybozu-API-Token", apiToken )
.get( uriWithParams );
//when error thrown
const responseJson = response.getResponseAsString();
const status = response.getStatusCode();
const accessLog = `---GET request--- ${status}`;
engine.log(accessLog);
if (status >= 300) {
engine.log(responseJson);
throw `Failed to get records. status: ${status}`;
}
const json = JSON.parse(responseJson);
Array.prototype.push.apply(records, json.records); // レコードオブジェクトを配列に追加
requestNum++; // リクエスト回数を加算

// レコード件数のチェック
if ( records.length === 0 ) { // 該当するレコードが一件もなければエラー
throw "No Choice Data found.";
}
if ( records.length > 150000 ) { // 15万件を超える場合はエラー
throw "Number of Choice Data is over 150,000.";
}

// 再帰呼び出し
if ( json.records.length === LIMIT ) {
// 取得レコードの件数が LIMIT と同じ場合は、未取得のレコードが残っている場合があるので、
// lastRecordId を更新し、getRecords を再帰呼び出しする
lastRecordId = json.records[json.records.length - 1].$id.value;
getRecords( apiUri, apiToken, { app, fields, query, lastRecordId }, records, requestNum );
}
}

/**
* 選択肢 ID と選択肢ラベルの配列の値をチェックし、以下の場合はエラーとする
* 1. 空文字列や null が含まれる場合
* 2. idList に重複がある場合
* 3. 1,000文字を超えるものがある場合
* @param {Array<String>} idList 選択肢 ID の配列
* @param {Array<String>} labelList 選択肢ラベルの配列
*/
function checkValues( idList, labelList ) {
// 空文字列や null が含まれる場合はエラー
if ( idList.indexOf("") !== -1 || idList.indexOf(null) !== -1 ) {
throw "Empty data is in Choice IDs.";
}
if ( labelList.indexOf("") !== -1 || labelList.indexOf(null) !== -1 ) {
throw "Empty data is in Choice Labels.";
}

// idList に重複があればエラー
const idSet = new Set( idList ); // idList の重複確認用の Set
if ( idSet.size !== idList.length ) {
throw "Same values are in Choice IDs.";
}

// 1,000文字を超えるものがあればエラー
if ( idList.length !== labelList.length ) { // 次の for 文に備えて配列の長さ一致をチェック
throw "Array length does not match.";
}
for ( let i in idList ) {
if ( idList[i].length > 1000 ) {
throw "Unable to use string over 1,000 characters as Choice ID.";
}
if ( labelList[i].length > 1000 ) {
throw "Unable to use string over 1,000 characters as Choice Label.";
}
}
}

/**
* 文字列データの配列を改行で繋ぎ、データ項目に出力する
* @param {ProcessDataDefinitionView} dataDef 保存先データ項目の ProcessDataDefinitionView
* @param {Array<String>} dataStringList 出力する文字列データの配列
*/
function setData( dataDef, dataStringList ) {
if ( dataDef !== null ) {
engine.setData( dataDef, dataStringList.join('\n') );
}
}

Download

Capture

Notes

  1. API トークンを取得するには、アプリの設定画面の「設定(App Settings)」のタブを開き、「API トークン(API Token)」へと進みます。

    「生成する(Generate)」をクリックし、権限(Permissions)を選択(「レコード参照(View records)」権限が必要です)したあと、「保存(Save)」をクリックします。

    「アプリを更新(Update App)」をクリックして変更を適用するのも忘れないようにしてください。
  2. ゲストスペース ID(アプリがゲストスペースにある場合)とアプリ ID は、API トークンの設定画面で確認することができます。
  3. このアドオンがサポートするフィールド型は、レコード番号レコード ID文字列(1行)数値計算リンク日付時刻日時ルックアップです。
  4. C7: 検索クエリで使用可能な演算子と関数については、kintone のリファレンスを参照してください。オプション(order by, limit, offset)は使用できません。
%d人のブロガーが「いいね」をつけました。