kintoneアプリにユーザ情報のバックアップする仕組みを作ってみた

はじめに

こんにちは。Y.HAMADAです。
cybozu.com共通管理にあるユーザー情報をkintoneアプリにバックアップするカスタマイズについてご紹介します。
こちらを作成するとカスタマイズの一部を変更するだけで、組織、役職、グループ、ユーザーの所属組織などのバックアップも保存できます。

このカスタマイズを作成した経緯ですが、ユーザー情報や組織などの変更するときに事前にバックアップを手動でファイルダウンロードしてました。このファイルは変更にミスがあったときに変更前の状態にするために使いますので一定期間経過後は不要なので削除します。もしかしたら使わないファイルの管理を簡単に楽にできないかと思い作成しました。

完成イメージ

バックアップの取得方法はボタンを押すと添付ファイルにユーザー.csvのレコードが追加されます。こちらがユーザー情報のバックアップのファイルとなります。
ファイル取得にはcybozu.com共通管理者の権限が必要となり、権限がないとエラーが表示されます。
権限については引用先のサイトをご覧ください。
引用:cybozu.com共通管理者の設定

ダウンロードの形式はユーザー情報 CSVとなります。

cybozu.com共通管理者の権限がないときに表示されます

では、始めていきましょう。

利用するライブラリについて

簡単にカスタマイズをするために、利用できる部分はライブラリを使います。

  • kintone UI Component
    kintoneライクなデザインを簡単に作成できるライブラリ。
    ボタンの表示に使います。
  • SweetAlert2
    おしゃれなダイアログが表示できるライブラリ。
    ボタンを押した後に表示される確認ダイアログに使います。
  • encoding.js
    文字コードを変換するライブラリ。
    ユーザー情報のCSVをAPIから取得した文字コードはUTF8ですので、Shift JISの変換に使います。手動でユーザ情報をダウンロードの時はShift JISですので、それに合わせるための対応となります。

ライブラリのファイルを取得する

kintone UI Componentの二つのファイル取得します。SweetAlert2、encoding.jsはCybozu CDNを利用するためファイル取得の必要はありません。
https://github.com/kintone-labs/kintone-ui-component/tree/v0_devにアクセスし、Zipファイルをダウンロードします。解答した中にある以下の2つのファイルを取得します。
 kintone-ui-component-master/dist/kintone-ui-component.min.js
 kintone-ui-component-master/dist/kintone-ui-component.min.css

ユーザー情報のCSVを取得するカスタマイズを実装する

こちらがカスタマイズの全体像となります。こちらを全てコピーしてファイルに保存します。保存するときの文字コードはUTF-8にしてください。ファイル名は一旦「ユーザー情報を取得.js」としてください。

(function () {
    "use strict";

    kintone.events.on("app.record.index.show", function (e) {

        let parent = kintone.app.getHeaderMenuSpaceElement();
        // ボタンがあるときは複数作成しない制御
        if (parent.querySelectorAll('button').length > 0) return;

        // ボタンの作成
        let button = new kintoneUIComponent.Button({
            text: 'ユーザー情報取得',
            type: 'submit'
        });
        button.on('click', async (event) => {
            const result = await Swal.fire({
                title: '確認',
                html: "ユーザー情報を取得します。</br>よろしいですか?",
                icon: 'info',
                showCancelButton: true,
            })
            if (!result.value) return;
            Swal.fire({
                title: 'データを登録中',
                text: 'お待ちください....',
                onBeforeOpen: () => { Swal.showLoading() }
            })
            await clickButton();
        });

        // ボタンを追加
        parent.appendChild(button.render())
        return e;
    });

    const clickButton = async function () {
        // ユーザー情報CSVファイルダウンロード
        let url = window.location.protocol + "//" + window.location.host + '/v1/csv/user.csv';
        const res = await fetch(url, {
            method: 'GET',
            headers: { "x-requested-with": "XMLHttpRequest" }
        })

        // 権限がないときはエラー表示して終了
        if (res.status != 200) {
            Swal.fire({
                title: '失敗',
                html: "CSVの取得に失敗しました。",
                icon: 'error',
            });
            return
        }

        // 文字コードをutf8→sjisに変換
        let blob = await res.blob();
        let arrayBuffer = await blob.arrayBuffer();
        let uint8Array = new Uint8Array(arrayBuffer);
        let sjisArray = Encoding.convert(uint8Array, { to: 'SJIS', from: 'UTF8' });
        let sjisBuffer = new Uint8Array(sjisArray).buffer
        let sjisBlob = new Blob([sjisBuffer], { type: "application/csv" });

        // CSVファイルアップロード
        let formData = new FormData();
        formData.append('__REQUEST_TOKEN__', kintone.getRequestToken());
        formData.append('file', sjisBlob, 'ユーザー.csv');
        let { fileKey } = await fetch(kintone.api.url('/k/v1/file', true), {
            headers: { "x-requested-with": "XMLHttpRequest" },
            method: 'POST',
            body: formData
        }).then(res => res.json())

        // アップロードしたCSVファイルのキーを添付ファイルに設定して、レコードに追加
        let body = {
            'app': kintone.app.getId(),
            'record': { '添付ファイル': { 'value': [{ fileKey }] } }
        };
        await kintone.api(kintone.api.url('/k/v1/record', true), 'POST', body);

        Swal.fire({
            title: '完了',
            html: "CSVの取得が完了しました。",
            icon: 'info',
            timer: 1500,
            onClose: () => { location.reload(); }
        });
    }
})();


