diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2681c9d..768dffd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,11 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
-## Version 1.11.6 - 2023-09-xx
+## Version 1.11.6 - 2023-08-10
### Changed
- Switch to `better-sqlite3` via `@cap-js/sqlite`
+- Suppress analytical conversion via entity annotation `@cov2ap.analytics.skipForKey`, if all dimension key elements are requested
## Version 1.11.5 - 2023-08-02
diff --git a/README.md b/README.md
index eab93bc..38fda97 100644
--- a/README.md
+++ b/README.md
@@ -167,6 +167,7 @@ The following OData V2 adapter for CDS specific annotations are supported:
**Entity Level**:
- `@cov2ap.analytics: false`: Suppress analytics conversion for the annotated entity, if set to `false`.
+- `@cov2ap.analytics.skipForKey`: Suppress analytical conversion for the annotated entity, if all dimension key elements are requested
- `@cov2ap.deltaResponse: 'timestamp'`: Delta response '\_\_delta' is added to response data of annotated entity with current timestamp information.
- `@cov2ap.isoTime`: Values of type cds.Time (Edm.Time) are represented in ISO 8601 format for annotated entity.
- `@cov2ap.isoDate`: Values of type cds.Date (Edm.DateTime) are represented in ISO 8601 format for annotated entity.
diff --git a/package-lock.json b/package-lock.json
index f89eb94..01afe20 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1709,9 +1709,9 @@
"devOptional": true
},
"node_modules/@types/node": {
- "version": "20.4.8",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.8.tgz",
- "integrity": "sha512-0mHckf6D2DiIAzh8fM8f3HQCvMKDpK94YQ0DSVkfWTG9BZleYIWudw9cJxX8oCk9bM+vAkDyujDV6dmKHbvQpg=="
+ "version": "20.4.9",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.9.tgz",
+ "integrity": "sha512-8e2HYcg7ohnTUbHk8focoklEQYvemQmu9M/f43DZVx43kHn0tE3BY/6gSDxS7k0SprtS0NHvj+L80cGLnoOUcQ=="
},
"node_modules/@types/qs": {
"version": "6.9.7",
@@ -3013,9 +3013,9 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
},
"node_modules/electron-to-chromium": {
- "version": "1.4.487",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.487.tgz",
- "integrity": "sha512-XbCRs/34l31np/p33m+5tdBrdXu9jJkZxSbNxj5I0H1KtV2ZMSB+i/HYqDiRzHaFx2T5EdytjoBRe8QRJE2vQg==",
+ "version": "1.4.488",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.488.tgz",
+ "integrity": "sha512-Dv4sTjiW7t/UWGL+H8ZkgIjtUAVZDgb/PwGWvMsCT7jipzUV/u5skbLXPFKb6iV0tiddVi/bcS2/kUrczeWgIQ==",
"dev": true
},
"node_modules/emittery": {
diff --git a/src/index.js b/src/index.js
index 5816a1f..2acc894 100644
--- a/src/index.js
+++ b/src/index.js
@@ -2123,24 +2123,17 @@ function cov2ap(options = {}) {
function convertAnalytics(url, req) {
const definition = req.context && req.context.definition;
- if (
- !(
- definition &&
- definition.kind === "entity" &&
- definition["@cov2ap.analytics"] !== false &&
- url.query["$select"] &&
- (definition["@cov2ap.analytics"] === true ||
- definition["@Analytics"] ||
- definition["@Analytics.AnalyticalContext"] ||
- definition["@Analytics.query"] ||
- definition["@AnalyticalContext"] ||
- definition["@Aggregation.ApplySupported.PropertyRestrictions"] ||
- definition["@sap.semantics"] === "aggregate")
- )
- ) {
+ if (!(isAnalyticsEntity(definition) && url.query["$select"])) {
return;
}
const elements = req.context.definitionElements;
+ const keyDimensions = [];
+ for (const name of structureKeys(elements)) {
+ const element = elements[name];
+ if (isDimensionElement(element) && element.key) {
+ keyDimensions.push(element);
+ }
+ }
const measures = [];
const dimensions = [];
const selects = url.query["$select"].split(",");
@@ -2154,20 +2147,22 @@ function cov2ap(options = {}) {
selects.forEach((select) => {
const element = elements[select];
if (element) {
- if (
- element["@Analytics.AnalyticalContext.Measure"] ||
- element["@AnalyticalContext.Measure"] ||
- element["@Analytics.Measure"] ||
- element["@sap.aggregation.role"] === "measure"
- ) {
+ if (isMeasureElement(element)) {
measures.push(element);
} else {
- // element["@Analytics.AnalyticalContext.Dimension"] || element["@AnalyticalContext.Dimension"] || element["@Analytics.Dimension"] || element["@sap.aggregation.role"] === "dimension"
+ // isDimensionElement(element)
dimensions.push(element);
}
}
});
+ if (
+ definition["@cov2ap.analytics.skipForKey"] &&
+ keyDimensions.every((keyDimension) => dimensions.includes(keyDimension))
+ ) {
+ return;
+ }
+
if (dimensions.length > 0 || measures.length > 0) {
url.query["$apply"] = "";
if (dimensions.length) {
@@ -2184,8 +2179,7 @@ function cov2ap(options = {}) {
}
url.query["$apply"] += `aggregate(${measures
.map((measure) => {
- const aggregation =
- measure["@Aggregation.default"] || measure["@Aggregation.Default"] || measure["@DefaultAggregation"];
+ const aggregation = aggregationDefault(measure);
const aggregationName = aggregation ? aggregation["#"] || aggregation : DefaultAggregation;
const aggregationFunction = aggregationName ? AggregationMap[aggregationName.toUpperCase()] : undefined;
if (!aggregationFunction) {
@@ -2197,12 +2191,7 @@ function cov2ap(options = {}) {
if (aggregationFunction.startsWith("$")) {
return `${aggregationFunction} as ${AggregationPrefix}${measure.name}`;
} else {
- const referenceElement =
- measure["@Aggregation.referenceElement"] ||
- measure["@Aggregation.ReferenceElement"] ||
- measure["@Aggregation.reference"] ||
- measure["@Aggregation.Reference"];
- return `${referenceElement || measure.name} with ${aggregationFunction} as ${AggregationPrefix}${
+ return `${referenceElement(measure) || measure.name} with ${aggregationFunction} as ${AggregationPrefix}${
measure.name
}`;
}
@@ -2229,13 +2218,7 @@ function cov2ap(options = {}) {
.map((orderBy) => {
let [name, order] = orderBy.split(" ");
const element = elements[name];
- if (
- element &&
- (element["@Analytics.AnalyticalContext.Measure"] ||
- element["@AnalyticalContext.Measure"] ||
- element["@Analytics.Measure"] ||
- element["@sap.aggregation.role"] === "measure")
- ) {
+ if (element && isMeasureElement(element)) {
name = `${AggregationPrefix}${element.name}`;
}
return name + (order ? ` ${order}` : "");
@@ -2257,6 +2240,53 @@ function cov2ap(options = {}) {
}
}
+ function isAnalyticsEntity(entity) {
+ return (
+ entity &&
+ entity.kind === "entity" &&
+ entity["@cov2ap.analytics"] !== false &&
+ (entity["@cov2ap.analytics"] ||
+ entity["@cov2ap.analytics.skipForKey"] ||
+ entity["@Analytics"] ||
+ entity["@Analytics.AnalyticalContext"] ||
+ entity["@Analytics.query"] ||
+ entity["@AnalyticalContext"] ||
+ entity["@Aggregation.ApplySupported.PropertyRestrictions"] ||
+ entity["@sap.semantics"] === "aggregate")
+ );
+ }
+
+ function isDimensionElement(element) {
+ return (
+ element["@Analytics.AnalyticalContext.Dimension"] ||
+ element["@AnalyticalContext.Dimension"] ||
+ element["@Analytics.Dimension"] ||
+ element["@sap.aggregation.role"] === "dimension"
+ );
+ }
+
+ function isMeasureElement(element) {
+ return (
+ element["@Analytics.AnalyticalContext.Measure"] ||
+ element["@AnalyticalContext.Measure"] ||
+ element["@Analytics.Measure"] ||
+ element["@sap.aggregation.role"] === "measure"
+ );
+ }
+
+ function aggregationDefault(element) {
+ return element["@Aggregation.default"] || element["@Aggregation.Default"] || element["@DefaultAggregation"];
+ }
+
+ function referenceElement(element) {
+ return (
+ element["@Aggregation.referenceElement"] ||
+ element["@Aggregation.ReferenceElement"] ||
+ element["@Aggregation.reference"] ||
+ element["@Aggregation.Reference"]
+ );
+ }
+
function convertValue(url, req) {
if (url.contextPath.endsWith("/$value")) {
url.contextPath = url.contextPath.substr(0, url.contextPath.length - "/$value".length);
diff --git a/test/__snapshots__/analytics-test.js.snap b/test/__snapshots__/analytics-test.js.snap
index 763a950..c225f06 100644
--- a/test/__snapshots__/analytics-test.js.snap
+++ b/test/__snapshots__/analytics-test.js.snap
@@ -35,6 +35,7 @@ exports[`analytics GET $metadata 1`] = `
+
@@ -160,6 +161,19 @@ exports[`analytics GET $metadata 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -729,6 +743,18 @@ exports[`analytics GET $metadata 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/_env/srv/analytics.cds b/test/_env/srv/analytics.cds
index fdf5791..d3d0445 100644
--- a/test/_env/srv/analytics.cds
+++ b/test/_env/srv/analytics.cds
@@ -91,4 +91,19 @@ service AnalyticsService {
} actions {
action order(number: Integer) returns Book;
};
+
+ @cov2ap.analytics.skipForKey
+ entity HeaderSkipKey as projection on test.Header {
+ key ID,
+ description,
+ @Analytics.Dimension
+ key country,
+ @Analytics.Dimension
+ key currency,
+ @Analytics.Measure
+ stock,
+ @Analytics.Measure
+ @Aggregation.default : #AVG
+ price,
+ };
}
diff --git a/test/analytics-test.js b/test/analytics-test.js
index bcf0b11..b000965 100644
--- a/test/analytics-test.js
+++ b/test/analytics-test.js
@@ -949,4 +949,34 @@ describe("analytics", () => {
},
});
});
+
+ it("Skip analytics if all dimension key elements are requested", async () => {
+ let response = await util.callRead(request, "/odata/v2/analytics/HeaderSkipKey?$select=country,currency,stock");
+ expect(response.body).toBeDefined();
+ expect(response.body.d).toBeDefined();
+ expect(response.body.d.results).toBeDefined();
+ // no aggregation should have happened
+ expect(response.body.d.results.length).toEqual(7);
+
+ response = await util.callRead(request, "/odata/v2/analytics/HeaderSkipKey?$select=currency,stock");
+ expect(response.body).toBeDefined();
+ expect(response.body.d).toBeDefined();
+ expect(response.body.d.results).toBeDefined();
+ // aggregation should have happened
+ expect(response.body.d.results.length).toEqual(5);
+
+ response = await util.callRead(request, "/odata/v2/analytics/HeaderSkipKey?$select=ID,country,currency,stock");
+ expect(response.body).toBeDefined();
+ expect(response.body.d).toBeDefined();
+ expect(response.body.d.results).toBeDefined();
+ // no aggregation should have happened
+ expect(response.body.d.results.length).toEqual(7);
+
+ response = await util.callRead(request, "/odata/v2/analytics/HeaderSkipKey?$select=stock");
+ expect(response.body).toBeDefined();
+ expect(response.body.d).toBeDefined();
+ expect(response.body.d.results).toBeDefined();
+ // aggregation should have happened
+ expect(response.body.d.results.length).toEqual(1);
+ });
});