ActiveReportsJS帳票のデータソースにDataverseを利用する

JavaScript帳票ライブラリ「ActiveReportsJS(アクティブレポートJS)」では、様々なクラウドサービスのデータソースへの接続が可能です。ActiveReportsJSの帳票アプリを、各クラウドサービスで用意されているWeb APIに接続することで簡単にWeb帳票を作成することができます。

今回の記事では、ローコードツール「Microsoft Power Apps」や、企業向けシステム「Microsoft Dynamics 365」のデータソースとしても知られている「Microsoft Dataverse」をActiveReportsJS帳票のデータソースとして利用する方法についてご紹介します。

「Dataverse」については、以前公開した記事で詳しく紹介していますので、あわせてご確認ください。

事前準備

事前準備として次の環境やツールを準備します。

Power Apps環境

Dataverseの環境の準備として、「Power Apps」の開発者向けプランを使用します。事前準備として開発者向けプランのアカウントの準備をしてください。

Microsoft 365環境

DataverseのWeb APIと作成したアプリケーションの接続を行う為、アプリケーションへの認証・認可の設定が行える、Microsoft Entra管理センター(旧Azure AD管理センター)を使用します。準備するMicrosoft365環境では、この機能を使用可能な権限を持つユーザーアカウントを使用します。今回は記事ではMicrosoft 365 E5 開発者サンドボックス サブスクリプションの環境を利用していきます。この環境はMicrosoft 365 開発者プログラムに参加することで使用可能ですので、Microsoft Entra管理センターを使用できない場合は、こちらの環境を準備してください。

環境のセットアップ手順については、Microsoft Learnのセットアップ手順を参考にデフォルトの状態でセットアップしてください。

Postman

PostmanはWeb APIの設計・開発、テストをサポートする為のツールです。ツールを通してWeb APIに対しリクエストを送りレスポンスの確認などが行えます。今回はWeb APIの認証情報(アクセストークン)を取得する為に利用します。こちらから最新バージョンを取得しインストールしてご使用ください。

ベータ版ですが、Visual Studio Code拡張機能としてPostmanがリリースされています。インストール版と同様の機能が使えるようですので、ご興味ある方はこちらの動作も確認してみてください。

Visual Studio Code

Webアプリケーションの実行時に使用します。こちらから最新バージョンを取得しインストールしてご使用ください。

ActiveReportsJS

今回はActiveReportsJS「V4J(v4.0.2)」を使用します。事前準備として、あらかじめ製品版、またはトライアル版をインストールしてください。トライアル版は無料で以下より入手可能です。

テーブルの作成

最初にレポートファイルの出力する為のテーブルとデータを準備します。
今回は以前紹介した記事の中で作成した、「Orders」テーブルとデータを利用したいと思います。テーブルの作成方法およびデータの準備については以下の記事をご確認ください。

Dataverseテーブル

レポートファイルの作成

Dataverse上にテーブルの用意が出来たらレポートファイルを作成します。ActiveReportsJSのデザイナを開き、新規レポートを作成します(今回はページレポートで作成します)。

ActiveReportsJSデザイナ

データソースの作成

続いてデザイナよりデータソースの指定を行っていきます。今回はデータソースを「Dataverse」のWeb APIに接続したいと思いますが、接続を行うには、認証情報(アクセストークン)の設定が必要となります。ActiveReportsJSのデザイナーには認証情報(アクセストークン)の取得機能はありませんので、Web API開発ツールの「Postman」を使用して情報を取得します。

「Postman」を使用し認証情報(アクセストークン)の取得するには、「Dataverse」へ接続設定を行う必要があります。Microsoft LearnのPostman 環境の設定に詳しい設定方法が記載されていますので、同様に設定していきます。接続環境設定後、認証情報(アクセストークン)を生成すると次の画像のように、リクエストヘッダーの「Authorization」のアクセストークンが生成されますので、このアクセストークンをActiveReportsJSのデザイナーで使用します。

Postmanアクセストークン

続いて、Dataverseの接続対象となるWeb APIエンドポイントを取得します。次の画像のようにDataverseのテーブルを開き、「ツール」→「テーブルデータへのAPIリンク」を選択します。

Web APIエンドポイントの取得

選択すると次のように別タブにテーブルデータが表示され、アドレスバーからWeb APIエンドポイントのリンクが参照可能となります。

Web APIエンドポイントの取得2

Web APIエンドポイントのリンクは以下となります。

https://“Dataverse環境のドメイン”/api/data/v9.2/cr13c_orderses?$top=10

