QuickSightの埋め込みダッシュボードを使ってサービス開発した話

この記事はレコチョク [Advent Calendar 2021](https://qiita.com/advent-calendar/2021/recochoku) の 6日目の記事となります。

https://qiita.com/advent-calendar/2021/recochoku

本記事では BIツールに Amazon QuickSightを利用し、Webサービスにダッシュボードを埋め込む際のTipsをまとめました。

## はじめに

レコチョクでは音楽配信による購入や再生の実績データをコンテンツホルダー(レコード会社など)にお返しするため、データが閲覧できるWebサービスの開発を行っています。

その際、下記のような要件を満たす必要がありました。

```
◆ データを見やすく・探しやすくするためにBIダッシュボードで提供すること
◆ ダッシュボードに表示するデータはコンテンツホルダー毎に自動でフィルタリングされること
◆ コンテンツホルダーのアカウント情報は既存のアカウント管理システムによって管理されているので、そちらを利用すること
```

「コンテンツホルダー毎に自動でフィルタリングされる」というのは下記のようなイメージです。

![requirement.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2277720/dae367aa-da32-a13d-6681-122d3d7e0f55.png)

コンテンツホルダーが閲覧可能なデータは所属アーティストの実績のみにしなければならず、もしそれが出来ていない場合はデータ漏洩に繋がる可能性があります。

つまり今回の場合、コンテンツホルダーAが閲覧可能なデータは所属アーティスト A1とA2のデータのみにし、それ以外の他コンテンツホルダーのデータは閲覧させないようにしなければなりません。

これらの要件を満たすため、既存のアカウント管理システムで認証を行うWebサービスを作成し、Amazon QuickSight(Enterprise Edition) で作成したダッシュボードを埋め込むことに決めました。

※ Amazon QuickSight Standard Edition には埋め込み機能がついておりません。

## 本記事の流れ

1. サービスのアーキテクチャ
2. Row-Level Security について
3. 埋め込みURL生成までのフロー
4. 埋め込みダッシュボードのドメイン管理
5. Webフロントの実装

## 1. サービスのアーキテクチャ

![architect.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2277720/71f6e074-223f-8754-cb19-76373b41705d.png)

本記事で取り上げるサービスのアーキテクチャです。

Webフロントは ALB+Fargate で構成されており、QuickSightへのアクセスやダッシュボード埋め込みURLの生成は API Gateway+Lambda で構成されるAPIで行っています。

QuickSightのデータソースはRedshiftとなっており、SPICE(QuickSightのインメモリデータベース)を利用しています。

## 2. Row-Level Security について

> ◆ ダッシュボードに表示するデータはコンテンツホルダー毎に自動でフィルタリングされること

上記の要件を満たすために、Row-Level Security(RLS) という機能を利用します。

![rls.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2277720/1106e256-565b-c595-92c8-3a74b1648573.png)

RLSルールを適用したデータセットには行レベルの制限がかかります。つまり、この場合だとQuickSightグループAに参加しているQuickSightユーザーには、`ContentsHolder=A` のデータのみ表示されるようになります。

この機能によりコンテンツホルダー毎に自動でフィルタリングされる要件もクリアでき、かつ各コンテンツホルダー毎にデータセットを用意する必要がなくなるので管理も楽になります。

#### 参考
https://docs.aws.amazon.com/ja_jp/quicksight/latest/user/restrict-access-to-a-data-set-using-row-level-security.html

## 3. 埋め込みURL生成までのフロー

今回のアーキテクチャにおいて、埋め込みURLの生成はLambdaで行っています。
実際のフローは下記のようになります。

```
[リクエスト]
 ↓
 a. QuickSightユーザーの存在チェック → 無ければ新規作成
 b. QuickSightグループへの参加有無を確認 → 無ければ追加
 c. 埋め込みURLの生成
 ↓
[レスポンス]
{
  "Status": "200",
  "EmbedUrl": "https://***",
  "RequestId": "***"
}
```

---

### a. QuickSightユーザーの存在チェック → 無ければ新規作成

次項のQuickSightグループへの追加をするために、QuickSightユーザーを作成する必要があります。

QuickSightのユーザー権限は3種類あります。
今回の想定ではダッシュボードの閲覧さえできればよいので、READER (閲覧者)で作成するようにします。

> ユーザーが Amazon QuickSight で、作成およびユーザー管理やSPICE 容量の購入などの管理タスクも実行できるようにするには、**[ADMIN (管理者)]** を選択します。
>
> ユーザーが Amazon QuickSight で分析とダッシュボードを作成するが、管理タスクは実行しない場合は、**[AUTHOR (作成者)]** を選択します。
>
> Enterprise Edition では、共有ダッシュボードを操作できるようにユーザーに設定できますが、分析やダッシュボードの作成や、管理タスクの実行を行うことはできません。これを行うには、**[READER (閲覧者)]** を選択します。


#### 参考

https://docs.aws.amazon.com/ja_jp/quicksight/latest/user/managing-users.html

QuickSightユーザーがまだ存在していない場合には新規作成して次項に進みます。

---

###  b. QuickSightグループへの参加有無を確認 → 無ければ追加

リクエスト時のトークンや紐づく情報からコンテンツホルダー名を取得し、同名のQuickSightグループへユーザーの追加を行います。

QuickSightグループにまだ追加していない場合には追加して次項に進みます。

---

### c. 埋め込みURLの生成

QuickSightグループに参加しているQuickSightユーザーを使ってダッシュボードの埋め込みURLを生成します。

これでRLSによってデータがフィルタリングされたダッシュボード埋め込みURLが得られました。

## 4. 埋め込みダッシュボードのドメイン管理

埋め込みダッシュボードは、QuickSight側で埋め込み元のドメインをホワイトリストに登録しておかないと正常に表示されません。
下記のQuickSight管理画面から登録しておきます。

![domain-management.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2277720/49ddb3d8-9767-a80c-c824-b56df3a57faa.png)

## 5. Webフロントの実装

通常のiframeに埋め込みURLを渡すだけでも埋め込みは可能ですが、専用のSDKがあるのでそちらを利用しました。

[amazon-quicksight-embedding-sdk](https://www.npmjs.com/package/amazon-quicksight-embedding-sdk) を利用することで、QuickSight独自の機能(PDFエクスポートのON/OFFやダッシュボードフィルターの初期値設定など)がカスタマイズ可能になります。

```js
var options = {
    url: "https:***",
    container: document.getElementById("embeddingContainer"),
    parameters: {
        country: "United States",
        states: [
            "California",
            "Washington"
        ]
    },
    scrolling: "no",
    height: "700px",
    iframeResizeOnSheetChange: false, // use this option in combination with height: AutoFit, to allow iframe height to resize dynamically, based on sheet height, on changing sheets.
    width: "1000px",
    locale: "en-US",
    footerPaddingEnabled: true,
    sheetId: 'YOUR_SHEETID' // use this option to specify initial sheet id to load for the embedded dashboard
    sheetTabsDisabled: false, // use this option to enable or disable sheet tab controls in dashboard embedding
    printEnabled: false, // use this option to enable or disable print option for dashboard embedding
    undoRedoDisabled: false, // set this option to true to disable undo and redo buttons for dashboard embedding
    resetDisabled: false, // set this option to true to disable reset button for dashboard embedding
    defaultEmbeddingVisualType: TABLE // this option only applies to experience embedding and will not be used for dashboard embedding
};
```

Web画面に埋め込んだ際、スクロールバーを出さずにダッシュボードの中身を全て表示したい場合には、下記の設定にすることで実現できました。

```js
{
    scrolling: "no",
    height: "AutoFit",
    iframeResizeOnSheetChange: false,
}
```

![embed.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2277720/7d616713-6e88-3b5b-4900-c8a04290da74.png)

#### 参考

https://docs.aws.amazon.com/ja_jp/quicksight/latest/user/embedded-analytics-dashboards-with-anonymous-users-step-3.html


## おわりに

主に Row-Level Security の機能と Webフロントの埋め込みSDK を使うことで要件を満たす仕組みを実現できました。

QuickSightユーザーのプロビジョニングの部分は追加でコーディングが必要でしたが、それ以外は既にあるものを使えたのでよかったです。

本記事がQuickSight利用時やサービスのアーキテクチャ構築時の参考になれば幸いです。

最後まで読んでいただきありがとうございました！

明日のレコチョク [Advent Calendar 2021](https://qiita.com/advent-calendar/2021/recochoku) は 7日目、`Hiltを使ってみよう` となります。お楽しみに！

---

この記事は[レコチョクのエンジニアブログ](https://techblog.recochoku.jp/7881)の記事を転載したものとなります。

https://techblog.recochoku.jp/7881