見出し画像

kintoneで「ランダム取得」を作ってみた【ドシキンカス道場】

はじめに

みなさんこんにちは。プロジェクト・アスノートの松田です。
今日は kintoneカスタマイズのお話です。

2022/7/27に開催された、imoniCampのLTでお話した内容をベースに、もっと持って帰りたい人や、もう少し勉強してみたい人のために残しておきます。

今回のお題

今回のお題は、とある製造業の会社です。
現場作業を行うときに、安全対策や労災防止のために、危険予知(KY)活動というものをしています。
そこで、過去の労働災害から導き出された、いろいろな作業時の安全チェックポイントが蓄積されていて、その中から日々の作業前にセルフチェックを行いたいという業務があります。

カスタマイズをする理由

安全チェックポイントはかなりの数が登録されています。
日々の作業前ミーティングで、それら全てのチェックを行うことは、作業負荷的にも好ましくない。また、あらかじめピックアップしたチェック項目を毎回やってもらうのも、マンネリ化を招く。
ということで、毎回(レコードごとに)ランダムにピックアップできるようにしたい、という仕様が適切だと考えました。

アプリ構成

チェック項目マスタアプリ

分類、チェック項目、およびそれに対してのKY(危険予知)アドバイスの項目を持ったマスタアプリ。
分類は10種類程度あり、別の分類マスタからルックアップで連携させている。

セルフチェックアプリ

作業前ミーティングで使用するアプリ。
各分類のチェック項目のフィールドが設置されており、新規レコード作成時に、ランダムにマスタからデータが入るようになっている。
ミーティングでは、チェック項目に対して5段階のチェック(ドロップダウン選択)を行い、レコードを保存して結果を参照する。

結果はレーダーチャートで見える化

kintoneで他のアプリからデータを取ってくる方法

まずは kintoneアプリで、マスタアプリからデータを取ってくる方法を考えてみました。

ルックアップ

  • キーワード検索による絞り込みが活用可(分類絞り込みには使える)

  • ランダムで1件取得の実現のためには、ルックアップ連携キーを直接指定する必要がある

実現手段としては、分類内の連携キーを全件取得し、その中から1つをランダムに選出し、ルックアップフィールドにセットし、取得処理を行う(手動)

REST API

  • チェック項目マスタから、各分類内のレコードをランダムに1件取得する方法が作れそう

REST APIによる直接取得であれば、直接フィールドにデータを取得してくることができる。ランダム取得ロジックさえ作ることができれば、ルックアップ取得処理も不要。今回はこちらを使うことにした

ランダム取得実現の方針検討

それでは、具体的に複数レコードから1つをランダムに取得する方法としては、この図に挙げた3つの方法が考えられます。
基本は レコードの取得(GET)APIを利用します。

方法1:レコードの取得(1件)で狙い撃ち

この方法は、狙いのレコードを予め決める必要があります($id指定)
取得対象を決めるためには、

  1. 対象レコードidを全件取得

  2. 対象の$IDをランダムに1つ選出

  3. 目的レコード1件を再度取得

方法2:レコードの一括取得(OFFSET指定で1件取得)

OFFSETとは、対象が複数ある場合に、取得開始するレコードを「何件目から」と指定するためにパラメータです。加えて、取得件数を1件とすることで、1件のレコードを取得することができます。
手順としては、以下のようになるはずです。

  1. クエリで対象分類のレコード件数を取得

  2. OFFSET値をランダムに生成

  3. OFFSET指定で目的レコードを1件取得

方法3:レコードの一括取得(取ってきてから選ぶ)

レコードを複数件まず取得してから、その中からランダムに選び出すという方法です。手順は次のようになります。

  1. クエリで対象の分類のレコードを指定し、全件取得

  2. 取得したデータからランダムに1件を選び出す

kintoneのAPI負荷の比較

どの方法でも取得可能だとは思いますが、どの方法でやるかを考えるにあたって、kintoneのAPIの消費回数を考慮する必要があります。

方法1:
対象レコードidを全件取得 【GET①複数件】
対象の$IDをランダムに1つ選出
目的レコード1件を再度取得【GET②1件】
分類は10種類あるので、2回×10で、計20回のAPI実行が必要となります。

方法2:
クエリで対象分類のレコード件数を取得【GET①複数件】
OFFSET値をランダムに生成
OFFSET指定で目的レコードを1件取得【GET②1件】
方法2も同様に、2回×10で、計20回のAPI実行が必要となります。
GET①は全分類をまとめてレコード取得するという方法も考えられるので、GET①は一回にまとめられそうです。1+10=11回のAPI実行が必要。