Dataverse環境への接続情報として、「認証情報(アクセストークン)」、「Web APIエンドポイント」がそれぞれ準備できましたので、実際にデザイナー上でデータソースを設定していきます。次のようにデザイナの「データ」タブを選択し、「データソース」の項目の[追加]をクリックします。

データソースの追加1

「データソースの編集」ダイアログが表示されたら次のように各項目を設定を行い、[変更を保存]をクリックしデータソースを追加します。

項目設定値
データプロバイダRemote JSON
エンドポイントDataverseのWeb APIエンドポイント
例:https://“Dataverse環境のドメイン”/api/data/v9.2/cr13c_orderses
※ クエリパラメータとなる「$top=10」は今回は外して設定します。
HTTPヘッダ
HTTPヘッダの「追加」を押し次の項目を追加します。
項目:「Authorization」
値:「Postmanより取得した認証情報(アクセストークン)」
データソースの追加2

データセットの作成

続いてデータセットの作成を行います。追加した「データソース」から[+]を選択します。

データセットの作成

「新規データセット」ダイアログが表示されたら、「JSONパス」に以下の値を設定して[検証]ボタンをクリックします。

$.value.*
データセットの作成2

検証後、データベースフィールドに値が設定されますので、右側の[]を選択し内容を確認します。

データセットの作成3

データベースフィールドが次のように設定されました。

データセットの作成4

自動設定されたフィールドには、Dataverseの管理フィールドも含まれている為、使用しないフィールドを削除します。またフィールド名についても接頭辞の「cr13_c」を除いた名称にしていきたいと思います。今回は次の表のようにデータベースフィールドを設定します。

フィールド名データフィールド削除
@odata.etag@odata.etag削除する
statecodestatecode削除する
shipaddresscr13c_shipaddress
createdoncreatedon削除する
_ownerid_value_ownerid_value削除する
shippostalcodecr13c_shippostalcode
shipviacr13c_shipvia
modifiedonmodifiedon削除する
versionnumberversionnumber削除する
requireddatecr13c_requireddate
timezoneruleversionnumbertimezoneruleversionnumber削除する
shippeddatecr13c_shippeddate
customeridcr13c_customerid
_modifiedby_value_modifiedby_value削除する
statuscodestatuscode削除する
shipnamecr13c_shipname
orderidcr13c_orderid
shipcitycr13c_shipcity
ordersidcr13c_ordersid
shipcountrycr13c_shipcountry
_createdby_value_createdby_value削除する
freightcr13c_freight
_owningbusinessunit_value_owningbusinessunit_value削除する
employeeidcr13c_employeeid
_owninguser_value_owninguser_value削除する
orderdatecr13c_orderdate
cr13c_shipregioncr13c_shipregion
overriddencreatedonoverriddencreatedon削除する
importsequencenumberimportsequencenumber削除する
_modifiedonbehalfby_value_modifiedonbehalfby_value削除する
utcconversiontimezonecodeutcconversiontimezonecode削除する
_createdonbehalfby_value_createdonbehalfby_value削除する
_owningteam_value_owningteam_value削除する

最終的には次のようなフィールド数になります。[変更を保存]を押してデータセットを作成します。

データセットの作成5

レイアウトの作成

データソースとデータセットの作成が完了したら、レイアウトを作成していきます。今回は以下のようなシンプルなレイアウトを作成しました。TextBoxコントロールにタイトルを設定し、明細部分はTableコントロールを使用しています。

レイアウトのデザイン

それぞれ以下のような要素で構成されています。

レイアウトのデザイン2

(1)は固定値を表示するラベルのTextBox。(2)~(6)はデータセットのフィールドを設定しています。

番号
(1)固定の文字列を設定します。
(2){orderid}
(3){orderdate}
(4){shippeddate}
(5){shipname}
(6){shipaddress}

細かな設定は割愛しますが、Tableの「データセット」プロパティには、追加した「DataSet」をバインドしています。

プレビューの実行

レイアウトを作成したら、デザイナ上部の[プレビュー]ボタンをクリックしてプレビューを実行すると、次のように「Dataverse」から取得したデータが帳票で表示されました。

レイアウトプレビュー

ActiveReportsJSでの帳票デザイン方法については、以下の記事もご参考ください。

レポートパラメータの設定

プレビューの実行を行い、「Dataverse」の「Web API」からデータの参照が出来ることを確認しましたが、接続の認証情報(アクセストークン)はPostmanを使用して取得したものを埋め込んでいるため、実際のアプリケーションには使用できません。

