@sap-ux/odata-service-inquirer 2.8.12 → 2.9.0

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.
@@ -206,7 +206,6 @@ export declare class ConnectionValidator {
206
206
  * @param connectConfig.serviceInfo the service info
207
207
  * @param connectConfig.odataVersion the odata version to restrict the catalog requests if only a specific version is required
208
208
  * @param connectConfig.destination the destination to connect with
209
- * @param connectConfig.refreshToken
210
209
  * @throws an error if the connection attempt fails, callers should handle the error
211
210
  */
212
211
  private createSystemConnection;
@@ -229,7 +228,7 @@ export declare class ConnectionValidator {
229
228
  */
230
229
  private refreshTokenChangedCb;
231
230
  /**
232
- * Get the service provider for the Abap on Cloud environment.
231
+ * Get the service provider for the Abap Cloud environment.
233
232
  *
234
233
  * @param url the system url
235
234
  * @param serviceInfo the service info
@@ -244,10 +243,9 @@ export declare class ConnectionValidator {
244
243
  *
245
244
  * @param serviceInfo the service info containing the UAA details
246
245
  * @param odataVersion the odata version to restrict the catalog requests if only a specific version is required
247
- * @param refreshToken the refresh token for the Abap on Cloud environment, will be used to avoid re-authentication while the token is valid
248
246
  * @returns true if the system is reachable and authenticated, if required, false if not, or an error message string
249
247
  */
250
- validateServiceInfo(serviceInfo: ServiceInfo, odataVersion?: ODataVersion, refreshToken?: string): Promise<ValidationResult>;
248
+ validateServiceInfo(serviceInfo: ServiceInfo, odataVersion?: ODataVersion): Promise<ValidationResult>;
251
249
  /**
252
250
  * Validate the specified destination connectivity, determining if authentication is required or if the destination is misconfigured.
253
251
  *
@@ -261,6 +261,9 @@ class ConnectionValidator {
261
261
  const axiosConfig = this.createAxiosConfig(url, ignoreCertError, username, password);
262
262
  if (isSystem) {
263
263
  await this.createSystemConnection({ axiosConfig, url, odataVersion });
264
+ const systemInfo = await this.serviceProvider.getSystemInfo();
265
+ this._connectedUserName = systemInfo?.userName;
266
+ this._connectedSystemName = systemInfo?.systemID;
264
267
  }
265
268
  else {
266
269
  // Full service URL
@@ -298,7 +301,8 @@ class ConnectionValidator {
298
301
  ignoreCertErrors: ignoreCertError,
299
302
  cookies: '',
300
303
  baseURL: url.origin,
301
- url: url.pathname
304
+ url: url.pathname,
305
+ logger: logger_helper_1.default.logger
302
306
  };
303
307
  if (username && password) {
304
308
  axiosConfig = Object.assign(axiosConfig, {
@@ -386,14 +390,13 @@ class ConnectionValidator {
386
390
  * @param connectConfig.serviceInfo the service info
387
391
  * @param connectConfig.odataVersion the odata version to restrict the catalog requests if only a specific version is required
388
392
  * @param connectConfig.destination the destination to connect with
389
- * @param connectConfig.refreshToken
390
393
  * @throws an error if the connection attempt fails, callers should handle the error
391
394
  */
392
- async createSystemConnection({ axiosConfig, url, serviceInfo, destination, odataVersion, refreshToken }) {
395
+ async createSystemConnection({ axiosConfig, url, serviceInfo, destination, odataVersion }) {
393
396
  this.resetConnectionState();
394
397
  this.resetValidity();
395
398
  if (this.systemAuthType === 'reentranceTicket' || this.systemAuthType === 'serviceKey') {
396
- this._serviceProvider = this.getAbapOnCloudServiceProvider(url, serviceInfo, refreshToken);
399
+ this._serviceProvider = this.getAbapOnCloudServiceProvider(url, serviceInfo);
397
400
  }
398
401
  else if (destination) {
399
402
  // Assumption: the destination configured URL is a valid URL, will be needed later for basic auth error handling
@@ -493,7 +496,7 @@ class ConnectionValidator {
493
496
  this._refreshToken = refreshToken;
494
497
  }
495
498
  /**
496
- * Get the service provider for the Abap on Cloud environment.
499
+ * Get the service provider for the Abap Cloud environment.
497
500
  *
498
501
  * @param url the system url
499
502
  * @param serviceInfo the service info
@@ -504,7 +507,8 @@ class ConnectionValidator {
504
507
  if (this.systemAuthType === 'reentranceTicket' && url) {
505
508
  return (0, axios_extension_1.createForAbapOnCloud)({
506
509
  environment: axios_extension_1.AbapCloudEnvironment.EmbeddedSteampunk,
507
- url: new URL(url.pathname, url.origin).toString()
510
+ url: new URL(url.pathname, url.origin).toString(),
511
+ logger: logger_helper_1.default.logger
508
512
  });
509
513
  }
510
514
  if (this.systemAuthType === 'serviceKey' && serviceInfo) {
@@ -512,7 +516,8 @@ class ConnectionValidator {
512
516
  environment: axios_extension_1.AbapCloudEnvironment.Standalone,
513
517
  service: serviceInfo,
514
518
  refreshToken,
515
- refreshTokenChangedCb: this.refreshTokenChangedCb.bind(this)
519
+ refreshTokenChangedCb: this.refreshTokenChangedCb.bind(this),
520
+ logger: logger_helper_1.default.logger
516
521
  });
517
522
  }
518
523
  throw new Error('Invalid system auth type');
@@ -524,10 +529,9 @@ class ConnectionValidator {
524
529
  *
525
530
  * @param serviceInfo the service info containing the UAA details
526
531
  * @param odataVersion the odata version to restrict the catalog requests if only a specific version is required
527
- * @param refreshToken the refresh token for the Abap on Cloud environment, will be used to avoid re-authentication while the token is valid
528
532
  * @returns true if the system is reachable and authenticated, if required, false if not, or an error message string
529
533
  */
530
- async validateServiceInfo(serviceInfo, odataVersion, refreshToken) {
534
+ async validateServiceInfo(serviceInfo, odataVersion) {
531
535
  if (!serviceInfo) {
532
536
  return false;
533
537
  }
@@ -538,7 +542,7 @@ class ConnectionValidator {
538
542
  }
539
543
  try {
540
544
  this.systemAuthType = 'serviceKey';
541
- await this.createSystemConnection({ serviceInfo, odataVersion, refreshToken });
545
+ await this.createSystemConnection({ serviceInfo, odataVersion });
542
546
  // Cache the user info
543
547
  this._connectedUserName = await this.serviceProvider.user();
544
548
  this._serviceInfo = serviceInfo;
@@ -657,6 +661,9 @@ class ConnectionValidator {
657
661
  this.validity.urlFormat = false;
658
662
  return false;
659
663
  }
664
+ if (systemAuthType) {
665
+ this.systemAuthType = systemAuthType;
666
+ }
660
667
  let url;
661
668
  try {
662
669
  // Check if the url is valid
@@ -664,13 +671,16 @@ class ConnectionValidator {
664
671
  if (url.origin === 'null') {
665
672
  return (0, i18n_1.t)('errors.invalidUrl', { input: serviceUrl });
666
673
  }
674
+ // Dont allow non origin URLs in for re-entrance tickets as the error handling would become complex to analyize.
675
+ // The connection may succeed but later we will get auth errors since axios-extension does not validate this.
676
+ // The new system name would also include the additional paths which would not make sense either.
677
+ if (this.systemAuthType === 'reentranceTicket' && !(url.pathname.length === 0 || url.pathname === '/')) {
678
+ return (0, i18n_1.t)('prompts.validationMessages.reentranceTicketSystemHostOnly');
679
+ }
667
680
  }
668
681
  catch (error) {
669
682
  return (0, i18n_1.t)('errors.invalidUrl', { input: serviceUrl });
670
683
  }
671
- if (systemAuthType) {
672
- this.systemAuthType = systemAuthType;
673
- }
674
684
  try {
675
685
  if (!forceReValidation && this.isUrlValidated(serviceUrl)) {
676
686
  return this.validity.reachable ?? false;
@@ -7,14 +7,12 @@ import { type ServiceAnswer } from '../service-selection';
7
7
  declare const systemUrlPromptName: "abapOnBtp:newSystemUrl";
8
8
  declare const abapOnBtpPromptNames: {
9
9
  readonly abapOnBtpAuthType: "abapOnBtpAuthType";
10
- readonly serviceKey: "serviceKey";
11
10
  readonly cloudFoundryAbapSystem: "cloudFoundryAbapSystem";
12
11
  };
13
- export type AbapOnBTPType = 'cloudFoundry' | 'serviceKey' | 'reentranceTicket';
12
+ export type AbapOnBTPType = 'cloudFoundry' | 'reentranceTicket';
14
13
  interface AbapOnBtpAnswers extends Partial<OdataServiceAnswers> {
15
14
  [abapOnBtpPromptNames.abapOnBtpAuthType]?: AbapOnBTPType;
16
15
  [systemUrlPromptName]?: string;
17
- [abapOnBtpPromptNames.serviceKey]?: string;
18
16
  [abapOnBtpPromptNames.cloudFoundryAbapSystem]?: ServiceInstanceInfo;
19
17
  }
20
18
  /**
@@ -22,10 +20,9 @@ interface AbapOnBtpAnswers extends Partial<OdataServiceAnswers> {
22
20
  *
23
21
  * @param promptOptions The prompt options which control the service selection and system name]
24
22
  * @param cachedConnectedSystem if available passing an already connected system connection will prevent re-authentication for re-entrance ticket and service keys connection types
25
- * @param serviceKeyToggle Feature toggle to enable/disable the BTP service key option - enabled by default
26
23
  * @returns The list of questions for the ABAP on BTP system
27
24
  */
28
- export declare function getAbapOnBTPSystemQuestions(promptOptions?: OdataServicePromptOptions, cachedConnectedSystem?: ConnectedSystem, serviceKeyToggle?: boolean): Question<AbapOnBtpAnswers & ServiceAnswer>[];
25
+ export declare function getAbapOnBTPSystemQuestions(promptOptions?: OdataServicePromptOptions, cachedConnectedSystem?: ConnectedSystem): Question<AbapOnBtpAnswers & ServiceAnswer>[];
29
26
  /**
30
27
  * Get the Cloud Foundry Abap system discovery prompt. This prompt will list all available ABAP environments in the connected Cloud Foundry space.
31
28
  * If the Cloud Foundry connection fails, a warning message will be displayed.
@@ -18,26 +18,21 @@ const types_1 = require("../new-system/types");
18
18
  const service_selection_1 = require("../service-selection");
19
19
  const shared_prompts_1 = require("../shared-prompts/shared-prompts");
20
20
  const prompt_helpers_2 = require("../system-selection/prompt-helpers");
21
- const validators_1 = require("../validators");
22
- const feature_toggle_1 = require("@sap-ux/feature-toggle");
23
21
  const abapOnBtpPromptNamespace = 'abapOnBtp';
24
22
  const systemUrlPromptName = `${abapOnBtpPromptNamespace}:${types_1.newSystemPromptNames.newSystemUrl}`;
25
23
  const cliCfAbapServicePromptName = 'cliCfAbapService';
26
24
  const abapOnBtpPromptNames = {
27
25
  'abapOnBtpAuthType': 'abapOnBtpAuthType',
28
- 'serviceKey': 'serviceKey',
29
26
  'cloudFoundryAbapSystem': 'cloudFoundryAbapSystem'
30
27
  };
31
- const SERVICE_KEY_FEATURE_TOGGLE = 'sap.ux.appGenerator.testBetaFeatures.disableBtpServiceKeyAuth';
32
28
  /**
33
29
  * Get the questions for the ABAP on BTP system within the VSCode platform. The questions will prompt the user for the system type (Cloud Foundry, Service Key, Re-entrance Ticket).
34
30
  *
35
31
  * @param promptOptions The prompt options which control the service selection and system name]
36
32
  * @param cachedConnectedSystem if available passing an already connected system connection will prevent re-authentication for re-entrance ticket and service keys connection types
37
- * @param serviceKeyToggle Feature toggle to enable/disable the BTP service key option - enabled by default
38
33
  * @returns The list of questions for the ABAP on BTP system
39
34
  */
40
- function getAbapOnBTPSystemQuestions(promptOptions, cachedConnectedSystem, serviceKeyToggle = (0, feature_toggle_1.isFeatureEnabled)(SERVICE_KEY_FEATURE_TOGGLE)) {
35
+ function getAbapOnBTPSystemQuestions(promptOptions, cachedConnectedSystem) {
41
36
  utils_1.PromptState.reset();
42
37
  const connectValidator = new connectionValidator_1.ConnectionValidator();
43
38
  const questions = [];
@@ -46,15 +41,6 @@ function getAbapOnBTPSystemQuestions(promptOptions, cachedConnectedSystem, servi
46
41
  name: abapOnBtpPromptNames.abapOnBtpAuthType,
47
42
  choices: [
48
43
  { name: (0, i18n_1.t)('prompts.abapOnBTPType.choiceCloudFoundry'), value: 'cloudFoundry' },
49
- // Feature toggle the service key option - enabled by default, can be disabled via VS Code settings or ENV
50
- ...(!serviceKeyToggle
51
- ? [
52
- {
53
- name: (0, i18n_1.t)('prompts.abapOnBTPType.choiceServiceKey'),
54
- value: 'serviceKey'
55
- }
56
- ]
57
- : []),
58
44
  { name: (0, i18n_1.t)('prompts.abapOnBTPType.choiceReentranceTicket'), value: 'reentranceTicket' }
59
45
  ],
60
46
  message: (0, i18n_1.t)('prompts.abapOnBTPType.message'),
@@ -73,12 +59,7 @@ function getAbapOnBTPSystemQuestions(promptOptions, cachedConnectedSystem, servi
73
59
  return true;
74
60
  }
75
61
  return false;
76
- })[0]);
77
- // Service Key file prompt - enabled by default
78
- if (!serviceKeyToggle) {
79
- questions.push((0, inquirer_common_1.withCondition)([getServiceKeyPrompt(connectValidator, cachedConnectedSystem)], (answers) => answers?.abapOnBtpAuthType === 'serviceKey')[0]);
80
- }
81
- questions.push(...(0, inquirer_common_1.withCondition)([...getCFDiscoverPrompts(connectValidator, undefined, undefined, cachedConnectedSystem)], (answers) => answers?.abapOnBtpAuthType === 'cloudFoundry'));
62
+ })[0], ...(0, inquirer_common_1.withCondition)([...getCFDiscoverPrompts(connectValidator, undefined, undefined, cachedConnectedSystem)], (answers) => answers?.abapOnBtpAuthType === 'cloudFoundry'));
82
63
  // New system store name propmt
83
64
  if (promptOptions?.userSystemName?.hide !== true) {
84
65
  // New system question will allow user to give the system a user friendly name
@@ -128,7 +109,7 @@ async function validateCFServiceInfo(abapService, connectionValidator, requiredO
128
109
  // In case the user has changed the URL, do not use the cached connection.
129
110
  if (cachedConnectedSystem &&
130
111
  cachedConnectedSystem.backendSystem?.url === uaaCreds.credentials.url &&
131
- JSON.stringify(cachedConnectedSystem.backendSystem.serviceKeys.uaa) ===
112
+ JSON.stringify(cachedConnectedSystem.backendSystem.serviceKeys?.uaa) ===
132
113
  JSON.stringify(uaaCreds.credentials.uaa)) {
133
114
  connectionValidator.setConnectedSystem(cachedConnectedSystem);
134
115
  }
@@ -220,46 +201,4 @@ function getCFDiscoverPrompts(connectionValidator, promptNamespace, requiredOdat
220
201
  }
221
202
  return questions;
222
203
  }
223
- /**
224
- * Get the service key prompt for the ABAP on BTP system. This prompt will allow the user to select a service key file from the file system.
225
- *
226
- * @param connectionValidator a connection validator instance
227
- * @param cachedConnectedSystem if available passing an already connected system connection will prevent re-authentication for re-entrance ticket and service keys connection types
228
- * @returns The service key prompt
229
- */
230
- function getServiceKeyPrompt(connectionValidator, cachedConnectedSystem) {
231
- const question = {
232
- type: 'input',
233
- name: abapOnBtpPromptNames.serviceKey,
234
- message: (0, i18n_1.t)('prompts.serviceKey.message'),
235
- guiType: 'file-browser',
236
- guiOptions: {
237
- hint: (0, i18n_1.t)('prompts.serviceKey.hint'),
238
- mandatory: true
239
- },
240
- validate: async (keyPath) => {
241
- utils_1.PromptState.resetConnectedSystem();
242
- const serviceKeyValResult = (0, validators_1.validateServiceKey)(keyPath);
243
- if (typeof serviceKeyValResult === 'string' || typeof serviceKeyValResult === 'boolean') {
244
- return serviceKeyValResult;
245
- }
246
- // Backend systems validation supports using a cached connections from a previous step execution to prevent re-authentication (e.g. re-opening a browser window)
247
- // In case the user has changed the URL, do not use the cached connection.
248
- if (cachedConnectedSystem &&
249
- cachedConnectedSystem.backendSystem?.url === serviceKeyValResult.url &&
250
- JSON.stringify(cachedConnectedSystem.backendSystem.serviceKeys.uaa) ===
251
- JSON.stringify(serviceKeyValResult.uaa)) {
252
- connectionValidator.setConnectedSystem(cachedConnectedSystem);
253
- }
254
- const connectValResult = await connectionValidator.validateServiceInfo(serviceKeyValResult);
255
- if (connectValResult === true && connectionValidator.serviceProvider) {
256
- utils_1.PromptState.odataService.connectedSystem = {
257
- serviceProvider: (0, utils_1.removeCircularFromServiceProvider)(connectionValidator.serviceProvider)
258
- };
259
- }
260
- return connectValResult;
261
- }
262
- };
263
- return question;
264
- }
265
204
  //# sourceMappingURL=questions.js.map
@@ -13,7 +13,7 @@ import { type NewSystemAnswers } from '../new-system/types';
13
13
  * @param connectValidator a connection validator instance used to validate the system url
14
14
  * @param promptNamespace The namespace for the prompt, used to identify the prompt instance and namespaced answers.
15
15
  * @param requiredOdataVersion The required OData version for the system connection, only catalogs supporting the specifc odata version will be used.
16
- * @param cachedConnectedSystem
16
+ * @param cachedConnectedSystem An existing connection may be passed which will prevent reauthentication
17
17
  * @returns the system url prompt
18
18
  */
19
19
  export declare function getSystemUrlQuestion<T extends Answers>(connectValidator: ConnectionValidator, promptNamespace?: string, requiredOdataVersion?: OdataVersion, cachedConnectedSystem?: ConnectedSystem): InputQuestion<T>;
@@ -19,8 +19,8 @@ const yeoman_ui_types_1 = require("@sap-devx/yeoman-ui-types");
19
19
  */
20
20
  function systemAuthTypeToAuthenticationType(systemAuthType) {
21
21
  switch (systemAuthType) {
22
- case 'serviceKey':
23
- return store_1.AuthenticationType.OAuth2RefreshToken;
22
+ case 'serviceKey' /** @deprecated All cloud auth is reentrance ticket based, legacy stored entries are still supported */:
23
+ return store_1.AuthenticationType.ReentranceTicket;
24
24
  case 'reentranceTicket':
25
25
  return store_1.AuthenticationType.ReentranceTicket;
26
26
  case 'basic':
@@ -35,7 +35,7 @@ function systemAuthTypeToAuthenticationType(systemAuthType) {
35
35
  * @param connectValidator a connection validator instance used to validate the system url
36
36
  * @param promptNamespace The namespace for the prompt, used to identify the prompt instance and namespaced answers.
37
37
  * @param requiredOdataVersion The required OData version for the system connection, only catalogs supporting the specifc odata version will be used.
38
- * @param cachedConnectedSystem
38
+ * @param cachedConnectedSystem An existing connection may be passed which will prevent reauthentication
39
39
  * @returns the system url prompt
40
40
  */
41
41
  function getSystemUrlQuestion(connectValidator, promptNamespace, requiredOdataVersion, cachedConnectedSystem) {
@@ -52,12 +52,21 @@ function getSystemUrlQuestion(connectValidator, promptNamespace, requiredOdataVe
52
52
  validate: async (url) => {
53
53
  utils_1.PromptState.resetConnectedSystem();
54
54
  // Backend systems validation supports using a cached connections from a previous step execution to prevent re-authentication (e.g. re-opening a browser window)
55
- // Only in the case or re-entrance tickets will we reuse an existing connection.
55
+ // Only in the case of re-entrance tickets will we reuse an existing connection.
56
56
  if (cachedConnectedSystem &&
57
57
  cachedConnectedSystem.backendSystem?.url === url &&
58
58
  cachedConnectedSystem.backendSystem?.authenticationType === 'reentranceTicket') {
59
59
  connectValidator.setConnectedSystem(cachedConnectedSystem);
60
60
  }
61
+ else {
62
+ const existingBackend = (0, utils_1.isBackendSystemKeyExisting)(utils_1.PromptState.backendSystemsCache, url);
63
+ if (existingBackend) {
64
+ // Not a cached connection so re-validate as new backend system entry
65
+ return (0, i18n_1.t)('prompts.validationMessages.backendSystemExistsWarning', {
66
+ backendName: existingBackend.name
67
+ });
68
+ }
69
+ }
61
70
  const valResult = await connectValidator.validateUrl(url, {
62
71
  isSystem: true,
63
72
  odataVersion: (0, utils_1.convertODataVersionType)(requiredOdataVersion)
@@ -143,13 +152,12 @@ function getUserSystemNameQuestion(connectValidator, promptNamespace) {
143
152
  client: connectValidator.validatedClient,
144
153
  username: connectValidator.axiosConfig?.auth?.username,
145
154
  password: connectValidator.axiosConfig?.auth?.password,
146
- serviceKeys: connectValidator.serviceInfo,
155
+ serviceKeys: connectValidator.serviceInfo, // This will not be persisted and is only used to determine cached connection equality for CF provided uaa keys
147
156
  userDisplayName: connectValidator.connectedUserName,
148
157
  systemType: (0, store_1.getBackendSystemType)({
149
158
  serviceKeys: connectValidator.serviceInfo,
150
159
  authenticationType: connectValidator.systemAuthType
151
- }),
152
- refreshToken: connectValidator.refreshToken
160
+ })
153
161
  });
154
162
  utils_1.PromptState.odataService.connectedSystem.backendSystem = backendSystem;
155
163
  utils_1.PromptState.odataService.connectedSystem.backendSystem.newOrUpdated = true;
@@ -35,13 +35,6 @@ export declare function connectWithBackendSystem(backendKey: BackendSystemKey, c
35
35
  * @returns the validation result of the destination connection attempt
36
36
  */
37
37
  export declare function connectWithDestination(destination: Destination, connectionValidator: ConnectionValidator, requiredOdataVersion?: OdataVersion, addServicePath?: string): Promise<ValidationResult>;
38
- /**
39
- * Creates and returns a display name for the system, appending the system type and user display name if available.
40
- *
41
- * @param system the backend system to create a display name for
42
- * @returns the display name for the system
43
- */
44
- export declare function getBackendSystemDisplayName(system: BackendSystem): string;
45
38
  /**
46
39
  * Creates a list of choices for the system selection prompt using destinations or stored backend systems, depending on the environment.
47
40
  *
@@ -6,7 +6,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.CfAbapEnvServiceChoice = exports.NewSystemChoice = void 0;
7
7
  exports.connectWithBackendSystem = connectWithBackendSystem;
8
8
  exports.connectWithDestination = connectWithDestination;
9
- exports.getBackendSystemDisplayName = getBackendSystemDisplayName;
10
9
  exports.createSystemChoices = createSystemChoices;
11
10
  exports.findDefaultSystemSelectionIndex = findDefaultSystemSelectionIndex;
12
11
  const btp_utils_1 = require("@sap-ux/btp-utils");
@@ -15,6 +14,7 @@ const store_1 = require("@sap-ux/store");
15
14
  const i18n_1 = require("../../../../i18n");
16
15
  const utils_1 = require("../../../../utils");
17
16
  const logger_helper_1 = __importDefault(require("../../../logger-helper"));
17
+ const fiori_generator_shared_1 = require("@sap-ux/fiori-generator-shared");
18
18
  // New system choice value is a hard to guess string to avoid conflicts with existing system names or user named systems
19
19
  // since it will be used as a new system value in the system selection prompt.
20
20
  exports.NewSystemChoice = '!@£*&937newSystem*X~qy^';
@@ -49,7 +49,7 @@ async function connectWithBackendSystem(backendKey, connectionValidator, require
49
49
  });
50
50
  }
51
51
  else if (backendSystem.serviceKeys) {
52
- connectValResult = await connectionValidator.validateServiceInfo(backendSystem.serviceKeys, (0, utils_1.convertODataVersionType)(requiredOdataVersion), backendSystem.refreshToken);
52
+ connectValResult = await connectionValidator.validateServiceInfo(backendSystem.serviceKeys, (0, utils_1.convertODataVersionType)(requiredOdataVersion));
53
53
  }
54
54
  else if (backendSystem.authenticationType === 'basic' || !backendSystem.authenticationType) {
55
55
  let errorType;
@@ -108,33 +108,6 @@ async function connectWithDestination(destination, connectionValidator, required
108
108
  // Deal with all destination errors here
109
109
  return connectValResult;
110
110
  }
111
- /**
112
- * Creates and returns a display name for the system, appending the system type and user display name if available.
113
- *
114
- * @param system the backend system to create a display name for
115
- * @returns the display name for the system
116
- */
117
- function getBackendSystemDisplayName(system) {
118
- const userDisplayName = system.userDisplayName ? ` [${system.userDisplayName}]` : '';
119
- const systemTypeName = getBackendSystemTypeName(system.systemType);
120
- return `${system.name}${systemTypeName}${userDisplayName}`;
121
- }
122
- /**
123
- * Returns the formatted system type name for the given backend system.
124
- *
125
- * @param systemType the system type to get the name for
126
- * @returns system type name formatted as a string, e.g. " (BTP)" or " (S4HC)".
127
- */
128
- function getBackendSystemTypeName(systemType) {
129
- let systemTypeName = ''; // for on prem we do not show the system type
130
- if (systemType === 'S4HC') {
131
- systemTypeName = ` (${(0, i18n_1.t)('texts.systemTypeS4HC')})`;
132
- }
133
- else if (systemType === 'BTP') {
134
- systemTypeName = ` (${(0, i18n_1.t)('texts.systemTypeBTP')})`;
135
- }
136
- return systemTypeName;
137
- }
138
111
  /**
139
112
  * Matches the destination against the provided filters. Returns true if the destination matches any filters, false otherwise.
140
113
  *
@@ -207,9 +180,11 @@ async function createSystemChoices(destinationFilters, includeCloudFoundryAbapEn
207
180
  }
208
181
  else {
209
182
  const backendSystems = await new store_1.SystemService(logger_helper_1.default.logger).getAll({ includeSensitiveData: false });
183
+ // Cache the backend systems
184
+ utils_1.PromptState.backendSystemsCache = backendSystems;
210
185
  systemChoices = backendSystems.map((system) => {
211
186
  return {
212
- name: getBackendSystemDisplayName(system),
187
+ name: (0, fiori_generator_shared_1.getBackendSystemDisplayName)(system),
213
188
  value: {
214
189
  system,
215
190
  type: 'backendSystem'
@@ -29,7 +29,7 @@ export type EntitySetFilter = 'filterDraftEnabled' | 'filterAggregateTransformat
29
29
  * Returns the entity choice options for use in a list inquirer prompt.
30
30
  *
31
31
  * @param edmx metadata string
32
- * @param options
32
+ * @param options Configuration options for entity filtering and selection
33
33
  * @param options.entitySetFilter
34
34
  * `filterDraftEnabled` : Only draft enabled entities wil be returned when true, useful for Form Object Page app generation.
35
35
  * `filterAggregateTransformationsOnly` : Only return entity choices that have an aggregate annotation (Aggregation.ApplySupported) with the `Transformations` property set,
@@ -63,11 +63,12 @@ export declare function filterDraftEnabledEntities(entitySets: EntitySet[]): Ent
63
63
  * @param templateType the template type of the application to be generated from the prompt answers
64
64
  * @param metadata the metadata (edmx) string of the service
65
65
  * @param odataVersion the OData version of the service
66
+ * @param isCapService whether the service is a CAP service or not
66
67
  * @param mainEntitySetName the name of the main entity set
67
68
  * @param currentTableType the current table type selected by the user
68
69
  * @returns the default table type and a boolean indicating if AnalyticalTable should be set as default
69
70
  */
70
- export declare function getDefaultTableType(templateType: TemplateType, metadata: ConvertedMetadata, odataVersion: OdataVersion, mainEntitySetName?: string, currentTableType?: TableType): {
71
+ export declare function getDefaultTableType(templateType: TemplateType, metadata: ConvertedMetadata, odataVersion: OdataVersion, isCapService: boolean, mainEntitySetName?: string, currentTableType?: TableType): {
71
72
  tableType: TableType;
72
73
  setAnalyticalTableDefault: boolean;
73
74
  };
@@ -57,7 +57,7 @@ function getNavigationPropertyForParameterisedEntity(entitySet, entityTypes) {
57
57
  * Returns the entity choice options for use in a list inquirer prompt.
58
58
  *
59
59
  * @param edmx metadata string
60
- * @param options
60
+ * @param options Configuration options for entity filtering and selection
61
61
  * @param options.entitySetFilter
62
62
  * `filterDraftEnabled` : Only draft enabled entities wil be returned when true, useful for Form Object Page app generation.
63
63
  * `filterAggregateTransformationsOnly` : Only return entity choices that have an aggregate annotation (Aggregation.ApplySupported) with the `Transformations` property set,
@@ -147,7 +147,7 @@ function findEntitySetName(entitySets, entityType) {
147
147
  */
148
148
  function getNavigationEntityChoices(metadata, odataVersion, mainEntityName) {
149
149
  const choices = [];
150
- const mainEntitySet = metadata.entitySets.find((entitySet) => entitySet.name === mainEntityName);
150
+ const mainEntitySet = (0, inquirer_common_1.findEntitySetByName)(metadata, mainEntityName);
151
151
  let navProps = [];
152
152
  if (odataVersion === odata_service_writer_1.OdataVersion.v4) {
153
153
  navProps = mainEntitySet?.entityType.navigationProperties.filter((navProp) => navProp.isCollection) ?? [];
@@ -181,28 +181,80 @@ function filterDraftEnabledEntities(entitySets) {
181
181
  return !!entitySetTypeProperties.find((property) => property.name === 'HasDraftEntity');
182
182
  });
183
183
  }
184
+ /**
185
+ * Determines if AnalyticalTable should be used based on entity annotations and service type.
186
+ *
187
+ * AnalyticalTable is used when entity has hierarchical and analytical data together, for CAP services with analytical data,
188
+ * or for non-CAP services with complete analytical transformations.
189
+ *
190
+ * @param entitySet The entity set to check for annotations.
191
+ * @param isCapService Whether the service is a CAP service (affects analytical requirements).
192
+ * @returns True if AnalyticalTable should be used, false otherwise.
193
+ */
194
+ function shouldUseAnalyticalTable(entitySet, isCapService) {
195
+ // Evaluate annotations once to avoid multiple iterations
196
+ const hasAnalytical = (0, inquirer_common_1.hasAggregateTransformations)(entitySet);
197
+ const hasHierarchy = (0, inquirer_common_1.hasRecursiveHierarchyForEntitySet)(entitySet);
198
+ // No analytical data means no need for AnalyticalTable
199
+ if (!hasAnalytical) {
200
+ return false;
201
+ }
202
+ // If entity has both analytical and hierarchical data, always use AnalyticalTable
203
+ if (hasHierarchy) {
204
+ return true;
205
+ }
206
+ // For CAP services, analytical annotations are sufficient
207
+ if (isCapService) {
208
+ return true;
209
+ }
210
+ // For non-CAP services, require complete analytical transformations
211
+ return (0, inquirer_common_1.hasAggregateTransformationsForEntitySet)(entitySet, inquirer_common_1.transformationsRequiredForAnalyticalTable);
212
+ }
184
213
  /**
185
214
  * Get the default table type based on the template type and previous answers.
186
215
  *
187
216
  * @param templateType the template type of the application to be generated from the prompt answers
188
217
  * @param metadata the metadata (edmx) string of the service
189
218
  * @param odataVersion the OData version of the service
219
+ * @param isCapService whether the service is a CAP service or not
190
220
  * @param mainEntitySetName the name of the main entity set
191
221
  * @param currentTableType the current table type selected by the user
192
222
  * @returns the default table type and a boolean indicating if AnalyticalTable should be set as default
193
223
  */
194
- function getDefaultTableType(templateType, metadata, odataVersion, mainEntitySetName, currentTableType) {
224
+ function getDefaultTableType(templateType, metadata, odataVersion, isCapService, mainEntitySetName, currentTableType) {
195
225
  let tableType;
196
226
  let setAnalyticalTableDefault = false;
197
- if ((templateType === 'lrop' || templateType === 'worklist') &&
198
- odataVersion === odata_service_writer_1.OdataVersion.v4 &&
199
- (0, inquirer_common_1.hasAggregateTransformationsForEntity)(metadata, mainEntitySetName)) {
200
- // For V4, if the selected entity has aggregate transformations, use AnalyticalTable as default
201
- tableType = 'AnalyticalTable';
202
- setAnalyticalTableDefault = true;
227
+ // Find the entity set once for all annotation checks
228
+ const entitySet = mainEntitySetName ? (0, inquirer_common_1.findEntitySetByName)(metadata, mainEntitySetName) : undefined;
229
+ if (entitySet) {
230
+ if ((templateType === 'lrop' || templateType === 'worklist') &&
231
+ odataVersion === odata_service_writer_1.OdataVersion.v4 &&
232
+ shouldUseAnalyticalTable(entitySet, isCapService)) {
233
+ // Use AnalyticalTable for entities with analytical data based on optimized annotation evaluation
234
+ tableType = 'AnalyticalTable';
235
+ setAnalyticalTableDefault = true;
236
+ }
237
+ else if ((templateType === 'lrop' || templateType === 'worklist') &&
238
+ odataVersion === odata_service_writer_1.OdataVersion.v4 &&
239
+ (0, inquirer_common_1.hasRecursiveHierarchyForEntitySet)(entitySet)) {
240
+ // If the main entity type is annotated with Hierarchy.RecursiveHierarchy, use TreeTable as default
241
+ tableType = 'TreeTable';
242
+ }
243
+ else if (templateType === 'alp') {
244
+ // For ALP, use AnalyticalTable as default
245
+ tableType = 'AnalyticalTable';
246
+ }
247
+ else if (currentTableType) {
248
+ // If the user has already selected a table type use it
249
+ tableType = currentTableType;
250
+ }
251
+ else {
252
+ // Default to ResponsiveTable for other cases
253
+ tableType = 'ResponsiveTable';
254
+ }
203
255
  }
204
256
  else if (templateType === 'alp') {
205
- // For ALP, use AnalyticalTable as default
257
+ // For ALP, use AnalyticalTable as default even if entity set is not found
206
258
  tableType = 'AnalyticalTable';
207
259
  }
208
260
  else if (currentTableType) {
@@ -213,7 +213,7 @@ function getTableLayoutQuestions(templateType, odataVersion, isCapService, metad
213
213
  },
214
214
  choices: tableTypeChoices,
215
215
  default: (prevAnswers) => {
216
- const tableTypeDefault = (0, entity_helper_1.getDefaultTableType)(templateType, metadata, odataVersion, prevAnswers?.mainEntity?.entitySetName, prevAnswers?.tableType);
216
+ const tableTypeDefault = (0, entity_helper_1.getDefaultTableType)(templateType, metadata, odataVersion, isCapService, prevAnswers?.mainEntity?.entitySetName, prevAnswers?.tableType);
217
217
  setAnalyticalTableDefault = tableTypeDefault.setAnalyticalTableDefault;
218
218
  return tableTypeDefault.tableType;
219
219
  },
@@ -236,7 +236,14 @@ function getTableLayoutQuestions(templateType, odataVersion, isCapService, metad
236
236
  breadcrumb: true,
237
237
  mandatory: true
238
238
  },
239
- default: '',
239
+ default: (prevAnswers) => {
240
+ // Auto-populate qualifier from RecursiveHierarchy annotation if available
241
+ if (prevAnswers?.mainEntity?.entitySetName) {
242
+ const entitySet = (0, inquirer_common_1.findEntitySetByName)(metadata, prevAnswers.mainEntity.entitySetName);
243
+ return entitySet ? (0, inquirer_common_1.getRecursiveHierarchyQualifierForEntitySet)(entitySet) : '';
244
+ }
245
+ return '';
246
+ },
240
247
  validate: (input) => {
241
248
  if (!input) {
242
249
  return (0, i18n_1.t)('prompts.hierarchyQualifier.qualifierRequiredForV4Warning');
@@ -58,7 +58,9 @@
58
58
  "metadataFilePathNotValid": "The metadata file does not exist or is not accessible. Please specify a valid file path.",
59
59
  "capProjectNotFound": "The folder you have selected does not contain a valid CAP project. Please check and try again.",
60
60
  "warningCertificateValidationDisabled": "Certificate validation has been disabled by the user.",
61
- "annotationsNotFound": "Annotations not found for the specified service."
61
+ "annotationsNotFound": "Annotations not found for the specified service.",
62
+ "backendSystemExistsWarning": "A saved system connection entry: \"{{- backendName}}\" already exists with the same URL and client. Please reuse the existing entry or remove it, using the systems panel, before adding a new connection.",
63
+ "reentranceTicketSystemHostOnly": "ABAP cloud system urls must only contain the hostname, for example, 'https://123.abc.com'"
62
64
  },
63
65
  "warnings": {
64
66
  "nonUIServiceTypeWarningMessage": "Services of service type: {{serviceType}} or without a service type are not intended to be used for generating SAP Fiori UI applications.",
@@ -252,8 +254,6 @@
252
254
  "suggestedSystemNameClient": ", client {{client}}",
253
255
  "seeLogForDetails": "For more information, view the logs.",
254
256
  "forUserName": "(for user [{{username}}])",
255
- "systemTypeBTP": "BTP",
256
- "systemTypeS4HC": "S4HC",
257
257
  "httpStatus": "HTTP Status {{httpStatus}}",
258
258
  "checkDestinationAuthConfig": "Please check the SAP BTP destination authentication configuration.",
259
259
  "choiceNameNone": "None"
package/dist/types.d.ts CHANGED
@@ -172,7 +172,7 @@ export interface EntitySelectionAnswers {
172
172
  * Answers related to the Page Building Block prompt.
173
173
  */
174
174
  export interface PageBuildingBlockAnswers {
175
- /** Indicates if the user wants to add a Page Building Block */
175
+ /** Indicates if a Page Building Block should be addedn*/
176
176
  [EntityPromptNames.addPageBuildingBlock]?: boolean;
177
177
  /** The title for the Page Building Block, required if addPageBuildingBlock is true */
178
178
  [EntityPromptNames.pageBuildingBlockTitle]?: string;
@@ -5,6 +5,7 @@ import { OdataVersion } from '@sap-ux/odata-service-writer';
5
5
  import type { ListChoiceOptions } from 'inquirer';
6
6
  import { PromptState } from './prompt-state';
7
7
  import type { ConvertedMetadata } from '@sap-ux/vocabularies-types';
8
+ import type { BackendSystem } from '@sap-ux/store';
8
9
  /**
9
10
  * Determine if the current prompting environment is cli or a hosted extension (app studio or vscode).
10
11
  *
@@ -63,5 +64,14 @@ export declare function getDefaultChoiceIndex(list: ListChoiceOptions[]): number
63
64
  * @returns the service provider with the circular dependencies removed
64
65
  */
65
66
  export declare function removeCircularFromServiceProvider(serviceProvider: ServiceProvider): ServiceProvider;
67
+ /**
68
+ * Checks if the specified backend systems contain a match for the specified url and client.
69
+ *
70
+ * @param backendSystems backend systems to search for a matching key
71
+ * @param url the url component of the backend system key
72
+ * @param client the client component of of the backend system key
73
+ * @returns the backend system if found or undefined
74
+ */
75
+ export declare function isBackendSystemKeyExisting(backendSystems: BackendSystem[], url: string, client?: string): BackendSystem | undefined;
66
76
  export { PromptState };
67
77
  //# sourceMappingURL=index.d.ts.map
@@ -11,6 +11,7 @@ exports.originToRelative = originToRelative;
11
11
  exports.convertODataVersionType = convertODataVersionType;
12
12
  exports.getDefaultChoiceIndex = getDefaultChoiceIndex;
13
13
  exports.removeCircularFromServiceProvider = removeCircularFromServiceProvider;
14
+ exports.isBackendSystemKeyExisting = isBackendSystemKeyExisting;
14
15
  const axios_extension_1 = require("@sap-ux/axios-extension");
15
16
  const btp_utils_1 = require("@sap-ux/btp-utils");
16
17
  const fiori_generator_shared_1 = require("@sap-ux/fiori-generator-shared");
@@ -23,6 +24,7 @@ Object.defineProperty(exports, "PromptState", { enumerable: true, get: function
23
24
  const annotation_converter_1 = require("@sap-ux/annotation-converter");
24
25
  const edmx_parser_1 = require("@sap-ux/edmx-parser");
25
26
  const circular_reference_remover_1 = require("circular-reference-remover");
27
+ const store_1 = require("@sap-ux/store");
26
28
  /**
27
29
  * Determine if the current prompting environment is cli or a hosted extension (app studio or vscode).
28
30
  *
@@ -146,4 +148,16 @@ function removeCircularFromServiceProvider(serviceProvider) {
146
148
  }
147
149
  return serviceProvider;
148
150
  }
151
+ /**
152
+ * Checks if the specified backend systems contain a match for the specified url and client.
153
+ *
154
+ * @param backendSystems backend systems to search for a matching key
155
+ * @param url the url component of the backend system key
156
+ * @param client the client component of of the backend system key
157
+ * @returns the backend system if found or undefined
158
+ */
159
+ function isBackendSystemKeyExisting(backendSystems, url, client) {
160
+ const newBackendSystemId = new store_1.BackendSystemKey({ url, client }).getId();
161
+ return backendSystems.find((backendSystem) => store_1.BackendSystemKey.from(backendSystem).getId() === newBackendSystemId);
162
+ }
149
163
  //# sourceMappingURL=index.js.map
@@ -1,3 +1,4 @@
1
+ import type { BackendSystem } from '@sap-ux/store';
1
2
  import type { OdataServiceAnswers } from '../types';
2
3
  /**
3
4
  * Much of the values returned by the service inquirer prompting are derived from prompt answers and are not direct answer values.
@@ -8,6 +9,10 @@ import type { OdataServiceAnswers } from '../types';
8
9
  export declare class PromptState {
9
10
  static odataService: Partial<OdataServiceAnswers>;
10
11
  static isYUI: boolean;
12
+ /**
13
+ * To prevent re-reads from store load the backend systems once.
14
+ */
15
+ static backendSystemsCache: BackendSystem[];
11
16
  static reset(): void;
12
17
  static resetConnectedSystem(): void;
13
18
  }
@@ -10,6 +10,10 @@ exports.PromptState = void 0;
10
10
  class PromptState {
11
11
  static odataService = {};
12
12
  static isYUI = false;
13
+ /**
14
+ * To prevent re-reads from store load the backend systems once.
15
+ */
16
+ static backendSystemsCache = [];
13
17
  static reset() {
14
18
  // Reset all values in the odataService object, do not reset the object reference itself as it may be used by external consumers
15
19
  Object.keys(PromptState.odataService).forEach((key) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sap-ux/odata-service-inquirer",
3
3
  "description": "Prompts module that can prompt users for inputs required for odata service writing",
4
- "version": "2.8.12",
4
+ "version": "2.9.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/SAP/open-ux-tools.git",
@@ -29,17 +29,17 @@
29
29
  "i18next": "25.3.0",
30
30
  "inquirer-autocomplete-prompt": "2.0.1",
31
31
  "os-name": "4.0.1",
32
- "@sap-ux/axios-extension": "1.22.10",
32
+ "@sap-ux/axios-extension": "1.23.0",
33
33
  "@sap-ux/btp-utils": "1.1.4",
34
- "@sap-ux/fiori-generator-shared": "0.13.22",
34
+ "@sap-ux/fiori-generator-shared": "0.13.23",
35
35
  "@sap-ux/guided-answers-helper": "0.4.0",
36
- "@sap-ux/telemetry": "0.6.28",
37
- "@sap-ux/inquirer-common": "0.7.50",
36
+ "@sap-ux/telemetry": "0.6.29",
37
+ "@sap-ux/inquirer-common": "0.8.0",
38
38
  "@sap-ux/logger": "0.7.0",
39
39
  "@sap-ux/nodejs-utils": "0.2.7",
40
40
  "@sap-ux/project-access": "1.32.4",
41
41
  "@sap-ux/project-input-validator": "0.6.26",
42
- "@sap-ux/store": "1.1.5"
42
+ "@sap-ux/store": "1.2.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@sap-ux/vocabularies-types": "0.13.0",
@@ -48,12 +48,12 @@
48
48
  "@types/inquirer": "8.2.6",
49
49
  "@types/lodash": "4.14.202",
50
50
  "jest-extended": "6.0.0",
51
- "@sap-ux/fiori-generator-shared": "0.13.22",
52
- "@sap-ux/fiori-elements-writer": "2.7.19",
53
- "@sap-ux/fiori-freestyle-writer": "2.4.50",
54
- "@sap-ux/feature-toggle": "0.3.1",
51
+ "@sap-ux/fiori-generator-shared": "0.13.23",
52
+ "@sap-ux/fiori-elements-writer": "2.7.24",
53
+ "@sap-ux/fiori-freestyle-writer": "2.4.51",
54
+ "@sap-ux/feature-toggle": "0.3.2",
55
55
  "@sap-ux/odata-service-writer": "0.27.25",
56
- "@sap-ux/cap-config-writer": "0.12.13"
56
+ "@sap-ux/cap-config-writer": "0.12.14"
57
57
  },
58
58
  "engines": {
59
59
  "node": ">=20.x"