From 432d1a94bbd08ed4f91fce5f026b2821dd02d3b1 Mon Sep 17 00:00:00 2001 From: Albin Ramovic Date: Mon, 17 Feb 2025 16:42:33 +0100 Subject: [PATCH] initial commit --- __tests__/__mocks__/dataProductCsn.json | 127 ++++++++++++++++++++++++ __tests__/mockedCsn.test.js | 17 +++- lib/constants.js | 8 ++ lib/ord.js | 20 +++- lib/templates.js | 49 +++++++-- 5 files changed, 209 insertions(+), 12 deletions(-) create mode 100644 __tests__/__mocks__/dataProductCsn.json diff --git a/__tests__/__mocks__/dataProductCsn.json b/__tests__/__mocks__/dataProductCsn.json new file mode 100644 index 0000000..08a485f --- /dev/null +++ b/__tests__/__mocks__/dataProductCsn.json @@ -0,0 +1,127 @@ +{ + "namespace": "sap.capire.incidents", + "definitions": { + "sap.capire.incidents.LocalService": { + "kind": "service", + "@AsyncAPI.Title": "SAP Incident Management", + "@AsyncAPI.SchemaVersion": "1.0", + "@ORD.Extensions.title": "This is Local Service title", + "@DataIntegration.dataProduct.type": "primary" + }, + "sap.capire.incidents.LocalService.Entertainment": { + "kind": "entity", + "@ODM.root": true, + "@ODM.entityName": "Cinema", + "@ODM.oid": "id", + "@title": "Cinema Title", + "projection": { + "from": { + "ref": ["sap.cds.demo.Cinema"] + } + }, + "elements": { + "id": { + "key": true, + "type": "cds.UUID" + }, + "name": { + "type": "cds.String", + "length": 50 + }, + "location": { + "type": "cds.String", + "length": 100 + } + } + }, + "sap.capire.incidents.LocalService.Film": { + "kind": "entity", + "@ObjectModel.compositionRoot": true, + "@EntityRelationship.entityType": "sap.sample:Movie", + "@title": "Movie Title", + "projection": { + "from": { + "ref": ["sap.cds.demo.Movie"] + } + }, + "elements": { + "id": { + "key": true, + "type": "cds.UUID" + }, + "title": { + "type": "cds.String", + "length": 100 + }, + "genre": { + "type": "cds.String", + "length": 50 + }, + "duration": { + "type": "cds.Integer" + } + } + }, + "sap.capire.incidents.LocalService.TitleChange": { + "kind": "event", + "elements": { + "ID": { + "type": "cds.Integer" + }, + "title": { + "@title": "Changed Title", + "type": "cds.String" + } + } + }, + "sap.cds.demo.Cinema": { + "kind": "entity", + "@ODM.root": true, + "@ODM.entityName": "Cinema", + "@ODM.oid": "id", + "@title": "Cinema Title", + "elements": { + "id": { + "key": true, + "type": "cds.UUID" + }, + "name": { + "type": "cds.String", + "length": 50 + }, + "location": { + "type": "cds.String", + "length": 100 + } + } + }, + "sap.cds.demo.Movie": { + "kind": "entity", + "@ObjectModel.compositionRoot": true, + "@EntityRelationship.entityType": "sap.sample:Movie", + "@title": "Movie Title", + "elements": { + "id": { + "key": true, + "type": "cds.UUID" + }, + "title": { + "type": "cds.String", + "length": 100 + }, + "genre": { + "type": "cds.String", + "length": 50 + }, + "duration": { + "type": "cds.Integer" + } + } + } + }, + "meta": { + "creator": "CDS Compiler v5.4.4", + "flavor": "inferred" + }, + "$version": "2.0" +} diff --git a/__tests__/mockedCsn.test.js b/__tests__/mockedCsn.test.js index 066d3b4..f38ade1 100644 --- a/__tests__/mockedCsn.test.js +++ b/__tests__/mockedCsn.test.js @@ -82,7 +82,7 @@ describe("Tests for ORD document generated out of mocked csn files", () => { }); describe("Tests for ORD document when all the resources are internal", () => { - test("All services are interal: Successfully create ORD Documents without packages, empty apiResources and eventResources lists", () => { + test("All services are internal: Successfully create ORD Documents without packages, empty apiResources and eventResources lists", () => { const csn = require("./__mocks__/internalResourcesCsn.json"); checkOrdDocument(csn); }); @@ -100,4 +100,19 @@ describe("Tests for ORD document generated out of mocked csn files", () => { expect(document.eventResources[0].ordId).toEqual(expect.stringContaining("CatalogService")); }); }); + + describe("Tests for ORD document when service is annotated as a primary Data Product`", () => { + test("Successfully create ORD Documents: ", () => { + const csn = require("./__mocks__/dataProductCsn.json"); + const document = ord(csn); + + expect(document).not.toBeUndefined(); + expect(document.apiResources).toHaveLength(2) + const dataProductApiResources = document.apiResources.filter(resource => resource.implementationStandard === "sap.dp:data-subscription-api:v1"); + expect(dataProductApiResources).toHaveLength(1); + expect(dataProductApiResources[0].resourceDefinitions).toHaveLength(1); + expect(dataProductApiResources[0].resourceDefinitions[0]).type === "sap-csn-interop-effective-v1"; + }); + }); + }); \ No newline at end of file diff --git a/lib/constants.js b/lib/constants.js index b4f3b40..0149630 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -17,6 +17,12 @@ const COMPILER_TYPES = Object.freeze({ const CONTENT_MERGE_KEY = "ordId"; +const DATA_PRODUCT_ANNOTATION = "@DataIntegration.dataProduct.type"; + +const DATA_PRODUCT_TYPE = Object.freeze({ + primary: "primary" +}); + const DESCRIPTION_PREFIX = "Description for "; const ENTITY_RELATIONSHIP_ANNOTATION = "@EntityRelationship.entityType"; @@ -54,6 +60,8 @@ module.exports = { CDS_ELEMENT_KIND, COMPILER_TYPES, CONTENT_MERGE_KEY, + DATA_PRODUCT_ANNOTATION, + DATA_PRODUCT_TYPE, DESCRIPTION_PREFIX, ENTITY_RELATIONSHIP_ANNOTATION, LEVEL, diff --git a/lib/ord.js b/lib/ord.js index 07b3cfb..b537e66 100644 --- a/lib/ord.js +++ b/lib/ord.js @@ -1,11 +1,14 @@ const { CDS_ELEMENT_KIND, CONTENT_MERGE_KEY, + DATA_PRODUCT_ANNOTATION, + DATA_PRODUCT_TYPE, ENTITY_RELATIONSHIP_ANNOTATION, ORD_ODM_ENTITY_NAME_ANNOTATION } = require('./constants'); const { createAPIResourceTemplate, + createDataProductAPIResourceTemplate, createEntityTypeTemplate, createEntityTypeMappingsItemTemplate, createEventResourceTemplate, @@ -34,6 +37,7 @@ const initializeAppConfig = (csn) => { const events = []; const entityTypeTargets = []; const serviceNames = []; + const dataProducts = []; const lastUpdate = getRFC3339Date(); const vendorNamespace = "customer"; @@ -50,6 +54,9 @@ const initializeAppConfig = (csn) => { case CDS_ELEMENT_KIND.service: if (!keyDefinition["@cds.external"]) { serviceNames.push(key); + if (keyDefinition[DATA_PRODUCT_ANNOTATION] === DATA_PRODUCT_TYPE.primary) { + dataProducts.push(keyDefinition); + } } break; // TODO: should be rewritten @@ -82,6 +89,7 @@ const initializeAppConfig = (csn) => { ordNamespace, eventApplicationNamespace, packageName, + dataProducts }; }; @@ -121,6 +129,13 @@ const _getAPIResources = (csn, appConfig, packageIds) => { .flatMap((serviceName) => createAPIResourceTemplate(serviceName, csn.definitions[serviceName], appConfig, packageIds)) }; +const _getDataProductAPIResources = (appConfig) => { + return appConfig.dataProducts + .flatMap((dataProduct) => { + return createDataProductAPIResourceTemplate(dataProduct); + }) +}; + const _getEventResources = (csn, appConfig, packageIds) => { if (appConfig.events.length === 0) return []; return appConfig.serviceNames @@ -145,11 +160,13 @@ function createDefaultORDDocument(linkedCsn, appConfig) { products: _getProducts(appConfig), groups: _getGroups(linkedCsn, appConfig), consumptionBundles: _getConsumptionBundles(appConfig), + apiResources: _getDataProductAPIResources(appConfig) }; if (_getAPIResources(linkedCsn, appConfig).length && _getEventResources(linkedCsn, appConfig).length) { ordDocument.packages = _getPackages(ordDocument.policyLevel, appConfig); } + return ordDocument; } @@ -171,7 +188,8 @@ module.exports = (csn) => { if (entityTypes.length != 0) { ordDocument.entityTypes = entityTypes; } - ordDocument.apiResources = _getAPIResources(linkedCsn, appConfig, packageIds); + + ordDocument.apiResources.push(..._getAPIResources(linkedCsn, appConfig, packageIds)); ordDocument.eventResources = _getEventResources(linkedCsn, appConfig, packageIds); ordDocument = extendCustomORDContentIfExists(appConfig, ordDocument); diff --git a/lib/templates.js b/lib/templates.js index 8fb9eb6..b5b25bf 100644 --- a/lib/templates.js +++ b/lib/templates.js @@ -125,7 +125,7 @@ function _getTitleFromServiceName(srv) { */ function _getEntityVersion(entity) { const entityVersion = entity.ordId.split(":").pop(); - const version = entityVersion.replace("v", "") + ".0.0"; // TODO: version can be stated/overwritten by annotation + const version = entityVersion.replace("v", "") + ".0.0"; // TODO: version can be stated/overwritten by annotation if (!SEM_VERSION_REGEX.test(version)) { Logger.warn(`Entity version "${version}" is not a valid semantic version.`); } @@ -244,17 +244,19 @@ const createAPIResourceTemplate = (serviceName, serviceDefinition, appConfig, pa }, // conditionally setting the entityTypeMappings field based on the presence of entityTypeTargets in appConfig ...(appConfig.entityTypeTargets?.length > 0 && - { - entityTypeMappings: [ - { - entityTypeTargets: appConfig.entityTypeTargets.map(m => { return ( + { + entityTypeMappings: [ + { + entityTypeTargets: appConfig.entityTypeTargets.map(m => { + return ( { - "ordId" : m.ordId + "ordId": m.ordId } - )} ) - } - ] - }), + ) + }) + } + ] + }), ...ordExtensions, }; @@ -266,6 +268,32 @@ const createAPIResourceTemplate = (serviceName, serviceDefinition, appConfig, pa return apiResources; }; +/** + * This is a function creating an API Resource object for each service defined as a data product. + * @returns {Array} An array of objects for the API Resources. + */ +const createDataProductAPIResourceTemplate = (dataProduct) => { + console.log(dataProduct); + + return { + apiProtocol: "api", + visibility: RESOURCE_VISIBILITY.internal, + direction: "outbound", + implementationStandard: "sap.dp:data-subscription-api:v1", + entryPoints: [], + resourceDefinitions: [{ + type: "sap-csn-interop-effective-v1", + mediaType: "application/json", + url: "/Product.csn.json", + accessStrategies: [ + { + type: "open" + } + ] + }] + } +}; + /** * This is a template function to create Event Resource object for Event Resource Array. * There can be only one event resource per service because all events are using the same protocol, they are always Cloud Events. @@ -323,5 +351,6 @@ module.exports = { createEntityTypeMappingsItemTemplate, createGroupsTemplateForService, createAPIResourceTemplate, + createDataProductAPIResourceTemplate, createEventResourceTemplate, };