アプリケーションとして作成する場合、通常はユーザー毎にアクセス許可が必要となりユーザーそれぞれで認証が必要となります。またアクセストークンは有効期限が1時間以内に設定されている他、ユーザーサインオンのタイミングなどで使用不可となります。こういった観点からもデータソースに認証情報(アクセストークン)が必要な場合は、引数として渡す必要があります。

「ActiveReportsJS」にはこういったユースケースに対応する為、「レポートパラメータ」と呼ばれる機能を用意しています。「レポートパラメータ」はビューワの起動時に外部からパラメータを渡すことが可能となる機能が含まれており、アプリケーション側で取得したアクセストークンをレポート側に渡すことができます。

では、実際にアクセストークンを引き渡すための「レポートパラメータ」をレポートファイルに追加していきたいと思います。

デザイナの「データ」タブを選択し、「パラメータ」の項目の[追加]をクリックします。

レポートパラメータの追加

「パラメータの編集」メニューが表示後、次のように「名前」プロパティと「ダイアログの表示文字列」プロパティに「Bearer」を設定、「非表示」プロパティを「はい」へ設定します。
※ 非表示プロパティを「いいえ」に設定した場合はパラメータパネルに表示されエンドユーザーから参照可能となりますので「非表示」プロパティは「はい」に設定することを推奨します。

レポートパラメータの追加2

[←]を選択し「パラメータの編集」メニューの設定を終えると次のようにレポートパラメーターが追加されています。

レポートパラメータの追加3

続いて作成したレポートパラメータをデータソースのパラメータとして設定していきます。
「データソース」項目から作成したデータソースの[鉛筆アイコン]を選択し編集を行います。

レポートパラメータの追加4

「データソースの編集」ダイアログが表示されたら、「パラメータ」項目の「HTTPヘッダ」の右にある[]を選択します。

レポートパラメータの追加5

「HTTPヘッダ」が展開されますので、「Authorization」項目の「値」を初期化します。続いて右側にある[データ設定]ボタンを押し、選択肢より「式」を選択します。

レポートパラメータの追加6

続けて「HTTPヘッダ」ダイアログが表示されますので、左の「値」リストよりさきほど追加したレポートパラメータ「Bearer」を選択しダブルクリックし「式」エディタに設定し[保存]ボタンを押します。

レポートパラメータの追加7

保存後、次のように「HTTPヘッダ」、「Authorization」項目の「値」にレポートパラメータが設定されましたので、[変更を保存]ボタンを押し、データソースの編集を終了します。

レポートパラメータの追加8

レポートファイルの作成が完了しましたので、「dataverse-orders.rdlx-json」として保存します。

Webアプリケーションの作成

レポートファイルが完成しましたので、続いてレポートを表示する為のWebアプリケーションの作成を行っていきます。

今回のWebアプリケーションは帳票のデータソースとして「Dataverse」のWeb APIを使用するので、「Dataverse環境へのユーザー認証」、「アクセストークンの取得」、「取得したアクセストークンをレポートへ設定する」といった処理を実装していきたいと思います。

MicrosoftではWeb APIへの接続する為の認証処理をWebアプリケーションに組み込みやすくするために、JavaScript認証ライブラリ「MSAL.js」を提供しています。

今回作成するWebアプリケーションでは、「MSAL.js」を使用しDataverseへ接続する、以下 Microsoft Learnクイックスタート記事を参考に実装していきたいと思います。

アプリケーションの登録

「MSAL.js」を使用したWebアプリケーションを作成する為に、事前に作成するアプリケーションをMicrosoft Entra(旧Azure AD)上に登録し、Dataverseへのアクセスを許可しておく必要があります、まずはアプリケーションの登録から進めていきます。

Microsoft Entra管理センターを開くには Power Platform 管理者センターにサインインし、左のメニューから「Azure Active Directory」を選択します。

PowerPlatform管理者センター

別タブでMicrosoft Entra管理センターが開かれますので、左のメニュー「アプリケーション」項目から「アプリの登録」を選択します。

Microsoft Entra管理センタ-

「アプリの登録」画面に遷移後「新規登録」を選択します。

アプリの登録

続いて「アプリケーションの登録」画面に遷移しますので、「名前」、「サポートされているアカウントの種類」、「リダイレクトURI」を以下のように設定し、[登録]を行います。

アプリケーションの登録
項目設定値
名前reportsspa
サポートされているアカウントの種類「この組織ディレクトリのみに含まれるアカウント(MSFTのみーシングルテナント)」を選択
リダイレクトURI(プラットフォームの選択)「シングルページアプリケーション(SPA)」を選択
リダイレクト URIhttp://localhost:5500/index.html

