新規投稿
フォローする

js-sdkを使ってアプリ&レコードコピー

js-sdkというSDKが登場しました。 以前からあるkintone-js-sdkとは別物になります。 今回はjs-sdkを使った、アプリとレコードコピーのサンプルを紹介します。 以前紹介したkintone-js-sdk版で対応していなかった一部設定値(カスタマイズ設定など)のコピーにも対応しました。

コード

const {KintoneRestAPIClient} = require('@kintone/rest-api-client');
const client = new KintoneRestAPIClient({
  baseUrl: 'https://****.cybozu.com',
  auth: {username: '****', password: '****'},
  // basicAuth: {username: '****', password: '****'} // ベーシック認証が必要な場合のみ
});
const reduce = require('awaity/reduce').default;
const originAppId = ****;

const addAllRecords = ({app, records}) => {
  const limit = 100;
  return Promise.all(
    records.reduce((recordsBlocks, record) => {
      if(recordsBlocks[recordsBlocks.length - 1].length === limit){
        recordsBlocks.push([record]);
      }else{
        recordsBlocks[recordsBlocks.length - 1].push(record);
      }
      return recordsBlocks;
    }, [[]]).map(recordsBlock => (
      client.record.addRecords({
        app: app,
        records: recordsBlock
      })
    ))
  );
};
const copyFile = async file => (
  (await client.file.uploadFile({
    file: {
      name: file.name,
      data: await client.file.downloadFile({fileKey: file.fileKey})
    }
  })).fileKey
);
const formatProperties = properties => (
  Object.fromEntries(Object.entries(properties).filter(([fieldCode]) => (
    ![
      'レコード番号',
      '$id',
      '$revision',
      '作成者',
      '更新者',
      '作成日時',
      '更新日時',
      'カテゴリー',
      '作業者',
      'ステータス'
    ].includes(fieldCode)
  )))
);
const formatRecords = records => (
  Promise.all(records.map(record => (
    reduce(Object.entries(record).filter(([fieldCode]) => (
      ![
        'レコード番号',
        '$id',
        '$revision',
        '作成者',
        '更新者',
        '作成日時',
        '更新日時'
      ].includes(fieldCode)
    )), async(record, [fieldCode, field]) => {
      record[fieldCode] = field;
      if(field.type === 'FILE'){
        record[fieldCode].value = await Promise.all(field.value.map(async file => ({
          ...file,
          fileKey: await copyFile(file)
        })));
      }else if(field.type === 'SUBTABLE'){
        record[fieldCode].value = await Promise.all(field.value.map(async row => ({
          ...row,
          value: await reduce(Object.entries(row.value), async(rowValue, [fieldCode, field]) => {
            rowValue[fieldCode] = field;
            if(field.type === 'FILE'){
              rowValue[fieldCode].value = await Promise.all(field.value.map(async file => ({
                ...file,
                fileKey: await copyFile(file)
              })));
            }
            return rowValue;
          }, {})
        })));
      }
      return record;
    }, {})
  )))
);
const formatAppCustomizeFiles = appCustomizeFiles => (
  Promise.all(appCustomizeFiles.map(async appCustomizeFile => (
    (appCustomizeFile.type !== 'FILE') ?
      appCustomizeFile : {
        type: 'FILE',
        file: {
          fileKey: await copyFile(appCustomizeFile.file)
        }
      }
  )))
);

