Kintone側からアプリ内レコードをGoogleスプレッドシートに出力したい

何を実現したいのかを書きましょう

Kintone側からアプリ内レコードをGoogleスプレッドシートに出力したいと考えています。

Kintoneのセキュリティー設定としては、IPアドレスによるセキュアアクセス設定を行っています。 Basic認証は設定していません。

発生した問題やエラーメッセージを具体的に書きましょう

アプリの一覧画面上にボタンを配置し、exportDataToGoogleSheet() を実行します。
実行しますと、Chromeのコンソールで次の箇所で下記エラーが発生してしまいます :smiling_face_with_tear:

どのようなところに原因が有りそうであるか、ご指摘を頂けますと幸いです。

プログラムができないため、様々なサイトを参照して組合せて実現しようとしておりますが、あまりにも実力不足のため苦戦しており、何卒皆様のお知恵をお借りしたく、ご質問させて頂きました。何卒お力添え賜りたく、お願い申し上げます :bowing_woman: :sweat_drops:

38行目
const data = JSON.parse(response[0]);

内容

download.do?app=59&contentId=2127&jsType=DESKTOP&hash=4e49de33a9e4c3b360c49ee481eb60d030cd8044:83 Error exporting data: SyntaxError: Unexpected token ‘<’, "<!DOCTYPE "... is not valid JSON at JSON.parse ()
at download.do?app=59&contentId=2127&jsType=DESKTOP&hash=4e49de33a9e4c3b360c49ee481eb60d030cd8044:38:27

esponse[0]の内容を見てみると、レコードではなく、なにかのHTMLを返しておりますが、内容からすると、セキュリティーの設定が影響しているような内容です。

現在、アクセス制限に「一部のIPアドレスを許可」としていることが影響している可能性は考えられますでしょうか?

**esponse[0]の内容
"

<!DOCTYPE html>
<html>
<head>
<script type="text/javascript"><!--
t = new String(location.hostname).split('.');
t.splice(1, 0, 's');
s = t.join('.');

url = "https://"+s+location.pathname+location.search+location.hash;
cookies = document.cookie.split(';');
for( i = 0; i < cookies.length; i++ ) {
    c = cookies[i].replace(/^\s+|\s+$/g, '').split('=');
    if( c.length == 2 && c[0] == 'goto_skylab' && c[1] == '1' ) {
        location.href = url;
    }
}
function goto_skylab() {
    document.cookie = 'goto_skylab=1; expires=Tue, 1-Dec-2037 00:00:00 GMT; path=/';
}
// -->
</script>
<meta charset="utf-8">
<title>Forbidden</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="/forest_error/css/style.css">
</head>

<body>
<div class="pagewrap">
  <div class="column"> <img src="/forest_error/image/cloud.png" width="178" height="107" alt="">
    <h2>アクセスするには認証が必要です。</h2>
    <p>クライアント証明書をお持ちでしたら、こちらでアクセスできます: </p>
    <p>
<script type="text/javascript"><!--
escaped_url = url.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
document.write('<a href="'+escaped_url+'">'+escaped_url+'</a>');
//-->
</script>
<a href=""></a>
            .</p>
          <form>
            <label><input class="checkbox" type="checkbox" name="auto" value="1" onclick="javascript:goto_skylab()" /> このボタンをチェックすると、以後自動的にリダイレクトします。</label>
          </form>
    <p>Code: 403 Forbidden</p>
  </div>
</div>
<div class="footer">
  <p>Copyright(C) Cybozu</p>
</div>
</body>
</html>
"

実行したコードをコピー&ペーストしましょう

UploadしたJSはこちらになります。


(() => {
    'use strict';
    kintone.events.on('app.record.index.show', (event) => {
      if (document.getElementById('my_index_button') !== null) {
        return;
      }
  
      const myIndexButton = document.createElement('button');
      myIndexButton.id = 'my_index_button';
      myIndexButton.innerText = '一覧のボタン';
  
      // ボタンクリック時の処理
      myIndexButton.onclick = () => {
        exportDataToGoogleSheet();
      };
  
      kintone.app.getHeaderMenuSpaceElement().appendChild(myIndexButton);
    });
  })();

  function getKintoneData(offset, allRecords = []) {
    const appId = '59';
    const apiUrl = `https://*****.cybozu.com/k/v1/records.json`;
    const apiToken = 'RdA2250bKAr3Du3Yq**************';
    const recordsPerRequest = 100;
  
    const headers = {
      'X-Cybozu-API-Token': apiToken,
    };
  
    const params = {
      app: appId,
      offset: offset,
    };
  
    return kintone.proxy(apiUrl, 'GET', headers, params)
      .then(response => {
        const data = JSON.parse(response[0]);
        allRecords = allRecords.concat(data.records);
  
        if (data.records.length === recordsPerRequest) {
          // There might be more records, make another request
          return getKintoneData(offset + recordsPerRequest, allRecords);
        } else {
          // All records have been retrieved
          return Promise.resolve(allRecords);
        }
      });
  }
  
  function writeToGoogleSheet(data) {
    const spreadsheetId = '1LfnU2xg4w0XNi***********************';
    const range = 'Sheet1'; // 任意のシート名
  
    const values = data.map(record => Object.values(record));
  
    const body = {
      values: values,
    };
  
    const options = {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(body),
    };
  
    const apiUrl = `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${range}:append?valueInputOption=RAW`;
  
    return kintone.proxy(apiUrl, 'POST', {}, options);
  }
  
  function exportDataToGoogleSheet() {
    getKintoneData(0)
      .then(kintoneData => {
        return writeToGoogleSheet(kintoneData);
      })
      .then(response => {
        console.log('Data exported successfully!', response);
      })
      .catch(error => {
        console.error('Error exporting data:', error);
      });
  }