アプリケーションが登録されると、以下の画面へ遷移します。ここには概要として各種情報が表示され、「アプリケーション(クライアント ID)」、「ディレクトリ(テナント)ID」という2つのIDも表示されています。この情報はWebアプリケーションに認証処理を作成する際に必要となりますので予めメモしておいてください。

アプリケーション概要

続いて登録したアプリケーション設定にAPIのアクセス許可を設定します。次の画像のように「APIのアクセス許可」タブを選択後、「アクセス許可の追加」を選択します。

APIのアクセス許可

右側に「APIアクセス許可の要求」画面が表示されますので、ここから「Dynamics CRM」を選択します。

APIアクセス許可の要求

選択後、「アクセス許可」設定が可能となりますので、次のように「user_impersonation」にチェックを入れ[アクセス許可の追加]を行います。

APIアクセス許可の要求2

次の画像のように「Dynamics CRM」のアクセス許可が追加されていればアプリケーションの登録は完了です。

APIのアクセス許可2

クライアントWebアプリケーションの作成

Microsoft Entra(旧Azure AD)上にアプリケーションの登録が完了しましたので、続いてクライアントWebアプリケーションの作成を行っていきたいと思います。

まずは、Dataverseへの接続が行えるかを確かめるため、Microsoft Learnのクイックスタート記事の「Web アプリケーション プロジェクトの作成」で作成されている、SPAアプリケーションを動作させてみます。

SPAアプリケーションを作成する為、任意の場所にフォルダ「reportsspa」を作成しフォルダ内に以下のコードを作成します。
※ 強調表示箇所は元のコードから、参照先のDataverseテーブルに合わせて変更しています。

<html>
 <head>
   <meta charset="UTF-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <script>
      const baseUrl = "https://org.api.crm.dynamics.com";      //<= 使用する環境に合わせて変更
      const clientId = "11111111-1111-1111-1111-111111111111"; //<= 使用する環境に合わせて変更
      const tenantId = "22222222-2222-2222-2222-222222222222"; //<= 使用する環境に合わせて変更
      const redirectUrl = "http://localhost:5500/index.html";
      const webAPIEndpoint = baseUrl +"/api/data/v9.2";

      
      // MSALインスタンスに渡される設定オブジェクト
      
      const msalConfig = {
         auth: {       
            clientId: clientId,
            // Full directory URL, in the form of https://login.microsoftonline.com/<tenant-id>
            authority: "https://login.microsoftonline.com/"+tenantId,       
            redirectUri: redirectUrl,
         },
         cache: {
            cacheLocation: "sessionStorage" // キャッシュの保存場所の設定
         },
         system: {   
            loggerOptions: {   
               loggerCallback: (level, message, containsPii) => {   
                     if (containsPii) {      
                        return;      
                     }      
                     switch (level) {      
                        case msal.LogLevel.Error:      
                           console.error(message);      
                           return;      
                        case msal.LogLevel.Info:      
                           console.info(message);      
                           return;      
                        case msal.LogLevel.Verbose:      
                           console.debug(message);      
                           return;      
                        case msal.LogLevel.Warning:      
                           console.warn(message);      
                           return;      
                     }   
               }   
            }   
         }
      };

   </script>
      <!-- CDNからmsal-browser.jsの最新バージョンを取得する -->
   <script
         type="text/javascript" 
         src="https://alcdn.msauth.net/browser/2.28.1/js/msal-browser.min.js">
   </script>
   <style>
      body {  
         font-family: 'Segoe UI';  
      }  

      table {  
         border-collapse: collapse;  
      }  

      td, th {  
         border: 1px solid black;  
      }

      #message {  
         color: green;  
      }
</style>
</head>
<body>
<div>
   <button id="loginButton" onclick="signIn()">Login</button>
   <button id="logoutButton" onclick="signOut()" style="display:none;">Logout</button>
   <button id="getAccountsButton" onclick="getAccounts(writeTable)" style="display:none;">Get Accounts</button>  
   <div id="message"></div>
   <table id="accountsTable" style="display:none;">  
      <thead><tr><th>Name</th><th>City</th></tr></thead>  
      <tbody id="accountsTableBody"></tbody>  
   </table>   
