Skip to Content
Technical Articles
Author's profile photo Mio Yasutake

Adding a Donut Chart on top of CAP service

Introduction

 

I’ve been exploring CAP lately and this time, I thought of creating a Fiori Ovierview Page (OVP) on top of CAP.
In OVP, charts are important elements for helping data visualization.
What is OVP in general and how to create them is explained in detail in Ashish Anand‘s blog series.

In this blog, I’m going to share my experience on creating charts on top of CAP service.
Actually, I haven’t been able to add UI.Chart annotation directly on CAP, and resorted to Fiori elements app’s local annotation. If someone knows how to do this, please let me know in the comments section below.

update: Thanks to Oliver Klemenz, the app is running without local annotations.

The code is available at my GitHub repository.

Context

 

I’m going to develop a simple OVP app which shows past expenses and aggregated expenses grouped by category. The app will look like below picture.

 

Developing Environment

 

Steps

  1. Create a CAP service
  2. Create a OVP app

 

1. Create a CAP service

 

1.1. Create db/schema.cds.

I refereed to Gregor Wolf‘s GitHub repository and got the idea about how to create an entity capable of showing aggregated values.
First I created a simple Expenses entity and added a view ExpensisAnalyitcs which aggregates its amount.

namespace demo.aggregate;
using { Currency, cuid } from '@sap/cds/common';

entity Expenses : cuid {
    category: String @title: 'Category';
    amount: Decimal(9,2) @title: 'Amount';
    currency: Currency @title: 'Currency';
    postingDate: DateTime @title: 'Posting Date'
}

@Aggregation.ApplySupported.PropertyRestrictions: true
view ExpensesAnalytics as select from Expenses {
  key ID,
  @Analytics.Dimension: true
  category,
  @Analytics.Measure: true
  @Aggregation.default: #SUM
  amount,
  @Analytics.Dimension: true
  currency
};

 

1.2. Create srv/cat-service.cds.

I simply exposed the two entities and added UI annotations.

using demo.aggregate as db from '../db/schema';

service CatalogService {
    entity Expenses as projection on db.Expenses
    view ExpensesAnalytics as select from db.ExpensesAnalytics
}

annotate CatalogService.ExpensesAnalytics with @(
    UI: {
        Chart: {
            $Type: 'UI.ChartDefinitionType',
            ChartType: #Donut,
            Measures: ['amount'],
            MeasureAttributes: [{
                $Type: 'UI.ChartMeasureAttributeType',
                Measure: 'amount',
                Role: #Axis1
            }],             
            Dimensions: ['category'],
            DimensionAttributes: [{
                $Type: 'UI.ChartDimensionAttributeType',
                Dimension: 'category',
                Role: #Category
            }]       
        }
    }
);

annotate CatalogService.Expenses with @(
    UI: {
        SelectionFields: [postingDate, category],
        LineItem: [
            { Value: postingDate },
            { Value: category },
            { Value: amount },
            { Value: currency_code }
        ],   
    }
);

 

1.3. Adapt CAP service to OData v2.

CAP services needs to be adapted to OData v2 to be consumed by Fiori Tools.
For this, use @sap/cds-odata-v2-adapter-proxy.
The settings are described in my previous blog.

 

2. Create a OVP app

 

Next, add a OVP app within the same project. I used Fiori Tools for generating the app.

 

2.1. Generate a Fiori project

 

1. Select Overview Page from the templates.

 

2. Select Connect to an OData Source as Data source, and specify your local CAP OData URL:  http://localhost:4004/v2/catalog/

 

3. Select “Expenses” as Filter Entity.

 

4. Type below information.

Module Name ovptest
Title OVP Test
Namespace demo
Description OVP Test
Project Folder Your CAP project’s root folder

 

5. Move to ovptest folder you’ve just created and run the app.

npm start

 

At this moment, nothing is there (even the filter!).

 

2.2. Add a Global Filter

 

In ovptest/webapp/manifest.json, fill in globalFilterEntityType.

  "sap.ovp": {
    "globalFilterModel": "mainService",
    "globalFilterEntityType": "Expenses",
    "containerLayout": "resizable",
    "enableLiveFilter": true,
    "considerAnalyticalParameters": false,
    "cards": {}
  }

 

As soon as you do so, the FilterBar will appear.

 

2.3. Add a List Card

 

