Skip to content

03. Extending the model

Nikica Josipovic edited this page Jul 8, 2025 · 5 revisions

Extend the data model

We already created a CDS file to carry the extension in the previous exercise. We will make this a usable extension now. Please expand ths file to look like this:

using {sap.capire.incidents as my} from '@capire/incidents';

extend my.Customers with {
  status : String(8) @title: 'Customer Status' default 'Silver';
};

extend service ProcessorService with {
  @cds.redirection.target: false
  entity CustomersProjection as
    projection on my.Customers {
      @readonly ID,
      @readonly name,
      status
    };
}

extend ProcessorService.Incidents with columns {
  @readonly customer.status as CustomerStatus
};

What are we doing here?

The Customers entity has an additional attribute for a Customer Status. Since the ProcessorService only as an autoexposed entity Customers available (which is always read-only), we need to provide an additional, write-enabled entity, if we want to promote a customer in status. Since we want to expose the absolute minimum surface for writing, we use the @readonly annotations, and only enable the status attribute for writing. Since we already have auto-exposed the entity Customers, we must make sure that the redirection-target for the Customers association in Incidentsis clear. We use @cds.redirection.target: false for this. We also extend the projection of the Incidents entity to flatten the customer status into the attribute list.

Handling test data

In a typical use case, Application Developers would provide useful test data and other things via an extension project template. We can shortcut this by copying the db/data folder from our incidents application to the test/datafolder in our extension project.

Note

Initial data is handled automatically by CAP, if there are .csv files present in the db/data folder. In an extension project, this means that such files are also pushed with the extension, potentially overwriting data deployed to the productive tenant. In order to have useful initial test data, which is not pushed with the extension, simply put these files in the testdata` folder instead

Enhance the UI

In order to see the new attribute on the UI, we need to add some Fiori Elements annotations to our extension file:

annotate ProcessorService.Incidents with @(UI : {
  LineItem                   : [
    ...,
    {
      Value         : urgency.descr,
      Criticality   : (urgency.code = 'H' ? 1 : (urgency.code = 'M' ? 2 : 0)),
      Label         : 'Urgency',
      @UI.Importance: #High
    },
    {
      Value            : CustomerStatus,
      Label            : 'Customer Status',
      ![@UI.Importance]: #High
    },
    {
      $Type : 'UI.DataFieldForAction',
      Action: 'ProcessorService.promoteCustomer(ProcessorService.Incidents)',
      Label : 'promote Customer',
    },
    {
      $Type : 'UI.DataFieldForAction',
      Action: 'ProcessorService.promoteIncident',
      Label : 'promote Incident',
    },
  ],
  FieldGroup #GeneratedGroup1: {Data: [..., {Value: CustomerStatus}]}

});

What are we doing here?

In the Incidents List view, the Customer Status needs to be added vial the LineItemsection. You can observe the delta annotations used to extend array values conveniently. We are also adding a color coding to urgency via Criticality and leverage the fairly new support for expressions in annotations . The Customer Status is also added to the object page using FieldGroup.

Testing the extension

Instead of constantly pushing the extension to the MTX Sidecar, you can also start the extension project locally using cds watch or cds run command. You can immediately see the result of your changes, and control whether your UI changes look like expected. Once you are happy with your changes, you can push the extension to the actual application.

Note

A SaaS application usually already has soa fair amount of business logic deployed. Even though you pulled the base model from the application, the extension project does not get the source code of the application. If there is logic, which alters data or changes the flow of the UI (e.g. actions and events emitted in the background) these will not be observable in the local test of an extension. In such cases, the application developer either provides some stubs in the extension project template to emulate the deployed business logic, or extension developers test against a test tenant provided.

It is a good idea to shut down the base application and sidecar while testing locally to avoid the extension project competing against port 4004.

Adding the actions

We want two buttons on our list page - one to promote a customer to Gold, and one to promote selected incidents to high. This is achieved with this snippet:

extend ProcessorService.Incidents with actions {
  @(Common.SideEffects: {TargetEntities: ['/ProcessorService.EntityContainer/Incidents']}, )

  action promoteIncident();

  @(Common.SideEffects: {TargetEntities: ['/ProcessorService.EntityContainer/Incidents']
  // TargetProperties: [in.CustomerStatus, in.urgency_code, in.urgency.descr],
  })
  action promoteCustomer(in : many $self,
                         @(
                           title: 'promote Customer to Gold',
                           Common: {
                             ValueListWithFixedValues: true,
                             ValueList               : {
                               CollectionPath: 'CustomersProjection',
                               Parameters    : [
                                 {
                                   $Type            : 'Common.ValueListParameterInOut',
                                   ValueListProperty: 'ID',
                                   LocalDataProperty: Customer_ID
                                 },
                                 {
                                   $Type            : 'Common.ValueListParameterDisplayOnly',
                                   ValueListProperty: 'name'
                                 }
                               ]
                             }
                           }
                         )
                         Customer_ID : String);
};

What are we doing here?

The actions are both bound to ProcessorService.Incidents from a CDS perspective, but for Fiori Elements, promoteIncident is entity bound while promoteCustomer is collection bound, and requires a more complex annotation to achieve a value list for customer selection. If you test the UI, you will observe two buttons:

  • promote Customer should bring up a selection dialog
  • promote Incident should be disabled and only become active once you select one or more incidents from the list.

In the Fourth exercise — Creating the extension, we will bring these buttons to life.

Clone this wiki locally