【SAP CP CF環境】Fiori LaunchpadでDynamic Tileを作ってみる

# はじめに

本投稿はSAP CloudPlatformのCF環境上にFiori Launchpadを作成・公開までしていることが前提となっています。
[SAP CP Trial環境でMulti Target Applicationを作ってみる【Fiori LanchPadモジュールの作成】](https://qiita.com/yonekura-h/items/8d4b95f2532f4f57702a)

Launchpadモジュールを作成した際、簡単なDynamic Tile（動的タイル）を表示することはできましたが、
数値を表示するのみで終わっているのでもっといろいろな設定ができる動的タイルを試してみました。

以下を参考にしています。
ドキュメントの記載によれば決まった形式でレスポンスを返してくれるAPI（OData）があればいろいろ設定ができるみたいです。
[OData Structure for Dynamic App Launchers](https://help.sap.com/viewer/ad4b9f0b14b0458cad9bd27bf435637d/Cloud/en-US/8e7cec3cf6494b09a01d09606119b9dd.html)

最終的に以下赤枠のようなタイルの作成を目指します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/d2dc1803-26dd-d64e-3ab1-23ebccdad3ac.png)


# 動的タイル用のODataServiceを作成
ドキュメントを参考に、以下のようなレスポンスが返却されるODataServiceを作成します。

```json:動的タイルのConfig
{
    "d": {
    "icon": "sap-icon://travel-expense",
    "info": "Quarter Ends!",
    "infoState": "Critical",
    "number": 43.333,
    "numberDigits": 1,
    "numberFactor": "k",
    "numberState": "Positive",
    "numberUnit": "EUR",
    "stateArrow": "Up",
    "subtitle": "Quarterly overview",
    "title": "Travel Expenses"
    }
}
```

`People`というエンティティセットを公開していたODataのMetadataを変更し、
`DynamicAppSet`を追加しました。
※前提としてODataは下記で作成したもの
[SAP CP Trial環境でMulti Target Applicationを作ってみる【ODataService_V2の作成】](https://qiita.com/yonekura-h/items/62abe8b4ff1a747bd2e9)


```xml:DemoService.xml
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
    <edmx:DataServices m:DataServiceVersion="2.0">
        <Schema Namespace="demo" xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
            <EntityType Name="Person">
                <Key>
                    <PropertyRef Name="UniqueId" />
                </Key>
                <Property Name="UniqueId" Type="Edm.Int32" />
                <Property Name="Name" Type="Edm.String" />
            </EntityType>
            <EntityType Name="DynamicApp">
                <Key>
                    <PropertyRef Name="title" />
                </Key>
                <Property Name="icon" Type="Edm.String" />
                <Property Name="info" Type="Edm.String" />
                <Property Name="infoState" Type="Edm.String" />
                <Property Name="number" Type="Edm.Double" />
                <Property Name="numberDigits" Type="Edm.Int32" />
                <Property Name="numberFactor" Type="Edm.String" />
                <Property Name="numberState" Type="Edm.String" />
                <Property Name="numberUnit" Type="Edm.String" />
                <Property Name="stateArrow" Type="Edm.String" />
                <Property Name="subtitle" Type="Edm.String" />
                <Property Name="title" Type="Edm.String" />
            </EntityType>
            <EntityContainer Name="container" m:IsDefaultEntityContainer="true">
                <EntitySet Name="People" EntityType="demo.Person" />
                <EntitySet Name="DynamicAppSet" EntityType="demo.DynamicApp" />
            </EntityContainer>
        </Schema>
    </edmx:DataServices>
</edmx:Edmx>
```

EntitySetを増やしたので、Javaのコードに以下を追加します。
とりあえずドキュメントのサンプルと同じデータを返却するようにハードコーディングになっています。

```java:ServiceImplementation.java

    @Query(serviceName="DemoService", entity="DynamicAppSet")
    public QueryResponse getDynamicApp(QueryRequest request) {
    	Logger logger = LoggerFactory.getLogger(ServiceImplementation.class);
    	logger.info("Execute getDynamicApp");
        List<Map<String, Object>> LauncherMap = createAppLauncherList();
        return QueryResponse.setSuccess().setDataAsMap(LauncherMap).response();
    }
    // ユニークキー指定で単一件のデータを返却する
    @Read(serviceName="DemoService", entity="DynamicAppSet")
    public ReadResponse readDynamicApp(ReadRequest request) {
    	Logger logger = LoggerFactory.getLogger(ServiceImplementation.class);
    	logger.info("Execute readDynamicApp");
        // retrieve the requested person from URI 
        Map<String, Object> keyPredicates = request.getKeys();
        Object keyObject = keyPredicates.get("title");
        String id = (String)keyObject;

        // search the requested person in the database
        List<Map<String, Object>> AppList = createAppLauncherList();
        Map<String, Object> requestedAppMap = new HashMap<String, Object>();
        for(Map<String, Object> AppMap : AppList) {
            if(((String)AppMap.get("title")).equals(id)) {
                // found it
                requestedAppMap = AppMap;
            }
        }       

        // handle error: "not found" 
        if(requestedAppMap.isEmpty()) {
            // Logger logger = LoggerFactory.getLogger(ServiceImplementation.class);
            logger.error("App with title (" + id + ") doesn't exist! Service request was invoked with invalid key value"); 
            ErrorResponse response = ErrorResponse.getBuilder()
                    .setMessage("App with title (" + id + ") doesn't exist!")
                    .setStatusCode(404)
                    .response();
            return ReadResponse.setError(response);
        }

        return ReadResponse.setSuccess().setData(requestedAppMap).response();
    }

    // 固定で1件のみレスポンスデータを作成
    private List<Map<String, Object>> createAppLauncherList(){
    	List<Map<String, Object>> LauncherMap = new ArrayList<Map<String, Object>>();
    	Map<String, Object> singleMap = new HashMap<String, Object>();
    	singleMap.put("icon", "sap-icon://travel-expense");
    	singleMap.put("info", "Quarter Ends!");
    	singleMap.put("infoState", "Critical");
    	singleMap.put("number", 43.333);
    	singleMap.put("numberDigits", 1);
    	singleMap.put("numberFactor", "k");
    	singleMap.put("numberState", "Positive");
    	singleMap.put("numberUnit", "EUR");
    	singleMap.put("stateArrow", "Up");
    	singleMap.put("subtitle", "Quarterly overview");
    	singleMap.put("title", "Dynamic Tile");
    	LauncherMap.add(singleMap);

        return LauncherMap;
    }
```

ODataで以下のレスポンスを返却できるようになりました。
ユニークキーを指定しない場合はリスト形式で返却されますが、
ドキュメントのデータ構造から推測すると単一行のオブジェクトのみが返却される必要があると思われます。

そのため、タイルのタイトルをキーとして単一行のみ返却するように実装しました。

**リクエスト**
URL：`https://<applicationRoute>.cfapps.eu10.hana.ondemand.com/odata/v2/DemoService/DynamicAppSet('Dynamic%20Tile')`

**レスポンス**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/aadbf7f0-1f88-fafe-0576-0fb167a17880.png)

# HTML5モジュール・Launchpadモジュールでのタイル設定

新規にHTML5モジュールを作成し、`manifest.json`に以下を設定しました。
　・sap.app -> dataSources : OData(DemoService)のリソースを定義
　・sap.app -> crossNavigation : タイル設定を定義（下図）

`manifest.json`でのタイル設定ではTitleやIcon等の項目をODataServiceから取得される値とは違うものに設定しました。
ODataServiceからの取得値によってここで定義しているタイル設定が上書きされることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/e09e84e9-5c5a-e298-c0aa-f783299cb223.png)
※Pathの先頭に`/`を入れてしまうとリクエストURLが想定通りにならないので注意

※詳細な設定内容は以下記事の記載と同様
[SAP CP Trial環境でMulti Target Applicationを作ってみる【Fiori LanchPadモジュールの作成】](https://qiita.com/yonekura-h/items/8d4b95f2532f4f57702a)


HTML5モジュールでの設定が完了したら、Launchpadモジュールの`CommonDataModel.json`に動的タイルの設定を追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/e0efec95-0a7c-a27a-c19f-6d3b5bde5238.png)

上記まで設定が完了したらBuild -> Deployを実施します。
デプロイ後にLaunchpadを表示するとODataServiceからの情報を反映した動的タイルが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/db929ade-064b-1f74-e78a-8348909bb533.png)

# おわりに
実際の業務ではバックエンドとフロントエンドで分業をしていることも多く、
今回実際に試してみて「こういう構造のODataServiceを作ればここまで可能」という部分が明確になったので良かったです。

あとはLaunchpadでグラフが出せるようになりたいです。