フォルダツリーレポートについて

Yuko Taniguchi
Box Developer Japan Blog
13 min read6 days ago

--

Boxでプラットフォームソリューションエンジニアを務めている私は、Boxが直接解決できないエッジ要件を抱えているお客様に出会うことがよくあります。このようなケースでは、既存の統合を探すことが多いものの、場合によっては、BoxのAPIを使用してカスタムアプリを構築することが近道になります。最近、プロトタイプを作成する機会があったため、この記事で紹介します。

これを、エクスポート可能なディーリングルームの概要 (そのままの名前…)、または単にフォルダツリーレポートと呼ぶことにします。ディーリングルームの概念は、法曹界や金融業界のようないくつかの業界で、取引/プロジェクトなどでの共同作業に使用されています。多くの場合は、固定のフォルダ構造で構成されており、ドキュメントのコラボレーションが行われる権限は固定されています。このプロトタイプを使用して達成しようとしている要件は、ファイルとそのファイルへの共有リンクを含むフォルダツリー全体が表示される、エクスポート可能なディーリングルームの概要の作成です。これは、デューデリジェンスの確認に役立つほか、ディーリングルームの内容に簡単にアクセスして概要を把握するためのポイントインタイムリファレンスとしても、また取引成立時にアクセスするためのポイントインタイムリファレンスとしても機能します。当然ながら、上記の業界以外のユースケースもあります。

これがどのようになるかを以下に示します。

解決方法

私が思いついた解決方法は、フォルダで呼び出すことができるウェブアプリ統合として実装することでした。APIアクセスには、今年の初めにBoxが公開した新しいTypeScript SDKを使用しました。これは、Boxの自動生成された新しいSDKプッシュに含まれています。

ウェブアプリ統合と認証

Boxウェブアプリ統合を作成するには、まずOAuth 2.0アプリを作成します。このアプリでは、統合の承認と認証を管理します。

開発者コンソールでは、簡単な2つの手順でカスタムOAuth 2.0アプリを作成します。

[構成] タブにあるアプリのclientIdとclientSecretが必要になります。

次に、[統合] タブで統合を作成します。Boxウェブアプリでユーザーに表示される内容はこちらです。

コールバックURLはhttpsである必要があるため、ローカルホストで最初の開発とテストを行いましたが、最終的にアプリをhttpsエンドポイントに展開する必要があります。このプロトタイプには、無料で簡単に設定できるGitHubページを使用しました。認証の部分には、以前に使用したシンプルなAWS Lambda関数という方法を使用しました。この関数は、こちらのGitHubリポジトリの詳細な手順に従って設定できます。

私が使用するクライアントコールバックURLは以下のとおりです。パラメータは、トークンジェネレータで使用されるため、以下に示すように名前を付ける必要があります。clientIdは、トークン交換を実行するAWS Lambda関数に必要です。 https://pcboxdemo.github.io/indexer/index.html?clientId=THE_CLIENT_ID_COPIED_EARLIER

アプリを作成して構成すると、フォルダの [統合] メニューにこの新しい項目が表示されます。

認証はAWS Lambda関数で行われます。この関数では、承認が付与されているトークンアクセスリクエストを使用して、承認コードがアクセストークンに交換されます。LambdaのコードではNode.js SDKを使用します。

var sdk = new BoxSDK({
clientID: clientId,
clientSecret: secret,
});
//Get the tokens from Box using the Auth code
await sdk.getTokensAuthorizationCodeGrant(
authCode, null, function (err, tokenInfo) {
tokens = {
token: tokenInfo.accessToken,
refreshToken: tokenInfo.refreshToken
};
}
);

このLambdaにより、上記のトークン交換時に取得したトークンが呼び出し元ページに返されます。また、index.htmlページの認証部分は以下のようになります。ページが読み込まれると実行されるので、すぐにBox APIにアクセスできます。

$.ajax({
method: 'get',
url:"https://MY_LAMBDA_GATEWAY_URL/default/box-node-token-generator",
data: { authCode: params.get('auth'), clientId: params.get('clientId') },
crossDomain: true,
cache: false,
success: async function (response) {
//set the token
token = response.token;
try {
//Create a Box client using the token
client = new BoxClient({auth: new BoxDeveloperTokenAuth({token }),
});
//Enable the generate button as we now have access to Box API
$("#generate").prop("disabled",false);
} catch (e) {
console.error(e);
}
}
});

アプリケーション

アプリケーションは単一のウェブページとして作成します。

共有リンクの種類を選択する

ページの上部には、ユーザーがレポートに必要な共有リンクの種類を選択できるように以下のUIがあります (デフォルトでは [None] になっています)。