方法3:
クエリで対象の分類のレコードを取得【GET①複数件】
取得したレコードからランダムに1件選び出す。
分類毎に取得だと10回。まとめて取得してから各分類のデータを取り出すようにすれば、まとめて1回のAPI実行で実現できそうです。

kintone SIGNPOSTも参考に。一つのカスタマイズの負荷は大したことがないかもしれませんが、常にAPI消費はなるべく効率よくするよう、考えています。

制約等の確認

次に、今回使用するAPI レコードの一括取得の制約についても確認しておきます。

https://developer.cybozu.io/hc/ja/articles/202331474#step2

レコードをクエリで条件を指定して取得できます。

一度に取得できるレコードは 最大500件(初期値は100件)までです。
offset の上限値は1万件です。
リクエスト時にクエリで指定できる fields の添字は、0~99の範囲になります。
リクエスト時にボディで指定できる fields数は 1000個までです。
クエリで文字列検索する場合は単語検索となります。詳しくは「検索キーワードに関する注意」を参照ください。
クエリ検索結果が10万件以上存在するとき、絞り込みが中断されます。

※ 2020年7月定期メンテナンスで、offset 上限値1万件の設定を行いました。
レコード一括取得時にその結果が 1 万を超える可能性がある場合には、運用・適用中のプログラムのご確認ならびに修正対応の検討をお願いいたします。

https://developer.cybozu.io/hc/ja/articles/202331474#step2

一度に取得できるレコードは最大500件です。
今回使ったアプリ(チェック項目マスタアプリ)のレコード数は90件程度でしたので、とりあえずは考慮しないカタチで進めることにします。
レコード数が多い、または多くなることが想定されるアプリを使う場合は、レコードの一括取得を工夫する必要があります。詳しくは developer networkの次の記事を参照してください。レコードIDを使った複数回取得や、カーソルAPIを利用するという方法が説明されています。

https://developer.cybozu.io/hc/ja/articles/360030757312

コーディングの構造を考える(全体設計)

僕はいつも「型」から考えるようにしています。
今回の処理であれば、次のように考えました。

  1. 処理を動かすタイミング(kintoneのイベント)

  2. 対象分類のレコードを一括取得する

  3. 取得した複数レコードから1つを選び出す

  4. 取得したレコードのデータをフィールドに転記

コーディングの全体構成としては、次の図のようになります。

ランダムに選びだす処理

今回のキモとなる、ランダムに選び出す処理ですが、レコードのデータが複数入った配列から、1つの要素を選び出すという処理を作りました。
処理を行う回数は、分類の数だけあるので、繰り返し利用があるため、関数化を行いました

/**
* 配列からランダムに1つ取り出す関数: selectRandom
* @param array 取得対象の配列
* @returns array[num] 1つの配列要素
*/
const selectRandom = (array) => {
    // 配列に要素が無い場合の離脱処理
    if (array.length === 0) { return false; }
    //配列の要素番号をランダムに取得
    const num = Math.floor(Math.random() * array.length);
    return array[num];
};

乱数を発生させたり、配列から一つを選び出す処理については、ググるといろいろな記事が見つかりますので、詳しく知りたい人は調べてみるといいです。

コーディング全体

実際のコーディングにおいては、上記の基本設計をもとに、細かいところまで作っていきます。ポイントとなったのは次の点

  1. 対象分類と、それぞれの分類のチェック項目を格納するフィールドの組み合わせが複数あります。これらを最初にまとめて設定しておくことで、後の処理をなるべくシンプルに書けるように工夫しました。

  2. KYアドバイスについては、チェックを行っている時(レコード追加画面)では表示させず、保存をして詳細画面が表示されたときに見えるようにしています。

  3. 上部のセルフチェック結果に、アドバイスがまとめて文字列(複数行)に表示される処理も盛り込んでいます。レーダーチャート表示は別JSで処理しているため、今回は紹介していません。