const data = の前のresponse見るとrecordの取得からできてないようなので、console.log等使いながらどの段階で問題が発生しているか途中の情報確認してみてください。console.log(response)をconst data = ~の前に置いてみたり。

またレコードを取得,spreadSheetへの書き込みをasync await で同期的に処理してあげてください。レコード取得してる間に次の処理へ進み空の状態で書き込み始めます。

恐らくすべてのレコードを取得したいように見えたんでcursorを使いながらレコードを取得できるようにしたうえで、spreadSheetへ書き込んでいくようにしていくように作成してみてはいかがでしょうか?

(レコードの一括取得)カーソルを作成する

(レコードの一括取得)カーソルからレコードを取得する

カーソル部分だけのサンプル書きました(spreadsheet周りは試してません)

//kintone
const domain = "DOMAIN"
const apiToken = 'API_TOKEN';
const API_KEY = 'API_KEY';
const apiUrl = `https://${domain}.cybozu.com/k/v1/records.json`;

// スプレッドシートID
const SHEET_ID = 'SPREAD_SHEET_ID';
(() => {
	'use strict';
	kintone.events.on('app.record.index.show', (event) => {
		if (document.getElementById('my_index_button') !== null) return;

		const myIndexButton = document.createElement('button');
		myIndexButton.id = 'my_index_button';
		myIndexButton.innerText = '一覧のボタン';

		// ボタンクリック時の処理
		myIndexButton.addEventListener("click", exportDataToGoogleSheet);
		kintone.app.getHeaderMenuSpaceElement()?.appendChild(myIndexButton);
		return event
	});
})();

// レコード総合処理
const getKintoneData = async () => {
	let allRecords = []

	const body = {
		app: kintone.app.getId(),
		query: '',
		size: 500
	};
	return await kintone.api(kintone.api.url('/k/v1/records/cursor.json', true), 'POST', body)
		.then(async resp => { return await (getRecordsFromCursor(resp?.id, allRecords,)) });
}

// カーソルを使用してレコード取得
const getRecordsFromCursor = async (id, allRecords) => {
	const body = { id: id };
	return await kintone.api(kintone.api.url('/k/v1/records/cursor.json', true), 'GET', body)
		.then(async (resp) => {
			allRecords = [...allRecords, ...resp.records]
			if (resp.next) {
				allRecords = await getRecordsFromCursor(id, allRecords)
			}
			return allRecords
		})
		.catch(() => { deleteCursor(id); console.log("getRecords failed") });
}

const deleteCursor = async (id) => {
	// カーソルの削除
	const body = { id: id };
	await kintone.api(kintone.api.url('/k/v1/records/cursor.json', true), 'DELETE', body)
		.then(() => console.log("cursor delete success")).catch(() => console.log("cursor delete failed"));
}

const writeToGoogleSheet = async (records) => {
	const range = 'Sheet1'; // 任意のシート名
	const values = records.map((record) => Object.values(record));

	// const body = {
	// 	values: values,
	// };

	// const options = {
	// 	method: 'POST',
	// 	headers: {
	// 		'Content-Type': 'application/json',
	// 	},
	// 	body: JSON.stringify(body),
	// };
	// const apiUrl = `https://sheets.googleapis.com/v4/spreadsheets/${SHEET_ID}/values/${range}:append?valueInputOption=RAW`;
	// return kintone.proxy(apiUrl, 'POST', {}, options)
	// 	.then(resp => {
	// 		console.log(resp)
	// 	}).catch(err => {
	// 		console.log(err)
	// 	});
}

async function exportDataToGoogleSheet() {

	const records = await getKintoneData()
	await writeToGoogleSheet(records);
}

pomoさん、貴重なアドバイスを頂きありがとうございます :smiling_face_with_tear: 心より感謝です。ありがとうございます。 小職で分からない命令がたくさんですが、一つずつ理解して匍匐前進したいと思います。 まずは急ぎ御礼申し上げます。経過をまたご報告いたします。

「いいね!」 1

このトピックはベストアンサーに選ばれた返信から 3 日が経過したので自動的にクローズされました。新たに返信することはできません。