@sap-ux/preview-middleware 0.23.54 → 0.23.56
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.
- package/dist/base/flp.js +3 -3
- package/dist/client/cpe/additional-change-info/add-xml-additional-info.js +11 -10
- package/dist/client/cpe/additional-change-info/add-xml-additional-info.ts +14 -6
- package/dist/client/cpe/init.js +24 -1
- package/dist/client/cpe/init.ts +31 -1
- package/dist/client/cpe/odata-health/odata-health-checker.js +128 -0
- package/dist/client/cpe/odata-health/odata-health-checker.ts +149 -0
- package/dist/client/cpe/odata-health/odata-health-status.js +63 -0
- package/dist/client/cpe/odata-health/odata-health-status.ts +52 -0
- package/dist/client/messagebundle.properties +2 -0
- package/dist/client/utils/additional-change-info.js +13 -4
- package/dist/client/utils/additional-change-info.ts +13 -4
- package/dist/client/utils/core.js +33 -0
- package/dist/client/utils/core.ts +33 -0
- package/dist/client/utils/version.js +2 -2
- package/dist/client/utils/version.ts +2 -2
- package/package.json +4 -4
package/dist/base/flp.js
CHANGED
|
@@ -429,12 +429,12 @@ class FlpSandbox {
|
|
|
429
429
|
isCdn = responseJson?.name === 'SAPUI5 Distribution';
|
|
430
430
|
}
|
|
431
431
|
catch (error) {
|
|
432
|
-
this.logger.
|
|
432
|
+
this.logger.debug(error);
|
|
433
433
|
}
|
|
434
434
|
}
|
|
435
435
|
if (!version) {
|
|
436
|
-
this.logger.error('Could not get UI5 version of application. Using version: 1.130.
|
|
437
|
-
version = '1.130.
|
|
436
|
+
this.logger.error('Could not get UI5 version of application. Using version: 1.130.9 as fallback.');
|
|
437
|
+
version = '1.130.9';
|
|
438
438
|
isCdn = false;
|
|
439
439
|
}
|
|
440
440
|
const [major, minor, patch] = version.split('.').map((versionPart) => Number.parseInt(versionPart, 10));
|
|
@@ -4,6 +4,7 @@ sap.ui.define(["../../utils/core", "../../adp/quick-actions/control-types"], fun
|
|
|
4
4
|
"use strict";
|
|
5
5
|
|
|
6
6
|
const getControlById = ____utils_core["getControlById"];
|
|
7
|
+
const findViewByControl = ____utils_core["findViewByControl"];
|
|
7
8
|
const ANALYTICAL_TABLE_TYPE = ____adp_quick_actions_control_types["ANALYTICAL_TABLE_TYPE"];
|
|
8
9
|
const GRID_TABLE_TYPE = ____adp_quick_actions_control_types["GRID_TABLE_TYPE"];
|
|
9
10
|
const MDC_TABLE_TYPE = ____adp_quick_actions_control_types["MDC_TABLE_TYPE"];
|
|
@@ -11,20 +12,20 @@ sap.ui.define(["../../utils/core", "../../adp/quick-actions/control-types"], fun
|
|
|
11
12
|
function getAddXMLAdditionalInfo(change) {
|
|
12
13
|
const selectorId = change.getSelector()?.id ?? '';
|
|
13
14
|
const targetAggregation = change.getContent()?.targetAggregation ?? '';
|
|
14
|
-
const
|
|
15
|
+
const targetControl = getControlById(selectorId);
|
|
16
|
+
const controlType = targetControl?.getMetadata().getName() ?? '';
|
|
15
17
|
const templateName = getFragmentTemplateName(selectorId, targetAggregation);
|
|
18
|
+
const viewName = targetControl ? findViewByControl(targetControl)?.getViewName() ?? '' : '';
|
|
19
|
+
const result = {};
|
|
16
20
|
if (templateName) {
|
|
17
|
-
|
|
18
|
-
templateName
|
|
19
|
-
};
|
|
21
|
+
result.templateName = templateName;
|
|
20
22
|
}
|
|
21
|
-
if (controlType && targetAggregation) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
};
|
|
23
|
+
if (controlType && targetAggregation && viewName) {
|
|
24
|
+
result.targetAggregation = targetAggregation;
|
|
25
|
+
result.controlType = controlType;
|
|
26
|
+
result.viewName = viewName;
|
|
26
27
|
}
|
|
27
|
-
return undefined;
|
|
28
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
28
29
|
}
|
|
29
30
|
function getFragmentTemplateName(selectorId, targetAggregation) {
|
|
30
31
|
const control = getControlById(selectorId);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import FlexChange from 'sap/ui/fl/Change';
|
|
2
|
-
import { getControlById } from '../../utils/core';
|
|
2
|
+
import { getControlById, findViewByControl } from '../../utils/core';
|
|
3
3
|
import {
|
|
4
4
|
ANALYTICAL_TABLE_TYPE,
|
|
5
5
|
GRID_TABLE_TYPE,
|
|
@@ -12,6 +12,7 @@ export type AddXMLAdditionalInfo = {
|
|
|
12
12
|
templateName?: string;
|
|
13
13
|
targetAggregation?: string;
|
|
14
14
|
controlType?: string;
|
|
15
|
+
viewName?: string;
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
export type AddXMLChangeContent = {
|
|
@@ -21,15 +22,22 @@ export type AddXMLChangeContent = {
|
|
|
21
22
|
export function getAddXMLAdditionalInfo(change: FlexChange<AddXMLChangeContent>): AddXMLAdditionalInfo | undefined {
|
|
22
23
|
const selectorId = change.getSelector()?.id ?? '';
|
|
23
24
|
const targetAggregation = change.getContent()?.targetAggregation ?? '';
|
|
24
|
-
const
|
|
25
|
+
const targetControl = getControlById(selectorId);
|
|
26
|
+
const controlType = targetControl?.getMetadata().getName() ?? '';
|
|
25
27
|
const templateName = getFragmentTemplateName(selectorId, targetAggregation);
|
|
28
|
+
const viewName = targetControl ? (findViewByControl(targetControl)?.getViewName() ?? '') : '';
|
|
29
|
+
|
|
30
|
+
const result: AddXMLAdditionalInfo = {};
|
|
26
31
|
if (templateName) {
|
|
27
|
-
|
|
32
|
+
result.templateName = templateName;
|
|
28
33
|
}
|
|
29
|
-
if (controlType && targetAggregation) {
|
|
30
|
-
|
|
34
|
+
if (controlType && targetAggregation && viewName) {
|
|
35
|
+
result.targetAggregation = targetAggregation;
|
|
36
|
+
result.controlType = controlType;
|
|
37
|
+
result.viewName = viewName;
|
|
31
38
|
}
|
|
32
|
-
|
|
39
|
+
|
|
40
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
33
41
|
}
|
|
34
42
|
|
|
35
43
|
export function getFragmentTemplateName(selectorId: string, targetAggregation: string): string {
|
package/dist/client/cpe/init.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
sap.ui.define(["sap/base/Log", "open/ux/preview/client/thirdparty/@sap-ux-private/control-property-editor-common", "../utils/error", "./changes/service", "./communication-service", "./connector-service", "./context-menu-service", "./documentation", "./outline/service", "./quick-actions/quick-action-service", "./rta-service", "./selection", "./ui5-utils"], function (Log, ___sap_ux_private_control_property_editor_common, ___utils_error, ___changes_service, ___communication_service, ___connector_service, ___context_menu_service, ___documentation, ___outline_service, ___quick_actions_quick_action_service, ___rta_service, ___selection, ___ui5_utils) {
|
|
3
|
+
sap.ui.define(["sap/base/Log", "open/ux/preview/client/thirdparty/@sap-ux-private/control-property-editor-common", "../utils/error", "./changes/service", "./communication-service", "./connector-service", "./context-menu-service", "./documentation", "./outline/service", "./quick-actions/quick-action-service", "./rta-service", "./selection", "./ui5-utils", "./odata-health/odata-health-checker", "../utils/info-center-message", "./odata-health/odata-health-status"], function (Log, ___sap_ux_private_control_property_editor_common, ___utils_error, ___changes_service, ___communication_service, ___connector_service, ___context_menu_service, ___documentation, ___outline_service, ___quick_actions_quick_action_service, ___rta_service, ___selection, ___ui5_utils, ___odata_health_odata_health_checker, ___utils_info_center_message, ___odata_health_odata_health_status) {
|
|
4
4
|
"use strict";
|
|
5
5
|
|
|
6
6
|
const appLoaded = ___sap_ux_private_control_property_editor_common["appLoaded"];
|
|
7
7
|
const enableTelemetry = ___sap_ux_private_control_property_editor_common["enableTelemetry"];
|
|
8
8
|
const iconsLoaded = ___sap_ux_private_control_property_editor_common["iconsLoaded"];
|
|
9
|
+
const MessageBarType = ___sap_ux_private_control_property_editor_common["MessageBarType"];
|
|
9
10
|
const getError = ___utils_error["getError"];
|
|
10
11
|
const ChangeService = ___changes_service["ChangeService"];
|
|
11
12
|
const CommunicationService = ___communication_service["CommunicationService"];
|
|
@@ -17,6 +18,9 @@ sap.ui.define(["sap/base/Log", "open/ux/preview/client/thirdparty/@sap-ux-privat
|
|
|
17
18
|
const RtaService = ___rta_service["RtaService"];
|
|
18
19
|
const SelectionService = ___selection["SelectionService"];
|
|
19
20
|
const getIcons = ___ui5_utils["getIcons"];
|
|
21
|
+
const ODataHealthChecker = ___odata_health_odata_health_checker["ODataHealthChecker"];
|
|
22
|
+
const sendInfoCenterMessage = ___utils_info_center_message["sendInfoCenterMessage"];
|
|
23
|
+
const ODataUpStatus = ___odata_health_odata_health_status["ODataUpStatus"];
|
|
20
24
|
function init(rta) {
|
|
21
25
|
let registries = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
|
|
22
26
|
Log.info('Initializing Control Property Editor');
|
|
@@ -44,6 +48,25 @@ sap.ui.define(["sap/base/Log", "open/ux/preview/client/thirdparty/@sap-ux-privat
|
|
|
44
48
|
const outlineService = new OutlineService(rta, changesService);
|
|
45
49
|
const quickActionService = new QuickActionService(rta, outlineService, registries, changesService);
|
|
46
50
|
const services = [connectorService, selectionService, changesService, contextMenuService, outlineService, rtaService, quickActionService];
|
|
51
|
+
|
|
52
|
+
// Do health check to all available oData service instances.
|
|
53
|
+
const oDataHealthChecker = new ODataHealthChecker(rta);
|
|
54
|
+
oDataHealthChecker.getHealthStatus().then(healthStatus => healthStatus.map(status => status instanceof ODataUpStatus ? Promise.resolve() : sendInfoCenterMessage({
|
|
55
|
+
title: {
|
|
56
|
+
key: 'ADP_ODATA_HEALTH_CHECK_TITLE'
|
|
57
|
+
},
|
|
58
|
+
description: {
|
|
59
|
+
key: 'ADP_ODATA_SERVICE_DOWN_DESCRIPTION',
|
|
60
|
+
params: [status.serviceUrl, status.errorMessage]
|
|
61
|
+
},
|
|
62
|
+
type: MessageBarType.warning
|
|
63
|
+
}))).catch(error => sendInfoCenterMessage({
|
|
64
|
+
title: {
|
|
65
|
+
key: 'ADP_ODATA_HEALTH_CHECK_TITLE'
|
|
66
|
+
},
|
|
67
|
+
description: getError(error).message,
|
|
68
|
+
type: MessageBarType.warning
|
|
69
|
+
}));
|
|
47
70
|
try {
|
|
48
71
|
loadDefaultLibraries();
|
|
49
72
|
const allPromises = services.map(service => {
|
package/dist/client/cpe/init.ts
CHANGED
|
@@ -4,7 +4,8 @@ import type RuntimeAuthoring from 'sap/ui/rta/RuntimeAuthoring';
|
|
|
4
4
|
import {
|
|
5
5
|
appLoaded,
|
|
6
6
|
enableTelemetry,
|
|
7
|
-
iconsLoaded
|
|
7
|
+
iconsLoaded,
|
|
8
|
+
MessageBarType
|
|
8
9
|
} from '@sap-ux-private/control-property-editor-common';
|
|
9
10
|
|
|
10
11
|
import { getError } from '../utils/error';
|
|
@@ -20,6 +21,9 @@ import { RtaService } from './rta-service';
|
|
|
20
21
|
import { SelectionService } from './selection';
|
|
21
22
|
import type { ActionHandler, Service } from './types';
|
|
22
23
|
import { getIcons } from './ui5-utils';
|
|
24
|
+
import { ODataHealthChecker } from './odata-health/odata-health-checker';
|
|
25
|
+
import { sendInfoCenterMessage } from '../utils/info-center-message';
|
|
26
|
+
import { ODataUpStatus } from './odata-health/odata-health-status';
|
|
23
27
|
|
|
24
28
|
export default function init(
|
|
25
29
|
rta: RuntimeAuthoring,
|
|
@@ -59,6 +63,32 @@ export default function init(
|
|
|
59
63
|
quickActionService
|
|
60
64
|
];
|
|
61
65
|
|
|
66
|
+
// Do health check to all available oData service instances.
|
|
67
|
+
const oDataHealthChecker = new ODataHealthChecker(rta);
|
|
68
|
+
oDataHealthChecker
|
|
69
|
+
.getHealthStatus()
|
|
70
|
+
.then((healthStatus) =>
|
|
71
|
+
healthStatus.map((status) =>
|
|
72
|
+
status instanceof ODataUpStatus
|
|
73
|
+
? Promise.resolve()
|
|
74
|
+
: sendInfoCenterMessage({
|
|
75
|
+
title: { key: 'ADP_ODATA_HEALTH_CHECK_TITLE' },
|
|
76
|
+
description: {
|
|
77
|
+
key: 'ADP_ODATA_SERVICE_DOWN_DESCRIPTION',
|
|
78
|
+
params: [status.serviceUrl, status.errorMessage]
|
|
79
|
+
},
|
|
80
|
+
type: MessageBarType.warning
|
|
81
|
+
})
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
.catch((error) =>
|
|
85
|
+
sendInfoCenterMessage({
|
|
86
|
+
title: { key: 'ADP_ODATA_HEALTH_CHECK_TITLE' },
|
|
87
|
+
description: getError(error).message,
|
|
88
|
+
type: MessageBarType.warning
|
|
89
|
+
})
|
|
90
|
+
);
|
|
91
|
+
|
|
62
92
|
try {
|
|
63
93
|
loadDefaultLibraries();
|
|
64
94
|
const allPromises = services.map((service) => {
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
sap.ui.define(["sap/base/Log", "sap/ui/model/odata/v2/ODataModel", "sap/ui/model/odata/v4/ODataModel", "./odata-health-status"], function (Log, ODataModelV2, ODataModelV4, ___odata_health_status) {
|
|
4
|
+
"use strict";
|
|
5
|
+
|
|
6
|
+
const ODataDownStatus = ___odata_health_status["ODataDownStatus"];
|
|
7
|
+
const ODataUpStatus = ___odata_health_status["ODataUpStatus"];
|
|
8
|
+
/**
|
|
9
|
+
* The OData version type.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Describes an OData service instance.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Use this class to do a health check for all available OData services, supports both v2 and v4
|
|
16
|
+
* format. This health checker ensures not only that $metadata is valid, but also that the UI5 framework
|
|
17
|
+
* itself can consume the service via its models.
|
|
18
|
+
*/
|
|
19
|
+
class ODataHealthChecker {
|
|
20
|
+
/**
|
|
21
|
+
* The OData type.
|
|
22
|
+
*/
|
|
23
|
+
static ODATA_TYPE = 'OData';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The default OData version.
|
|
27
|
+
*/
|
|
28
|
+
static DEFAULT_ODATA_VERSION = 'v2';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Use this helper function to filter the OData data source items from the manifest.
|
|
32
|
+
* @param src The service data source.
|
|
33
|
+
* @returns True if the data source represents an OData service.
|
|
34
|
+
*/
|
|
35
|
+
isOdataService = src => src.type === ODataHealthChecker.ODATA_TYPE;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Use this helper function to map the OData data source to the internal structure
|
|
39
|
+
* used in this class.
|
|
40
|
+
* @param src The OData service data source.
|
|
41
|
+
* @returns The OData service info object.
|
|
42
|
+
*/
|
|
43
|
+
toOdataServiceInfo = src => ({
|
|
44
|
+
serviceUrl: src.uri,
|
|
45
|
+
oDataVersion: src.settings?.odataVersion ?? ODataHealthChecker.DEFAULT_ODATA_VERSION
|
|
46
|
+
});
|
|
47
|
+
constructor(rta) {
|
|
48
|
+
this.rta = rta;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Does a health check to all available OData services.
|
|
53
|
+
*
|
|
54
|
+
* @returns Resolves with an array containing the health
|
|
55
|
+
* status for each OData service.
|
|
56
|
+
*/
|
|
57
|
+
async getHealthStatus() {
|
|
58
|
+
const oDataHealthCheckStartTime = Date.now();
|
|
59
|
+
const services = this.getServices();
|
|
60
|
+
const metadataPromises = await Promise.allSettled(services.map(_ref => {
|
|
61
|
+
let {
|
|
62
|
+
serviceUrl,
|
|
63
|
+
oDataVersion
|
|
64
|
+
} = _ref;
|
|
65
|
+
return this.getServiceMetadata(serviceUrl, oDataVersion);
|
|
66
|
+
}));
|
|
67
|
+
const oDataHealthCheckDurationInSec = ((Date.now() - oDataHealthCheckStartTime) / 1000).toFixed(2);
|
|
68
|
+
Log.info(`OData service health check took ${oDataHealthCheckDurationInSec} sec.`);
|
|
69
|
+
return metadataPromises.map((metadataPromise, idx) => metadataPromise.status === 'fulfilled' ? new ODataUpStatus(services[idx].serviceUrl) : new ODataDownStatus(services[idx].serviceUrl, metadataPromise.reason));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* This method does a strong health check (with the ODataModel). This ensures not only
|
|
74
|
+
* that the $metadata is valid, but also that the UI5 framework itself can consume the service via its models.
|
|
75
|
+
* Some services may have valid $metadata but still fail in the UI5’s ODataModel
|
|
76
|
+
* (e.g., weird annotations, CORS issues, etc.).
|
|
77
|
+
*
|
|
78
|
+
* @param serviceUrl The OData service url.
|
|
79
|
+
* @param oDataVersion The OData version.
|
|
80
|
+
* @returns Resolved with valid metadata.
|
|
81
|
+
*/
|
|
82
|
+
getServiceMetadata(serviceUrl, oDataVersion) {
|
|
83
|
+
switch (oDataVersion) {
|
|
84
|
+
case 'v2':
|
|
85
|
+
case '2.0':
|
|
86
|
+
return this.getServiceV2Metadata(serviceUrl);
|
|
87
|
+
case 'v4':
|
|
88
|
+
case '4.0':
|
|
89
|
+
return this.getServiceV4Metadata(serviceUrl);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
getServiceV2Metadata(serviceUrl) {
|
|
93
|
+
const oModel = new ODataModelV2({
|
|
94
|
+
serviceUrl,
|
|
95
|
+
json: true,
|
|
96
|
+
// We do not want the annotations concatenated to the final result.
|
|
97
|
+
loadAnnotationsJoined: false
|
|
98
|
+
});
|
|
99
|
+
// This method actually returns promise which is resolved with the metadata.
|
|
100
|
+
return oModel.metadataLoaded(true).finally(
|
|
101
|
+
// Do clean up in case the helath check is done multiple times.
|
|
102
|
+
() => oModel.destroy());
|
|
103
|
+
}
|
|
104
|
+
getServiceV4Metadata(serviceUrl) {
|
|
105
|
+
const oModel = new ODataModelV4({
|
|
106
|
+
serviceUrl,
|
|
107
|
+
// Only metadata loaded. We only want the model to load $metadata,
|
|
108
|
+
// not fetch entity data or bind to any UI controls.
|
|
109
|
+
synchronizationMode: 'None'
|
|
110
|
+
});
|
|
111
|
+
// This method actually returns promise which is resolved with the metadata.
|
|
112
|
+
return oModel.getMetaModel().requestObject('/').finally(
|
|
113
|
+
// Do clean up in case the helath check is done multiple times.
|
|
114
|
+
() => oModel.destroy());
|
|
115
|
+
}
|
|
116
|
+
getServices() {
|
|
117
|
+
const manifest = this.rta.getRootControlInstance().getManifest();
|
|
118
|
+
const dataSources = manifest?.['sap.app']?.dataSources;
|
|
119
|
+
return Object.values(dataSources ?? {}).filter(this.isOdataService).map(this.toOdataServiceInfo);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
var __exports = {
|
|
123
|
+
__esModule: true
|
|
124
|
+
};
|
|
125
|
+
__exports.ODataHealthChecker = ODataHealthChecker;
|
|
126
|
+
return __exports;
|
|
127
|
+
});
|
|
128
|
+
//# sourceMappingURL=odata-health-checker.js.map
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type * as ManifestNamespace from '@ui5/manifest/types/manifest';
|
|
2
|
+
import Log from 'sap/base/Log';
|
|
3
|
+
import ODataModelV2 from 'sap/ui/model/odata/v2/ODataModel';
|
|
4
|
+
import ODataModelV4 from 'sap/ui/model/odata/v4/ODataModel';
|
|
5
|
+
import RuntimeAuthoring from 'sap/ui/rta/RuntimeAuthoring';
|
|
6
|
+
import { ODataDownStatus, ODataHealthStatus, ODataMetadata, ODataUpStatus } from './odata-health-status';
|
|
7
|
+
|
|
8
|
+
type Manifest = ManifestNamespace.SAPJSONSchemaForWebApplicationManifestFile;
|
|
9
|
+
type DataSource = ManifestNamespace.DataSource;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The OData version type.
|
|
13
|
+
*/
|
|
14
|
+
type ODataVersion = 'v2' | 'v4' | '2.0' | '4.0';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Describes an OData service instance.
|
|
18
|
+
*/
|
|
19
|
+
interface ODataServiceInfo {
|
|
20
|
+
/**
|
|
21
|
+
* The URL of the OData service.
|
|
22
|
+
*/
|
|
23
|
+
serviceUrl: string;
|
|
24
|
+
/**
|
|
25
|
+
* The OData protocol version.
|
|
26
|
+
*/
|
|
27
|
+
oDataVersion: ODataVersion;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Use this class to do a health check for all available OData services, supports both v2 and v4
|
|
32
|
+
* format. This health checker ensures not only that $metadata is valid, but also that the UI5 framework
|
|
33
|
+
* itself can consume the service via its models.
|
|
34
|
+
*/
|
|
35
|
+
export class ODataHealthChecker {
|
|
36
|
+
/**
|
|
37
|
+
* The OData type.
|
|
38
|
+
*/
|
|
39
|
+
private static readonly ODATA_TYPE: string = 'OData';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The default OData version.
|
|
43
|
+
*/
|
|
44
|
+
private static readonly DEFAULT_ODATA_VERSION: ODataVersion = 'v2';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Use this helper function to filter the OData data source items from the manifest.
|
|
48
|
+
* @param src The service data source.
|
|
49
|
+
* @returns True if the data source represents an OData service.
|
|
50
|
+
*/
|
|
51
|
+
private readonly isOdataService = (src: DataSource): boolean => src.type === ODataHealthChecker.ODATA_TYPE;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Use this helper function to map the OData data source to the internal structure
|
|
55
|
+
* used in this class.
|
|
56
|
+
* @param src The OData service data source.
|
|
57
|
+
* @returns The OData service info object.
|
|
58
|
+
*/
|
|
59
|
+
private readonly toOdataServiceInfo = (src: DataSource): ODataServiceInfo => ({
|
|
60
|
+
serviceUrl: src.uri,
|
|
61
|
+
oDataVersion: src.settings?.odataVersion ?? ODataHealthChecker.DEFAULT_ODATA_VERSION
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
constructor(private readonly rta: RuntimeAuthoring) {}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Does a health check to all available OData services.
|
|
68
|
+
*
|
|
69
|
+
* @returns Resolves with an array containing the health
|
|
70
|
+
* status for each OData service.
|
|
71
|
+
*/
|
|
72
|
+
async getHealthStatus(): Promise<ODataHealthStatus[]> {
|
|
73
|
+
const oDataHealthCheckStartTime = Date.now();
|
|
74
|
+
|
|
75
|
+
const services = this.getServices();
|
|
76
|
+
const metadataPromises = await Promise.allSettled(
|
|
77
|
+
services.map(({ serviceUrl, oDataVersion }) => this.getServiceMetadata(serviceUrl, oDataVersion))
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const oDataHealthCheckDurationInSec = ((Date.now() - oDataHealthCheckStartTime) / 1000).toFixed(2);
|
|
81
|
+
Log.info(`OData service health check took ${oDataHealthCheckDurationInSec} sec.`);
|
|
82
|
+
|
|
83
|
+
return metadataPromises.map((metadataPromise, idx) =>
|
|
84
|
+
metadataPromise.status === 'fulfilled'
|
|
85
|
+
? new ODataUpStatus(services[idx].serviceUrl)
|
|
86
|
+
: new ODataDownStatus(services[idx].serviceUrl, metadataPromise.reason)
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* This method does a strong health check (with the ODataModel). This ensures not only
|
|
92
|
+
* that the $metadata is valid, but also that the UI5 framework itself can consume the service via its models.
|
|
93
|
+
* Some services may have valid $metadata but still fail in the UI5’s ODataModel
|
|
94
|
+
* (e.g., weird annotations, CORS issues, etc.).
|
|
95
|
+
*
|
|
96
|
+
* @param serviceUrl The OData service url.
|
|
97
|
+
* @param oDataVersion The OData version.
|
|
98
|
+
* @returns Resolved with valid metadata.
|
|
99
|
+
*/
|
|
100
|
+
private getServiceMetadata(serviceUrl: string, oDataVersion: ODataVersion): Promise<ODataMetadata> {
|
|
101
|
+
switch (oDataVersion) {
|
|
102
|
+
case 'v2':
|
|
103
|
+
case '2.0':
|
|
104
|
+
return this.getServiceV2Metadata(serviceUrl);
|
|
105
|
+
case 'v4':
|
|
106
|
+
case '4.0':
|
|
107
|
+
return this.getServiceV4Metadata(serviceUrl);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private getServiceV2Metadata(serviceUrl: string): Promise<ODataMetadata> {
|
|
112
|
+
const oModel = new ODataModelV2({
|
|
113
|
+
serviceUrl,
|
|
114
|
+
json: true,
|
|
115
|
+
// We do not want the annotations concatenated to the final result.
|
|
116
|
+
loadAnnotationsJoined: false
|
|
117
|
+
});
|
|
118
|
+
// This method actually returns promise which is resolved with the metadata.
|
|
119
|
+
return oModel.metadataLoaded(true).finally(
|
|
120
|
+
// Do clean up in case the helath check is done multiple times.
|
|
121
|
+
() => oModel.destroy()
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private getServiceV4Metadata(serviceUrl: string): Promise<ODataMetadata> {
|
|
126
|
+
const oModel = new ODataModelV4({
|
|
127
|
+
serviceUrl,
|
|
128
|
+
// Only metadata loaded. We only want the model to load $metadata,
|
|
129
|
+
// not fetch entity data or bind to any UI controls.
|
|
130
|
+
synchronizationMode: 'None'
|
|
131
|
+
});
|
|
132
|
+
// This method actually returns promise which is resolved with the metadata.
|
|
133
|
+
return oModel
|
|
134
|
+
.getMetaModel()
|
|
135
|
+
.requestObject('/')
|
|
136
|
+
.finally(
|
|
137
|
+
// Do clean up in case the helath check is done multiple times.
|
|
138
|
+
() => oModel.destroy()
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private getServices(): ODataServiceInfo[] {
|
|
143
|
+
const manifest: Manifest = this.rta.getRootControlInstance().getManifest() as unknown as Manifest;
|
|
144
|
+
const dataSources = manifest?.['sap.app']?.dataSources;
|
|
145
|
+
return Object.values(dataSources ?? {})
|
|
146
|
+
.filter(this.isOdataService)
|
|
147
|
+
.map(this.toOdataServiceInfo);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
sap.ui.define([], function () {
|
|
4
|
+
"use strict";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Represents the status for a healthy OData service.
|
|
8
|
+
*/
|
|
9
|
+
class ODataUpStatus {
|
|
10
|
+
/**
|
|
11
|
+
* Creates an instance representing a healthy OData service.
|
|
12
|
+
*
|
|
13
|
+
* @param serviceUrl The service url.
|
|
14
|
+
*/
|
|
15
|
+
constructor(serviceUrl) {
|
|
16
|
+
this.serviceUrl = serviceUrl;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Represents the status for an unhealthy OData service.
|
|
22
|
+
*/
|
|
23
|
+
class ODataDownStatus {
|
|
24
|
+
/**
|
|
25
|
+
* Creates an instance representing an unhealthy OData service.
|
|
26
|
+
*
|
|
27
|
+
* @param serviceUrl The service url.
|
|
28
|
+
* @param reason The provided reason for the failure.
|
|
29
|
+
*/
|
|
30
|
+
constructor(serviceUrl, reason) {
|
|
31
|
+
this.serviceUrl = serviceUrl;
|
|
32
|
+
this.reason = reason;
|
|
33
|
+
this.errorMessage = this.formatReason(reason);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Formats the reason.
|
|
38
|
+
*
|
|
39
|
+
* @param reason The provided reason for the failure.
|
|
40
|
+
* @returns Formatted reason as string.
|
|
41
|
+
*/
|
|
42
|
+
formatReason(reason) {
|
|
43
|
+
if (reason instanceof Error) {
|
|
44
|
+
return reason.message;
|
|
45
|
+
}
|
|
46
|
+
if (typeof reason === 'string') {
|
|
47
|
+
return reason;
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
return JSON.stringify(reason);
|
|
51
|
+
} catch {
|
|
52
|
+
return String(reason);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
var __exports = {
|
|
57
|
+
__esModule: true
|
|
58
|
+
};
|
|
59
|
+
__exports.ODataUpStatus = ODataUpStatus;
|
|
60
|
+
__exports.ODataDownStatus = ODataDownStatus;
|
|
61
|
+
return __exports;
|
|
62
|
+
});
|
|
63
|
+
//# sourceMappingURL=odata-health-status.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export type ODataMetadata = Record<string, unknown>;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents the status for a healthy OData service.
|
|
5
|
+
*/
|
|
6
|
+
export class ODataUpStatus {
|
|
7
|
+
/**
|
|
8
|
+
* Creates an instance representing a healthy OData service.
|
|
9
|
+
*
|
|
10
|
+
* @param serviceUrl The service url.
|
|
11
|
+
*/
|
|
12
|
+
constructor(public readonly serviceUrl: string) {}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Represents the status for an unhealthy OData service.
|
|
17
|
+
*/
|
|
18
|
+
export class ODataDownStatus {
|
|
19
|
+
readonly errorMessage: string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates an instance representing an unhealthy OData service.
|
|
23
|
+
*
|
|
24
|
+
* @param serviceUrl The service url.
|
|
25
|
+
* @param reason The provided reason for the failure.
|
|
26
|
+
*/
|
|
27
|
+
constructor(public readonly serviceUrl: string, public readonly reason: unknown) {
|
|
28
|
+
this.errorMessage = this.formatReason(reason);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Formats the reason.
|
|
33
|
+
*
|
|
34
|
+
* @param reason The provided reason for the failure.
|
|
35
|
+
* @returns Formatted reason as string.
|
|
36
|
+
*/
|
|
37
|
+
private formatReason(reason: unknown): string {
|
|
38
|
+
if (reason instanceof Error) {
|
|
39
|
+
return reason.message;
|
|
40
|
+
}
|
|
41
|
+
if (typeof reason === 'string') {
|
|
42
|
+
return reason;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
return JSON.stringify(reason);
|
|
46
|
+
} catch {
|
|
47
|
+
return String(reason);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type ODataHealthStatus = ODataUpStatus | ODataDownStatus;
|
|
@@ -65,6 +65,8 @@ ADP_CONTROLLER_ERROR_TITLE = Controller Error
|
|
|
65
65
|
ADP_EXTENSION_POINT_ERROR_TITLE = Extension Point Error
|
|
66
66
|
ADP_CREATE_CONTROLLER_EXTENSION_TITLE = Create Controller Extension
|
|
67
67
|
ADP_CREATE_CONTROLLER_EXTENSION_DESCRIPTION = Controller extension with name ''{0}'' was created.
|
|
68
|
+
ADP_ODATA_HEALTH_CHECK_TITLE = OData Service Health Check
|
|
69
|
+
ADP_ODATA_SERVICE_DOWN_DESCRIPTION = The OData service with the {0} endpoint is down. Error: {1}.
|
|
68
70
|
CHANGES_VISIBLE_AFTER_SAVE_AND_RELOAD_TITLE = Save and Reload Required
|
|
69
71
|
CHANGES_VISIBLE_AFTER_SAVE_AND_RELOAD_DESCRIPTION = Note: The change will be visible after save and reload.
|
|
70
72
|
CHANGE_CREATION_FAILED_TITLE = Change Creation Failed
|
|
@@ -20,10 +20,19 @@ sap.ui.define(["../cpe/additional-change-info/add-xml-additional-info"], functio
|
|
|
20
20
|
if (change?.getChangeType?.() === 'addXML') {
|
|
21
21
|
additionalChangeInfo = getAddXMLAdditionalInfo(change);
|
|
22
22
|
}
|
|
23
|
-
if (additionalChangeInfo
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
if (additionalChangeInfo) {
|
|
24
|
+
const existingInfo = additionalChangeInfoMap.get(key);
|
|
25
|
+
if (existingInfo) {
|
|
26
|
+
// Merge new info with existing info, keeping existing values and only adding new ones
|
|
27
|
+
const mergedInfo = {
|
|
28
|
+
...additionalChangeInfo,
|
|
29
|
+
...existingInfo
|
|
30
|
+
};
|
|
31
|
+
additionalChangeInfoMap.set(key, mergedInfo);
|
|
32
|
+
} else {
|
|
33
|
+
// No existing info, set the new info
|
|
34
|
+
additionalChangeInfoMap.set(key, additionalChangeInfo);
|
|
35
|
+
}
|
|
27
36
|
}
|
|
28
37
|
}
|
|
29
38
|
function setAdditionalChangeInfoForChangeFile(fileName, additionalChangeInfo) {
|
|
@@ -26,10 +26,19 @@ export function setAdditionalChangeInfo(change: FlexChange<AddXMLChangeContent>
|
|
|
26
26
|
additionalChangeInfo = getAddXMLAdditionalInfo(change);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
if (additionalChangeInfo
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
if (additionalChangeInfo) {
|
|
30
|
+
const existingInfo = additionalChangeInfoMap.get(key);
|
|
31
|
+
if (existingInfo) {
|
|
32
|
+
// Merge new info with existing info, keeping existing values and only adding new ones
|
|
33
|
+
const mergedInfo = {
|
|
34
|
+
...additionalChangeInfo,
|
|
35
|
+
...existingInfo
|
|
36
|
+
};
|
|
37
|
+
additionalChangeInfoMap.set(key, mergedInfo);
|
|
38
|
+
} else {
|
|
39
|
+
// No existing info, set the new info
|
|
40
|
+
additionalChangeInfoMap.set(key, additionalChangeInfo);
|
|
41
|
+
}
|
|
33
42
|
}
|
|
34
43
|
}
|
|
35
44
|
|
|
@@ -68,6 +68,38 @@ sap.ui.define(["sap/ui/core/Component", "sap/ui/core/Element"], function (Compon
|
|
|
68
68
|
}
|
|
69
69
|
return hasParent(parent, parentIdToFind);
|
|
70
70
|
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Utility function to safely call getParent on UI5 elements
|
|
74
|
+
* @param element UI5 element
|
|
75
|
+
* @returns parent element or null
|
|
76
|
+
*/
|
|
77
|
+
function getElementParent(element) {
|
|
78
|
+
if (typeof element.getParent === 'function') {
|
|
79
|
+
return element.getParent();
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Finds the view that contains the given control.
|
|
86
|
+
*
|
|
87
|
+
* @param control - Control instance
|
|
88
|
+
* @returns View instance if found, undefined otherwise
|
|
89
|
+
*/
|
|
90
|
+
function findViewByControl(control) {
|
|
91
|
+
if (!control) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
if (isA('sap.ui.core.mvc.View', control)) {
|
|
95
|
+
return control;
|
|
96
|
+
}
|
|
97
|
+
const parent = getElementParent(control);
|
|
98
|
+
if (!parent) {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
return findViewByControl(parent);
|
|
102
|
+
}
|
|
71
103
|
function findNestedElements(ownerElement, candidates) {
|
|
72
104
|
const ownerId = ownerElement.getId();
|
|
73
105
|
return candidates.filter(item => hasParent(item, ownerId));
|
|
@@ -80,6 +112,7 @@ sap.ui.define(["sap/ui/core/Component", "sap/ui/core/Element"], function (Compon
|
|
|
80
112
|
__exports.isManagedObject = isManagedObject;
|
|
81
113
|
__exports.isA = isA;
|
|
82
114
|
__exports.hasParent = hasParent;
|
|
115
|
+
__exports.findViewByControl = findViewByControl;
|
|
83
116
|
__exports.findNestedElements = findNestedElements;
|
|
84
117
|
return __exports;
|
|
85
118
|
});
|
|
@@ -2,6 +2,7 @@ import Component from 'sap/ui/core/Component';
|
|
|
2
2
|
import type { ID } from 'sap/ui/core/library';
|
|
3
3
|
import type ManagedObject from 'sap/ui/base/ManagedObject';
|
|
4
4
|
import Element from 'sap/ui/core/Element';
|
|
5
|
+
import View from 'sap/ui/core/mvc/View';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Gets Component by id.
|
|
@@ -71,6 +72,38 @@ export function hasParent(component: ManagedObject, parentIdToFind: string): boo
|
|
|
71
72
|
return hasParent(parent, parentIdToFind);
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Utility function to safely call getParent on UI5 elements
|
|
77
|
+
* @param element UI5 element
|
|
78
|
+
* @returns parent element or null
|
|
79
|
+
*/
|
|
80
|
+
function getElementParent(element: Element | ManagedObject): ManagedObject | null {
|
|
81
|
+
if (typeof element.getParent === 'function') {
|
|
82
|
+
return element.getParent();
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Finds the view that contains the given control.
|
|
89
|
+
*
|
|
90
|
+
* @param control - Control instance
|
|
91
|
+
* @returns View instance if found, undefined otherwise
|
|
92
|
+
*/
|
|
93
|
+
export function findViewByControl(control: Element | ManagedObject): View | undefined {
|
|
94
|
+
if (!control) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
if (isA<View>('sap.ui.core.mvc.View', control)) {
|
|
98
|
+
return control;
|
|
99
|
+
}
|
|
100
|
+
const parent = getElementParent(control);
|
|
101
|
+
if (!parent) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
return findViewByControl(parent);
|
|
105
|
+
}
|
|
106
|
+
|
|
74
107
|
export function findNestedElements(
|
|
75
108
|
ownerElement: Element,
|
|
76
109
|
candidates: Element[]
|
|
@@ -48,8 +48,8 @@ sap.ui.define(["sap/ui/VersionInfo", "sap/base/Log", "./info-center-message", "o
|
|
|
48
48
|
let version = versionInfo?.libraries?.find(lib => lib.name === library)?.version;
|
|
49
49
|
const isCdn = versionInfo?.name === 'SAPUI5 Distribution';
|
|
50
50
|
if (!version) {
|
|
51
|
-
Log.error('Could not get UI5 version of application. Using version: 1.130.
|
|
52
|
-
version = '1.130.
|
|
51
|
+
Log.error('Could not get UI5 version of application. Using version: 1.130.9 as fallback.');
|
|
52
|
+
version = '1.130.9';
|
|
53
53
|
await sendInfoCenterMessage({
|
|
54
54
|
title: {
|
|
55
55
|
key: 'FLP_UI_VERSION_RETRIEVAL_FAILURE_TITLE'
|
|
@@ -58,8 +58,8 @@ export async function getUi5Version(library: string = 'sap.ui.core'): Promise<Ui
|
|
|
58
58
|
let version = versionInfo?.libraries?.find((lib) => lib.name === library)?.version;
|
|
59
59
|
const isCdn = versionInfo?.name === 'SAPUI5 Distribution';
|
|
60
60
|
if (!version) {
|
|
61
|
-
Log.error('Could not get UI5 version of application. Using version: 1.130.
|
|
62
|
-
version = '1.130.
|
|
61
|
+
Log.error('Could not get UI5 version of application. Using version: 1.130.9 as fallback.');
|
|
62
|
+
version = '1.130.9';
|
|
63
63
|
await sendInfoCenterMessage({
|
|
64
64
|
title: { key: 'FLP_UI_VERSION_RETRIEVAL_FAILURE_TITLE' },
|
|
65
65
|
description: { key: 'FLP_UI_VERSION_RETRIEVAL_FAILURE_DESCRIPTION', params: [version] },
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"bugs": {
|
|
10
10
|
"url": "https://github.com/SAP/open-ux-tools/issues?q=is%3Aopen+is%3Aissue+label%3Abug+label%3Apreview-middleware"
|
|
11
11
|
},
|
|
12
|
-
"version": "0.23.
|
|
12
|
+
"version": "0.23.56",
|
|
13
13
|
"license": "Apache-2.0",
|
|
14
14
|
"author": "@SAP/ux-tools-team",
|
|
15
15
|
"main": "dist/index.js",
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
"mem-fs-editor": "9.4.0",
|
|
28
28
|
"qrcode": "1.5.4",
|
|
29
29
|
"@sap/bas-sdk": "3.12.0",
|
|
30
|
-
"@sap-ux/adp-tooling": "0.18.
|
|
30
|
+
"@sap-ux/adp-tooling": "0.18.4",
|
|
31
31
|
"@sap-ux/btp-utils": "1.1.5",
|
|
32
32
|
"@sap-ux/control-property-editor-sources": "npm:@sap-ux/control-property-editor@0.7.2",
|
|
33
|
-
"@sap-ux/feature-toggle": "0.3.4",
|
|
34
33
|
"@sap-ux/logger": "0.7.1",
|
|
34
|
+
"@sap-ux/feature-toggle": "0.3.4",
|
|
35
35
|
"@sap-ux/project-access": "1.32.8",
|
|
36
36
|
"@sap-ux/system-access": "0.6.28",
|
|
37
37
|
"@sap-ux/i18n": "0.3.5"
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"nock": "13.4.0",
|
|
54
54
|
"npm-run-all2": "6.2.0",
|
|
55
55
|
"supertest": "7.1.4",
|
|
56
|
-
"@private/preview-middleware-client": "npm:@sap-ux-private/preview-middleware-client@0.
|
|
56
|
+
"@private/preview-middleware-client": "npm:@sap-ux-private/preview-middleware-client@0.18.2",
|
|
57
57
|
"@sap-ux/axios-extension": "1.24.2",
|
|
58
58
|
"@sap-ux/store": "1.3.3",
|
|
59
59
|
"@sap-ux/ui5-info": "0.13.2"
|