/**
 *  レコードのランダム取得・フィールド格納処理
 *      
 */
 (function () {
    'use strict';

    // 設定値 key: 分類名、setFields: マスタからの取得値の転記先フィールドコード
    const settings = [
        { key: '無考', setFields: ['key_無考', 'item_無考', 'KYアドバイス_無考'] },
        { key: '無謀', setFields: ['key_無謀', 'item_無謀', 'KYアドバイス_無謀'] },
        { key: '無視', setFields: ['key_無視', 'item_無視', 'KYアドバイス_無視'] },
        { key: '無自覚', setFields: ['key_無自覚', 'item_無自覚', 'KYアドバイス_無自覚'] },
        { key: '無知', setFields: ['key_無知', 'item_無知', 'KYアドバイス_無知'] },
        { key: '無意識', setFields: ['key_無意識', 'item_無意識', 'KYアドバイス_無意識'] },
        { key: '無神経', setFields: ['key_無神経', 'item_無神経', 'KYアドバイス_無神経'] },
        { key: '無関心', setFields: ['key_無関心', 'item_無関心', 'KYアドバイス_無関心'] },
        { key: '無断', setFields: ['key_無断', 'item_無断', 'KYアドバイス_無断'] }
    ];
    const createEvents = [
        'app.record.create.show',
        'mobile.app.record.create.show'
    ];

    /**
     * 配列からランダムに1つ取り出す関数: selectRandom
     * @param array 取得対象の配列
     * @returns array[num] 1つの配列要素
     */
    const selectRandom = (array) => {
        // 配列に要素が無い場合の離脱処理
        if (array.length === 0) { return false; }
        //配列の要素番号をランダムに取得
        const num = Math.floor(Math.random() * array.length);
        return array[num];
    };

    kintone.events.on(createEvents, async (event) => {
        // チェック項目レコード全件取得
        const appId = 1116;
        const getURL = kintone.api.url('/k/v1/records.json', true);
        const param = { app: appId };
        const resp = await kintone.api(getURL, 'GET', param);

        // 各分類の項目ランダム取得&フィールド格納処理
        settings.forEach((setting) => {
            const result = resp.records.filter((record) => {
                return record['分類'].value === setting.key;
            });
            const selectedRecord = selectRandom(result);

            if (selectedRecord) {
                event.record[setting.setFields[0]].value = selectedRecord['チェック項目key'].value;
                event.record[setting.setFields[1]].value = selectedRecord['チェック項目'].value;
                event.record[setting.setFields[2]].value = selectedRecord['KYアドバイス'].value;
            }
        });

        // 新規レコード作成画面では「KYアドバイス」は非表示とする処理
        const advFields = [
          'KYアドバイス_無考',
          'KYアドバイス_無謀',
          'KYアドバイス_無視',
          'KYアドバイス_無自覚',
          'KYアドバイス_無知',
          'KYアドバイス_無意識',
          'KYアドバイス_無神経',
          'KYアドバイス_無関心',
          'KYアドバイス_無断'
          ];
        advFields.forEach((field) => {
          kintone.app.record.setFieldShown(field, false);
        });
        
        return event;
    });
    
    // 保存前イベントで、KYアドバイスをまとめ表示する処理
    const submitEvents = [
        'app.record.create.submit',
        'app.record.edit.submit',
        'mobile.app.record.create.submit',
        'mobile.app.record.edit.submit'
    ];
    
    kintone.events.on(submitEvents, (event) => {
        const record = event.record;
        const advices =[
            '【無考】' + record['KYアドバイス_無考'].value,
            '【無謀】' + record['KYアドバイス_無謀'].value,
            '【無視】' + record['KYアドバイス_無視'].value,
            '【無自覚】' + record['KYアドバイス_無自覚'].value,
            '【無知】' + record['KYアドバイス_無知'].value,
            '【無意識】' + record['KYアドバイス_無意識'].value,
            '【無神経】' + record['KYアドバイス_無神経'].value,
            '【無関心】' + record['KYアドバイス_無関心'].value,
            '【無断】' + record['KYアドバイス_無断'].value
        ];
        record['KYアドバイス集約'].value = advices.join('\n');
        return event;
    });
})();

一緒に学ぶ仲間になろう

最後に、私がやっている活動を紹介します。
ドシキンカス道場(ど素人が始める kintoneカスタマイズ)として、WebサイトやYouTubeでの情報発信を行っています。
毎週日曜日夜22時から行っているライブ配信でも、カスタマイズの話題を取り上げることもありますし、チャットでの他の参加者とのコミュニケーションも活発に行われています。

https://pj.asunote.jp/category/doshikinkasu/

他にも、サイボウズのチーム応援ライセンスを、仲間で割り勘で利用した、kintoneの学びの場を作ったり、勉強会を開催したりしています。

このような場を使って、kintoneを学ぶ仲間を見つけることもできます。

あなたも一緒に、kintoneや業務改善、そしてkintoneカスタマイズを学ぶ仲間になりませんか?
興味のある人はぜひ連絡してください!本記事のコメントやTwitter等でお待ちしています。

LTで使用したスライド


いただいたサポートは、今後とも有益な情報を提供する活動資金として活用させていただきます! 対価というよりも、応援のキモチでいただけたら嬉しいです。