</div>
<script>
   const loginButton = document.getElementById("loginButton");
   const logoutButton = document.getElementById("logoutButton");
   const getAccountsButton = document.getElementById("getAccountsButton");
   const accountsTable = document.getElementById("accountsTable");
   const accountsTableBody = document.getElementById("accountsTableBody");
   const message = document.getElementById("message");
   // メインのmyMSALObjインスタンスを作成
   const myMSALObj = new msal.PublicClientApplication(msalConfig);

   let username = "";

   // アカウントの選択
   function selectAccount() {

      const currentAccounts = myMSALObj.getAllAccounts();
      if (currentAccounts.length === 0) {
         return;
      } else if (currentAccounts.length > 1) {
         // アカウントコードを入力してください
         console.warn("複数アカウントが検出されました。");
      } else if (currentAccounts.length === 1) {
         username = currentAccounts[0].username;
         showWelcomeMessage(username);
      }
   }

   // loginButtonから呼び出し
   function signIn() {
      myMSALObj.loginPopup({
         scopes: ["User.Read"] //<= Dataverse scopeを含めます
         })
         .then(response =>{
            if (response !== null) {
            username = response.account.username;
            showWelcomeMessage(username);
               } else {
                  selectAccount();
               }
         })
         .catch(error => {
               console.error(error);
         });
   }

   // ログオン後、ログオンユーザ名を表示し、logoutButton、getTokenButton、ActiveReportsJSのviewerhost使用を有効化します。
   // signInまたはselectAccount関数から呼び出されます。
   function showWelcomeMessage(username) {
    message.innerHTML = ` ${username}`;
    loginButton.style.display = "none";
    logoutButton.style.display = "block";
    getAccountsButton.style.display = "block";
   }

   // logoutButtonによって呼び出されます。
   function signOut() {

      const logoutRequest = {
         account: myMSALObj.getAccountByUsername(username),
         postLogoutRedirectUri: msalConfig.auth.redirectUri,
         mainWindowRedirectUri: msalConfig.auth.redirectUri
      };

      myMSALObj.logoutPopup(logoutRequest);
   }

   // リクエストのアクセストークンを提供し、必要に応じてポップアップを開きます。
   // getToken関数で使用されます。
   function getTokenPopup(request) {

      request.account = myMSALObj.getAccountByUsername(username);

      return myMSALObj.acquireTokenSilent(request)
         .catch(error => {
               console.warn("Silent token acquisition fails. Acquiring token using popup");
               if (error instanceof msal.InteractionRequiredAuthError) {
                  // サイレント・コールが失敗した場合、インタラクションにフォールバックする
                  return myMSALObj.acquireTokenPopup(request)
                     .then(tokenResponse => {
                           console.log(tokenResponse);
                           return tokenResponse;
                     }).catch(error => {
                           console.error(error);
                     });
               } else {
                  console.warn(error);   
               }
      });
   }

   // データバースから上位10件のアカウントレコードを取得
   function getAccounts(callback) {
      // アクセストークンを取得します。
      getTokenPopup({
            scopes: [baseUrl+"/.default"]
         })
         .then(response => {
            getDataverse("cr13c_employeeses?$select=cr13c_lastname,cr13c_city&$top=10", response.accessToken, callback);
         }).catch(error => {
            console.error(error);
         });
   }   

   /** 
   * データバースからデータを取得するヘルパー関数
   * 認可bearer token schemeを使用
   * コールバックは以下のwriteTable関数です。
   */
   function getDataverse(url, token, callback) {
      const headers = new Headers();
      const bearer = `Bearer ${token}`;
      headers.append("Authorization", bearer);
      // その他のDataverseヘッダー
      headers.append("Accept", "application/json"); 
      headers.append("OData-MaxVersion", "4.0");  
      headers.append("OData-Version", "4.0");  

      const options = {
         method: "GET",
         headers: headers
      };

    console.log('GET Request made to Dataverse at: ' + new Date().toString());

    fetch(webAPIEndpoint+"/"+url, options)
         .then(response => response.json())
         .then(response => callback(response))
         .catch(error => console.log(error));
   }

   // GetAccounts からのデータでテーブルをレンダリングします。
   function writeTable(data) {

      data.value.forEach(function (account) {

          var name = account.cr13c_lastname;
          var city = account.cr13c_city;

          var nameCell = document.createElement("td");
          nameCell.textContent = name;

          var cityCell = document.createElement("td");
          cityCell.textContent = city;

          var row = document.createElement("tr");

          row.appendChild(nameCell);
          row.appendChild(cityCell);

          accountsTableBody.appendChild(row); 

      });

      accountsTable.style.display = "block";
      getAccountsButton.style.display = "none";
   }   
   
   selectAccount();
  </script>
 </body>
</html>

