@sap-ux/axios-extension 1.22.3 → 1.22.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -40,13 +40,20 @@ export declare abstract class CatalogService extends ODataService {
|
|
|
40
40
|
entitySet: string;
|
|
41
41
|
services: ODataServiceInfo[];
|
|
42
42
|
isS4Cloud: Promise<boolean>;
|
|
43
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Fetch all services from the backend.
|
|
45
|
+
*
|
|
46
|
+
* @param useNextLink if true, the next link will be used to fetch the next page of results, pages are fetched serially.
|
|
47
|
+
*/
|
|
48
|
+
protected abstract fetchServices(useNextLink?: boolean): Promise<ODataServiceInfo[]>;
|
|
44
49
|
/**
|
|
45
50
|
* Returns list of services from the catalog service.
|
|
46
51
|
*
|
|
52
|
+
* @param useNextLink if true, the next link tags will be used to fetch the next page of results, pages are fetched serially.
|
|
53
|
+
* Note that this will be less performant for larger datasets.
|
|
47
54
|
* @returns list of services
|
|
48
55
|
*/
|
|
49
|
-
listServices(): Promise<ODataServiceInfo[]>;
|
|
56
|
+
listServices(useNextLink?: boolean): Promise<ODataServiceInfo[]>;
|
|
50
57
|
abstract getAnnotations({ id, title, path }: FilterOptions): Promise<Annotations[]>;
|
|
51
58
|
abstract getServiceType(path: string): Promise<ServiceType | undefined>;
|
|
52
59
|
}
|
|
@@ -18,11 +18,13 @@ class CatalogService extends odata_service_1.ODataService {
|
|
|
18
18
|
/**
|
|
19
19
|
* Returns list of services from the catalog service.
|
|
20
20
|
*
|
|
21
|
+
* @param useNextLink if true, the next link tags will be used to fetch the next page of results, pages are fetched serially.
|
|
22
|
+
* Note that this will be less performant for larger datasets.
|
|
21
23
|
* @returns list of services
|
|
22
24
|
*/
|
|
23
|
-
async listServices() {
|
|
25
|
+
async listServices(useNextLink = false) {
|
|
24
26
|
if (!this.services) {
|
|
25
|
-
this.services = await this.fetchServices();
|
|
27
|
+
this.services = await this.fetchServices(useNextLink);
|
|
26
28
|
}
|
|
27
29
|
return this.services;
|
|
28
30
|
}
|
|
@@ -25,21 +25,37 @@ export interface ServiceGroup {
|
|
|
25
25
|
* OData V4 specific implmentation of SAP's catalog service
|
|
26
26
|
*/
|
|
27
27
|
export declare class V4CatalogService extends CatalogService {
|
|
28
|
+
private readonly logger;
|
|
28
29
|
static readonly PATH = "/sap/opu/odata4/iwfnd/config/default/iwfnd/catalog/0002";
|
|
29
30
|
/**
|
|
30
31
|
* Map the V4 service information to a version independent structure.
|
|
31
32
|
*
|
|
32
33
|
* @param groups v4 service groups
|
|
33
|
-
* @param entitySet entity set used for service selection
|
|
34
|
+
* @param entitySet entity set used for service selection. e.g. `RecommendedServices`
|
|
35
|
+
* @param dedup if true, duplicate services will be removed based on their id. Duplicate services may appear in multiple groups, e.g. '/IWBEP/ALL'.
|
|
34
36
|
* @returns version independent information
|
|
35
37
|
*/
|
|
36
|
-
protected mapServices(groups: ServiceGroup[], entitySet: string): ODataServiceInfo[];
|
|
38
|
+
protected mapServices(groups: ServiceGroup[], entitySet: string, dedup?: boolean): ODataServiceInfo[];
|
|
37
39
|
/**
|
|
38
|
-
* Fetch all services from the backend.
|
|
40
|
+
* Fetch all services from the backend using the @nexlink parameter to fetch all pages serially.
|
|
39
41
|
*
|
|
40
42
|
* @returns version independent service information
|
|
41
43
|
*/
|
|
42
|
-
protected
|
|
44
|
+
protected fetchServicesNextLink(): Promise<ODataServiceInfo[]>;
|
|
45
|
+
/**
|
|
46
|
+
* Fetches all services from the catalog.
|
|
47
|
+
*
|
|
48
|
+
* @param useNextLink if true, uses the nextLink parameter to fetch all pages serially, otherwise fetches all pages in parallel.
|
|
49
|
+
* @returns v4 services
|
|
50
|
+
*/
|
|
51
|
+
protected fetchServices(useNextLink?: boolean): Promise<ODataServiceInfo[]>;
|
|
52
|
+
/**
|
|
53
|
+
* Fetches all services from the catalog in parallel. Uses the total service count to fetch all service group pages in parallel
|
|
54
|
+
* to improve performance where larger numbers of services and therefore pages are available.
|
|
55
|
+
*
|
|
56
|
+
* @returns List of unique services
|
|
57
|
+
*/
|
|
58
|
+
protected fetchServicesParallel(): Promise<ODataServiceInfo[]>;
|
|
43
59
|
/**
|
|
44
60
|
* For OData v4, all annotations are already included in the metadata and no additional request is required.
|
|
45
61
|
*
|
|
@@ -4,26 +4,37 @@ exports.V4CatalogService = void 0;
|
|
|
4
4
|
const base_1 = require("./base");
|
|
5
5
|
const odata_service_1 = require("../../base/odata-service");
|
|
6
6
|
const odata_request_error_1 = require("../../base/odata-request-error");
|
|
7
|
+
const logger_1 = require("@sap-ux/logger");
|
|
7
8
|
const V4_RECOMMENDED_ENTITYSET = 'RecommendedServices';
|
|
8
9
|
const V4_CLASSIC_ENTITYSET = 'Services';
|
|
9
10
|
/**
|
|
10
11
|
* OData V4 specific implmentation of SAP's catalog service
|
|
11
12
|
*/
|
|
12
13
|
class V4CatalogService extends base_1.CatalogService {
|
|
14
|
+
logger = new logger_1.ToolsLogger();
|
|
13
15
|
static PATH = '/sap/opu/odata4/iwfnd/config/default/iwfnd/catalog/0002';
|
|
14
16
|
/**
|
|
15
17
|
* Map the V4 service information to a version independent structure.
|
|
16
18
|
*
|
|
17
19
|
* @param groups v4 service groups
|
|
18
|
-
* @param entitySet entity set used for service selection
|
|
20
|
+
* @param entitySet entity set used for service selection. e.g. `RecommendedServices`
|
|
21
|
+
* @param dedup if true, duplicate services will be removed based on their id. Duplicate services may appear in multiple groups, e.g. '/IWBEP/ALL'.
|
|
19
22
|
* @returns version independent information
|
|
20
23
|
*/
|
|
21
|
-
mapServices(groups, entitySet) {
|
|
24
|
+
mapServices(groups, entitySet, dedup = false) {
|
|
22
25
|
const services = [];
|
|
26
|
+
// Duplicates can appear in multiple groups, e.g. '/IWBEP/ALL'
|
|
27
|
+
const uniqueServiceIds = new Set();
|
|
23
28
|
groups
|
|
24
29
|
.filter((group) => group?.DefaultSystem?.[entitySet]?.length > 0)
|
|
25
30
|
.forEach((group) => {
|
|
26
|
-
services.push(...group.DefaultSystem[entitySet].
|
|
31
|
+
services.push(...group.DefaultSystem[entitySet].flatMap((service) => {
|
|
32
|
+
if (dedup) {
|
|
33
|
+
if (uniqueServiceIds.has(service.ServiceId)) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
uniqueServiceIds.add(service.ServiceId);
|
|
37
|
+
}
|
|
27
38
|
return {
|
|
28
39
|
id: service.ServiceId,
|
|
29
40
|
group: group.GroupId,
|
|
@@ -38,11 +49,11 @@ class V4CatalogService extends base_1.CatalogService {
|
|
|
38
49
|
return services;
|
|
39
50
|
}
|
|
40
51
|
/**
|
|
41
|
-
* Fetch all services from the backend.
|
|
52
|
+
* Fetch all services from the backend using the @nexlink parameter to fetch all pages serially.
|
|
42
53
|
*
|
|
43
54
|
* @returns version independent service information
|
|
44
55
|
*/
|
|
45
|
-
async
|
|
56
|
+
async fetchServicesNextLink() {
|
|
46
57
|
if (this.entitySet === undefined) {
|
|
47
58
|
const metadata = await this.metadata();
|
|
48
59
|
this.entitySet = metadata.includes('Name="RecommendedServices"')
|
|
@@ -56,18 +67,85 @@ class V4CatalogService extends base_1.CatalogService {
|
|
|
56
67
|
const response = await this.get('/ServiceGroups', { params }, true);
|
|
57
68
|
let serviceGroupResponseOdata = response.odata();
|
|
58
69
|
const serviceGroups = serviceGroupResponseOdata.value;
|
|
70
|
+
let numPageRequests = 1;
|
|
59
71
|
// Page by using the backends nextLink search parameters for the next request
|
|
60
72
|
while (serviceGroupResponseOdata['@odata.nextLink']) {
|
|
61
73
|
const nextLink = new URL(serviceGroupResponseOdata['@odata.nextLink'], this.defaults.baseURL);
|
|
62
74
|
serviceGroupResponseOdata = (await this.get('/ServiceGroups', { params: nextLink.searchParams }, true)).odata();
|
|
75
|
+
numPageRequests++;
|
|
63
76
|
serviceGroups.push(...serviceGroupResponseOdata.value);
|
|
64
77
|
}
|
|
78
|
+
this.logger.log(`Fetched ${serviceGroups.length} service groups in ${numPageRequests} requests.`);
|
|
65
79
|
// check if the service responded with an odata error
|
|
66
80
|
if (odata_request_error_1.ODataRequestError.containsError(serviceGroups)) {
|
|
67
81
|
throw new odata_request_error_1.ODataRequestError(serviceGroups);
|
|
68
82
|
}
|
|
69
83
|
return this.mapServices(serviceGroups, this.entitySet);
|
|
70
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Fetches all services from the catalog.
|
|
87
|
+
*
|
|
88
|
+
* @param useNextLink if true, uses the nextLink parameter to fetch all pages serially, otherwise fetches all pages in parallel.
|
|
89
|
+
* @returns v4 services
|
|
90
|
+
*/
|
|
91
|
+
async fetchServices(useNextLink = false) {
|
|
92
|
+
if (useNextLink) {
|
|
93
|
+
return this.fetchServicesNextLink();
|
|
94
|
+
}
|
|
95
|
+
return this.fetchServicesParallel();
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Fetches all services from the catalog in parallel. Uses the total service count to fetch all service group pages in parallel
|
|
99
|
+
* to improve performance where larger numbers of services and therefore pages are available.
|
|
100
|
+
*
|
|
101
|
+
* @returns List of unique services
|
|
102
|
+
*/
|
|
103
|
+
async fetchServicesParallel() {
|
|
104
|
+
const defaultInitialPageSize = 1000; // default page size for the services, large enough to get the first page and skiptoken to determine max page size
|
|
105
|
+
if (this.entitySet === undefined) {
|
|
106
|
+
const metadata = await this.metadata();
|
|
107
|
+
this.entitySet = metadata.includes('Name="RecommendedServices"')
|
|
108
|
+
? V4_RECOMMENDED_ENTITYSET
|
|
109
|
+
: V4_CLASSIC_ENTITYSET;
|
|
110
|
+
}
|
|
111
|
+
const params = new URLSearchParams([
|
|
112
|
+
['$count', 'true'],
|
|
113
|
+
['$top', defaultInitialPageSize.toString()], // Get the first page of services,
|
|
114
|
+
['$expand', `DefaultSystem($expand=${this.entitySet})`]
|
|
115
|
+
]);
|
|
116
|
+
const response = await this.get('/ServiceGroups', { params }, true);
|
|
117
|
+
const serviceGroupResponseOdata = response.odata();
|
|
118
|
+
const serviceGroups = serviceGroupResponseOdata.value;
|
|
119
|
+
const serviceGroupCount = serviceGroupResponseOdata['@odata.count'];
|
|
120
|
+
const pageSize = parseInt(serviceGroupResponseOdata['@odata.nextLink']?.split('skiptoken=')[1], 10);
|
|
121
|
+
let numPageRequests = 1;
|
|
122
|
+
// If we dont have a valid skip token, we assume we have all services in the first page
|
|
123
|
+
if (!isNaN(pageSize)) {
|
|
124
|
+
const numPages = Math.ceil(serviceGroupCount / pageSize);
|
|
125
|
+
// Create an array of promises to fetch all pages in parallel
|
|
126
|
+
const fetchPromises = Array.from({ length: numPages - 1 }, (_, index) => {
|
|
127
|
+
const nextParams = new URLSearchParams([
|
|
128
|
+
['$count', 'true'],
|
|
129
|
+
['$skip', String((index + 1) * pageSize)],
|
|
130
|
+
['$top', pageSize.toString()], // Fetch the next 200 services
|
|
131
|
+
['$expand', `DefaultSystem($expand=${this.entitySet})`]
|
|
132
|
+
]);
|
|
133
|
+
numPageRequests++;
|
|
134
|
+
return this.get('/ServiceGroups', { params: nextParams }, true);
|
|
135
|
+
});
|
|
136
|
+
const pageResults = await Promise.all(fetchPromises); // Fetch all remaining pages in parallel
|
|
137
|
+
pageResults.forEach((pageResponse) => {
|
|
138
|
+
const pageData = pageResponse.odata();
|
|
139
|
+
serviceGroups.push(...pageData.value);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
this.logger.log(`Fetched ${serviceGroups.length} service groups in ${numPageRequests} requests.`);
|
|
143
|
+
// check if the service responded with an odata error
|
|
144
|
+
if (odata_request_error_1.ODataRequestError.containsError(serviceGroups)) {
|
|
145
|
+
throw new odata_request_error_1.ODataRequestError(serviceGroups);
|
|
146
|
+
}
|
|
147
|
+
return this.mapServices(serviceGroups, this.entitySet, true);
|
|
148
|
+
}
|
|
71
149
|
/**
|
|
72
150
|
* For OData v4, all annotations are already included in the metadata and no additional request is required.
|
|
73
151
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap-ux/axios-extension",
|
|
3
|
-
"version": "1.22.
|
|
3
|
+
"version": "1.22.4",
|
|
4
4
|
"description": "Extension of the Axios module adding convenience methods to interact with SAP systems especially with OData services.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"nock": "13.4.0",
|
|
35
35
|
"supertest": "6.3.3",
|
|
36
36
|
"@types/proxy-from-env": "1.0.1",
|
|
37
|
-
"@sap-ux/project-access": "1.30.
|
|
37
|
+
"@sap-ux/project-access": "1.30.7"
|
|
38
38
|
},
|
|
39
39
|
"files": [
|
|
40
40
|
"dist",
|