cybozu.com に登録されているユーザーの情報を、kintone アプリに同期する。

# ０．はじめに

[cybozu\.com](https://www.cybozu.com/jp/) に登録されているユーザー情報の一覧を、[kintone](https://kintone.cybozu.co.jp/) アプリで、簡単に取得したかったので、やってみました。

# １． [kintone](https://kintone.cybozu.co.jp/) アプリに JavaScript ライブラリ (reloadCybozuUsers.js) を追加する。

1. まずは、[kintone](https://kintone.cybozu.co.jp/) アプリの「アプリの設定」を開きます。
<br><br>
1. 「設定」タブを選択し、「カスタマイズ／サービス連携」→「JavaScript / CSSでカスタマイズ」を開きます。
<br><br>
1. 以下の JavaScript ライブラリを追加します。

    ```javascript:reloadCybozuUsers.js
    /*
    cybozu.com ユーザー一覧更新処理
    */
    
    // kintone API – cybozu developer network
    //https://developer.cybozu.io/hc/ja/articles/360028177472
    
    // PromiseがわからないのでAsync/Awaitを使う - Qiita
    // https://qiita.com/_muraham/items/697e1d53a09ff40530e5
    
    // プログレスバーを簡単に実装できるprogressbar.js - bagelee（ベーグリー）
    // https://bagelee.com/design/javascript/create_progress_bars_with_progressbarjs/
    
    // https://rawgit.com/kimmobrunfeldt/progressbar.js/master/dist/progressbar.js
    // https://rawgit.com/kimmobrunfeldt/progressbar.js/master/dist/progressbar.min.js
    
    (function() {
        "use strict";
        kintone.events.on('app.record.index.show', function(event) {
            var myProgressDiv = document.createElement('div');
            myProgressDiv.id = "my_progress_container";
            //myProgressDiv.style.backgroundColor = 'red';
            myProgressDiv.style.width = '600px';
            myProgressDiv.style.height = '8px';
            if (document.getElementById('my_index_button') !== null) {
                return;
            }
            var myIndexButton = document.createElement('button');
            myIndexButton.id = 'my_index_button';
            myIndexButton.innerText = '　更　　新　';
            myIndexButton.onclick = async function() {
                var bar = new ProgressBar.Line(my_progress_container, {
                    strokeWidth: 4,
                    easing: 'easeInOut',
                    duration: 100,
                    color: '#FFEA82',
                    trailColor: '#eee',
                    trailWidth: 1,
                    svgStyle: {width: '100%', height: '100%'},
                    text: {
                        style: {
                            color: '#999',
                            position: 'relative',
                            left: '560px',
                            top: '-70px',
                            padding: 0,
                            margin: 0,
                            transform: null
                        },
                        autoStyleContainer: false
                    },
                    from: {color: '#FFEA82'},
                    to: {color: '#ED6A5A'},
                    step: (state, bar) => {
                        bar.path.setAttribute('stroke', state.color);
                        bar.setText(Math.round(bar.value() * 100) + ' %');
                    }
                });
                //console.log(event.records);
                var respUsers = await kintone.api(kintone.api.url('/v1/users', true), 'GET', {});
                //console.log(respUsers);
                var respRecords = await kintone.api(kintone.api.url('/k/v1/records', true), 'GET', {
                    'app': kintone.app.getId()
                });
                //console.log(respRecords);
                var recordIDs = {};
                var postRecords = [];
                var putRecords = [];
                for(let i = 0; i < respRecords.records.length; i++) {
                    recordIDs[respRecords.records[i]['$id']['value']] = {
                        'userid': respRecords.records[i]['id']['value'],
                        'idx': i
                    }
                }
                //console.log(recordIDs);
                for(let i = 0; i < respUsers.users.length; i++) {
                    bar.animate(i/respUsers.users.length);
                    var respUserOrgs = await kintone.api(kintone.api.url('/v1/user/organizations', true), 'GET', {
                        'code': respUsers.users[i]['code']
                    });
                    //console.log(respUserOrgs);
                    var organizations = []
                    for(let ii = 0; ii < respUserOrgs['organizationTitles'].length; ii++) {
                        organizations.push({
                            'code': respUserOrgs['organizationTitles'][ii]['organization']['code'],
                            'name': respUserOrgs['organizationTitles'][ii]['organization']['name']
                        });
                    }
                    let record = {};
                    record['id'] = {'value': respUsers.users[i]['id']}
                    record['name'] = {'value': respUsers.users[i]['name']}
                    record['surName'] = {'value': respUsers.users[i]['surName']}
                    record['givenName'] = {'value': respUsers.users[i]['givenName']}
                    record['surNameReading'] = {'value': respUsers.users[i]['surNameReading']}
                    record['givenNameReading'] = {'value': respUsers.users[i]['givenNameReading']}
                    record['code'] = {'value': respUsers.users[i]['code']}
                    record['email'] = {'value': respUsers.users[i]['email']}
                    if (respUsers.users[i]['valid']) {
                        record['valid'] = {'value': ['使用中']}
                    } else {
                        record['valid'] = {'value': []}
                    }
                    record['organizations'] = {'value': organizations}
                    record['primaryOrganization'] = {'value': respUsers.users[i]['primaryOrganization']}
                    record['phone'] = {'value': respUsers.users[i]['phone']}
                    record['url'] = {'value': respUsers.users[i]['url']}
                    //
                    record['callto'] = {'value': respUsers.users[i]['callto']}
                    record['mobilePhone'] = {'value': respUsers.users[i]['mobilePhone']}
                    record['locale'] = {'value': respUsers.users[i]['locale']}
                    record['timezone'] = {'value': respUsers.users[i]['timezone']}
                    record['localNameLocale'] = {'value': respUsers.users[i]['localNameLocale']}
                    record['localName'] = {'value': respUsers.users[i]['localName']}
                    //
                    record['employeeNumber'] = {'value': respUsers.users[i]['employeeNumber']}
                    record['joinDate'] = {'value': respUsers.users[i]['joinDate']}
                    record['birthDate'] = {'value': respUsers.users[i]['birthDate']}
                    record['sortOrder'] = {'value': respUsers.users[i]['sortOrder']}
                    record['description'] = {'value': respUsers.users[i]['description']}
                    //
                    record['customItemValues'] = {'value': respUsers.users[i]['customItemValues']}
                    record['extensionNumber'] = {'value': respUsers.users[i]['extensionNumber']}
                    //
                    record['ctime'] = {'value': respUsers.users[i]['ctime']}
                    record['mtime'] = {'value': respUsers.users[i]['mtime']}
                    //console.log(record);
                    var matchRecordIDs = []
                    Object.keys(recordIDs).forEach(function(key) {
                        if (this[key]['userid'] == respUsers.users[i]['id']) {
                            matchRecordIDs.push({
                                'key': key,
                                'idx': this[key]['idx']
                            });
                        }
                    }, recordIDs);
                    //console.log(matchRecordIDs);
                    if (matchRecordIDs.length > 0) {
                        //console.log(respRecords.records[matchRecordIDs[0]['idx']]);
                        if (record['id']['value'] != respRecords.records[matchRecordIDs[0]['idx']]['id']['value']
                        || record['name']['value'] != respRecords.records[matchRecordIDs[0]['idx']]['name']['value']
                        || record['surName']['value'] != respRecords.records[matchRecordIDs[0]['idx']]['surName']['value']
                        || record['givenName']['value'] != respRecords.records[matchRecordIDs[0]['idx']]['givenName']['value']
                        || record['surNameReading']['value'] != respRecords.records[matchRecordIDs[0]['idx']]['surNameReading']['value']
                        || record['givenNameReading']['value'] != respRecords.records[matchRecordIDs[0]['idx']]['givenNameReading']['value']
                        || record['valid']['value'] != respRecords.records[matchRecordIDs[0]['idx']]['valid']['value']
                        || record['code']['value'] != respRecords.records[matchRecordIDs[0]['idx']]['id']['code']
                        || record['email']['value'] != respRecords.records[matchRecordIDs[0]['idx']]['id']['email'] ) {
                            putRecords.push({'id': matchRecordIDs[0]['key'], 'record': record});
                        }
                        delete recordIDs[matchRecordIDs[0]['key']];
                    } else {
                        postRecords.push(record);
                    }
                }
                console.log("recordIDs :");
                //console.log(recordIDs);
                console.log(Object.keys(recordIDs));
                console.log("putRecords :");
                console.log(putRecords);
                console.log("postRecords :");
                console.log(postRecords);
                if (Object.keys(recordIDs).length > 0) {
                    var resp = await kintone.api(kintone.api.url('/k/v1/records', true), 'DELETE', {
                        'app': kintone.app.getId(),
                        'ids': Object.keys(recordIDs)
                    });
                }
                if (putRecords.length > 0) {
                    var resp = await kintone.api(kintone.api.url('/k/v1/records', true), 'PUT', {
                        'app': kintone.app.getId(),
                        'records': putRecords
                    });
                }
                if (postRecords.length > 0) {
                    var resp = await kintone.api(kintone.api.url('/k/v1/records', true), 'POST', {
                        'app': kintone.app.getId(),
                        'records': postRecords
                    });
                }
                bar.animate(1.0);
                location.reload();
                return event;
            };
            kintone.app.getHeaderMenuSpaceElement().appendChild(myIndexButton);
            kintone.app.getHeaderMenuSpaceElement().appendChild(myProgressDiv);
            //kintone.app.getHeaderSpaceElement().appendChild(myProgressDiv);
        });
    })();
```
<br>
    * ※ 以降は、コードの簡単な説明。
<br><br>
    * ユーザー情報の取得
        * [ユーザーエクスポートAPI – cybozu developer network](https://developer.cybozu.io/hc/ja/articles/202363040-%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88API#step2)

    ```javascript
    var respUsers = await kintone.api(kintone.api.url('/v1/users', true), 'GET', {});
```
    * アプリのレコード情報の取得
        * [レコードの取得（GET） – cybozu developer network](https://developer.cybozu.io/hc/ja/articles/202331474-%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E5%8F%96%E5%BE%97-GET-#step2)

    ```javascript
    var respRecords = await kintone.api(kintone.api.url('/k/v1/records', true), 'GET', {
        'app': kintone.app.getId()
    });
```
    * ユーザーの所属する組織情報の取得
        * [ユーザーの所属組織エクスポートAPI – cybozu developer network](https://developer.cybozu.io/hc/ja/articles/202124774#step2)

    ```javascript
    var respUserOrgs = await kintone.api(kintone.api.url('/v1/user/organizations', true), 'GET', {
        'code': respUsers.users[i]['code']
    });
```
    * アプリのレコード情報の削除
        * [レコード削除（DELETE） – cybozu developer network](https://developer.cybozu.io/hc/ja/articles/201941794)

    ```javascript
    var resp = await kintone.api(kintone.api.url('/k/v1/records', true), 'DELETE', {
        'app': kintone.app.getId(),
        'ids': Object.keys(recordIDs)
    });
```
    * アプリのレコード情報の更新
        * [レコードの更新（PUT） – cybozu developer network](https://developer.cybozu.io/hc/ja/articles/201941784)

    ```javascript
    var resp = await kintone.api(kintone.api.url('/k/v1/records', true), 'PUT', {
        'app': kintone.app.getId(),
        'records': putRecords
    });
```
    * アプリのレコード情報の登録
        * [レコードの登録（POST） – cybozu developer network](https://developer.cybozu.io/hc/ja/articles/202166160)

    ```javascript
    var resp = await kintone.api(kintone.api.url('/k/v1/records', true), 'POST', {
        'app': kintone.app.getId(),
        'records': postRecords
    });
```

# ２． [kintone](https://kintone.cybozu.co.jp/) アプリに JavaScript ライブラリ ([ProgressBar.js](https://kimmobrunfeldt.github.io/progressbar.js/#intro)) を追加する。

1. こちらの手順に従って、追加します。
    * [Kintone アプリにプログレスバーを表示してみる。 \- Qiita](https://qiita.com/kusokamayarou/items/70acde31d3fd4e790478#%EF%BC%91-kintone-%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AB-javascript-%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA-progressbarjs-%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B)

# ３． [kintone](https://kintone.cybozu.co.jp/) アプリにフォームを設定する。

1. 以下のデータ構造に合わせて、項目を追加します。
    * [User APIで使用するデータの構造 – cybozu developer network](https://developer.cybozu.io/hc/ja/articles/205309740)
        * ※ フィールドコードには、上記のデータ構造のキーを設定します。
        * ※ 上記のデータ構造の項目を全て追加しなくても、大丈夫です。
    * ![FireShot Capture 383 - アプリの設定 - genbasupport.cybozu.com.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161939/265f21c9-6cbf-9c10-8b82-8808cc91f697.png)

# ９９．ハマりポイント

* ハマりポイントというか、そもそも [kintone](https://kintone.cybozu.co.jp/) アプリの JavaScript カスタマイズとか、HTML とか、JavaScript とかを普段あまりいじっておらず…、慣れてないので時間掛かっちゃいました…。
<br><br>
* Async/Await の使い方もよく知らなかったので、こちらの記事を参考にさせて頂きました。クロージャ使わないと、コードが綺麗になって見易くて Good !!!
    * [PromiseがわからないのでAsync/Awaitを使う \- Qiita](https://qiita.com/_muraham/items/697e1d53a09ff40530e5)
<br><br>
* あと、これは [kintone](https://kintone.cybozu.co.jp/) アプリの JavaScript カスタマイズでいつも困るんですが、POST や PUT の際の JSON フォーマットの形式ですね。いつもどの形式で登録するのかわからなくて、今回も結構ハマりました…。

# ＸＸ．まとめ

[cybozu\.com](https://www.cybozu.com/jp/) に登録されているユーザー情報について、組織によっては、全てのユーザー情報は見せられない、といった場合もあるかと思いますので、今回の Tips がそのまま使えるか難しい面もあるかと思いますが…。

参考になれば♪

👋👋👋