上記のHTMLファイルをVisual Studio Codeの拡張機能「Live Server」で実行します。ブラウザ上に[Login]ボタンが表示され、ボタンを押すとアカウントへのサインイン画面となります。サインイン後、アプリケーションへのアクセス許可として、「ユーザープロファイルの読み取り」、「アクセス許可の維持」が求められますので、「組織の代理として同意する」にチェックを入れアクセス許可を行います。

サインイン後、[Get Accounts]ボタンが表示されます。このボタンを押すと再度アクセス許可要求として「Common Data Service(Dataverseの旧称)へのアクセス許可」、「ユーザープロファイルの読み取り」が求められますので、同様に「組織の代理として同意する」にチェックを入れアクセス許可を行います。

正常にアクセス許可が行われると、画面にDataverseのテーブルより取得したデータが表に描画されます。これでDataverseへの接続が行えるかを確認できました。

ActiveReportsJSの組込み

それでは、作成したアプリケーションにActiveReportsJSのViewerを組み込んでいきたいと思います。

作成済みのコードに強調表示した箇所を追加します。このコードでは画面上に[Get Token]ボタンとActiveReportsJSのViewerを追加しています。

<html>
 <head>
   <meta charset="UTF-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">

   <!-- ActiveReportsJS CSS/jsライブラリのCDNから読込 -->
   <link rel="stylesheet" href="https://cdn.grapecity.com/activereportsjs/4.0.2/styles/ar-js-ui.css" />
   <link rel="stylesheet" href="https://cdn.grapecity.com/activereportsjs/4.0.2/styles/ar-js-viewer.css" />
   <script type="text/javascript" src="https://cdn.grapecity.com/activereportsjs/4.0.2/dist/ar-js-core.js"></script>
   <script type="text/javascript" src="https://cdn.grapecity.com/activereportsjs/4.0.2/dist/ar-js-viewer.js"></script>
   <script type="text/javascript" src="https://cdn.grapecity.com/activereportsjs/4.0.2/dist/ar-js-pdf.js"></script>
   <script type="text/javascript" src="https://cdn.grapecity.com/activereportsjs/4.0.2/dist/ar-js-xlsx.js"></script>
   <script type="text/javascript" src="https://cdn.grapecity.com/activereportsjs/4.0.2/dist/ar-js-html.js"></script>
   <script type="text/javascript" src="https://cdn.grapecity.com/activereportsjs/4.0.2/dist/ar-js-tabular-data.js"></script>
   <script type="text/javascript" src="https://cdn.grapecity.com/activereportsjs/4.0.2/dist/locales/ar-js-locales.js"></script>

   <script>
      const baseUrl = "https://org.api.crm.dynamics.com";      //<= 使用する環境に合わせて変更
      const clientId = "11111111-1111-1111-1111-111111111111"; //<= 使用する環境に合わせて変更
      const tenantId = "22222222-2222-2222-2222-222222222222"; //<= 使用する環境に合わせて変更
      const redirectUrl = "http://localhost:5500/index.html";
      const webAPIEndpoint = baseUrl +"/api/data/v9.2";
      //ActiveReportsJSライセンス設定 以下にライセンスキーを設定してください。
      GC.ActiveReports.Core.setLicenseKey("YOUR LICENSE KEY GOES HERE");


      // MSALインスタンスに渡される設定オブジェクト
      const msalConfig = {
         auth: {       
            clientId: clientId,
            // Full directory URL, in the form of https://login.microsoftonline.com/<tenant-id>
            authority: "https://login.microsoftonline.com/"+tenantId,       
            redirectUri: redirectUrl,
         },
         cache: {
            cacheLocation: "sessionStorage" // キャッシュの保存場所の設定
         },
         system: {   
            loggerOptions: {   
               loggerCallback: (level, message, containsPii) => {   
                     if (containsPii) {      
                        return;      
                     }      
                     switch (level) {      
                        case msal.LogLevel.Error:      
                           console.error(message);      
                           return;      
                        case msal.LogLevel.Info:      
                           console.info(message);      
                           return;      
                        case msal.LogLevel.Verbose:      
                           console.debug(message);      
                           return;      
                        case msal.LogLevel.Warning:      
                           console.warn(message);      
                           return;      
                     }   
               }   
            }   
         }
      };

   </script>
      <!-- CDNからmsal-browser.jsの最新バージョンを取得する -->
   <script
         type="text/javascript" 
         src="https://alcdn.msauth.net/browser/2.28.1/js/msal-browser.min.js">
   </script>
   <style>
      body {  
         font-family: 'Segoe UI';  
      }  

      table {  
         border-collapse: collapse;  
      }  

      td, th {  
         border: 1px solid black;  
      }

      #message {  
         color: green;  
      }
