Skip to Content
Technical Articles
Author's profile photo Peter Neumayer

How to create an ALP for a Custom Object Data Quality Drill-Down

In MDG Central Governance many  customers already use the Custom Object framework to apply the governance functionality also to reference data. Thus the question came up how to enable the MDG Data Quality functionality also for custom objects.

The SAP Master Data Quality configuration guide (link) describes the steps required to enable rule management and rule mining for your custom object and also how to configure tiles for the Evaluation Run application. These applications allow you to create rules, run and schedule data quality evaluations, and define data quality dimensions as KPIs for monitoring. You then can visualize the results of the evaluation run however you can view only aggregated results based on the defined scores and KPIs.

The purpose of this document is to describe the steps required to create an application to analyze and drill down into evaluation results for your custom object. As with the ALPs for the product and business partner domains we will use an analytical list page (ALP) template for our application. The template builds on the defined CDS views and the corresponding annotations. Thus, the main work is in the creation of the different CDS views required. Based on the existing database tables for the custom object, we must create the custom-object specific CDS views, and then we must link them to the analytical consumption CDS views for data quality analytics.

Recommendation: before you start, please read the following blog: https://blogs.sap.com/2016/09/20/fioriui5-cds-bopf-for-beginners/

 

Create CDS views for your custom object

In my example we will use the type carrier from the SFLIGHT data model. We are following the MDC configuration guide for custom object and thus the generated database table is ZZCARRIER_ACT and the object type defined is ZZCARR.

Figure 1: Screenshot of the custom object database table ZZCARRIERS_ACT

 

Figure 2: Screenshot of the object type ZZCARR in the MDC data model (TA MDCMODEL)

 

1.     Create a basic CDS view for ZZCARRIER_ACT

Use the ABAP Development Tools to create the following CDS views

@AbapCatalog.sqlViewName: 'ZDDL_C_ZZCARR'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'ZPN_C_ZZCARRIERS_ACT'

@OData.publish: true

@Search.searchable: true
@UI.headerInfo.typeName: 'CarrierID'
@UI.headerInfo.title.value: 'CarrierID'

@ObjectModel.transactionalProcessingDelegated: true
@ObjectModel.semanticKey: ['CARRID']
@ObjectModel.representativeKey: 'CARRID'
@ObjectModel.createEnabled: true
@ObjectModel.updateEnabled: true
@ObjectModel.deleteEnabled: true