冒頭に述べたカスタマイズの一部を変更するだけで組織、役職、グループ、ユーザーの所属組織などのバックアップも保存できますという点ですが、組織の方法について説明します。
変更箇所はユーザー情報を取得.jsの38行目と65行目を以下の通りに変更してください。

変更前

(function () {
    "use strict";

    kintone.events.on("app.record.index.show", function (e) {

        let parent = kintone.app.getHeaderMenuSpaceElement();
        // ボタンがあるときは複数作成しない制御
        if (parent.querySelectorAll('button').length > 0) return;

        // ボタンの作成
        let button = new kintoneUIComponent.Button({
            text: 'ユーザー情報取得',
            type: 'submit'
        });
        button.on('click', async (event) => {
            const result = await Swal.fire({
                title: '確認',
                html: "ユーザー情報を取得します。</br>よろしいですか?",
                icon: 'info',
                showCancelButton: true,
            })
            if (!result.value) return;
            Swal.fire({
                title: 'データを登録中',
                text: 'お待ちください....',
                onBeforeOpen: () => { Swal.showLoading() }
            })
            await clickButton();
        });

        // ボタンを追加
        parent.appendChild(button.render())
        return e;
    });

    const clickButton = async function () {
        // ユーザー情報CSVファイルダウンロード
        let url = window.location.protocol + "//" + window.location.host + '/v1/csv/user.csv';
        const res = await fetch(url, {
            method: 'GET',
            headers: { "x-requested-with": "XMLHttpRequest" }
        })

        // 権限がないときはエラー表示して終了
        if (res.status != 200) {
            Swal.fire({
                title: '失敗',
                html: "CSVの取得に失敗しました。",
                icon: 'error',
            });
            return
        }

        // 文字コードをutf8→sjisに変換
        let blob = await res.blob();
        let arrayBuffer = await blob.arrayBuffer();
        let uint8Array = new Uint8Array(arrayBuffer);
        let sjisArray = Encoding.convert(uint8Array, { to: 'SJIS', from: 'UTF8' });
        let sjisBuffer = new Uint8Array(sjisArray).buffer
        let sjisBlob = new Blob([sjisBuffer], { type: "application/csv" });

        // CSVファイルアップロード
        let formData = new FormData();
        formData.append('__REQUEST_TOKEN__', kintone.getRequestToken());
        formData.append('file', sjisBlob, 'ユーザー.csv');
        let { fileKey } = await fetch(kintone.api.url('/k/v1/file', true), {
            headers: { "x-requested-with": "XMLHttpRequest" },
            method: 'POST',
            body: formData
        }).then(res => res.json())

        // アップロードしたCSVファイルのキーを添付ファイルに設定して、レコードに追加
        let body = {
            'app': kintone.app.getId(),
            'record': { '添付ファイル': { 'value': [{ fileKey }] } }
        };
        await kintone.api(kintone.api.url('/k/v1/record', true), 'POST', body);

        Swal.fire({
            title: '完了',
            html: "CSVの取得が完了しました。",
            icon: 'info',
            timer: 1500,
            onClose: () => { location.reload(); }
        });
    }
})();