</style>
</head>
<body>
<div>
   <button id="loginButton" onclick="signIn()">Login</button>
   <button id="logoutButton" onclick="signOut()" style="display:none;">Logout</button>
   <button id="getAccountsButton" onclick="getAccounts(writeTable)" style="display:none;">Get Accounts</button>  
   <button id="getTokenButton" onclick="getToken()" style="display:none;">Get Token</button>  
   <div id="message"></div>
   <table id="accountsTable" style="display:none;">  
      <thead><tr><th>Name</th><th>City</th></tr></thead>  
      <tbody id="accountsTableBody"></tbody>  
   </table>   
   <!-- ActiveReportJSのviewer-hostを追加 -->
   <div id="viewer-host" style="display:none;"></div>
</div>
<script>
   const loginButton = document.getElementById("loginButton");
   const logoutButton = document.getElementById("logoutButton");
   const getAccountsButton = document.getElementById("getAccountsButton");
   const getTokenButton = document.getElementById("getTokenButton");
   const accountsTable = document.getElementById("accountsTable");
   const accountsTableBody = document.getElementById("accountsTableBody");
   const message = document.getElementById("message");
   // メインのmyMSALObjインスタンスを作成
   const myMSALObj = new msal.PublicClientApplication(msalConfig);

   // ActiveReportJS viewer 追加
   const viewerhost = document.getElementById("viewer-host");
   const viewer = new ActiveReports.Viewer(viewerhost, { language: "ja" });

   let username = "";

   // アカウントの選択
   function selectAccount() {

      const currentAccounts = myMSALObj.getAllAccounts();
      if (currentAccounts.length === 0) {
         return;
      } else if (currentAccounts.length > 1) {
         // アカウントコードを入力してください
         console.warn("複数アカウントが検出されました。");
      } else if (currentAccounts.length === 1) {
         username = currentAccounts[0].username;
         showWelcomeMessage(username);
      }
   }

   // loginButtonから呼び出し
   function signIn() {
      myMSALObj.loginPopup({
         scopes: ["User.Read"] //<= Dataverse scopeを含めます
         })
         .then(response =>{
            if (response !== null) {
            username = response.account.username;
            showWelcomeMessage(username);
               } else {
                  selectAccount();
               }
         })
         .catch(error => {
               console.error(error);
         });
   }

   // ログオン後、ログオンユーザ名を表示し、logoutButton、getTokenButton、ActiveReportsJSのviewerhost使用を有効化します。
   // signInまたはselectAccount関数から呼び出されます。
   function showWelcomeMessage(username) {
    message.innerHTML = ` ${username}`;
    loginButton.style.display = "none";
    logoutButton.style.display = "block";
    getAccountsButton.style.display = "block";
   //ActiveReportsJSレポートへトークン情報設定ボタンの表示
    getTokenButton.style.display = "block";
   //ActiveReportsJSviewerの表示
   viewerhost.style.display = "block";    
   }

   // logoutButtonによって呼び出されます。
   function signOut() {

      const logoutRequest = {
         account: myMSALObj.getAccountByUsername(username),
         postLogoutRedirectUri: msalConfig.auth.redirectUri,
         mainWindowRedirectUri: msalConfig.auth.redirectUri
      };

      myMSALObj.logoutPopup(logoutRequest);
   }

   // リクエストのアクセストークンを提供し、必要に応じてポップアップを開きます。
   // getToken関数で使用されます。
   function getTokenPopup(request) {

      request.account = myMSALObj.getAccountByUsername(username);

      return myMSALObj.acquireTokenSilent(request)
         .catch(error => {
               console.warn("Silent token acquisition fails. Acquiring token using popup");
               if (error instanceof msal.InteractionRequiredAuthError) {
                  // サイレント・コールが失敗した場合、インタラクションにフォールバックする
                  return myMSALObj.acquireTokenPopup(request)
                     .then(tokenResponse => {
                           console.log(tokenResponse);
                           return tokenResponse;
                     }).catch(error => {
                           console.error(error);
                     });
               } else {
                  console.warn(error);   
               }
      });
   }

   // データバースから上位10件のアカウントレコードを取得
   function getAccounts(callback) {
      // アクセストークンを取得します。
      getTokenPopup({
            scopes: [baseUrl+"/.default"]
         })
         .then(response => {
            getDataverse("cr13c_employeeses?$select=cr13c_lastname,cr13c_city&$top=10", response.accessToken, callback);
         }).catch(error => {
            console.error(error);
         });
   }   

   function getToken() {
      // アクセストークンを取得します。
      getTokenPopup({
            scopes: [baseUrl+"/.default"]
         }).then(response => {
         //ActiveReportsJSのビューワをオープンします。
         //使用するレポートファイルの指定と、アクセストークンをレポートパラメータとして設定します。
         viewer.open("/reports/dataverse-orders.rdlx-json", {
            ReportParams: [{ Name: "Bearer", Value: response.accessToken }],
          });         
         }).catch(error => {
         console.error(error);
      }); 
   }

   /** 
   * データバースからデータを取得するヘルパー関数
   * 認可bearer token schemeを使用
   * コールバックは以下のwriteTable関数です。
   */
   function getDataverse(url, token, callback) {
      const headers = new Headers();
      const bearer = `Bearer ${token}`;
      headers.append("Authorization", bearer);
      // その他のDataverseヘッダー
      headers.append("Accept", "application/json"); 
      headers.append("OData-MaxVersion", "4.0");  
      headers.append("OData-Version", "4.0");  

      const options = {
         method: "GET",
         headers: headers
      };

    console.log('GET Request made to Dataverse at: ' + new Date().toString());

    fetch(webAPIEndpoint+"/"+url, options)
         .then(response => response.json())
         .then(response => callback(response))
         .catch(error => console.log(error));
   }

   // GetAccounts からのデータでテーブルをレンダリングします。
   function writeTable(data) {

      data.value.forEach(function (account) {

          var name = account.cr13c_lastname;
          var city = account.cr13c_city;

          var nameCell = document.createElement("td");
          nameCell.textContent = name;

          var cityCell = document.createElement("td");
          cityCell.textContent = city;

          var row = document.createElement("tr");

          row.appendChild(nameCell);
          row.appendChild(cityCell);

          accountsTableBody.appendChild(row); 

      });

      accountsTable.style.display = "block";
      getAccountsButton.style.display = "none";
   }   
    selectAccount();
  </script>
 </body>
