Skip to content

Commit

Permalink
Support annotating common expressions as sort order of entity sets (#333
Browse files Browse the repository at this point in the history
)

This proposal addresses the requirement to use a common expression in
the sort order annotated to an entity set. For this purpose, this
proposal extends the structure type of term `Common.SortOrder`.

**Rationale for this extension**: In the concrete use case of this
requirement, EDMX is used to as an _interface definition language_
(a.k.a. _service provider interface (SPI)_) to describe the sort
behavior a service implementation compliant with the API definition must
offer for the annotated entity set. Allowing the use of an expression
for a sort item mirrors the expressiveness of the protocol ABNF and
leads to a self-contained API description that can be confidently
published on the SAP Business Accelerator Hub (that is, no need for an
out-of-band textual description).

**Discussion needed**: The type of term `Common.SortOrder` is reused in
`UI.PresentationVariant`. If there are concerns that this proposal adds
to much freedom for services and makes it hard for UIs to interpret
`Common.SortOrder` annotations because they lack a common expression
parser, an **alternative design** would be to keep the term
`Common.SortOrder` as it is today and create a new term
`Common.SortOrderExpressions` with a type derived from
`Common.SortOrderType` adding the expression to this structure.

Example from the use case of the SAP Build Work Zone product: Sort
instances of the `entities` entity set such that instances with
`identification/entityType = "Site"` are returned before all other
instances

```xml
<Annotations Target="entities">
  <Annotation Term="Common.SortOrder">
    <Collection>
      <Record>
        <PropertyValue Property="Expression">
          <Eq>
            <Path>identification/entityType</Path>
            <String>Site</String>
          </Eq>
        </PropertyValue>
      </Record>
    <Collection>
  </Annotation>
</Annotations>

  …
</Annotations>

```

---------

Co-authored-by: Ralf Handl <[email protected]>
Co-authored-by: Heiko Theißen <[email protected]>
  • Loading branch information