(function () {
    "use strict";

    kintone.events.on("app.record.index.show", function (e) {

        let parent = kintone.app.getHeaderMenuSpaceElement();
        // ボタンがあるときは複数作成しない制御
        if (parent.querySelectorAll('button').length > 0) return;

        // ボタンの作成
        let button = new kintoneUIComponent.Button({
            text: 'ユーザー情報取得',
            type: 'submit'
        });
        button.on('click', async (event) => {
            const result = await Swal.fire({
                title: '確認',
                html: "ユーザー情報を取得します。</br>よろしいですか?",
                icon: 'info',
                showCancelButton: true,
            })
            if (!result.value) return;
            Swal.fire({
                title: 'データを登録中',
                text: 'お待ちください....',
                onBeforeOpen: () => { Swal.showLoading() }
            })
            await clickButton();
        });

        // ボタンを追加
        parent.appendChild(button.render())
        return e;
    });

    const clickButton = async function () {
        // ユーザー情報CSVファイルダウンロード
        let url = window.location.protocol + "//" + window.location.host + '/v1/csv/user.csv';
        const res = await fetch(url, {
            method: 'GET',
            headers: { "x-requested-with": "XMLHttpRequest" }
        })

        // 権限がないときはエラー表示して終了
        if (res.status != 200) {
            Swal.fire({
                title: '失敗',
                html: "CSVの取得に失敗しました。",
                icon: 'error',
            });
            return
        }

        // 文字コードをutf8→sjisに変換
        let blob = await res.blob();
        let arrayBuffer = await blob.arrayBuffer();
        let uint8Array = new Uint8Array(arrayBuffer);
        let sjisArray = Encoding.convert(uint8Array, { to: 'SJIS', from: 'UTF8' });
        let sjisBuffer = new Uint8Array(sjisArray).buffer
        let sjisBlob = new Blob([sjisBuffer], { type: "application/csv" });

        // CSVファイルアップロード
        let formData = new FormData();
        formData.append('__REQUEST_TOKEN__', kintone.getRequestToken());
        formData.append('file', sjisBlob, 'ユーザー.csv');
        let { fileKey } = await fetch(kintone.api.url('/k/v1/file', true), {
            headers: { "x-requested-with": "XMLHttpRequest" },
            method: 'POST',
            body: formData
        }).then(res => res.json())

        // アップロードしたCSVファイルのキーを添付ファイルに設定して、レコードに追加
        let body = {
            'app': kintone.app.getId(),
            'record': { '添付ファイル': { 'value': [{ fileKey }] } }
        };
        await kintone.api(kintone.api.url('/k/v1/record', true), 'POST', body);

        Swal.fire({
            title: '完了',
            html: "CSVの取得が完了しました。",
            icon: 'info',
            timer: 1500,
            onClose: () => { location.reload(); }
        });
    }
})();


変更後

(function () {
    "use strict";

    kintone.events.on("app.record.index.show", function (e) {

        let parent = kintone.app.getHeaderMenuSpaceElement();
        // ボタンがあるときは複数作成しない制御
        if (parent.querySelectorAll('button').length > 0) return;

        // ボタンの作成
        let button = new kintoneUIComponent.Button({
            text: 'ユーザー情報取得',
            type: 'submit'
        });
        button.on('click', async (event) => {
            const result = await Swal.fire({
                title: '確認',
                html: "ユーザー情報を取得します。</br>よろしいですか?",
                icon: 'info',
                showCancelButton: true,
            })
            if (!result.value) return;
            Swal.fire({
                title: 'データを登録中',
                text: 'お待ちください....',
                onBeforeOpen: () => { Swal.showLoading() }
            })
            await clickButton();
        });

        // ボタンを追加
        parent.appendChild(button.render())
        return e;
    });

    const clickButton = async function () {
        // ユーザー情報CSVファイルダウンロード
        let url = window.location.protocol + "//" + window.location.host + '/v1/csv/organization.csv';
        const res = await fetch(url, {
            method: 'GET',
            headers: { "x-requested-with": "XMLHttpRequest" }
        })

        // 権限がないときはエラー表示して終了
        if (res.status != 200) {
            Swal.fire({
                title: '失敗',
                html: "CSVの取得に失敗しました。",
                icon: 'error',
            });
            return
        }

        // 文字コードをutf8→sjisに変換
        let blob = await res.blob();
        let arrayBuffer = await blob.arrayBuffer();
        let uint8Array = new Uint8Array(arrayBuffer);
        let sjisArray = Encoding.convert(uint8Array, { to: 'SJIS', from: 'UTF8' });
        let sjisBuffer = new Uint8Array(sjisArray).buffer
        let sjisBlob = new Blob([sjisBuffer], { type: "application/csv" });

        // CSVファイルアップロード
        let formData = new FormData();
        formData.append('__REQUEST_TOKEN__', kintone.getRequestToken());
        formData.append('file', sjisBlob, 'ユーザー.csv');
        let { fileKey } = await fetch(kintone.api.url('/k/v1/file', true), {
            headers: { "x-requested-with": "XMLHttpRequest" },
            method: 'POST',
            body: formData
        }).then(res => res.json())

        // アップロードしたCSVファイルのキーを添付ファイルに設定して、レコードに追加
        let body = {
            'app': kintone.app.getId(),
            'record': { '添付ファイル': { 'value': [{ fileKey }] } }
        };
        await kintone.api(kintone.api.url('/k/v1/record', true), 'POST', body);

        Swal.fire({
            title: '完了',
            html: "CSVの取得が完了しました。",
            icon: 'info',
            timer: 1500,
            onClose: () => { location.reload(); }
        });
    }
})();