</html>

作成したレポートファイル「dataverse-orders.rdlx-json」を読みこむ為、「reportsspa」フォルダ内に「repots」フォルダを作成しファイルを格納します。最終的なフォルダ構成は以下になります。

フォルダ校正

アプリケーションの実行

作成したアプリケーションを先ほど同様、Visual Studio Codeの拡張機能「Live Server」で実行してみます。[Get Token]ボタンを押すと、次のようにレポートが表示されました。

[Get Token]ボタンの処理内容は「getToken()」ファンクションで実装しています。処理を見ると、Dataverse接続用にアクセストークンを取得し、ビューワのオープン時にレポートパラメータとして渡しているのがわかります。アプリケーション側にはこの他にもActiveReportsJSライブラリの読み込みやViewerの組込みの為のコードの記述はありますが、最初に説明した処理のために追加したコードは以下の内容だけですので、とても少ないコード記述でアプリケーションの作成が行えます。

   function getToken() {
      // アクセストークンを取得します。
      getTokenPopup({
            scopes: [baseUrl+"/.default"]
         }).then(response => {
         //ActiveReportsJSのビューワをオープンします。
         //使用するレポートファイルの指定と、アクセストークンをレポートパラメータとして設定します。
         viewer.open("/reports/dataverse-orders.rdlx-json", {
            ReportParams: [{ Name: "Bearer", Value: response.accessToken }],
          });         
         }).catch(error => {
         console.error(error);
      }); 
   }

さいごに

今回の記事では「ActiveReportsJS」の帳票アプリケーションのデータソースに「Dataverse」のWeb APIを使用する方法として、「レポートファイルの作成」、「ビューワの組込み」、「アクセストークンの設定方法」などをご紹介しました。

事前にMicrosoft Entra(旧Azure AD)上へアプリケーションを登録し、アクセス許可を設定する必要はありますが、「Dataverse」をデータソースとするWeb帳票アプリケーションが簡単に作成できました。「Power Apps」や「Dynamics 365」のデータソースとしても「Dataverse」は利用されていますので、既存のデータを活用しWeb帳票アプリを追加したい場合は、是非この方法を参考にしていただけると幸いです。

製品サイトでは、今回ご紹介したActiveReportsJSの機能を手軽に体験できるデモアプリケーションやトライアル版も公開しておりますので、こちらもご確認ください。

また、ご導入前の製品に関するご相談、ご導入後の各種サービスに関するご質問など、お気軽にお問合せください。

\  この記事をシェアする  /