define view ZPN_C_ZZCARRIERS_ACT
  as select from ZPN_I_ZZCARRIERS_ACT
{

      // Key CarrierID
      @Search.defaultSearchElement: true
      @ObjectModel.readOnly: true
      @ObjectModel.text.element: [ 'CarrName' ]
      @UI.identification: [{position:10, importance: #HIGH }]
      @UI.lineItem: [{position: 10, importance: #HIGH }]
      @UI.textArrangement: #TEXT_FIRST
      @EndUserText: { label: 'Airline ID', quickInfo: 'Airline ID' }
  key ZPN_I_ZZCARRIERS_ACT.CarrID,

      // Name
      @Semantics.text: true
      @UI.identification: [{position:70, importance: #HIGH }]
      @UI.lineItem: [{position: 70, importance: #HIGH }]
      @EndUserText: { label: 'Airline Name', quickInfo: 'Airline Name' }
      ZPN_I_ZZCARRIERS_ACT.CarrName,

      // Currency
      @UI.identification: [{position:80, importance: #HIGH }]
      @UI.lineItem: [{position: 80, importance: #HIGH }]
      @EndUserText: { label: 'Airline Currency', quickInfo: 'Airline Currency' }
      ZPN_I_ZZCARRIERS_ACT.CurrCode

}

Listing 1: Basic CDS view ZPN_I_ZZCARRIERS_ACT

 

2.     Create a corresponding consumption view

@AbapCatalog.sqlViewName: 'ZDDL_C_ZZCARR'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'ZPN_C_ZZCARRIERS_ACT'

@OData.publish: true

@Search.searchable: true
@UI.headerInfo.typeName: 'CarrierID'
@UI.headerInfo.title.value: 'CarrierID'

@ObjectModel.transactionalProcessingDelegated: true
@ObjectModel.semanticKey: ['CARRID']
@ObjectModel.representativeKey: 'CARRID'
@ObjectModel.createEnabled: true
@ObjectModel.updateEnabled: true
@ObjectModel.deleteEnabled: true

define view ZPN_C_ZZCARRIERS_ACT
  as select from ZPN_I_ZZCARRIERS_ACT
{

      // Key CarrierID
      @Search.defaultSearchElement: true
      @ObjectModel.readOnly: true
      @ObjectModel.text.element: [ 'CarrName' ]
      @UI.identification: [{position:10, importance: #HIGH }]
      @UI.lineItem: [{position: 10, importance: #HIGH }]
      @UI.textArrangement: #TEXT_FIRST
      @EndUserText: { label: 'Airline ID', quickInfo: 'Airline ID' }
  key ZPN_I_ZZCARRIERS_ACT.CarrID,

      // Name
      @Semantics.text: true
      @UI.identification: [{position:70, importance: #HIGH }]
      @UI.lineItem: [{position: 70, importance: #HIGH }]
      @EndUserText: { label: 'Airline Name', quickInfo: 'Airline Name' }
      ZPN_I_ZZCARRIERS_ACT.CarrName,

      // Currency
      @UI.identification: [{position:80, importance: #HIGH }]
      @UI.lineItem: [{position: 80, importance: #HIGH }]
      @EndUserText: { label: 'Airline Currency', quickInfo: 'Airline Currency' }
      ZPN_I_ZZCARRIERS_ACT.CurrCode

}

Listing 2: consumption CDS view ZPN_C_ZZCARRIERS_ACT

 

For this cds view the annotation ‘oData.publish : true‘ is set and the corresponding service needs to be activated via transaction /IFWND/MAINT_SERVICE .

 

3.     Create data quality CDS views

Now we switch to the data quality related structures. As part of the configuration activities the table ZZCARR_DQ_RES was generated, which stores the results of the evaluation. Therefore, we create the following basic CDS view ZPN_I_MDGLTYCustomObject.

@AbapCatalog.sqlViewName: 'ZPNICUSTOBJ'
@AbapCatalog.compiler.compareFilter: true
@ClientHandling.algorithm: #SESSION_VARIABLE
@AccessControl.authorizationCheck: #NOT_REQUIRED
@ObjectModel.usageType: { sizeCategory: #L, dataClass: #MIXED, serviceQuality: #C }
@ObjectModel.representativeKey: 'MasterDataChangeProcess'
@VDM.viewType: #BASIC
@AbapCatalog.preserveKey:true 
@Metadata.allowExtensions:true
@EndUserText.label: 'ALP for Custom Object ZZCarrier_ACT'
define view ZPN_I_MDGLTYCustomObject 
    as select from zzcarr_dq_res
    inner join mdc_d_prcroot as _MasterDataChangeProcess on zzcarr_dq_res.process_id = _MasterDataChangeProcess.id

  association [0..1] to I_MDQltyBusRuleEvalResult     as _MDQltyBusRuleEvalResult     on _MDQltyBusRuleEvalResult.MDQltyBusRuleEvalResultCode = $projection.MDQltyBusRuleEvalResultCode
  association [0..*] to I_MDQltyBusRuleEvalResultText as _MDQltyBusRuleEvalResultText on _MDQltyBusRuleEvalResultText.MDQltyBusRuleEvalResultCode = $projection.MDQltyBusRuleEvalResultCode
{
       //  keys
  key  carrid                                                                       as   Carrier,
  key  ruleuuid                                                                     as   MDQualityBusinessRuleUUID,
       @ObjectModel.foreignKey.association: '_MDQltyBusRuleEvalResult'
       @ObjectModel.text.association: '_MDQltyBusRuleEvalResultText'
  key  resulttype                                                                   as   MDQltyBusRuleEvalResultCode,
  key  zzcarr_dq_res.process_id                                                     as   MasterDataChangeProcess,

       //  attributes
       result_count                                                                 as   MDQltyNmbrOfBusRuleEvalResults,
       cast (dats_tims_to_tstmp( _MasterDataChangeProcess.finish_date,
                           _MasterDataChangeProcess.finish_time,
                            abap_system_timezone( $session.client,'NULL' ),
                            $session.client,
                           'NULL' )      as timestampl )                            as   MDChgProcessFinishDateTime,
       basetable                                                                    as   MDQltyBusinessRuleBaseTable,
       
       
       // associations
       @Consumption.filter.hidden: true
       _MDQltyBusRuleEvalResult,
       @Consumption.filter.hidden: true
       _MDQltyBusRuleEvalResultText
}

Listing 3: Basic CDS view ZPN_I_MDGLTYCustomObject

 

And the corresponding consumption CDS view with the definition for the keys:

@AbapCatalog.sqlViewName: 'ZPNCCUSTOBJ'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@ClientHandling.algorithm: #SESSION_VARIABLE
@ObjectModel.usageType: { sizeCategory: #XL, dataClass: #MIXED, serviceQuality: #D }
@ObjectModel.representativeKey: 'MasterDataChangeProcess'
@VDM.viewType: #CONSUMPTION
@EndUserText.label: 'ALP for Custom Object ZZCarrier_ACT'

@ObjectModel.text.control: #('ASSOCIATED_TEXT_UI_HIDDEN')
@OData.publish: true
@Metadata: {
    allowExtensions: true,
    ignorePropagatedAnnotations: true
}

define view ZPN_C_MDGLTYCustomObject
  as select from ZPN_I_MDGLTYCustomObject
  association [0..1] to C_MDQualityBusinessRuleTP      as _MDQualityBusinessRuleTP      on  $projection.MDQualityBusinessRuleUUID = _MDQualityBusinessRuleTP.MDQualityBusinessRuleUUID
  association [0..1] to C_MDQualityEvaluations         as _MDQualityEvaluations         on  $projection.MasterDataChangeProcess = _MDQualityEvaluations.MasterDataChangeProcess
  association [0..1] to ZPN_C_ZZCARRIERS_ACT           as _Carrier                      on  $projection.Carrier = _Carrier.CarrID
  association [0..1] to C_MDQltyBusinessRuleVH         as _MDQltyBusinessRuleVH         on  $projection.MDQualityBusinessRule        = _MDQltyBusinessRuleVH.MDQualityBusinessRule
                                                                                        and $projection.MDQltyBusinessObjectTypeCode = _MDQltyBusinessRuleVH.MDQltyBusinessObjectTypeCode
  association [0..1] to C_MDQltyBusinessRuleBaseTblVH  as _MDQltyBusinessRuleBaseTblVH  on  $projection.MDQltyBusinessRuleBaseTable  = _MDQltyBusinessRuleBaseTblVH.MDQltyBusinessRuleBaseTable
                                                                                        and $projection.MDQltyBusinessObjectTypeCode = _MDQltyBusinessRuleBaseTblVH.MDQltyBusinessObjectTypeCode
  association [0..*] to I_MDQltyBusinessRuleBaseTableT as _MDQltyBusinessRuleBaseTableT on  $projection.MDQltyBusinessRuleBaseTable  = _MDQltyBusinessRuleBaseTableT.MDQltyBusinessRuleBaseTable

Listing 4: Consumption CDS view ZPN_C_MDGLTYCustomObject (part1)

 

With the following definition for the keys

{
      // keys
      @Consumption.derivation: {
        lookupEntity: 'C_MDQualityEvaluations',
        resultElement: 'MasterDataChangeProcess',
        binding:
        [
          { targetElement: 'MDChgProcessIsLatest', type: #CONSTANT, value: 'X' },
          { targetElement: 'MDQltyBusinessObjectTypeCode', type: #CONSTANT, value: 'ZZCARR' }
        ]
      }
      @Consumption.filter: {selectionType: #SINGLE, multipleSelections: false }
      @Consumption.valueHelpDefinition: [
        {
          entity: {
              name: 'C_MDQualityEvaluations',
              element: 'MasterDataChangeProcess'
          },
          additionalBinding: [{
              localElement: 'MDQltyBusinessObjectTypeCode',
              element: 'MDQltyBusinessObjectTypeCode',
              usage: #FILTER
          }]
        }
      ]
      @ObjectModel.foreignKey.association: '_MDQualityEvaluations'
  key MasterDataChangeProcess,

      @ObjectModel.foreignKey.association: '_Carrier'
      @Consumption.valueHelpDefinition: [
         { association: '_Carrier' }
      ]
      @ObjectModel.text.association: '_Carrier'
  key Carrier,

  key MDQualityBusinessRuleUUID,

      @Consumption.valueHelpDefinition: [
        { association: '_MDQltyBusRuleEvalResult' },
        {
            qualifier: 'FilterEvalsByRes' ,
            presentationVariantQualifier: 'FilterEvalsByRes' ,
            entity: { name: 'ZPN_C_MDGLTYCustomObject', element: 'MDQltyBusRuleEvalResultCode' } ,
            additionalBinding: [
                { usage: #FILTER_AND_RESULT, element: 'MasterDataChangeProcess', localElement: 'MasterDataChangeProcess' }
            ]
        }
      ]
      @Consumption.filter.defaultValue: 'F'
      @ObjectModel.foreignKey.association: '_MDQltyBusRuleEvalResult'
      @ObjectModel.text.association: '_MDQltyBusRuleEvalResultText'
  key MDQltyBusRuleEvalResultCode,

Listing 5: Consumption CDS view ZPN_C_MDGLTYCustomObject (part2)

 

And the following data definitions:

     // data
      @Consumption.filter: { hidden: true, multipleSelections: false, selectionType: #SINGLE, defaultValue: 'ZZCARR'}
      _MDQualityBusinessRuleTP.MDQltyBusinessObjectTypeCode as MDQltyBusinessObjectTypeCode,

      @DefaultAggregation: #SUM
      MDQltyNmbrOfBusRuleEvalResults,

      @Consumption.valueHelpDefinition: [
        { association: '_MDQltyBusinessRuleVH' }
      ]
      @ObjectModel.foreignKey.association: '_MDQltyBusinessRuleVH'
      @ObjectModel.text.element: [ 'MDQualityBusinessRuleName' ]
      _MDQualityBusinessRuleTP.MDQualityBusinessRule        as MDQualityBusinessRule,

      _MDQualityBusinessRuleTP.MDQualityBusinessRuleName    as MDQualityBusinessRuleName,

      @Consumption.valueHelpDefinition: [
          { association: '_MDQltyBusinessRuleBaseTblVH' }
      ]
      @ObjectModel.foreignKey.association: '_MDQltyBusinessRuleBaseTblVH'
      @ObjectModel.text.association: '_MDQltyBusinessRuleBaseTableT'
      MDQltyBusinessRuleBaseTable,

      @Consumption.valueHelpDefinition: [
        {
            qualifier: 'FilterEvalsByCurrency' ,
            presentationVariantQualifier: 'FilterEvalsByCurrency' ,
            entity: {
                name: 'ZPN_C_MDGLTYCustomObject',
                element: 'CurrencyCode'
            }
        }
      ]
      _Carrier.CurrCode                                     as CurrCode,

      // associations
      @Consumption.filter.hidden
      _Carrier,
      @Consumption.filter.hidden
      _MDQltyBusRuleEvalResult,
      @Consumption.filter.hidden
      _MDQltyBusRuleEvalResultText,
      @Consumption.filter.hidden
      _MDQltyBusinessRuleVH,
      @Consumption.filter.hidden
      _MDQltyBusinessRuleBaseTblVH,
      @Consumption.filter.hidden
      _MDQltyBusinessRuleBaseTableT,
      @Consumption.filter.hidden
      _MDQualityEvaluations
}

Listing 6: Consumption CDS view ZPN_C_MDGLTYCustomObject (part 3)

 

4.     Create Annotation file

Next, we create an Annotation file, which describes the UI specific aspects.

@Metadata.layer: #CUSTOMER

@UI.headerInfo:{
    typeName: 'Evaluation Item',
    typeNamePlural: 'Evaluation Items',
    description.value: '_MDQualityEvaluations.MDChgProcessDescription',
    title.value: 'MasterDataChangeProcess'
}

@UI.chart: [
    {
        qualifier: 'TotalNumEvalsByResult',
        title: 'Results by Outcome',
        chartType: #DONUT,
        dimensions:  [ 'MDQltyBusRuleEvalResultCode' ] ,
        measures:  [ 'MDQltyNmbrOfBusRuleEvalResults' ] ,
        dimensionAttributes: [
            { dimension: 'MDQltyBusRuleEvalResultCode', role: #CATEGORY }
        ],
        measureAttributes: [
            { measure: 'MDQltyNmbrOfBusRuleEvalResults', role: #AXIS_1, asDataPoint: true }
        ]
    },
    {
        qualifier: 'FilterEvalsByCurrency',
        title: 'Results By Carrier Currency',
        chartType: #COLUMN,
        dimensions:  [ 'CurrCode' ] ,
        measures:  [ 'MDQltyNmbrOfBusRuleEvalResults' ] ,
        dimensionAttributes: [
            { dimension: 'CurrCode',role: #CATEGORY }
        ],
        measureAttributes: [
            { measure: 'MDQltyNmbrOfBusRuleEvalResults', role: #AXIS_1, asDataPoint: true }
        ]
    }
]

@UI.presentationVariant: [
    {
        text: 'Visual Filter: Results by Outcome',
        qualifier: 'FilterEvalsByRes',
        sortOrder: [{ by: 'MDQltyNmbrOfBusRuleEvalResults', direction: #DESC  }],
        visualizations: [
            { type: #AS_CHART, qualifier: 'TotalNumEvalsByResult' }
        ]
    },
    {
        text: 'Visual Filter: Results by Currency',
        qualifier: 'FilterEvalsByCurrency',
        sortOrder: [{ by: 'MDQltyNmbrOfBusRuleEvalResults', direction: #DESC  }],
        visualizations: [
            { type: #AS_CHART, qualifier: 'FilterEvalsByCurrency' }
        ]
    },
    {
        qualifier: 'DefaultVariantALP' ,
        groupBy: [ 'Carrier' ],
        sortOrder: [{ by: 'Carrier', direction: #ASC }],
        initialExpansionLevel: 1,
        visualizations: [
            { type: #AS_LINEITEM, qualifier: 'DefaultVariantALP' }
        ],
        requestAtLeast: ['MasterDataChangeProcess']
    },
    {
        qualifier: 'TotalNumEvalsByResultBase',
        sortOrder: [{ by: 'MDQltyNmbrOfBusRuleEvalResults', direction: #DESC  }],
        visualizations: [
            { type: #AS_CHART, qualifier: 'TotalNumEvalsByResultBase' }
        ]
    }
]
annotate view ZPN_C_MDGLTYCustomObject with
{
  @UI.selectionField: [{ position: 40 }]
  @EndUserText: { label: 'Evaluation', quickInfo: 'Evaluation' }
  MasterDataChangeProcess;
  
  @Consumption.semanticObject: 'Carrier'
  @UI.identification: [{ position: 10 }]
  @UI.lineItem: [
    { qualifier: 'DefaultVariantALP', position: 10, label: 'Airline' }
  ]
  @UI.selectionField:[{ position: 10 }]
  @UI.textArrangement: #TEXT_FIRST
  @EndUserText: { label: 'Airline', quickInfo: 'Airline' }
  Carrier;

  @UI.hidden: true
  MDQualityBusinessRuleUUID;

  @UI.selectionField:[{ position: 60 }]
  @UI.textArrangement: #TEXT_ONLY
  @EndUserText: { label: 'Outcome', quickInfo: 'Outcome' }
  MDQltyBusRuleEvalResultCode;

  @UI.identification: [{ position: 20 }]
  @UI.lineItem: [
    { qualifier: 'DefaultVariantALP', position: 20, label: 'Airline Currency' }
  ]
  @UI.selectionField: [{ position: 20 }]
  @EndUserText: { label: 'Airline Currency', quickInfo: 'Airline Currency' }
  CurrCode;

  @UI.identification: [{ position: 30 }]
  @UI.lineItem: [
    { qualifier: 'DefaultVariantALP', position: 30, label: 'Rule' }
  ]
  @UI.selectionField: [{ position: 30 }]
  @UI.textArrangement: #TEXT_LAST
  @EndUserText: { label: 'Rule', quickInfo: 'Rule' }
  MDQualityBusinessRule;
  
  @UI.hidden: true
  MDQualityBusinessRuleName;

  @UI.identification: [{ position: 40 }]
  @UI.lineItem: [
    { qualifier: 'DefaultVariantALP', position: 40, label: 'Base Table' }
  ]
  @UI.selectionField: [{ position: 50 }]
  @UI.textArrangement: #TEXT_FIRST
  @EndUserText: { label: 'Base Table', quickInfo: 'Base Table' }
  MDQltyBusinessRuleBaseTable;

  @UI.dataPoint: { valueFormat.numberOfFractionalDigits: 0, title: 'Results' }
  @UI.lineItem: [
    { qualifier: 'DefaultVariantALP', position: 50,  label: 'Results' }
  ]
  @EndUserText: { label: 'Results', quickInfo: 'Results' }
  MDQltyNmbrOfBusRuleEvalResults;
}

Listing 7: Annotation file for view ZPN_C_MDGLTYCustomObject

 

5.     Activate the oData Service

For this CDS view the annotation “oData.publish: true” is set and the gateway oData service is generated with the activation of the CDS view. As a last step, the service then needs to be activated using transaction /IFWND/MAINT_SERVICE.

 

Summary

We have now created the following 5 files:

Figure 3: List of created files

 

We have now created 2 basic CDS views and 2 consumption views where the 2 consumption views provide the oData services access by the ALP , which we create in the next  paragraph. The created Metadata Extension file is applied to adapt the layout of the ALP.

 

Create the ALP application in your Web IDE

 

  1. Start a web IDE that is connected to the system where you developed the ALP in order to create a new project. For example: WEB IDE https://webidecp-fiori.dispatcher.int.sap.hana.ondemand.com/.Note: We assume here that it is an external application not built using SAP internal processes, thus add the following parameter &sap-ide-external=true to the URL of your SAP  Web IDE (if not done automatically).
  2. From the menu choose Create Project from Template.
  3. Clear filters and select New Analytical List Page and enter a project name and a title.Note: We assume here that it is an external application not built using SAP internal processes, thus add the following parameter sap-ide-external=true to the URL of your SAP  Web IDE (if not done automatically).Figure 4: first entry screen for creating the ALP from template
  4. Enter the service details: Search for the newly create consumption view ZPN_C_MDGLTYCUSTOMOBJECT_CDS

    Figure 5: Select the oData service  used by the ALP

    and, on the next screen, select both proposed annotations.

  5. Next select data collection ZPN_C_MDGLTYCustomObject.
  6. Important: To get the ALP working you have to do a minor correction in the generated code: Remove the following code snippet from the manifest file of your web app:
    "metadataUrlParams": {
    "sap-value-list": "none"                
    }​
  7. Run as a Web Application (to use the already existing data in the database), logon to the backend system, and select GO, so that data is selected.

Then the resulting ALP should look like this:

Figure 6: Final ALP displaying evaluation resuts for the defined custom object ZZCARRIERS

 

Summary:

Our example is based on the assumption that you can use the generated persistence from the MDC model , because our focus is on the explanation how to create an ALP for analysing and drilling down into the rule evaluation result.

In the first section we created the required CDS views and in the section section we showed step by step how to create a FIORI application based on the ALP template.

In our example we restricted to  the main ALP capabilities. Usually more navigation possibilities are required by customers. For implementing those, please see as a starting point the given links below.

 

Further Reading for ALPs

Assigned Tags

      Be the first to leave a comment
      You must be Logged on to comment or reply to a post.