Add below settings to ovptest/webapp/manifest.json.

  "sap.ovp": {
    ...
    "cards": {
      "list01": {
        "model": "mainService",
        "template": "sap.ovp.cards.list",
        "settings": {
            "title": "Expense List",
            "entitySet": "Expenses",
            "identificationAnnotationPath": "com.sap.vocabularies.UI.v1.Identification",
            "presentationAnnotationPath": "com.sap.vocabularies.UI.v1.PresentationVariant#Default",
            "annotationPath": "com.sap.vocabularies.UI.v1.LineItem"
        }
      }
    }

 

A list card will be shown.

 

2.4. Add a Donut Chart

 

Add below settings to ovptest/webapp/manifest.json, below “list01” card.
For the chart I’m using aggregated entity: ExpensesAnalytics.

      "chart01": {
          "model": "mainService",
          "template": "sap.ovp.cards.charts.analytical",
          "settings": {
              "title": "Expense Chart",
              "entitySet": "ExpensesAnalytics",
              "chartAnnotationPath": "com.sap.vocabularies.UI.v1.Chart"
          }
      }

 

 

The chart has been loaded, and we can observe that it’s showing aggregated amount.

 

For comparison, I added the same chart on top of normal Expenses entity.
Resulting chart is not aggregated as shown below.

 

Conclusion

 

These are what I’ve learned so far:

  • To show aggregated chart on Fiori elements app, the base entity requires @Aggregation annotation.
  • UI.Chart annotation on CAP doesn’t seem to work and one has to add local annotation to Fiori elements apps.

 

References

 

Assigned Tags

      7 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Pierre Dominique
      Pierre Dominique

      Thanks for this great post and useful information!

      Author's profile photo Helmut Tammen
      Helmut Tammen

      Thanks for this blog.

      Intersting information that CAP generates wrong edmx annotation. I'm currently struggling with complex valuehelp annotation. Will investigate into that direction.

      Author's profile photo Oliver Klemenz
      Oliver Klemenz

      The correct syntax should be (UI.Chart as Object, not as Collection):

      annotate CatalogService.ExpensesAnalytics with @(
        UI: {
          Chart: {
            $Type: 'UI.ChartDefinitionType',
            ChartType: #Donut,
            Measures: ['amount'],
            MeasureAttributes: [{
              $Type: 'UI.ChartMeasureAttributeType',
              Measure: 'amount',
              Role: #Axis1      
            }],
            Dimensions: ['category'],
            DimensionAttributes: [{
              $Type: 'UI.ChartDimensionAttributeType',
              Dimension: 'category',
              Role: #Category
            }]
          }
        }
      );

      To me it looks as if the issue happens with regards to the element "currency". This element is typed with Currency from "@sap/cds/common", which in fact is an association to an entity. By annotating this element with @Analytics.Dimension, not only the foreign key field "currency_code" is a Dimension, but also the association element "currency".

      Maybe this is the source of the CDS compilation issue. In fact you do not use "currency" in your chart at all, if I see it correctly... So you could try it without field "currency" or by flattening "currency.code" in the view definition explicitly...

      Author's profile photo Mio Yasutake
      Mio Yasutake
      Blog Post Author

      Hi Oliver,

      Thank you very much for giving the correct syntax.

      Now the Fiori app is working without local annotation. I have updated the post.

      "currency" in Analytics view is still there, but not causing error anymore.

       

      Regards,

      Mio

      Author's profile photo Svende Landwehrkamp
      Svende Landwehrkamp

      Hi I tried to follow your tutorial with CAP Project and Launchpad. My pages.overview does not show anything. Can you help me find the reason?

      My ui5.yaml

      specVersion: '2.4'
      metadata:
        name: 'project1'
      type: application
      resources:
        configuration:
          propertiesFileSourceEncoding: UTF-8
      builder:
        resources:
          excludes:
            - "/test/**"
            - "/localService/**"
        customTasks:
        - name: webide-extension-task-updateManifestJson
          afterTask: generateVersionInfo
          configuration:
            appFolder: webapp
            destDir: dist
        - name: webide-extension-task-resources
          afterTask: webide-extension-task-updateManifestJson
          configuration:
            nameSpace: ns
        - name: webide-extension-task-copyFile
          afterTask: webide-extension-task-resources
          configuration:
            srcFile: "/xs-app.json"
            destFile: "/xs-app.json"

      My package.json:

      {
        "name": "project1",
        "version": "0.0.1",
        "devDependencies": {
          "@sapui5/ts-types": "1.71.x",
          "@ui5/cli": "2.2.6",
          "@sap/ui5-builder-webide-extension": "1.0.x",
          "bestzip": "2.1.4",
          "rimraf": "3.0.2"
        },
        "scripts": {
          "build": "npm run clean && ui5 build --include-task=generateManifestBundle generateCachebusterInfo && npm run zip",
          "zip": "cd dist && npx bestzip ../project1-content.zip *",
          "clean": "npx rimraf project1-content.zip dist"
        },
        "ui5": {
          "dependencies": [
            "@sap/ui5-builder-webide-extension"
          ]
        }
      }
      

      My manifest:

      {
          "_version": "1.32.0",
          "sap.app": {
              "id": "project1",
              "type": "application",
              "i18n": "i18n/i18n.properties",
              "applicationVersion": {
                  "version": "1.0.0"
              },
              "title": "{{appTitle}}",
              "description": "{{appDescription}}",
              "tags": {
                  "keywords": []
              },
              "dataSources": {
                  "mainService": {
                      "uri": "/v2/catalog/",
                      "type": "OData",
                      "settings": {
                          "annotations": [
                              "annotation"
                          ],
                          "localUri": "localService/metadata.xml",
                          "odataVersion": "2.0"
                      }
                  },
                  "annotation": {
                      "type": "ODataAnnotation",
                      "uri": "annotations/annotation.xml",
                      "settings": {
                          "localUri": "annotations/annotation.xml"
                      }
                  }
              },
              "offline": false,
              "sourceTemplate": {
                  "id": "OVP.smartovptemplate",
                  "version": "1.41.1"
              },
              "crossNavigation": {
                  "inbounds": {
                      "project1-inbound": {
                          "signature": {
                              "parameters": {},
                              "additionalParameters": "allowed"
                          },
                          "semanticObject": "Test",
                          "action": "test",
                          "title": "{{flpTitle}}",
                          "subTitle": "{{flpSubtitle}}",
                          "icon": ""
                      }
                  }
              }
          },    "sap.cloud": {
              "public": true,
              "service": "project1_service"
          },
          "sap.ui": {
              "technology": "UI5",
              "icons": {
                  "icon": "",
                  "favIcon": "",
                  "phone": "",
                  "phone@2": "",
                  "tablet": "",
                  "tablet@2": ""
              },
              "deviceTypes": {
                  "desktop": true,
                  "tablet": true,
                  "phone": true
              },
              "supportedThemes": [
                  "sap_hcb",
                  "sap_belize"
              ]
          },
          "sap.ui5": {
              "resources": {
                  "js": [],
                  "css": []
              },
              "dependencies": {
                  "minUI5Version": "1.97.0",
                  "libs": {
                      "sap.ovp": {}
                  },
                  "components": {}
              },
              "models": {
                  "i18n": {
                      "type": "sap.ui.model.resource.ResourceModel",
                      "uri": "i18n/i18n.properties",
                      "preload": false
                  },
                  "@i18n": {
                      "type": "sap.ui.model.resource.ResourceModel",
                      "uri": "i18n/i18n.properties"
                  },
                  "mainService": {
                      "dataSource": "mainService",
                      "preload": true,
                      "settings": {
                          "defaultCountMode": "Inline"
                      }
                  }
              },
              "extends": {
                  "extensions": {}
              },
              "contentDensities": {
                  "compact": true,
                  "cozy": true
              }
          },
          "sap.fiori": {
              "registrationIds": [],
              "archeType": "analytical"
          },
          "sap.ovp": {
              "globalFilterModel": "mainService",
              "globalFilterEntityType": "Expenses",
              "containerLayout": "resizable",
              "enableLiveFilter": true,
              "considerAnalyticalParameters": false,
              "cards": {}
          }
      }

       

      Overview over the fiori ovp:

      Server and DB are exactly like in your project.

      I cannot find the solution maybe it is something small. Would appreciate help.

      Thank you, Svende

      Author's profile photo Mio Yasutake
      Mio Yasutake
      Blog Post Author

      Hi Svende,

      Do you see any error in the browser's console?

      Your project works in local environment, and fails in the Launchpad, right?

      Author's profile photo Svende Landwehrkamp
      Svende Landwehrkamp

      Hi Mio,

      there is no error message when I start it. The mta goes smooth and also the deployment.

      The freestyle Floorplan Apps and List Report Object Page Apps show up without error in Launchpad. I just cannot get OVP to work

      But I can also not open the server listening on { url: 'http://localhost:4004' } it just closes the window directly.

      Any suggestions where I can search for the error?

      Thank you,

      Svende