3 people authored Jul 19, 2024
1 parent 7d3115c commit 555792a
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 16 deletions.
25 changes: 25 additions & 0 deletions examples/Common.SortOrder-sample.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$Version": "4.0",
"$Reference": {
"https://sap.github.io/odata-vocabularies/vocabularies/Common.json": {
"$Include": [{ "$Namespace": "com.sap.vocabularies.Common.v1", "$Alias": "Common" }]
}
},
"sortorder.sample": {
"WorkerTimeSheet": {
"$Kind": "EntityType",
"ClockInDateTime": { "$Type": "Edm.DateTimeOffset", "$Nullable": true, "$Precision": 0 },
"ClockOutDateTime": { "$Type": "Edm.DateTimeOffset", "$Nullable": true, "$Precision": 0 }
},
"$Annotations": {
"timezone.sample.WorkerTimeSheet": {
"@Common.SortOrder#HeroesOfLabor": [
{
"Expression": { "$Sub": [{ "$Path": "ClockOutDateTime" }, { "$Path": "ClockInDateTime" }] },
"Descending": true
}
]
}
}
}
}
29 changes: 29 additions & 0 deletions examples/Common.SortOrder-sample.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
<edmx:Reference Uri="https://sap.github.io/odata-vocabularies/vocabularies/Common.xml">
<edmx:Include Namespace="com.sap.vocabularies.Common.v1" Alias="Common" />
</edmx:Reference>
<edmx:DataServices>
<Schema Namespace="sortorder.sample" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<EntityType Name="WorkerTimeSheet">
<Property Name="ClockInDateTime" Type="Edm.DateTimeOffset" />
<Property Name="ClockOutDateTime" Type="Edm.DateTimeOffset" />
</EntityType>
<Annotations Target="timezone.sample.WorkerTimeSheet">
<Annotation Term="Common.SortOrder" Qualifier="HeroesOfLabor">
<Collection>
<Record>
<PropertyValue Property="Expression">
<Sub>
<Path>ClockOutDateTime</Path>
<Path>ClockInDateTime</Path>
</Sub>
</PropertyValue>
<PropertyValue Property="Descending" Bool="true"/>
</Record>
</Collection>
</Annotation>
</Annotations>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
8 changes: 7 additions & 1 deletion vocabularies/Common.json
Original file line number Diff line number Diff line change
Expand Up @@ -1175,7 +1175,7 @@
},
"SortOrderType": {
"$Kind": "ComplexType",
"@Core.Description": "Exactly one of `Property` and `DynamicProperty` must be present",
"@Core.Description": "Exactly one of `Property`, `DynamicProperty` and `Expression` must be present",
"Property": {
"$Type": "Edm.PropertyPath",
"$Nullable": true,
Expand All @@ -1190,6 +1190,12 @@
"@Core.LongDescription": "If the annotation referenced by the annotation path does not apply to the same collection of entities\n as the one being sorted according to the [`UI.PresentationVariant`](UI.md#PresentationVariant) or `Common.SortOrder` annotation,\n this instance of `UI.PresentationVariant/SortOrder` or `Common.SortOrder` MUST be silently ignored.",
"@Validation.AllowedTerms": ["Analytics.AggregatedProperty", "Aggregation.CustomAggregate"]
},
"Expression": {
"$Type": "Edm.PrimitiveType",
"$Nullable": true,
"@Common.Experimental": true,
"@Core.Description": "Dynamic expression whose primitive result value is used to sort the instances"
},
"Descending": {
"$Type": "Edm.Boolean",
"$Nullable": true,
Expand Down
29 changes: 15 additions & 14 deletions vocabularies/Common.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,16 @@ Term|Type|Description
[FilterDefaultValueHigh](Common.xml#L1285) *([Experimental](Common.md#Experimental))*|PrimitiveType?|<a name="FilterDefaultValueHigh"></a>A default upper limit for the property to be used in 'less than or equal' filter expressions.
[DerivedFilterDefaultValue](Common.xml#L1290) *([Experimental](Common.md#Experimental))*|String|<a name="DerivedFilterDefaultValue"></a>Function import to derive a default value for the property from a given context in order to use it in filter expressions.<br>Function import has two parameters of complex types:<br/> - `parameters`, a structure resembling the entity type the parameter entity set related to the entity set of the annotated property<br/> - `properties`, a structure resembling the type of the entity set of the annotated property<br/> The return type must be of the same type as the annotated property.<br/> Arguments passed to the function import are used as context for deriving the default value. The function import returns this default value, or null in case such a value could not be determined.
[SortOrder](Common.xml#L1314)|\[[SortOrderType](#SortOrderType)\]|<a name="SortOrder"></a>List of sort criteria<br>The items of the annotated entity set or the items of the collection of the annotated entity type are sorted by the first entry of the SortOrder collection. Items with same value for this first sort criteria are sorted by the second entry of the SortOrder collection, and so on.
[RecursiveHierarchy](Common.xml#L1370) *(Deprecated)*|[RecursiveHierarchyType](#RecursiveHierarchyType)|<a name="RecursiveHierarchy"></a>Use terms [Aggregation.RecursiveHierarchy](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Aggregation.V1.md#RecursiveHierarchy) and [Hierarchy.RecursiveHierarchy](https://github.com/SAP/odata-vocabularies/blob/main/vocabularies/Hierarchy.md#RecursiveHierarchy) instead
[CreatedAt](Common.xml#L1418)|DateTimeOffset?|<a name="CreatedAt"></a>Creation timestamp
[CreatedBy](Common.xml#L1422)|[UserID?](#UserID)|<a name="CreatedBy"></a>First editor
[ChangedAt](Common.xml#L1426)|DateTimeOffset?|<a name="ChangedAt"></a>Last modification timestamp
[ChangedBy](Common.xml#L1430)|[UserID?](#UserID)|<a name="ChangedBy"></a>Last editor
[OriginalProtocolVersion](Common.xml#L1442)|String|<a name="OriginalProtocolVersion"></a>Original protocol version of a converted (V4) CSDL document, allowed values `2.0` and `3.0`
[ApplyMultiUnitBehaviorForSortingAndFiltering](Common.xml#L1447) *([Experimental](Common.md#Experimental))*|[Tag](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Core.V1.md#Tag)|<a name="ApplyMultiUnitBehaviorForSortingAndFiltering"></a>Sorting and filtering of amounts in multiple currencies needs special consideration<br>TODO: add link to UX documentation on https://experience.sap.com/fiori-design/
[mediaUploadLink](Common.xml#L1453) *([Experimental](Common.md#Experimental))*|URL|<a name="mediaUploadLink"></a>URL for uploading new media content to a Document Management Service<br>In contrast to the `@odata.mediaEditLink` this URL allows to upload new media content without directly changing a stream property or media resource. The upload request typically uses HTTP POST with `Content-Type: multipart/form-data` following RFC 7578. The upload request must contain one multipart representing the content of the file. The `name` parameter in the `Content-Disposition` header (as described in RFC 7578) is irrelevant, but the `filename` parameter is expected. If the request succeeds the response will contain a JSON body of `Content-Type: application/json` with a JSON property `readLink`. The newly uploaded media resource can be linked to the stream property by changing the `@odata.mediaReadLink` to the value of this `readLink` in a subsequent PATCH request to the OData entity.
[PrimitivePropertyPath](Common.xml#L1468) *([Experimental](Common.md#Experimental))*|[Tag](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Core.V1.md#Tag)|<a name="PrimitivePropertyPath"></a>A term or term property with this tag whose type is (a collection of) `Edm.PropertyPath` MUST resolve to a primitive structural property
[WebSocketBaseURL](Common.xml#L1473) *([Experimental](Common.md#Experimental))*|URL|<a name="WebSocketBaseURL"></a>Base URL for WebSocket connections
[RecursiveHierarchy](Common.xml#L1374) *(Deprecated)*|[RecursiveHierarchyType](#RecursiveHierarchyType)|<a name="RecursiveHierarchy"></a>Use terms [Aggregation.RecursiveHierarchy](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Aggregation.V1.md#RecursiveHierarchy) and [Hierarchy.RecursiveHierarchy](https://github.com/SAP/odata-vocabularies/blob/main/vocabularies/Hierarchy.md#RecursiveHierarchy) instead
[CreatedAt](Common.xml#L1422)|DateTimeOffset?|<a name="CreatedAt"></a>Creation timestamp
[CreatedBy](Common.xml#L1426)|[UserID?](#UserID)|<a name="CreatedBy"></a>First editor
[ChangedAt](Common.xml#L1430)|DateTimeOffset?|<a name="ChangedAt"></a>Last modification timestamp
[ChangedBy](Common.xml#L1434)|[UserID?](#UserID)|<a name="ChangedBy"></a>Last editor
[OriginalProtocolVersion](Common.xml#L1446)|String|<a name="OriginalProtocolVersion"></a>Original protocol version of a converted (V4) CSDL document, allowed values `2.0` and `3.0`
[ApplyMultiUnitBehaviorForSortingAndFiltering](Common.xml#L1451) *([Experimental](Common.md#Experimental))*|[Tag](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Core.V1.md#Tag)|<a name="ApplyMultiUnitBehaviorForSortingAndFiltering"></a>Sorting and filtering of amounts in multiple currencies needs special consideration<br>TODO: add link to UX documentation on https://experience.sap.com/fiori-design/
[mediaUploadLink](Common.xml#L1457) *([Experimental](Common.md#Experimental))*|URL|<a name="mediaUploadLink"></a>URL for uploading new media content to a Document Management Service<br>In contrast to the `@odata.mediaEditLink` this URL allows to upload new media content without directly changing a stream property or media resource. The upload request typically uses HTTP POST with `Content-Type: multipart/form-data` following RFC 7578. The upload request must contain one multipart representing the content of the file. The `name` parameter in the `Content-Disposition` header (as described in RFC 7578) is irrelevant, but the `filename` parameter is expected. If the request succeeds the response will contain a JSON body of `Content-Type: application/json` with a JSON property `readLink`. The newly uploaded media resource can be linked to the stream property by changing the `@odata.mediaReadLink` to the value of this `readLink` in a subsequent PATCH request to the OData entity.
[PrimitivePropertyPath](Common.xml#L1472) *([Experimental](Common.md#Experimental))*|[Tag](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Core.V1.md#Tag)|<a name="PrimitivePropertyPath"></a>A term or term property with this tag whose type is (a collection of) `Edm.PropertyPath` MUST resolve to a primitive structural property
[WebSocketBaseURL](Common.xml#L1477) *([Experimental](Common.md#Experimental))*|URL|<a name="WebSocketBaseURL"></a>Base URL for WebSocket connections

<a name="TextFormatType"></a>
## [TextFormatType](Common.xml#L120)
Expand Down Expand Up @@ -423,20 +423,21 @@ All side effects are essentially value changes, differentiation not needed.

<a name="SortOrderType"></a>
## [SortOrderType](Common.xml#L1322)
Exactly one of `Property` and `DynamicProperty` must be present
Exactly one of `Property`, `DynamicProperty` and `Expression` must be present

Property|Type|Description
:-------|:---|:----------
[Property](Common.xml#L1324)|PropertyPath?|Sort property
[DynamicProperty](Common.xml#L1336)|AnnotationPath?|Dynamic property introduced by an annotation and used as sort property<br>If the annotation referenced by the annotation path does not apply to the same collection of entities as the one being sorted according to the [`UI.PresentationVariant`](UI.md#PresentationVariant) or `Common.SortOrder` annotation, this instance of `UI.PresentationVariant/SortOrder` or `Common.SortOrder` MUST be silently ignored.<br>Allowed terms:<ul><li>[AggregatedProperty](Analytics.md#AggregatedProperty)</li><li>[CustomAggregate](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Aggregation.V1.md#CustomAggregate)</li></ul>
[Descending](Common.xml#L1350)|Boolean?|Sort direction, ascending if not specified otherwise
[Expression](Common.xml#L1350) *([Experimental](Common.md#Experimental))*|PrimitiveType?|Dynamic expression whose primitive result value is used to sort the instances
[Descending](Common.xml#L1354)|Boolean?|Sort direction, ascending if not specified otherwise

<a name="RecursiveHierarchyType"></a>
## [RecursiveHierarchyType](Common.xml#L1383) *(Deprecated)*
## [RecursiveHierarchyType](Common.xml#L1387) *(Deprecated)*
Use terms [Aggregation.RecursiveHierarchy](https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Aggregation.V1.md#RecursiveHierarchy) and [Hierarchy.RecursiveHierarchy](https://github.com/SAP/odata-vocabularies/blob/main/vocabularies/Hierarchy.md#RecursiveHierarchy) instead

<a name="UserID"></a>
## [UserID](Common.xml#L1434)
## [UserID](Common.xml#L1438)
**Type:** String

User ID
6 changes: 5 additions & 1 deletion vocabularies/Common.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1320,7 +1320,7 @@ Upon modification field control logic is invoked so that meta-information like h
</Annotation>
</Term>
<ComplexType Name="SortOrderType">
<Annotation Term="Core.Description" String="Exactly one of `Property` and `DynamicProperty` must be present" />
<Annotation Term="Core.Description" String="Exactly one of `Property`, `DynamicProperty` and `Expression` must be present" />
<Property Name="Property" Type="Edm.PropertyPath">
<Annotation Term="Common.PrimitivePropertyPath" />
<Annotation Term="Core.Description" String="Sort property" />
Expand All @@ -1347,6 +1347,10 @@ Upon modification field control logic is invoked so that meta-information like h
</Collection>
</Annotation>
</Property>
<Property Name="Expression" Type="Edm.PrimitiveType">
<Annotation Term="Common.Experimental" />
<Annotation Term="Core.Description" String="Dynamic expression whose primitive result value is used to sort the instances" />
</Property>
<Property Name="Descending" Type="Edm.Boolean" Nullable="true">
<Annotation Term="Core.Description" String="Sort direction, ascending if not specified otherwise" />
</Property>
Expand Down

0 comments on commit 555792a

Please sign in to comment.