(function () {
    "use strict";

    kintone.events.on("app.record.index.show", function (e) {

        let parent = kintone.app.getHeaderMenuSpaceElement();
        // ボタンがあるときは複数作成しない制御
        if (parent.querySelectorAll('button').length > 0) return;

        // ボタンの作成
        let button = new kintoneUIComponent.Button({
            text: 'ユーザー情報取得',
            type: 'submit'
        });
        button.on('click', async (event) => {
            const result = await Swal.fire({
                title: '確認',
                html: "ユーザー情報を取得します。</br>よろしいですか?",
                icon: 'info',
                showCancelButton: true,
            })
            if (!result.value) return;
            Swal.fire({
                title: 'データを登録中',
                text: 'お待ちください....',
                onBeforeOpen: () => { Swal.showLoading() }
            })
            await clickButton();
        });

        // ボタンを追加
        parent.appendChild(button.render())
        return e;
    });

    const clickButton = async function () {
        // ユーザー情報CSVファイルダウンロード
        let url = window.location.protocol + "//" + window.location.host + '/v1/csv/organization.csv';
        const res = await fetch(url, {
            method: 'GET',
            headers: { "x-requested-with": "XMLHttpRequest" }
        })

        // 権限がないときはエラー表示して終了
        if (res.status != 200) {
            Swal.fire({
                title: '失敗',
                html: "CSVの取得に失敗しました。",
                icon: 'error',
            });
            return
        }

        // 文字コードをutf8→sjisに変換
        let blob = await res.blob();
        let arrayBuffer = await blob.arrayBuffer();
        let uint8Array = new Uint8Array(arrayBuffer);
        let sjisArray = Encoding.convert(uint8Array, { to: 'SJIS', from: 'UTF8' });
        let sjisBuffer = new Uint8Array(sjisArray).buffer
        let sjisBlob = new Blob([sjisBuffer], { type: "application/csv" });

        // CSVファイルアップロード
        let formData = new FormData();
        formData.append('__REQUEST_TOKEN__', kintone.getRequestToken());
        formData.append('file', sjisBlob, '組織.csv');
        let { fileKey } = await fetch(kintone.api.url('/k/v1/file', true), {
            headers: { "x-requested-with": "XMLHttpRequest" },
            method: 'POST',
            body: formData
        }).then(res => res.json())

        // アップロードしたCSVファイルのキーを添付ファイルに設定して、レコードに追加
        let body = {
            'app': kintone.app.getId(),
            'record': { '添付ファイル': { 'value': [{ fileKey }] } }
        };
        await kintone.api(kintone.api.url('/k/v1/record', true), 'POST', body);

        Swal.fire({
            title: '完了',
            html: "CSVの取得が完了しました。",
            icon: 'info',
            timer: 1500,
            onClose: () => { location.reload(); }
        });
    }
})();


その他については38行目の/v1/csv/user.csvの部分を取得したいものに変更し、65行目のユーザー.csvも同様に変更ください。
 役職       :/v1/csv/title.csv
 グループ     :/v1/csv/group.csv
 ユーザーの所属組織:/v1/csv/userOrganizations.csv

アプリを作成する

CSVを保存する添付ファイルのフィールドのみのアプリを作成します。フィールドコードは「添付ファイル」としてください。

カスタマイズを追加する

「JavaScript/CSSでカスタマイズ」に以下のように設定してください。
作成したファイルはアップロードし、Cybozu CDNはURLで追加してください。
Cybozu CDNの一覧は引用元をご覧ください。
引用元:Cybozu CDN

PC用のJavaScriptファイル
ユーザー情報を取得.js
kintone-ui-component.min.js
https://js.cybozu.com/sweetalert2/v9.15.3/sweetalert2.min.js
https://js.cybozu.com/encodingjs/1.0.30/encoding.min.js
PC用のCSSファイル
kintone-ui-component.min.css
https://js.cybozu.com/sweetalert2/v9.15.3/sweetalert2.min.css

以上で設定は終わりです。
完成イメージのようになったか確認してみてください。

最後に

いかがでしょうか。ライブラリを使うことでコード量も少なくカスタマイズができたかと思います。カスタマイズをすることで同じ作業の手間が少しでも削減すると助かりますよね。
最後まで記事を読んでいただき、ありがとうございました。

※本記事の内容に関するサポートは行っておりませんので、ご了承ください。