(async()=>{
  const originApp = await Promise.all([
    client.app.getApp({id: originAppId}).then(app => ({app})),
    client.app.getAppSettings({app: originAppId}).then(appSettings => ({appSettings})),
    client.app.getFormFields({app: originAppId}).then(formFields => ({formFields})),
    client.app.getFormLayout({app: originAppId}).then(formLayout => ({formLayout})),
    client.app.getViews({app: originAppId}).then(views => ({views})),
    client.app.getAppAcl({app: originAppId}).then(appAcl => ({appAcl})),
    client.app.getFieldAcl({app: originAppId}).then(fieldAcl => ({fieldAcl})),
    client.app.getRecordAcl({app: originAppId}).then(recordAcl => ({recordAcl})),
    client.app.getAppCustomize({app: originAppId}).then(appCustomize => ({appCustomize})),
    client.app.getProcessManagement({app: originAppId}).then(processManagement => ({processManagement})),
    client.record.getAllRecordsWithCursor({
      app: originAppId,
      query: 'order by $id asc'
    }).then(records => ({records}))
  ]).then(responses => {
    return responses.reduce((originApp, response) => {
      return {
        ...originApp,
        [Object.keys(response)[0]]: Object.values(response)[0]
      };
    }, {});
  });
  const copyAppName = `${originApp.app.name}_copy_${new Date()}`;
  const copyAppId = await client.app.addApp({name: copyAppName}).then(response => response.app);
  await client.app.updateAppSettings({
    app: copyAppId,
    description: originApp.appSettings.description,
    icon: originApp.appSettings.icon,
    theme: originApp.appSettings.theme
  });
  await client.app.addFormFields({
    app: copyAppId,
    properties: formatProperties(originApp.formFields.properties)
  });
  await client.app.updateFormLayout({
    app: copyAppId,
    layout: originApp.formLayout.layout
  });
  await client.app.updateViews({
    app: copyAppId,
    views: originApp.views.views
  });
  await client.app.updateAppAcl({
    app: copyAppId,
    rights: originApp.appAcl.rights
  });
  await client.app.updateFieldAcl({
    app: copyAppId,
    rights: originApp.fieldAcl.rights
  });
  await client.app.updateRecordAcl({
    app: copyAppId,
    rights: originApp.recordAcl.rights
  });
  await client.app.updateAppCustomize({
    app: copyAppId,
    scope: originApp.appCustomize.scope,
    desktop: {
      js: await formatAppCustomizeFiles(originApp.appCustomize.desktop.js),
      css: await formatAppCustomizeFiles(originApp.appCustomize.desktop.css)
    },
    mobile: {
      js: await formatAppCustomizeFiles(originApp.appCustomize.mobile.js),
      css: await formatAppCustomizeFiles(originApp.appCustomize.mobile.css)
    },
  });
  await client.app.updateProcessManagement({
    app: copyAppId,
    enable: originApp.processManagement.enable,
    states: originApp.processManagement.states,
    actions: originApp.processManagement.actions,
  });
  await client.app.deployApp({apps: [{app: copyAppId}]});
  await new Promise(resolve => {
    const timer = setInterval(async() => {
      const status = await client.app.getDeployStatus({apps: [copyAppId]}).then(response => response.apps[0].status);
      if(status === 'SUCCESS'){
        clearInterval(timer);
        resolve();
      }
    }, 1000);
  });
  await addAllRecords({
    app: copyAppId,
    records: await formatRecords(originApp.records)
  });
  console.log(`「${originApp.app.name}(appId:${originAppId})」を「${copyAppName}(appId:${copyAppId})」へコピーしました。`);
})();

・Node.js v12以上で動作します。(Object.fromEntries()を利用しているため。)
・「@kintone/js-sdk」及び「awaity」はnpm等でインストールして下さい。
・add、update処理に関してはエラーを避けるために直列処理としております。(参考)

kintone-js-sdkに対するjs-sdkの違い

・APIを利用するまでの記述が少ない。
・対応しているAPIの種類が多い。
・ファイル操作時にローカルのストレージを介さない。

1

1件のコメント

Avatar
kin太郎

サンプルのご紹介や「kintone-js-sdk」との違いの説明ありがとうございます。

「kintone Utility Library for JavaScript」→「kintone-js-sdk」→「js-sdk」

と移り変わりが早いですね。

 

0
サインインしてコメントを残してください。