ボタンと共有リンクオプションを含むページヘッダー

フォルダ構造を生成する

ユーザーが [Generate Report (レポートの生成)] ボタンをクリックすると、以下の関数が呼び出されます。フォルダ構造全体を生成するには、フォルダ内の項目のリストを取得する関数を再帰的に呼び出します。

async function getItems(folderId,parentId) {
let items = await client.folders.getFolderItems(folderId);

次に、フォルダの項目を反復処理し、項目がフォルダの場合は、関数を再度呼び出して、必要なすべてのデータを確実に取得します。レンダリングでは、コンテナとして各フォルダに<ul>要素を使用し、その中に含まれる個々のフォルダに<li>要素を使用します。

$.each(items.entries, function(k, data) {
//If folder, add it to the list and get items for this folder
if(data.type=='folder') {
var folderListContainer = $("<ul id=" +data.id + "></ul");
//Append this folder to the list of folders
folderListContainer.append($("<li class='uf' parent=" +
parentId + ">"+data.name + "</li>"));
//Append the list of folders to the parent
$("#" + folderId).append(pfolderListContainer);
getItems(data.id,folderId);
}

項目がファイルの場合は、そのファイルとの共有リンクをリクエストされたかどうかを確認します。リクエストされなかった場合は、<li>要素にファイル名を追加し、親フォルダに追加するだけです。アプリには標準のBoxファイル用のアイコンが備わっているため、ファイル拡張子に基づいて適切なアイコンが使用されます。

else if(data.type=='file') {
//If no shared link required, just output the file name and icon and attach to parent
if(sharedLink=='None') {
fileListContainer.append($("<li class='pf' id='u_" +
folderId + "'><img height=16 width=16 src='iconsbox/" +
data.name.split('.').pop().toLowerCase() + ".svg'/> "+
data.name +"</li>"));
}

共有リンクがリクエストされた場合は、リンクを生成 (または再利用) した後、親フォルダに追加します。多くのリンクを作成しなくてはならない可能性があるため、共有リンクが作成されるまでは、この処理を非同期で実行し、ファイルにプレースホルダテキストを使用してリンクを動的にします。

else {
//If shared link requested - first check if shared link already exists
var link;
if(data.shared_link==null || data.shared_link.access!=sharedLink) {
//Create shared link and append file with place holder text.
//Will be updated by async function
link = createSharedLink(data.id,sharedLink,data.name);
fileListContainer.append($("<li class='pf' id='u_" + folderId + "'>" +
"<img height=16 width=16 src='iconsbox/" +
data.name.split('.').pop().toLowerCase() + ".svg'/> "+
"<a id=l_"+data.id + " href=" + link + " target=_blank>" +
"Generating link..</li>"));
}
else {
//Use existing shared link and append file
link = data.shared_link.url;
fileListContainer.append($("<li class='pf' id='u_" + folderId + "'>" +
"<img height=16 width=16 src='iconsbox/" +
data.name.split('.').pop().toLowerCase() + ".svg'/> "+
"<a id=l_"+data.id + " href=" + link + " target=_blank>" +
data.name +"</li>"));
}
}
}

共有リンクは、共有リンクをファイルに追加する関数を使用して非同期的に作成されます。

async function createSharedLink(fileId,type,name) {
let sharedLinkObject = await client.sharedLinksFiles.addShareLinkToFile(
fileId,
{sharedLink: { access: sharedLink } }
,{ fields: 'shared_link' } );
//Set the URL for the file <li> to the shared link
$("#l_" + fileId).attr("href",sharedLinkObject.sharedLink.url);
//Set the text to the name of the file
$("#l_" + fileId).text(name);
};

PDFにエクスポートする

PDFにエクスポートする場合は、html2pdfライブラリを使用します。このライブラリは、任意のDOM要素を取得してPDFに変換し、ユーザー用にダウンロードします。

//Generates a PDF from specified HTML DOM Element
$("#pdfgen").click( function() {
const element = document.getElementById('index');
// Choose the element and save the PDF for your user.
html2pdf().from(element).save();
})

動的なレポートの生成は、完了すると次のようになります。

ここまで、お読みいただきありがとうございました。このプロトタイプのリポジトリ一式を以下に示します。これには、アプリをBoxの統合として設定するための手順のほか、ローカルでのテストと開発のためにスタンドアロンアプリとして実行するための手順が記載されています。

フォルダツリーレポートのGitHubリポジトリ

Box Platformの上級者から学びたい場合🦄

ポートや知識共有のためのBox Developer Community (英語のみ) にご参加ください。

--

--