From 77b0800ad0be9e63f94011476ab5ccf306fde340 Mon Sep 17 00:00:00 2001 From: IainSAP <46536134+IainSAP@users.noreply.github.com> Date: Fri, 17 Jan 2025 13:15:57 +0000 Subject: [PATCH 1/4] feat(`@sap-ux/odata-service-inquirer`): Adds entity related prompting (#2767) * Feature: Adds entity related prompting https://github.com/SAP/open-ux-tools/issues/2766 * Update types used in simple-generator * Revert tsdoc change to avoid version bump * Linting auto fix commit * Add major version type change example * Update clever-eyes-greet.md * Update clever-eyes-greet.md * Update clever-eyes-greet.md * Update README.md * Remove trailing comma * Adds unit tests * Updated lock file * Adds unit tests for table prompts * Add alp specific questions tests * Sonar issue fixes * Sonar complexity fix * Reduce code complexity * Fix lint warnings * Adds tsdocs * Update index.ts --------- Co-authored-by: github-actions[bot] --- .changeset/clever-eyes-greet.md | 36 + examples/simple-generator/src/app/index.ts | 2 +- .../src/data/manifestSettings.ts | 14 +- packages/fiori-elements-writer/src/types.ts | 46 +- packages/fiori-freestyle-writer/src/types.ts | 12 +- packages/inquirer-common/src/types.ts | 1 + packages/odata-service-inquirer/README.md | 5 +- packages/odata-service-inquirer/package.json | 5 + packages/odata-service-inquirer/src/index.ts | 67 +- .../src/prompts/edmx/alp-questions.ts | 149 + .../src/prompts/edmx/entity-helper.ts | 192 + .../src/prompts/edmx/questions.ts | 346 ++ .../src/prompts/prompt-helpers.ts | 2 +- .../odata-service-inquirer.i18n.json | 86 +- packages/odata-service-inquirer/src/types.ts | 78 +- .../odata-service-inquirer/src/utils/index.ts | 29 +- .../unit/__snapshots__/index-api.test.ts.snap | 4 +- .../__snapshots__/prompts.test.ts.snap | 4 +- .../__snapshots__/entity-helper.test.ts.snap | 517 ++ .../edmx/__snapshots__/questions.test.ts.snap | 250 + .../unit/prompts/edmx/alp-questions.test.ts | 138 + .../unit/prompts/edmx/entity-helper.test.ts | 181 + .../test/unit/prompts/edmx/questions.test.ts | 388 ++ .../annotationsWithPresentationQualifier.xml | 2065 ++++++++ .../prompts/edmx/test-data/metadataV2.xml | 1806 +++++++ .../edmx/test-data/metadataV2NoEntities.xml | 15 + .../test-data/metadataV2WithDraftRoot.xml | 1094 +++++ .../metadataV4WithAggregateTransforms.xml | 4355 +++++++++++++++++ ...ataV4WithDraftAnnotationAndShareAction.xml | 2907 +++++++++++ .../test-data/metadataV4WithDraftEntities.xml | 2905 +++++++++++ .../test/unit/prompts/prompts.test.ts | 2 +- .../sap-system/abap-on-btp/questions.test.ts | 2 +- .../unit/prompts/sap-system/questions.test.ts | 2 +- .../service-selection/questions.test.ts | 1 - .../__snapshots__/questions.test.ts.snap | 8 +- .../system-selection/prompt-helpers.test.ts | 1 - packages/odata-service-inquirer/tsconfig.json | 6 + pnpm-lock.yaml | 59 +- 38 files changed, 17664 insertions(+), 116 deletions(-) create mode 100644 .changeset/clever-eyes-greet.md create mode 100644 packages/odata-service-inquirer/src/prompts/edmx/alp-questions.ts create mode 100644 packages/odata-service-inquirer/src/prompts/edmx/entity-helper.ts create mode 100644 packages/odata-service-inquirer/src/prompts/edmx/questions.ts create mode 100644 packages/odata-service-inquirer/test/unit/prompts/edmx/__snapshots__/entity-helper.test.ts.snap create mode 100644 packages/odata-service-inquirer/test/unit/prompts/edmx/__snapshots__/questions.test.ts.snap create mode 100644 packages/odata-service-inquirer/test/unit/prompts/edmx/alp-questions.test.ts create mode 100644 packages/odata-service-inquirer/test/unit/prompts/edmx/entity-helper.test.ts create mode 100644 packages/odata-service-inquirer/test/unit/prompts/edmx/questions.test.ts create mode 100644 packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/annotationsWithPresentationQualifier.xml create mode 100644 packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/metadataV2.xml create mode 100755 packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/metadataV2NoEntities.xml create mode 100644 packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/metadataV2WithDraftRoot.xml create mode 100644 packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/metadataV4WithAggregateTransforms.xml create mode 100644 packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/metadataV4WithDraftAnnotationAndShareAction.xml create mode 100644 packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/metadataV4WithDraftEntities.xml diff --git a/.changeset/clever-eyes-greet.md b/.changeset/clever-eyes-greet.md new file mode 100644 index 0000000000..ac10f061db --- /dev/null +++ b/.changeset/clever-eyes-greet.md @@ -0,0 +1,36 @@ +--- +'@sap-ux/fiori-freestyle-writer': major +'@sap-ux/odata-service-inquirer': major +'@sap-ux/fiori-elements-writer': major +'@sap-ux/generator-simple-fe': patch +--- + +Adds support for entity related prompting. Update some exported types `fiori-elements-writer`, `fiori-freestyle-writer` to remove problematic enum usage. +The major version updates for modules `@sap-ux/fiori-elements-writer` and `@sap-ux/fiori-freestyle-writer` indicates a breaking change of type definitions: + +`@sap-ux/fiori-elements-writer`: + `TemplateType` + `TableType` + `TableSelectionMode` + + `@sap-ux/fiori-freestyle-writer`: + `TemplateType` + +These changes are required to reduce the impact of importing these types by consumers. Previously defined as enums, requiring full dependencies and bloating +consumer code where only these types are required. The new type defintions still allow both uses (type or value) but the `enums` are now defined as `const`. +This change requires updates to consuming code where the type is imported and referenced. +Example where a single enum was used as a value type: + +``` +type Template = { + template: TemplateType.ListReportObjectPage +} +``` + +should now be defined as: + +``` +type Template = { + template: typeof TemplateType.ListReportObjectPage +} +``` diff --git a/examples/simple-generator/src/app/index.ts b/examples/simple-generator/src/app/index.ts index cb34dbd90b..4e12f50195 100644 --- a/examples/simple-generator/src/app/index.ts +++ b/examples/simple-generator/src/app/index.ts @@ -20,7 +20,7 @@ export default class extends Generator { private app!: Ui5App & { app: { flpAppId: string } }; private service!: OdataService; private template: { - type: TemplateType | FreestyleTemplateType.Basic; + type: TemplateType | typeof FreestyleTemplateType.Basic; settings?: LROPSettings | OVPSettings | {}; }; diff --git a/packages/fiori-elements-writer/src/data/manifestSettings.ts b/packages/fiori-elements-writer/src/data/manifestSettings.ts index 46187b4522..59c9101009 100644 --- a/packages/fiori-elements-writer/src/data/manifestSettings.ts +++ b/packages/fiori-elements-writer/src/data/manifestSettings.ts @@ -28,12 +28,14 @@ export function extendManifestJson( // FEOP and ALP v4 are variants of LROP and so we use the same template and settings if ( feApp.service.version === OdataVersion.v4 && - [ - TemplateType.FormEntryObjectPage, - TemplateType.AnalyticalListPage, - TemplateType.ListReportObjectPage, - TemplateType.Worklist - ].includes(feApp.template.type) + ( + [ + TemplateType.FormEntryObjectPage, + TemplateType.AnalyticalListPage, + TemplateType.ListReportObjectPage, + TemplateType.Worklist + ] as TemplateType[] + ).includes(feApp.template.type) ) { templatePath = TemplateType.ListReportObjectPage; diff --git a/packages/fiori-elements-writer/src/types.ts b/packages/fiori-elements-writer/src/types.ts index 453a20847b..c01a7d4348 100644 --- a/packages/fiori-elements-writer/src/types.ts +++ b/packages/fiori-elements-writer/src/types.ts @@ -1,14 +1,16 @@ import type { Ui5App, App, AppOptions } from '@sap-ux/ui5-application-writer'; import type { OdataService } from '@sap-ux/odata-service-writer'; -export enum TemplateType { - Worklist = 'worklist', - ListReportObjectPage = 'lrop', - AnalyticalListPage = 'alp', - OverviewPage = 'ovp', - FormEntryObjectPage = 'feop', - FlexibleProgrammingModel = 'fpm' -} +export const TemplateType = { + Worklist: 'worklist', + ListReportObjectPage: 'lrop', + AnalyticalListPage: 'alp', + OverviewPage: 'ovp', + FormEntryObjectPage: 'feop', + FlexibleProgrammingModel: 'fpm' +} as const; + +export type TemplateType = (typeof TemplateType)[keyof typeof TemplateType]; /** * General validation error thrown if app config options contain invalid combinations @@ -33,19 +35,23 @@ export interface EntityConfig { }; } -export enum TableType { - GRID = 'GridTable', - ANALYTICAL = 'AnalyticalTable', - RESPONSIVE = 'ResponsiveTable', - TREE = 'TreeTable' -} +export const TableType = { + GRID: 'GridTable', + ANALYTICAL: 'AnalyticalTable', + RESPONSIVE: 'ResponsiveTable', + TREE: 'TreeTable' +} as const; + +export type TableType = (typeof TableType)[keyof typeof TableType]; + +export const TableSelectionMode = { + NONE: 'None', + AUTO: 'Auto', + MULTI: 'Multi', + SINGLE: 'Single' +} as const; +export type TableSelectionMode = (typeof TableSelectionMode)[keyof typeof TableSelectionMode]; -export enum TableSelectionMode { - NONE = 'None', - AUTO = 'Auto', - MULTI = 'Multi', - SINGLE = 'Single' -} export interface TableSettings { tableType?: TableType; qualifier?: string; diff --git a/packages/fiori-freestyle-writer/src/types.ts b/packages/fiori-freestyle-writer/src/types.ts index e9d06e670d..a1b80d8f49 100644 --- a/packages/fiori-freestyle-writer/src/types.ts +++ b/packages/fiori-freestyle-writer/src/types.ts @@ -1,11 +1,13 @@ import type { Ui5App, App } from '@sap-ux/ui5-application-writer'; import type { OdataService } from '@sap-ux/odata-service-writer'; -export enum TemplateType { - Basic = 'basic', - Worklist = 'worklist', - ListDetail = 'listdetail' -} +export const TemplateType = { + Basic: 'basic', + Worklist: 'worklist', + ListDetail: 'listdetail' +} as const; + +export type TemplateType = (typeof TemplateType)[keyof typeof TemplateType]; interface Entity { name: string; diff --git a/packages/inquirer-common/src/types.ts b/packages/inquirer-common/src/types.ts index b353b1eaf1..ed680798a7 100644 --- a/packages/inquirer-common/src/types.ts +++ b/packages/inquirer-common/src/types.ts @@ -99,6 +99,7 @@ export interface ListQuestion extends BaseListQuest export interface ConfirmQuestion extends BaseConfirmQuestion { name: YUIQuestion['name']; guiOptions?: YUIQuestion['guiOptions']; + additionalMessages?: PromptSeverityMessage; } export interface EditorQuestion extends BaseEditorQuestion { diff --git a/packages/odata-service-inquirer/README.md b/packages/odata-service-inquirer/README.md index b4da1933f6..3ca3deab07 100644 --- a/packages/odata-service-inquirer/README.md +++ b/packages/odata-service-inquirer/README.md @@ -1,9 +1,6 @@ # @sap-ux/odata-service-inquirer -Provides Inquirer based end-user prompting to allow selection of a service from multiple data source types. This involves acquiring a connection to backend systems and retrieving edmx metadata for services provided by the catalog, from a local file or CAP project. - -**Note:** -Current implementation is limited to metadata file and Local Cap projects as datasources only. +Provides Inquirer based end-user prompting to allow selection of a service from multiple data source types. This involves acquiring a connection to backend systems and retrieving edmx metadata for services provided by the catalog, from a local file or CAP project. This module also provides prompts that may be used to gather user selections to define the main and navigation entities and related prompts relating to application layout and annotation generation when creating a UI5 application using the `@sap-ux/fiori-freestyle-writer` and `@sap-ux/fiori-elements-writer` modules. ## Installation Npm diff --git a/packages/odata-service-inquirer/package.json b/packages/odata-service-inquirer/package.json index 61520c7295..26b8cdeb59 100644 --- a/packages/odata-service-inquirer/package.json +++ b/packages/odata-service-inquirer/package.json @@ -31,8 +31,10 @@ ], "dependencies": { "@sap/cf-tools": "3.2.2", + "@sap-ux/annotation-converter": "0.9.10", "@sap-ux/axios-extension": "workspace:*", "@sap-ux/btp-utils": "workspace:*", + "@sap-ux/edmx-parser": "0.9.0", "@sap-ux/fiori-generator-shared": "workspace:*", "@sap-ux/guided-answers-helper": "workspace:*", "@sap-ux/telemetry": "workspace:*", @@ -50,8 +52,11 @@ }, "devDependencies": { "@sap-ux/fiori-generator-shared": "workspace:*", + "@sap-ux/fiori-elements-writer": "workspace:*", + "@sap-ux/fiori-freestyle-writer": "workspace:*", "@sap-ux/feature-toggle": "workspace:*", "@sap-ux/odata-service-writer": "workspace:*", + "@sap-ux/vocabularies-types": "0.11.7", "@sap-devx/yeoman-ui-types": "1.14.4", "@types/inquirer-autocomplete-prompt": "2.0.1", "@types/inquirer": "8.2.6", diff --git a/packages/odata-service-inquirer/src/index.ts b/packages/odata-service-inquirer/src/index.ts index 757bc0f181..18868707c0 100644 --- a/packages/odata-service-inquirer/src/index.ts +++ b/packages/odata-service-inquirer/src/index.ts @@ -1,33 +1,37 @@ -import { type InquirerAdapter } from '@sap-ux/inquirer-common'; -import type { Question } from 'inquirer'; +import { type InquirerAdapter, ERROR_TYPE, ErrorHandler, setTelemetryClient } from '@sap-ux/inquirer-common'; import { type Logger } from '@sap-ux/logger'; import { OdataVersion } from '@sap-ux/odata-service-writer'; import { type ToolsSuiteTelemetryClient } from '@sap-ux/telemetry'; +import type { Question } from 'inquirer'; import autocomplete from 'inquirer-autocomplete-prompt'; -import { ERROR_TYPE, ErrorHandler, setTelemetryClient } from '@sap-ux/inquirer-common'; import { initI18nOdataServiceInquirer } from './i18n'; import { getQuestions } from './prompts'; +import type { ServiceAnswer } from './prompts/datasources/sap-system/service-selection'; import { type SystemSelectionAnswers, - SystemSelectionAnswerType, - getSystemSelectionQuestions as getSystemSelectionQuestionsBase + getSystemSelectionQuestions as getSystemSelectionQuestionsBase, + SystemSelectionAnswerType } from './prompts/datasources/sap-system/system-selection'; -import type { ServiceAnswer } from './prompts/datasources/sap-system/service-selection'; import type { - NewSystemChoice, - CfAbapEnvServiceChoice + CfAbapEnvServiceChoice, + NewSystemChoice } from './prompts/datasources/sap-system/system-selection/prompt-helpers'; +import type { Annotations } from '@sap-ux/axios-extension'; +import type { TemplateType } from '@sap-ux/fiori-elements-writer'; +import { getEntitySelectionQuestions } from './prompts/edmx/questions'; import LoggerHelper from './prompts/logger-helper'; import { - DatasourceType, - promptNames, type CapRuntime, type CapService, type OdataServiceAnswers, type OdataServicePromptOptions, type OdataServiceQuestion, - type SapSystemType + type SapSystemType, + type EntityPromptOptions, + DatasourceType, + EntityRelatedAnswers, + promptNames } from './types'; import { getPromptHostEnvironment, PromptState } from './utils'; @@ -84,6 +88,35 @@ async function getSystemSelectionQuestions( }; } +/** + * Get the questions that may be used to prompt for entity selection, table configuration, annotation generation, and ALP specific table configuration. + * Since these are releated to service metadata processing and entity selection, they are grouped together. + * + * @param metadata the metadata (edmx) string from which to extract entity choices + * @param templateType the template type which will define the type of prompts and their choices + * @param isCapService if true, the service is a CAP service, some prompts will be adjusted accordingly + * @param promptOptions options that can control some of the prompt behavior. See {@link EntityPromptOptions} for details + * @param annotations annotations to be used for entity selection, only used for analytical list page presentation variant qualifier choices when the edmx odata version is `2` + * @param logger a logger compatible with the {@link Logger} interface + * @param isYUI if true, the prompt is being called from the Yeoman UI extension host + * @returns the prompts which may be used to prompt for entity selection, table configuration, annotation generation, and ALP specific table configuration + */ +function getEntityRelatedPrompts( + metadata: string, + templateType: TemplateType, + isCapService = false, + promptOptions?: EntityPromptOptions, + annotations?: Annotations, + logger?: Logger, + isYUI = false +): Question[] { + if (logger) { + LoggerHelper.logger = logger; + } + PromptState.isYUI = isYUI; + return getEntitySelectionQuestions(metadata, templateType, isCapService, promptOptions, annotations); +} + /** * Prompt for odata service writer inputs. * @@ -116,13 +149,18 @@ async function prompt( } export { - // @derecated - temp export to support to support open source migration + CfAbapEnvServiceChoice, + // @deprecated - temp export to support to support open source migration DatasourceType, + EntityRelatedAnswers, // @deprecated - temp export to support to support open source migration ERROR_TYPE, // @deprecated - temp export to support to support open source migration ErrorHandler, + getEntityRelatedPrompts, getPrompts, + getSystemSelectionQuestions, + NewSystemChoice, // @deprecated - temp export to support to support open source migration OdataVersion, prompt, @@ -135,8 +173,5 @@ export { type OdataServiceAnswers, type OdataServicePromptOptions, // @deprecated - temp export to support to support open source migration - type SapSystemType, - NewSystemChoice, - CfAbapEnvServiceChoice, - getSystemSelectionQuestions + type SapSystemType }; diff --git a/packages/odata-service-inquirer/src/prompts/edmx/alp-questions.ts b/packages/odata-service-inquirer/src/prompts/edmx/alp-questions.ts new file mode 100644 index 0000000000..d9922ef5c2 --- /dev/null +++ b/packages/odata-service-inquirer/src/prompts/edmx/alp-questions.ts @@ -0,0 +1,149 @@ +import type { Annotations } from '@sap-ux/axios-extension'; +import type { TableSelectionMode } from '@sap-ux/fiori-elements-writer'; +import type { ConfirmQuestion, ListQuestion } from '@sap-ux/inquirer-common'; +import { OdataVersion } from '@sap-ux/odata-service-writer'; +import type { ChoiceOptions, Question } from 'inquirer'; +import { t } from '../../i18n'; +import type { AlpTableConfigAnswers, EntitySelectionAnswers } from '../../types'; +import { EntityPromptNames } from '../../types'; +import { xmlToJson } from '../../utils'; + +/** + * Return the annotation `UI.selectionPresentationVariant.qualifier` properties as prompt choices for the specified annotations and entityType. + * + * @param annotations the annotations in which to search for the annotation: `UI.selectionPresentationVariant.qualifier` + * @param entityType the entityType of the annotations target in which to search for the annotation: `UI.selectionPresentationVariant.qualifier` + * @returns the matching `UI.selectionPresentationVariant` qualifers as prompt choices + */ +function getQualifierChoices(annotations: Annotations, entityType: string): ChoiceOptions[] { + const qualifierChoices: ChoiceOptions[] = [{ name: t('texts.choiceNameNone'), value: undefined }]; + + const parsedDefinitions: any = xmlToJson(annotations?.Definitions) || {}; + const parsedAnnotations = parsedDefinitions.Edmx.DataServices.Schema?.Annotations; + + let filteredAnnotations = []; + if (Array.isArray(parsedAnnotations)) { + filteredAnnotations = parsedAnnotations; + } else if (typeof parsedAnnotations === 'object') { + filteredAnnotations.push(parsedAnnotations); + } + filteredAnnotations = filteredAnnotations.filter((a) => { + return a.Target === entityType; + }); + + if (filteredAnnotations.length > 0) { + const selectionPresentationTerm = 'UI.SelectionPresentationVariant'; + let filterQualifiers = filteredAnnotations[0]?.Annotation; + if (Array.isArray(filterQualifiers)) { + filterQualifiers + .filter((a) => a.Term === selectionPresentationTerm) + .forEach((a) => { + if (a.Qualifier) { + qualifierChoices.push({ name: a.Qualifier, value: a.Qualifier }); + } + }); + } else if (filterQualifiers.Term === selectionPresentationTerm) { + filterQualifiers = []; + qualifierChoices.push({ name: filterQualifiers.Qualifier, value: filterQualifiers.Qualifier }); + } + } + return qualifierChoices; +} + +/** + * Get questions that related to generation of Analytical List Page type applications. + * + * @param odataVersion odata version '2' or '4' will the table layout prompts to be shown + * @param annotations used to determine if the select presentation qualifier prompt should be shown + * @param hideTableLayoutPrompts hide the table layout prompts, certain consumers do not need these prompts + * @returns alp specific questions + */ +export function getAnalyticListPageQuestions( + odataVersion: OdataVersion, + annotations?: Annotations, + hideTableLayoutPrompts = false +): Question[] { + const alpQuestions: Question[] = []; + + if (annotations && odataVersion === OdataVersion.v2) { + const qualifierChoices: ChoiceOptions[] = []; + + alpQuestions.push({ + when: (answers: EntitySelectionAnswers) => { + if (answers.mainEntity) { + qualifierChoices.push(...getQualifierChoices(annotations, answers.mainEntity?.entitySetType)); + } + return qualifierChoices.length > 1; + }, + type: 'list', + name: EntityPromptNames.presentationQualifier, + message: t('prompts.presentationQualifier.message'), + guiOptions: { + hint: t('prompts.presentationQualifier.hint'), + breadcrumb: true + }, + choices: () => qualifierChoices, + default: 0 + } as ListQuestion); + } + + // Layout prompts + if (!hideTableLayoutPrompts) { + // v4 specific options + if (odataVersion === OdataVersion.v4) { + alpQuestions.push({ + when: (prevAnswers: EntitySelectionAnswers) => { + return !!prevAnswers.mainEntity; + }, + type: 'list', + name: EntityPromptNames.tableSelectionMode, + message: t('prompts.tableSelectionMode.message'), + guiOptions: { + hint: t('prompts.tableSelectionMode.hint'), + breadcrumb: true + }, + choices: (): { name: string; value: TableSelectionMode }[] => [ + { name: t('prompts.tableSelectionMode.choiceNone'), value: 'None' }, + { name: t('prompts.tableSelectionMode.choiceAuto'), value: 'Auto' }, + { name: t('prompts.tableSelectionMode.choiceMulti'), value: 'Multi' }, + { name: t('prompts.tableSelectionMode.choiceSingle'), value: 'Single' } + ] + } as ListQuestion); + } else { + // v2 specific options + alpQuestions.push( + { + type: 'confirm', + name: EntityPromptNames.tableMultiSelect, + message: t('prompts.tableMultiSelect.message'), + guiOptions: { + hint: t('prompts.tableMultiSelect.hint'), + breadcrumb: true + }, + default: false + } as ConfirmQuestion, + { + type: 'confirm', + name: EntityPromptNames.tableAutoHide, + message: t('prompts.tableAutoHide.message'), + guiOptions: { + hint: t('prompts.tableAutoHide.hint'), + breadcrumb: true + }, + default: true + } as ConfirmQuestion, + { + type: 'confirm', + name: EntityPromptNames.smartVariantManagement, + message: t('prompts.smartVariantManagement.message'), + guiOptions: { + hint: t('prompts.smartVariantManagement.hint'), + breadcrumb: true + }, + default: false + } as ConfirmQuestion + ); + } + } + return alpQuestions; +} diff --git a/packages/odata-service-inquirer/src/prompts/edmx/entity-helper.ts b/packages/odata-service-inquirer/src/prompts/edmx/entity-helper.ts new file mode 100644 index 0000000000..5d64b5d81e --- /dev/null +++ b/packages/odata-service-inquirer/src/prompts/edmx/entity-helper.ts @@ -0,0 +1,192 @@ +import { convert } from '@sap-ux/annotation-converter'; +import { parse } from '@sap-ux/edmx-parser'; +import { OdataVersion } from '@sap-ux/odata-service-writer'; +import type { ConvertedMetadata, EntitySet, NavigationProperty } from '@sap-ux/vocabularies-types'; +import type { ListChoiceOptions } from 'inquirer'; +import { t } from '../../i18n'; +import LoggerHelper from '../logger-helper'; + +export type EntityAnswer = { + entitySetName: string; + entitySetType: string; +}; + +export type NavigationEntityAnswer = { + navigationPropertyName: string; + entitySetName: string; +}; + +export interface EntityChoiceOptions { + choices: ListChoiceOptions[]; + draftRootIndex?: number; + defaultMainEntityIndex?: number; + convertedMetadata?: ConvertedMetadata; + odataVersion?: OdataVersion; +} + +export type EntitySetFilter = 'filterDraftEnabled' | 'filterAggregateTransformationsOnly'; +/** + * Returns the entity choice options for use in a list inquirer prompt. + * + * @param edmx metadata string + * @param options + * @param options.useEntityTypeAsName Choice options will use the non-namepspaced entity set type as the choice name (label) and value property `entitySetName` when true, otherwise the entity set name will be used. + * @param options.entitySetFilter + * `filterDraftEnabled` : Only draft enabled entities wil be returned when true, useful for Form Object Page app generation. + * `filterAggregateTransformationsOnly` : Only return entity choices that have an aggregate annotation (Aggregation.ApplySupported) with the `Transformations` property set, + * specifically used for ALP V4 app generation. If this option is set and the specified metadata is not V4, the option will be ignored. + * @param options.defaultMainEntityName The default selected entity set name + * @returns entity options + */ +export function getEntityChoices( + edmx: string, + { + useEntityTypeAsName = false, + entitySetFilter, + defaultMainEntityName + }: { + useEntityTypeAsName?: boolean; + entitySetFilter?: EntitySetFilter; + defaultMainEntityName?: string; + } = {} +): EntityChoiceOptions { + const choices: ListChoiceOptions[] = []; + let draftRootIndex: number | undefined; + let defaultMainEntityIndex: number | undefined; + let convertedMetadata: ConvertedMetadata | undefined; + let odataVersion: OdataVersion | undefined; + try { + convertedMetadata = convert(parse(edmx)); + const parsedOdataVersion = parseInt(convertedMetadata?.version, 10); + + if (Number.isNaN(parsedOdataVersion)) { + LoggerHelper.logger.error(t('errors.unparseableOdataVersion')); + throw new Error(t('errors.unparseableOdataVersion')); + } + // Note that odata version > `4` e.g. `4.1`, is not currently supported by `@sap-ux/edmx-converter` + odataVersion = parsedOdataVersion === 4 ? OdataVersion.v4 : OdataVersion.v2; + + let entitySets: EntitySet[] = []; + + if (entitySetFilter === 'filterDraftEnabled') { + entitySets = filterDraftEnabledEntities(convertedMetadata.entitySets) ?? []; + } else if (entitySetFilter === 'filterAggregateTransformationsOnly' && odataVersion === OdataVersion.v4) { + // Only for v4 odata version, if a v2 metadata is passed, this will be ignored + entitySets = filterAggregateTransformations(convertedMetadata.entitySets); + } else { + entitySets = convertedMetadata.entitySets; + } + + entitySets.forEach((entitySet, index) => { + // Determine whether to use the entity set type name or the entity set name as the choice name. + // Note that in the case of the entity type name, the namespace will be removed. + const entitySetChoiceName = useEntityTypeAsName + ? entitySet.entityTypeName.substring(entitySet.entityTypeName.lastIndexOf('.') + 1) + : entitySet.name; + const choice: ListChoiceOptions = { + name: entitySetChoiceName, + value: { + entitySetName: entitySetChoiceName, + entitySetType: entitySet.entityTypeName // Fully qualified entity type name + } + }; + choices.push(choice); + // Select the first found draft root index + if (!draftRootIndex && entitySet.annotations?.Common?.DraftRoot) { + draftRootIndex = index; + } + + if (defaultMainEntityName && entitySet.name === defaultMainEntityName) { + defaultMainEntityIndex = index; + } + }); + } catch (err) { + LoggerHelper.logger.log(t('errors.unparseableMetadata', { error: err.message })); + } + + return { + choices, + draftRootIndex, + defaultMainEntityIndex, + convertedMetadata, + odataVersion + }; +} + +/** + * Get the entity set name from its type name. + * + * @param entitySets the entity sets to search + * @param entityType the entity type name to search for + * @returns the entity set name if found, otherwise undefined + */ +function findEntitySetName(entitySets: EntitySet[], entityType: string): string | undefined { + const foundEntitySet = entitySets.find((entitySet) => { + return entitySet.entityTypeName === entityType; + }); + return foundEntitySet ? foundEntitySet.name : undefined; +} + +/** + * Get the navigation entity choices for a main entity. + * + * @param metadata the converted metadata (edmx) + * @param odataVersion the odata version, which will determine the navigation properties to return + * @param mainEntityName the main entity name to get the navigation properties for + * @returns the navigation entity choices + */ +export function getNavigationEntityChoices( + metadata: ConvertedMetadata, + odataVersion: OdataVersion, + mainEntityName: string +): ListChoiceOptions[] { + const choices: ListChoiceOptions[] = []; + const mainEntitySet = metadata.entitySets.find((entitySet) => entitySet.name === mainEntityName); + + let navProps: NavigationProperty[] = []; + if (odataVersion === OdataVersion.v4) { + navProps = mainEntitySet?.entityType.navigationProperties.filter((navProp) => navProp.isCollection) ?? []; + } else { + navProps = mainEntitySet?.entityType.navigationProperties ?? []; + } + + navProps.forEach((navProp) => { + choices.push({ + name: navProp.name, + value: { + navigationPropertyName: navProp.name, + entitySetName: findEntitySetName(metadata.entitySets, navProp.targetTypeName) + } as NavigationEntityAnswer + }); + }); + + if (choices.length > 0) { + choices.unshift({ name: t('prompts.navigationEntitySelection.choiceNone'), value: {} }); + } + return choices; +} + +/** + * Returns only entity sets that have the `Aggregation.ApplySupported` annotation term with the `Transformations` property. + * + * @param entitySets the entity sets to filter + * @returns the filtered entity sets + */ +function filterAggregateTransformations(entitySets: EntitySet[]): EntitySet[] { + return entitySets.filter((entitySet) => { + return !!entitySet.annotations?.Aggregation?.ApplySupported?.Transformations; + }); +} + +/** + * Returns only entities that have a type property of 'HasDraftEnabled'. + * + * @param entitySets the entity sets to filter by draft enabled entities + * @returns the filtered entity sets + */ +function filterDraftEnabledEntities(entitySets: EntitySet[]): EntitySet[] | undefined { + return entitySets.filter((entitySet) => { + const entitySetTypeProperties = entitySet.entityType.entityProperties; + return !!entitySetTypeProperties.find((property) => property.name === 'HasDraftEntity'); + }); +} diff --git a/packages/odata-service-inquirer/src/prompts/edmx/questions.ts b/packages/odata-service-inquirer/src/prompts/edmx/questions.ts new file mode 100644 index 0000000000..6d9524149e --- /dev/null +++ b/packages/odata-service-inquirer/src/prompts/edmx/questions.ts @@ -0,0 +1,346 @@ +import { Severity } from '@sap-devx/yeoman-ui-types'; +import type { Annotations } from '@sap-ux/axios-extension'; +import type { TableType, TemplateType } from '@sap-ux/fiori-elements-writer'; +import type { ConfirmQuestion, InputQuestion, ListQuestion } from '@sap-ux/inquirer-common'; +import { searchChoices } from '@sap-ux/inquirer-common'; +import { OdataVersion } from '@sap-ux/odata-service-writer'; +import type { ListChoiceOptions, Question } from 'inquirer'; +import { t } from '../../i18n'; +import type { + AlpTableConfigAnswers, + AnnotationGenerationAnswers, + EntityPromptOptions, + EntitySelectionAnswers, + TableConfigAnswers +} from '../../types'; +import { EntityPromptNames, MetadataSizeWarningLimitKb } from '../../types'; +import { PromptState } from '../../utils'; +import LoggerHelper from '../logger-helper'; +import { getAnalyticListPageQuestions } from './alp-questions'; +import { + type EntityAnswer, + type EntityChoiceOptions, + type EntitySetFilter, + getEntityChoices, + getNavigationEntityChoices, + type NavigationEntityAnswer +} from './entity-helper'; + +/** + * Validate the entity choice options. If the entity choice options are empty, a validation message will be returned. + * + * @param entityChoiceOptions the entity choice options to validate + * @param templateType the template type, the validation message may vary based on the template type + * @param odataVersion the OData version, used to generate a specific validation message for ALP V4 + * @param isCapService whether the service is a CAP service or not + * @returns true is validation passes, otherwise a string with the validation message + */ +function validateEntityChoices( + entityChoiceOptions: ListChoiceOptions[], + templateType: TemplateType, + odataVersion: OdataVersion, + isCapService: boolean +): string | boolean { + let validationMsg; + if (entityChoiceOptions.length === 0) { + if (templateType === 'feop' && isCapService) { + validationMsg = t('prompts.mainEntitySelection.noDraftEnabledEntitiesError'); + } else if (templateType === 'alp' && odataVersion === OdataVersion.v4) { + validationMsg = t('prompts.mainEntitySelection.noEntitiesAlpV4Error'); + } else { + validationMsg = t('prompts.mainEntitySelection.noEntitiesError'); + } + } + + if (!PromptState.isYUI && validationMsg) { + LoggerHelper.logger.debug(`Exiting due to validation error: ${validationMsg}`); + throw new Error(t('errors.exitingGeneration', { exitReason: validationMsg })); + } + return validationMsg ?? true; +} + +/** + * Get the questions that may be used to prompt for entity selection and related information, table types, layout options and annotation generation. + * + * @param metadata the metadata (edmx) string of the service + * @param templateType the template type of the application to be generated from the prompt answers + * @param isCapService whether the service is a CAP service or not + * @param promptOptions options that can control some of the prompt behavior. See {@link EntityPromptOptions} for details + * @param annotations annotations should be provided when the specified template type is analytic list page, and the metadata odata version is '2', in order to determine the presentation variant qualifiers. + * If none are provided, or the odata version is not '2', the presentation variant qualifier prompt will not be shown. + * @returns the prompts used to provide input for system selection and a reference to the answers object which will be populated with the user's responses once `inquirer.prompt` returns + */ +export function getEntitySelectionQuestions( + metadata: string, + templateType: TemplateType, + isCapService = false, + promptOptions?: EntityPromptOptions, + annotations?: Annotations +): Question[] { + const useAutoComplete = promptOptions?.useAutoComplete; + let entitySetFilter: EntitySetFilter | undefined; + if (templateType === 'feop' && !!isCapService) { + entitySetFilter = 'filterDraftEnabled'; + } else if (templateType === 'alp') { + entitySetFilter = 'filterAggregateTransformationsOnly'; + } + + const entityChoices = getEntityChoices(metadata, { + useEntityTypeAsName: templateType === 'ovp', + defaultMainEntityName: promptOptions?.defaultMainEntityName, + entitySetFilter + }); + + if (!entityChoices.convertedMetadata || !entityChoices.odataVersion) { + // getEntityChoiceOptions will log an error if the metadata or its odata version are unparseable + return []; + } + const odataVersion = entityChoices.odataVersion; + + const entityQuestions: Question< + EntitySelectionAnswers & TableConfigAnswers & AnnotationGenerationAnswers & AlpTableConfigAnswers + >[] = []; + + // OVP only has filter entity, does not use tables and we do not add annotations + if (templateType === 'ovp') { + entityQuestions.push(getFilterEntityTypeQuestions(entityChoices, useAutoComplete)); + // Return early since OVP does not have table layout prompts + return entityQuestions; + } + + entityQuestions.push({ + type: useAutoComplete ? 'autocomplete' : 'list', + name: EntityPromptNames.mainEntity, + message: t('prompts.mainEntitySelection.message'), + guiOptions: { + breadcrumb: true + }, + choices: entityChoices.choices, + source: (prevAnswers: unknown, input: string) => + searchChoices(input, entityChoices.choices as ListChoiceOptions[]), + default: entityChoices.defaultMainEntityIndex ?? entityChoices.draftRootIndex ?? 0, + validate: () => validateEntityChoices(entityChoices.choices, templateType, odataVersion, isCapService), + additionalMessages: () => { + if (promptOptions?.defaultMainEntityName && entityChoices.defaultMainEntityIndex === undefined) { + return { + message: t('prompts.mainEntitySelection.defaultEntityNameNotFoundWarning'), + severity: Severity.warning + }; + } + } + } as ListQuestion); + + const convertedMetadata = entityChoices.convertedMetadata; + // No nav entity for FPM + if (templateType !== 'fpm') { + let navigationEntityChoices: ListChoiceOptions[]; + entityQuestions.push({ + when: (answers: EntitySelectionAnswers) => { + if (answers.mainEntity) { + navigationEntityChoices = getNavigationEntityChoices( + convertedMetadata, + odataVersion, + answers.mainEntity.entitySetName + ); + return navigationEntityChoices.length > 0; + } + return false; + }, + type: useAutoComplete ? 'autocomplete' : 'list', + name: EntityPromptNames.navigationEntity, + message: t('prompts.navigationEntitySelection.message'), + guiOptions: { + applyDefaultWhenDirty: true, // Selected nav entity may no longer be present if main entity changes + breadcrumb: true + }, + choices: () => navigationEntityChoices, + source: (preAnswers: EntitySelectionAnswers, input: string) => + searchChoices(input, navigationEntityChoices as ListChoiceOptions[]), + default: 0 + } as ListQuestion); + } + + entityQuestions.push(...getAddAnnotationQuestions(metadata, templateType, odataVersion, isCapService)); + + if (!promptOptions?.hideTableLayoutPrompts) { + entityQuestions.push(...getTableLayoutQuestions(templateType, odataVersion, isCapService)); + } + + if (templateType === 'alp') { + entityQuestions.push( + ...getAnalyticListPageQuestions(odataVersion, annotations, promptOptions?.hideTableLayoutPrompts) + ); + } + return entityQuestions; +} + +/** + * Get the questions that may be used to prompt for table layout options. + * + * @param templateType used to determine if the tree table option should be included + * @param odataVersion used to determine if the hierarchy qualifier is required when the selected table type is TreeTable + * @param isCapService used to determine if the tree table option should be included + * @returns the table layout questions + */ +function getTableLayoutQuestions( + templateType: TemplateType, + odataVersion: OdataVersion, + isCapService: boolean +): Question[] { + const tableTypeChoices: { name: string; value: TableType }[] = [ + { name: t('prompts.tableType.choiceAnalytical'), value: 'AnalyticalTable' }, + { name: t('prompts.tableType.choiceGrid'), value: 'GridTable' }, + { name: t('prompts.tableType.choiceResponsive'), value: 'ResponsiveTable' } + ]; + + if (templateType !== 'alp' && !isCapService) { + tableTypeChoices.push({ name: t('prompts.tableType.choiceTree'), value: 'TreeTable' }); + } + const tableLayoutQuestions: Question[] = []; + + if (templateType === 'lrop' || templateType === 'worklist' || templateType === 'alp') { + const tableTypeDefault: TableType = templateType === 'alp' ? 'AnalyticalTable' : 'ResponsiveTable'; + tableLayoutQuestions.push({ + when: (prevAnswers: EntitySelectionAnswers) => !!prevAnswers.mainEntity, + type: 'list', + name: EntityPromptNames.tableType, + message: t('prompts.tableType.message'), + guiOptions: { + hint: t('prompts.tableType.hint'), + breadcrumb: true + }, + choices: tableTypeChoices, + default: tableTypeDefault + } as ListQuestion); + + tableLayoutQuestions.push({ + when: (prevAnswers: TableConfigAnswers) => + prevAnswers?.tableType === 'TreeTable' && odataVersion === OdataVersion.v4, + type: 'input', + name: EntityPromptNames.hierarchyQualifier, + message: t('prompts.hierarchyQualifier.message'), + guiOptions: { + hint: t('prompts.hierarchyQualifier.hint'), + breadcrumb: true, + mandatory: true + }, + default: '', + validate: (input: string) => { + if (!input) { + return t('prompts.hierarchyQualifier.qualifierRequiredForV4Warning'); + } + return true; + } + } as InputQuestion); + } + return tableLayoutQuestions; +} + +/** + * Returns the size of an EDMX string in kilobytes. + * + * @param {string} edmx The EDMX string to measure. + * @returns {number} The size of the EDMX string in kilobytes. Returns 0 if the input is null, undefined, or an empty string. + */ +function getEdmxSizeInKb(edmx: string): number { + if (edmx) { + const sizeInBytes = Buffer.byteLength(edmx); + return sizeInBytes / 1024; + } + return 0; +} + +/** + * Get the questions that may be used to prompt for adding annotations. Only a subset of the questions will be returned based on the template type and OData version. + * + * @param metadata the metadata (edmx) string of the service, used to determine if the metadata is large and the user should be warned about processing time + * @param templateType only specific template types will have line item annotations + * @param odataVersion only specific OData versions will have line item annotations + * @param isCapService whether the service is a CAP service or not + * @returns the annotation generation questions + */ +function getAddAnnotationQuestions( + metadata: string, + templateType: TemplateType, + odataVersion: OdataVersion, + isCapService: boolean +): ConfirmQuestion[] { + const largeEdmxDataset = getEdmxSizeInKb(metadata) > MetadataSizeWarningLimitKb; + const annotationQuestions: ConfirmQuestion[] = []; + + if (templateType === 'feop') { + annotationQuestions.push({ + type: 'confirm', + name: EntityPromptNames.addFEOPAnnotations, + guiOptions: { + breadcrumb: t('prompts.addFEOPAnnotations.breadcrumb') + }, + message: t('prompts.addFEOPAnnotations.message'), + additionalMessages: (addFEOPAnnotations: boolean) => { + if (addFEOPAnnotations && largeEdmxDataset) { + return { + message: t('warnings.largeMetadataDocument'), + severity: Severity.warning + }; + } + }, + default: !largeEdmxDataset + } as ConfirmQuestion); + // Return early since FEOP does not have line item annotations + return annotationQuestions; + } + + if ((templateType === 'lrop' || templateType === 'worklist') && odataVersion === OdataVersion.v4) { + annotationQuestions.push({ + type: 'confirm', + name: EntityPromptNames.addLineItemAnnotations, + guiOptions: { + breadcrumb: t('prompts.addLineItemAnnotations.breadcrumb') + }, + message: t('prompts.addLineItemAnnotations.message'), + additionalMessages: (answer: boolean) => { + if (answer) { + if (largeEdmxDataset) { + return { + message: t('warnings.largeMetadataDocument'), + severity: Severity.warning + }; + } else if (isCapService) { + return { + message: t('prompts.addLineItemAnnotations.valueHelpsAnnotationsInfoMessage'), + severity: Severity.information + }; + } + } + }, + default: !largeEdmxDataset + } as ConfirmQuestion); + } + return annotationQuestions; +} + +/** + * Get the questions that may be used to prompt for filter entity selection for the OVP template type. + * + * @param entityChoices Filter entity type prompt choices + * @param useAutoComplete Determines if entity related prompts should use auto complete on user input + * @returns the ovp specific filter entity type selection question + */ +function getFilterEntityTypeQuestions( + entityChoices: EntityChoiceOptions, + useAutoComplete = false +): Question { + return { + type: useAutoComplete ? 'autocomplete' : 'list', + name: EntityPromptNames.filterEntityType, + message: t('prompts.filterEntityType.message'), + guiOptions: { + breadcrumb: true + }, + choices: entityChoices.choices, + source: (preAnswers: EntitySelectionAnswers, input: string) => + searchChoices(input, entityChoices.choices as ListChoiceOptions[]), + default: entityChoices.defaultMainEntityIndex ?? entityChoices.draftRootIndex ?? 0, + validate: () => (entityChoices.choices.length === 0 ? t('prompts.filterEntityType.noEntitiesError') : true) + } as ListQuestion; +} diff --git a/packages/odata-service-inquirer/src/prompts/prompt-helpers.ts b/packages/odata-service-inquirer/src/prompts/prompt-helpers.ts index 35fbe09cc9..a186219de7 100644 --- a/packages/odata-service-inquirer/src/prompts/prompt-helpers.ts +++ b/packages/odata-service-inquirer/src/prompts/prompt-helpers.ts @@ -31,7 +31,7 @@ export function getDatasourceTypeChoices({ choices.push({ name: t('prompts.datasourceType.metadataFileChoiceText'), value: DatasourceType.metadataFile }); if (includeNone) { - choices.unshift({ name: t('prompts.datasourceType.noneName'), value: DatasourceType.none }); + choices.unshift({ name: t('prompts.datasourceType.choiceNone'), value: DatasourceType.none }); } return choices; diff --git a/packages/odata-service-inquirer/src/translations/odata-service-inquirer.i18n.json b/packages/odata-service-inquirer/src/translations/odata-service-inquirer.i18n.json index f259fa4189..687aee78cb 100644 --- a/packages/odata-service-inquirer/src/translations/odata-service-inquirer.i18n.json +++ b/packages/odata-service-inquirer/src/translations/odata-service-inquirer.i18n.json @@ -8,7 +8,7 @@ "odataServiceUrlName": "OData Service", "capProjectName": "Local CAP Project", "metadataFileName": "Metadata Document", - "noneName": "None", + "choiceNone": "$t(texts.choiceNameNone)", "projectSpecificDestChoiceText": "Connect to a $t(prompts.datasourceType.projectSpecificDestName)", "businessHubChoiceText": "Connect to $t(prompts.datasourceType.businessHubName)", "sapSystemChoiceText": "Connect to a $t(prompts.datasourceType.sapSystemName)", @@ -101,7 +101,7 @@ }, "systemSelection": { "newSystemChoiceLabel": "New system", - "hint": "Select a system configuration", + "hint": "Select a system configuration.", "message": "System", "authenticationFailedUpdateCredentials": "Authentication failed. Please try updating the credentials." }, @@ -113,18 +113,82 @@ }, "serviceKey": { "message": "Service key file path", - "hint": "Select a local file that defines the service connection for an ABAP Environment on SAP Business Technology Platform", + "hint": "Select a local file that defines the service connection for an ABAP Environment on SAP Business Technology Platform.", "incompleteServiceKeyInfo": "Service keys file does not contain the required information", "unparseableServiceKey": "Service keys file contents are not a valid JSON format" }, "cloudFoundryAbapSystem": { "message": "ABAP environment", - "hint": "Enter the name of the Cloud Foundry service that contains the ABAP Environment instance" + "hint": "Enter the name of the Cloud Foundry service that contains the ABAP Environment instance." }, "destinationServicePath": { "message": "Service path", - "hint": "Enter the path to the OData service, relative to the selected destination URL", + "hint": "Enter the path to the OData service, relative to the selected destination URL.", "invalidServicePathWarning": "Please enter a valid service path" + }, + "mainEntitySelection": { + "message": "Main entity", + "defaultEntityNameNotFoundWarning": "The supplied entity cannot be found in the service. Please choose from the list above.", + "noDraftEnabledEntitiesError": "The CAP service you have chosen does not have any entities that are draft enabled. In order to generate a V4 Fiori application using this floor plan for a CAP project, the entity selected must be draft enabled.", + "noEntitiesError": "$t(errors.noRelevantEntities)", + "noEntitiesAlpV4Error": "The OData V4 service you have provided is not suitable for use in an Analytical List Page application. The service must contain aggregate based entity sets for this template." + }, + "navigationEntitySelection": { + "message": "Navigation entity", + "choiceNone": "$t(texts.choiceNameNone)" + }, + "filterEntityType": { + "message": "Filter entity", + "noEntitiesError": "$t(errors.noRelevantEntities)" + }, + "tableType": { + "message": "Table type", + "hint": "Defines the table type for the List Report Page.", + "choiceNone": "$t(texts.choiceNameNone)", + "choiceGrid": "Grid", + "choiceAnalytical": "Analytical", + "choiceResponsive": "Responsive", + "choiceTree": "Tree" + }, + "hierarchyQualifier": { + "message": "Hierarchy qualifier", + "hint": "Leading property that decides between either a recursive hierarchy or data aggregation.", + "qualifierRequiredForV4Warning": "Hierarchy qualifier is required for V4 OData services" + }, + "addFEOPAnnotations": { + "message": "Automatically add a form to the generated application if none already exists?", + "hint": "Choose the annotation generation method.", + "breadcrumb": "Generate Annotations" + }, + "addLineItemAnnotations": { + "message": "Automatically add table columns to the list page and a section to the object page if none already exists?", + "hint": "Choose the annotation generation method.", + "breadcrumb": "Generate Annotations", + "valueHelpsAnnotationsInfoMessage": "Basic value helps will also be created." + }, + "presentationQualifier": { + "message": "Qualifier", + "hint": "Represents the qualifier of the SelectionPresentationVariant. Analytical List Page looks for SelectionPresentationVariant with this qualifier and if not found, it looks for PresentationVariant with this qualifier." + }, + "tableSelectionMode": { + "message": "Selection mode", + "hint": "Defines different selection modes for the SmartTable in Analytical List Page.", + "choiceNone":"$t(texts.choiceNameNone)", + "choiceSingle": "Single", + "choiceAuto": "Auto", + "choiceMulti": "Multi" + }, + "tableMultiSelect": { + "message": "Allow multi select", + "hint": "Allows you to show a checkbox for selecting multiple items in a table. This setting comes into effect only if there are actions defined either through annotation or manifest." + }, + "tableAutoHide":{ + "message": "Auto hide", + "hint": "Determines chart/table interaction. If set to true, the chart acts as a filter for the table. If set to false, the matching table rows are highlighted but the table is not filtered." + }, + "smartVariantManagement": { + "message": "Enable smart variant management", + "hint": "Enables page level variant." } }, "errors": { @@ -153,7 +217,14 @@ "serviceCatalogRequest": "An error occurred requesting services from: {{- catalogRequestUri }} and entity set: {{entitySet}}. {{error}}", "storedSystemConnectionError": "An error occurred while validating the stored system connection info. System name: {{-systemName}}, error: {{- error}}", "noCatalogOrServiceAvailable": "No active system or OData service endpoint connection available to retrieve service(s).", - "allCatalogServiceRequestsFailed": "All catalog service requests failed for the selected system. OData version(s): V{{version}}." + "allCatalogServiceRequestsFailed": "All catalog service requests failed for the selected system. OData version(s): V{{version}}.", + "unparseableMetadata": "Unable to parse entities from metadata document. {{-error}}", + "unparseableOdataVersion": "Unable to parse the odata version from the metadata.", + "unparseableXML": "Unparseable XML was specified: {{-error}}", + "noRelevantEntities": "The template and service selected have no relevant entities that you can use." + }, + "warnings": { + "largeMetadataDocument": "The metadata for this OData service is significantly large. It may take some time before this operation completes." }, "texts": { "suggestedSystemNameClient": ", client {{client}}", @@ -162,6 +233,7 @@ "systemTypeBTP": "BTP", "systemTypeS4HC": "S4HC", "httpStatus": "http status {{httpStatus}}", - "checkDestinationAuthConfig": "Please check the SAP BTP destination authentication configuration." + "checkDestinationAuthConfig": "Please check the SAP BTP destination authentication configuration.", + "choiceNameNone": "None" } } \ No newline at end of file diff --git a/packages/odata-service-inquirer/src/types.ts b/packages/odata-service-inquirer/src/types.ts index 37c7a332c1..3d92dbc808 100644 --- a/packages/odata-service-inquirer/src/types.ts +++ b/packages/odata-service-inquirer/src/types.ts @@ -5,6 +5,8 @@ import type { OdataVersion } from '@sap-ux/odata-service-writer'; import type { CdsVersionInfo } from '@sap-ux/project-access'; import type { BackendSystem } from '@sap-ux/store'; import type { ListChoiceOptions } from 'inquirer'; +import type { EntityAnswer, NavigationEntityAnswer } from './prompts/edmx/entity-helper'; +import type { TableSelectionMode, TableType } from '@sap-ux/fiori-elements-writer'; /** * This file contains types that are exported by the module and are needed for consumers using the APIs `prompt` and `getPrompts`. @@ -28,6 +30,12 @@ export const SapSystemTypes = { export type SapSystemType = keyof typeof SapSystemTypes; +export const SAP_CLIENT_KEY = 'sap-client'; +/** + * The limit for the metadata file size in KB above which a warning will be displayed to the user regarding processing time. + */ +export const MetadataSizeWarningLimitKb = 1000; + /** * Answers returned by the OdataServiceInquirer prompt API. * These values may be used to write an OData service and may be derived from the user's input rather than direct answers. @@ -143,6 +151,57 @@ export enum promptNames { systemSelection = 'systemSelection' } +/** + * Prompt names for entity related prompts. These indirectly define the properties of the answers object returned by the entity related prompts. + */ +export const EntityPromptNames = { + mainEntity: 'mainEntity', + navigationEntity: 'navigationEntity', + filterEntityType: 'filterEntityType', + tableType: 'tableType', + hierarchyQualifier: 'hierarchyQualifier', + addFEOPAnnotations: 'addFEOPAnnotations', + addLineItemAnnotations: 'addLineItemAnnotations', + presentationQualifier: 'presentationQualifier', + tableSelectionMode: 'tableSelectionMode', + tableMultiSelect: 'tableMultiSelect', + tableAutoHide: 'tableAutoHide', + smartVariantManagement: 'smartVariantManagement' +} as const; +export type EntityPromptNames = (typeof EntityPromptNames)[keyof typeof EntityPromptNames]; + +export interface EntitySelectionAnswers { + [EntityPromptNames.mainEntity]?: EntityAnswer; + [EntityPromptNames.navigationEntity]?: NavigationEntityAnswer; + [EntityPromptNames.filterEntityType]?: EntityAnswer; +} + +export interface TableConfigAnswers { + [EntityPromptNames.tableType]: TableType; + [EntityPromptNames.hierarchyQualifier]: string; +} + +export interface AnnotationGenerationAnswers { + [EntityPromptNames.addFEOPAnnotations]?: boolean; + [EntityPromptNames.addLineItemAnnotations]?: boolean; +} + +export interface AlpTableConfigAnswers { + [EntityPromptNames.tableAutoHide]: boolean; + [EntityPromptNames.tableMultiSelect]: boolean; + [EntityPromptNames.tableSelectionMode]: TableSelectionMode; + [EntityPromptNames.presentationQualifier]: string; + [EntityPromptNames.smartVariantManagement]: boolean; +} + +/** + * Convienience alias type for the entity related answers + */ +export type EntityRelatedAnswers = EntitySelectionAnswers & + TableConfigAnswers & + AnnotationGenerationAnswers & + AlpTableConfigAnswers; + export type CapRuntime = 'Node.js' | 'Java'; export interface CapService { @@ -328,4 +387,21 @@ export type OdataServiceQuestion = YUIQuestion; export type OdataServicePromptOptions = Partial; -export const SAP_CLIENT_KEY = 'sap-client'; +/** + * The entity related prompt options. These options are used to configure the entity related prompts. + */ +export type EntityPromptOptions = { + /** + * Determines if entity related prompts should use auto complete on user input. + * Note that the auto-complete module must be registered with the inquirer instance to use this feature. + */ + useAutoComplete?: boolean; + /** + * Provide an entity name that will be preselected as the default option for the prompt. + */ + defaultMainEntityName?: string; + /** + * Hides the table layout related prompts when true, default is false. + */ + hideTableLayoutPrompts?: boolean; +}; diff --git a/packages/odata-service-inquirer/src/utils/index.ts b/packages/odata-service-inquirer/src/utils/index.ts index ad5570bbf1..3eabb319c4 100644 --- a/packages/odata-service-inquirer/src/utils/index.ts +++ b/packages/odata-service-inquirer/src/utils/index.ts @@ -1,12 +1,12 @@ import { ODataVersion } from '@sap-ux/axios-extension'; import { isAppStudio } from '@sap-ux/btp-utils'; +import { hostEnvironment, type HostEnvironmentId } from '@sap-ux/fiori-generator-shared'; import { OdataVersion } from '@sap-ux/odata-service-writer'; import { XMLParser } from 'fast-xml-parser'; import type { ListChoiceOptions } from 'inquirer'; import { t } from '../i18n'; import LoggerHelper from '../prompts/logger-helper'; import { PromptState } from './prompt-state'; -import { hostEnvironment, type HostEnvironmentId } from '@sap-ux/fiori-generator-shared'; /** * Determine if the current prompting environment is cli or a hosted extension (app studio or vscode). @@ -28,6 +28,23 @@ export function getPromptHostEnvironment(): { name: string; technical: HostEnvir * @returns the odata version of the specified metadata, throws an error if the metadata is invalid */ export function parseOdataVersion(metadata: string): OdataVersion { + try { + const parsed = xmlToJson(metadata); + const odataVersion: OdataVersion = parsed['Edmx']['Version'] === 1 ? OdataVersion.v2 : OdataVersion.v4; + return odataVersion; + } catch (error) { + LoggerHelper.logger.error(error); + throw new Error(t('prompts.validationMessages.metadataInvalid')); + } +} + +/** + * Convert specified xml string to JSON. + * + * @param xml - the schema to parse + * @returns parsed object representation of passed XML + */ +export function xmlToJson(xml: string): any { const options = { attributeNamePrefix: '', ignoreAttributes: false, @@ -35,14 +52,12 @@ export function parseOdataVersion(metadata: string): OdataVersion { parseAttributeValue: true, removeNSPrefix: true }; - const parser: XMLParser = new XMLParser(options); + try { - const parsed = parser.parse(metadata, true); - const odataVersion: OdataVersion = parsed['Edmx']['Version'] === 1 ? OdataVersion.v2 : OdataVersion.v4; - return odataVersion; + const parser = new XMLParser(options); + return parser.parse(xml, true); } catch (error) { - LoggerHelper.logger.error(error); - throw new Error(t('prompts.validationMessages.metadataInvalid')); + throw new Error(t('error.unparseableXML', { error })); } } diff --git a/packages/odata-service-inquirer/test/unit/__snapshots__/index-api.test.ts.snap b/packages/odata-service-inquirer/test/unit/__snapshots__/index-api.test.ts.snap index 95ad489287..5c4b60d19b 100644 --- a/packages/odata-service-inquirer/test/unit/__snapshots__/index-api.test.ts.snap +++ b/packages/odata-service-inquirer/test/unit/__snapshots__/index-api.test.ts.snap @@ -63,7 +63,7 @@ exports[`API tests getPrompts, i18n is loaded 1`] = ` "default": -1, "guiOptions": { "breadcrumb": true, - "hint": "Select a system configuration", + "hint": "Select a system configuration.", }, "message": "System", "name": "systemSelection", @@ -241,7 +241,7 @@ exports[`API tests getPrompts, i18n is loaded 1`] = ` }, { "guiOptions": { - "hint": "Select a local file that defines the service connection for an ABAP Environment on SAP Business Technology Platform", + "hint": "Select a local file that defines the service connection for an ABAP Environment on SAP Business Technology Platform.", "mandatory": true, }, "guiType": "file-browser", diff --git a/packages/odata-service-inquirer/test/unit/prompts/__snapshots__/prompts.test.ts.snap b/packages/odata-service-inquirer/test/unit/prompts/__snapshots__/prompts.test.ts.snap index b141de3907..57a2a24227 100644 --- a/packages/odata-service-inquirer/test/unit/prompts/__snapshots__/prompts.test.ts.snap +++ b/packages/odata-service-inquirer/test/unit/prompts/__snapshots__/prompts.test.ts.snap @@ -63,7 +63,7 @@ exports[`getQuestions getQuestions 1`] = ` "default": -1, "guiOptions": { "breadcrumb": true, - "hint": "Select a system configuration", + "hint": "Select a system configuration.", }, "message": "System", "name": "systemSelection", @@ -253,7 +253,7 @@ exports[`getQuestions getQuestions 1`] = ` }, { "guiOptions": { - "hint": "Select a local file that defines the service connection for an ABAP Environment on SAP Business Technology Platform", + "hint": "Select a local file that defines the service connection for an ABAP Environment on SAP Business Technology Platform.", "mandatory": true, }, "guiType": "file-browser", diff --git a/packages/odata-service-inquirer/test/unit/prompts/edmx/__snapshots__/entity-helper.test.ts.snap b/packages/odata-service-inquirer/test/unit/prompts/edmx/__snapshots__/entity-helper.test.ts.snap new file mode 100644 index 0000000000..20e7e0deb1 --- /dev/null +++ b/packages/odata-service-inquirer/test/unit/prompts/edmx/__snapshots__/entity-helper.test.ts.snap @@ -0,0 +1,517 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Test entity helper functions Test filter entities with aggregate transformation annotation should filter ALP v4 entities 1`] = ` +[ + { + "name": "I_Currency", + "value": { + "entitySetName": "I_Currency", + "entitySetType": "SEPMRA_PROD_MAN.I_CurrencyType", + }, + }, + { + "name": "I_DraftAdministrativeData", + "value": { + "entitySetName": "I_DraftAdministrativeData", + "entitySetType": "SEPMRA_PROD_MAN.I_DraftAdministrativeDataType", + }, + }, + { + "name": "I_Language", + "value": { + "entitySetName": "I_Language", + "entitySetType": "SEPMRA_PROD_MAN.I_LanguageType", + }, + }, + { + "name": "SEPMRA_C_CurrencyValueHelp", + "value": { + "entitySetName": "SEPMRA_C_CurrencyValueHelp", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_CurrencyValueHelpType", + }, + }, + { + "name": "SEPMRA_C_PD_ContactPerson", + "value": { + "entitySetName": "SEPMRA_C_PD_ContactPerson", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ContactPersonType", + }, + }, + { + "name": "SEPMRA_C_PD_PoItmCube", + "value": { + "entitySetName": "SEPMRA_C_PD_PoItmCube", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_PoItmCubeType", + }, + }, + { + "name": "SEPMRA_C_PD_Product", + "value": { + "entitySetName": "SEPMRA_C_PD_Product", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ProductType", + }, + }, + { + "name": "SEPMRA_C_PD_ProductImage", + "value": { + "entitySetName": "SEPMRA_C_PD_ProductImage", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ProductImageType", + }, + }, + { + "name": "SEPMRA_C_PD_ProductPriceRange", + "value": { + "entitySetName": "SEPMRA_C_PD_ProductPriceRange", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ProductPriceRangeType", + }, + }, + { + "name": "SEPMRA_C_PD_ProductSalesData", + "value": { + "entitySetName": "SEPMRA_C_PD_ProductSalesData", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ProductSalesDataType", + }, + }, + { + "name": "SEPMRA_C_PD_ProductStock", + "value": { + "entitySetName": "SEPMRA_C_PD_ProductStock", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ProductStockType", + }, + }, + { + "name": "SEPMRA_C_PD_ProductText", + "value": { + "entitySetName": "SEPMRA_C_PD_ProductText", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ProductTextType", + }, + }, + { + "name": "SEPMRA_C_PD_Review", + "value": { + "entitySetName": "SEPMRA_C_PD_Review", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ReviewType", + }, + }, + { + "name": "SEPMRA_C_PD_ReviewPost", + "value": { + "entitySetName": "SEPMRA_C_PD_ReviewPost", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ReviewPostType", + }, + }, + { + "name": "SEPMRA_C_PD_StorageBinTP", + "value": { + "entitySetName": "SEPMRA_C_PD_StorageBinTP", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_StorageBinTPType", + }, + }, + { + "name": "SEPMRA_C_PD_Supplier", + "value": { + "entitySetName": "SEPMRA_C_PD_Supplier", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_SupplierType", + }, + }, + { + "name": "SEPMRA_I_Address", + "value": { + "entitySetName": "SEPMRA_I_Address", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_AddressType", + }, + }, + { + "name": "SEPMRA_I_DimensionUnit", + "value": { + "entitySetName": "SEPMRA_I_DimensionUnit", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_DimensionUnitType", + }, + }, + { + "name": "SEPMRA_I_OrganizationalUnit", + "value": { + "entitySetName": "SEPMRA_I_OrganizationalUnit", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_OrganizationalUnitType", + }, + }, + { + "name": "SEPMRA_I_PriceClassification", + "value": { + "entitySetName": "SEPMRA_I_PriceClassification", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_PriceClassificationType", + }, + }, + { + "name": "SEPMRA_I_ProductCategory", + "value": { + "entitySetName": "SEPMRA_I_ProductCategory", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_ProductCategoryType", + }, + }, + { + "name": "SEPMRA_I_ProductMainCategory", + "value": { + "entitySetName": "SEPMRA_I_ProductMainCategory", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_ProductMainCategoryType", + }, + }, + { + "name": "SEPMRA_I_QuantityUnit", + "value": { + "entitySetName": "SEPMRA_I_QuantityUnit", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_QuantityUnitType", + }, + }, + { + "name": "SEPMRA_I_StockAvailability", + "value": { + "entitySetName": "SEPMRA_I_StockAvailability", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_StockAvailabilityType", + }, + }, + { + "name": "SEPMRA_I_WeightUnit", + "value": { + "entitySetName": "SEPMRA_I_WeightUnit", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_WeightUnitType", + }, + }, +] +`; + +exports[`Test entity helper functions Test getEntityChoices should use entity type name as choice name 1`] = ` +[ + { + "name": "I_CurrencyType", + "value": { + "entitySetName": "I_CurrencyType", + "entitySetType": "SEPMRA_PROD_MAN.I_CurrencyType", + }, + }, + { + "name": "I_DraftAdministrativeDataType", + "value": { + "entitySetName": "I_DraftAdministrativeDataType", + "entitySetType": "SEPMRA_PROD_MAN.I_DraftAdministrativeDataType", + }, + }, + { + "name": "I_LanguageType", + "value": { + "entitySetName": "I_LanguageType", + "entitySetType": "SEPMRA_PROD_MAN.I_LanguageType", + }, + }, + { + "name": "SEPMRA_C_CurrencyValueHelpType", + "value": { + "entitySetName": "SEPMRA_C_CurrencyValueHelpType", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_CurrencyValueHelpType", + }, + }, + { + "name": "SEPMRA_C_PD_ContactPersonType", + "value": { + "entitySetName": "SEPMRA_C_PD_ContactPersonType", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ContactPersonType", + }, + }, + { + "name": "SEPMRA_C_PD_PoItmCubeType", + "value": { + "entitySetName": "SEPMRA_C_PD_PoItmCubeType", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_PoItmCubeType", + }, + }, + { + "name": "SEPMRA_C_PD_ProductType", + "value": { + "entitySetName": "SEPMRA_C_PD_ProductType", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ProductType", + }, + }, + { + "name": "SEPMRA_C_PD_ProductImageType", + "value": { + "entitySetName": "SEPMRA_C_PD_ProductImageType", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ProductImageType", + }, + }, + { + "name": "SEPMRA_C_PD_ProductPriceRangeType", + "value": { + "entitySetName": "SEPMRA_C_PD_ProductPriceRangeType", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ProductPriceRangeType", + }, + }, + { + "name": "SEPMRA_C_PD_ProductSalesDataType", + "value": { + "entitySetName": "SEPMRA_C_PD_ProductSalesDataType", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ProductSalesDataType", + }, + }, + { + "name": "SEPMRA_C_PD_ProductStockType", + "value": { + "entitySetName": "SEPMRA_C_PD_ProductStockType", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ProductStockType", + }, + }, + { + "name": "SEPMRA_C_PD_ProductTextType", + "value": { + "entitySetName": "SEPMRA_C_PD_ProductTextType", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ProductTextType", + }, + }, + { + "name": "SEPMRA_C_PD_ReviewType", + "value": { + "entitySetName": "SEPMRA_C_PD_ReviewType", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ReviewType", + }, + }, + { + "name": "SEPMRA_C_PD_ReviewPostType", + "value": { + "entitySetName": "SEPMRA_C_PD_ReviewPostType", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ReviewPostType", + }, + }, + { + "name": "SEPMRA_C_PD_StorageBinTPType", + "value": { + "entitySetName": "SEPMRA_C_PD_StorageBinTPType", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_StorageBinTPType", + }, + }, + { + "name": "SEPMRA_C_PD_SupplierType", + "value": { + "entitySetName": "SEPMRA_C_PD_SupplierType", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_SupplierType", + }, + }, + { + "name": "SEPMRA_I_AddressType", + "value": { + "entitySetName": "SEPMRA_I_AddressType", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_AddressType", + }, + }, + { + "name": "SEPMRA_I_DimensionUnitType", + "value": { + "entitySetName": "SEPMRA_I_DimensionUnitType", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_DimensionUnitType", + }, + }, + { + "name": "SEPMRA_I_OrganizationalUnitType", + "value": { + "entitySetName": "SEPMRA_I_OrganizationalUnitType", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_OrganizationalUnitType", + }, + }, + { + "name": "SEPMRA_I_PriceClassificationType", + "value": { + "entitySetName": "SEPMRA_I_PriceClassificationType", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_PriceClassificationType", + }, + }, + { + "name": "SEPMRA_I_ProductCategoryType", + "value": { + "entitySetName": "SEPMRA_I_ProductCategoryType", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_ProductCategoryType", + }, + }, + { + "name": "SEPMRA_I_ProductMainCategoryType", + "value": { + "entitySetName": "SEPMRA_I_ProductMainCategoryType", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_ProductMainCategoryType", + }, + }, + { + "name": "SEPMRA_I_QuantityUnitType", + "value": { + "entitySetName": "SEPMRA_I_QuantityUnitType", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_QuantityUnitType", + }, + }, + { + "name": "SEPMRA_I_StockAvailabilityType", + "value": { + "entitySetName": "SEPMRA_I_StockAvailabilityType", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_StockAvailabilityType", + }, + }, + { + "name": "SEPMRA_I_WeightUnitType", + "value": { + "entitySetName": "SEPMRA_I_WeightUnitType", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_WeightUnitType", + }, + }, +] +`; + +exports[`Test entity helper functions Test getNavigationEntityOptions should return v2 navigation entities 1`] = ` +[ + { + "name": "None", + "value": {}, + }, + { + "name": "DraftAdministrativeData", + "value": { + "entitySetName": "I_DraftAdministrativeData", + "navigationPropertyName": "DraftAdministrativeData", + }, + }, + { + "name": "SiblingEntity", + "value": { + "entitySetName": "SEPMRA_C_PD_Product", + "navigationPropertyName": "SiblingEntity", + }, + }, + { + "name": "to_CollaborativeReview", + "value": { + "entitySetName": "SEPMRA_C_PD_Review", + "navigationPropertyName": "to_CollaborativeReview", + }, + }, + { + "name": "to_CollaborativeReviewPost", + "value": { + "entitySetName": "SEPMRA_C_PD_ReviewPost", + "navigationPropertyName": "to_CollaborativeReviewPost", + }, + }, + { + "name": "to_Currency", + "value": { + "entitySetName": "I_Currency", + "navigationPropertyName": "to_Currency", + }, + }, + { + "name": "to_CurrencyVH", + "value": { + "entitySetName": "SEPMRA_C_CurrencyValueHelp", + "navigationPropertyName": "to_CurrencyVH", + }, + }, + { + "name": "to_DimensionUnit", + "value": { + "entitySetName": "SEPMRA_I_DimensionUnit", + "navigationPropertyName": "to_DimensionUnit", + }, + }, + { + "name": "to_MainProductCategory", + "value": { + "entitySetName": "SEPMRA_I_ProductMainCategory", + "navigationPropertyName": "to_MainProductCategory", + }, + }, + { + "name": "to_OriginalLanguage", + "value": { + "entitySetName": "I_Language", + "navigationPropertyName": "to_OriginalLanguage", + }, + }, + { + "name": "to_PriceRange", + "value": { + "entitySetName": "SEPMRA_C_PD_ProductPriceRange", + "navigationPropertyName": "to_PriceRange", + }, + }, + { + "name": "to_ProductBaseUnit", + "value": { + "entitySetName": "SEPMRA_I_QuantityUnit", + "navigationPropertyName": "to_ProductBaseUnit", + }, + }, + { + "name": "to_ProductCategory", + "value": { + "entitySetName": "SEPMRA_I_ProductCategory", + "navigationPropertyName": "to_ProductCategory", + }, + }, + { + "name": "to_ProductCategoryVH", + "value": { + "entitySetName": "SEPMRA_I_ProductCategory", + "navigationPropertyName": "to_ProductCategoryVH", + }, + }, + { + "name": "to_ProductImage", + "value": { + "entitySetName": "SEPMRA_C_PD_ProductImage", + "navigationPropertyName": "to_ProductImage", + }, + }, + { + "name": "to_ProductSalesData", + "value": { + "entitySetName": "SEPMRA_C_PD_ProductSalesData", + "navigationPropertyName": "to_ProductSalesData", + }, + }, + { + "name": "to_ProductStock", + "value": { + "entitySetName": "SEPMRA_C_PD_ProductStock", + "navigationPropertyName": "to_ProductStock", + }, + }, + { + "name": "to_ProductText", + "value": { + "entitySetName": "SEPMRA_C_PD_ProductText", + "navigationPropertyName": "to_ProductText", + }, + }, + { + "name": "to_ProductTextInOriginalLang", + "value": { + "entitySetName": "SEPMRA_C_PD_ProductText", + "navigationPropertyName": "to_ProductTextInOriginalLang", + }, + }, + { + "name": "to_StorageBin", + "value": { + "entitySetName": "SEPMRA_C_PD_StorageBinTP", + "navigationPropertyName": "to_StorageBin", + }, + }, + { + "name": "to_Supplier", + "value": { + "entitySetName": "SEPMRA_C_PD_Supplier", + "navigationPropertyName": "to_Supplier", + }, + }, + { + "name": "to_WeightUnit", + "value": { + "entitySetName": "SEPMRA_I_WeightUnit", + "navigationPropertyName": "to_WeightUnit", + }, + }, +] +`; diff --git a/packages/odata-service-inquirer/test/unit/prompts/edmx/__snapshots__/questions.test.ts.snap b/packages/odata-service-inquirer/test/unit/prompts/edmx/__snapshots__/questions.test.ts.snap new file mode 100644 index 0000000000..b9fe65753a --- /dev/null +++ b/packages/odata-service-inquirer/test/unit/prompts/edmx/__snapshots__/questions.test.ts.snap @@ -0,0 +1,250 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Test entity prompts getEntityQuestions should return the questions when no options specified 1`] = ` +[ + { + "additionalMessages": [Function], + "choices": [ + { + "name": "I_Currency", + "value": { + "entitySetName": "I_Currency", + "entitySetType": "SEPMRA_PROD_MAN.I_CurrencyType", + }, + }, + { + "name": "I_DraftAdministrativeData", + "value": { + "entitySetName": "I_DraftAdministrativeData", + "entitySetType": "SEPMRA_PROD_MAN.I_DraftAdministrativeDataType", + }, + }, + { + "name": "I_Language", + "value": { + "entitySetName": "I_Language", + "entitySetType": "SEPMRA_PROD_MAN.I_LanguageType", + }, + }, + { + "name": "SEPMRA_C_CurrencyValueHelp", + "value": { + "entitySetName": "SEPMRA_C_CurrencyValueHelp", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_CurrencyValueHelpType", + }, + }, + { + "name": "SEPMRA_C_PD_ContactPerson", + "value": { + "entitySetName": "SEPMRA_C_PD_ContactPerson", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ContactPersonType", + }, + }, + { + "name": "SEPMRA_C_PD_PoItmCube", + "value": { + "entitySetName": "SEPMRA_C_PD_PoItmCube", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_PoItmCubeType", + }, + }, + { + "name": "SEPMRA_C_PD_Product", + "value": { + "entitySetName": "SEPMRA_C_PD_Product", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ProductType", + }, + }, + { + "name": "SEPMRA_C_PD_ProductImage", + "value": { + "entitySetName": "SEPMRA_C_PD_ProductImage", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ProductImageType", + }, + }, + { + "name": "SEPMRA_C_PD_ProductPriceRange", + "value": { + "entitySetName": "SEPMRA_C_PD_ProductPriceRange", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ProductPriceRangeType", + }, + }, + { + "name": "SEPMRA_C_PD_ProductSalesData", + "value": { + "entitySetName": "SEPMRA_C_PD_ProductSalesData", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ProductSalesDataType", + }, + }, + { + "name": "SEPMRA_C_PD_ProductStock", + "value": { + "entitySetName": "SEPMRA_C_PD_ProductStock", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ProductStockType", + }, + }, + { + "name": "SEPMRA_C_PD_ProductText", + "value": { + "entitySetName": "SEPMRA_C_PD_ProductText", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ProductTextType", + }, + }, + { + "name": "SEPMRA_C_PD_Review", + "value": { + "entitySetName": "SEPMRA_C_PD_Review", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ReviewType", + }, + }, + { + "name": "SEPMRA_C_PD_ReviewPost", + "value": { + "entitySetName": "SEPMRA_C_PD_ReviewPost", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_ReviewPostType", + }, + }, + { + "name": "SEPMRA_C_PD_StorageBinTP", + "value": { + "entitySetName": "SEPMRA_C_PD_StorageBinTP", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_StorageBinTPType", + }, + }, + { + "name": "SEPMRA_C_PD_Supplier", + "value": { + "entitySetName": "SEPMRA_C_PD_Supplier", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_C_PD_SupplierType", + }, + }, + { + "name": "SEPMRA_I_Address", + "value": { + "entitySetName": "SEPMRA_I_Address", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_AddressType", + }, + }, + { + "name": "SEPMRA_I_DimensionUnit", + "value": { + "entitySetName": "SEPMRA_I_DimensionUnit", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_DimensionUnitType", + }, + }, + { + "name": "SEPMRA_I_OrganizationalUnit", + "value": { + "entitySetName": "SEPMRA_I_OrganizationalUnit", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_OrganizationalUnitType", + }, + }, + { + "name": "SEPMRA_I_PriceClassification", + "value": { + "entitySetName": "SEPMRA_I_PriceClassification", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_PriceClassificationType", + }, + }, + { + "name": "SEPMRA_I_ProductCategory", + "value": { + "entitySetName": "SEPMRA_I_ProductCategory", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_ProductCategoryType", + }, + }, + { + "name": "SEPMRA_I_ProductMainCategory", + "value": { + "entitySetName": "SEPMRA_I_ProductMainCategory", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_ProductMainCategoryType", + }, + }, + { + "name": "SEPMRA_I_QuantityUnit", + "value": { + "entitySetName": "SEPMRA_I_QuantityUnit", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_QuantityUnitType", + }, + }, + { + "name": "SEPMRA_I_StockAvailability", + "value": { + "entitySetName": "SEPMRA_I_StockAvailability", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_StockAvailabilityType", + }, + }, + { + "name": "SEPMRA_I_WeightUnit", + "value": { + "entitySetName": "SEPMRA_I_WeightUnit", + "entitySetType": "SEPMRA_PROD_MAN.SEPMRA_I_WeightUnitType", + }, + }, + ], + "default": 6, + "guiOptions": { + "breadcrumb": true, + }, + "message": "Main entity", + "name": "mainEntity", + "source": [Function], + "type": "list", + "validate": [Function], + }, + { + "choices": [Function], + "default": 0, + "guiOptions": { + "applyDefaultWhenDirty": true, + "breadcrumb": true, + }, + "message": "Navigation entity", + "name": "navigationEntity", + "source": [Function], + "type": "list", + "when": [Function], + }, + { + "choices": [ + { + "name": "Analytical", + "value": "AnalyticalTable", + }, + { + "name": "Grid", + "value": "GridTable", + }, + { + "name": "Responsive", + "value": "ResponsiveTable", + }, + { + "name": "Tree", + "value": "TreeTable", + }, + ], + "default": "ResponsiveTable", + "guiOptions": { + "breadcrumb": true, + "hint": "Defines the table type for the List Report Page.", + }, + "message": "Table type", + "name": "tableType", + "type": "list", + "when": [Function], + }, + { + "default": "", + "guiOptions": { + "breadcrumb": true, + "hint": "Leading property that decides between either a recursive hierarchy or data aggregation.", + "mandatory": true, + }, + "message": "Hierarchy qualifier", + "name": "hierarchyQualifier", + "type": "input", + "validate": [Function], + "when": [Function], + }, +] +`; diff --git a/packages/odata-service-inquirer/test/unit/prompts/edmx/alp-questions.test.ts b/packages/odata-service-inquirer/test/unit/prompts/edmx/alp-questions.test.ts new file mode 100644 index 0000000000..9b6993f53c --- /dev/null +++ b/packages/odata-service-inquirer/test/unit/prompts/edmx/alp-questions.test.ts @@ -0,0 +1,138 @@ +import type { Annotations } from '@sap-ux/axios-extension'; +import type { ListQuestion } from '@sap-ux/inquirer-common'; +import { OdataVersion } from '@sap-ux/odata-service-writer'; +import { readFile } from 'fs/promises'; +import { initI18nOdataServiceInquirer, t } from '../../../../src/i18n'; +import { getAnalyticListPageQuestions } from '../../../../src/prompts/edmx/alp-questions'; +import type { EntityAnswer } from '../../../../src/prompts/edmx/entity-helper'; +import { EntityPromptNames } from '../../../../src/types'; + +describe('Test analytic list page specific prompts', () => { + let annotationsWithPresentationQualifier: string; + + beforeAll(async () => { + // Read the test metadata files + annotationsWithPresentationQualifier = await readFile( + __dirname + '/test-data/annotationsWithPresentationQualifier.xml', + 'utf8' + ); + // Ensure i18n texts are loaded so we can test localised strings + await initI18nOdataServiceInquirer(); + }); + test('should return the correct questions for the analytic list page: odata version v2', async () => { + const questions = getAnalyticListPageQuestions(OdataVersion.v2); + expect(questions).toEqual([ + { + default: false, + guiOptions: { + breadcrumb: true, + hint: t('prompts.tableMultiSelect.hint') + }, + message: t('prompts.tableMultiSelect.message'), + name: 'tableMultiSelect', + type: 'confirm' + }, + { + default: true, + guiOptions: { + breadcrumb: true, + hint: t('prompts.tableAutoHide.hint') + }, + message: t('prompts.tableAutoHide.message'), + name: 'tableAutoHide', + type: 'confirm' + }, + { + default: false, + guiOptions: { + breadcrumb: true, + hint: t('prompts.smartVariantManagement.hint') + }, + message: t('prompts.smartVariantManagement.message'), + name: 'smartVariantManagement', + type: 'confirm' + } + ]); + }); + + test('should return the correct questions for the analytic list page: odata version v4', async () => { + let questions = getAnalyticListPageQuestions(OdataVersion.v4); + expect(questions).toEqual([ + { + choices: expect.any(Function), + guiOptions: { + breadcrumb: true, + hint: t('prompts.tableSelectionMode.hint') + }, + message: t('prompts.tableSelectionMode.message'), + name: 'tableSelectionMode', + type: 'list', + when: expect.any(Function) + } + ]); + const tableSelectionModePrompt = questions.find( + (question) => question.name === EntityPromptNames.tableSelectionMode + ) as ListQuestion; + expect( + (tableSelectionModePrompt.when as Function)({ + [EntityPromptNames.mainEntity]: { + entitySetName: 'anyEntitySetName', + entitySetType: 'anyEntitySetType' + } as EntityAnswer + }) + ).toBe(true); + expect((tableSelectionModePrompt.choices as Function)()).toEqual([ + { + name: 'None', + value: 'None' + }, + { + name: 'Auto', + value: 'Auto' + }, + { + name: 'Multi', + value: 'Multi' + }, + { + name: 'Single', + value: 'Single' + } + ]); + + questions = getAnalyticListPageQuestions(OdataVersion.v4, undefined, true); + expect(questions).toEqual([]); + }); + + test('should return the correct questions for the analytic list page with select presentation qualifier', async () => { + const annotations: Annotations = { + Definitions: annotationsWithPresentationQualifier, + TechnicalName: 'SEPMRA_ALP_SO_ANA_SRV', + Uri: '../../../sap/sepmra_alp_so_ana_srv/$metadata', + Version: '0001' + }; + + const questions = getAnalyticListPageQuestions(OdataVersion.v2, annotations); + const presentationQualifierPrompt = questions.find( + (question) => question.name === EntityPromptNames.presentationQualifier + ) as ListQuestion; + expect( + (presentationQualifierPrompt.when as Function)({ + [EntityPromptNames.mainEntity]: { + entitySetName: 'SEPMRA_C_ALP_SlsOrdItemCubeALPResults', + entitySetType: 'SEPMRA_ALP_SO_ANA_SRV.SEPMRA_C_ALP_SlsOrdItemCubeALPResult' + } as EntityAnswer + }) + ).toBe(true); + expect((presentationQualifierPrompt.choices as Function)()).toEqual([ + { + name: 'None', + value: undefined + }, + { + name: 'DefaultVariant', + value: 'DefaultVariant' + } + ]); + }); +}); diff --git a/packages/odata-service-inquirer/test/unit/prompts/edmx/entity-helper.test.ts b/packages/odata-service-inquirer/test/unit/prompts/edmx/entity-helper.test.ts new file mode 100644 index 0000000000..19d65bfbe8 --- /dev/null +++ b/packages/odata-service-inquirer/test/unit/prompts/edmx/entity-helper.test.ts @@ -0,0 +1,181 @@ +import { getEntityChoices, getNavigationEntityChoices } from '../../../../src/prompts/edmx/entity-helper'; +import { LogWrapper } from '@sap-ux/fiori-generator-shared'; +import { readFile } from 'fs/promises'; +import { parse } from '@sap-ux/edmx-parser'; +import { convert } from '@sap-ux/annotation-converter'; +import { OdataVersion } from '@sap-ux/odata-service-writer'; + +describe('Test entity helper functions', () => { + let metadataV4WithDraftAndShareAnnot: string; + let metadataV4WithAggregateTransforms: string; + let metadataV2: string; + let metadataV4WithDraftEntities: string; + let metadataV2WithDraftRoot: string; + + beforeAll(async () => { + // Read the test metadata files + metadataV4WithDraftAndShareAnnot = await readFile( + __dirname + '/test-data/metadataV4WithDraftAnnotationAndShareAction.xml', + 'utf8' + ); + metadataV4WithAggregateTransforms = await readFile( + __dirname + '/test-data/metadataV4WithAggregateTransforms.xml', + 'utf8' + ); + metadataV2 = await readFile(__dirname + '/test-data/metadataV2.xml', 'utf8'); + metadataV4WithDraftEntities = await readFile(__dirname + '/test-data/metadataV4WithDraftEntities.xml', 'utf8'); + metadataV2WithDraftRoot = await readFile(__dirname + '/test-data/metadataV2WithDraftRoot.xml', 'utf8'); + }); + + describe('Test getNavigationEntityOptions', () => { + test('should return v4 navigation entities', async () => { + const parsedEdmx = parse(metadataV4WithDraftEntities); + const convertedMetadata = convert(parsedEdmx); + const navChoices = getNavigationEntityChoices(convertedMetadata, OdataVersion.v4, 'Travel'); + expect(navChoices.length).toEqual(2); + expect(navChoices[0].name).toEqual('None'); + expect(navChoices[0].value).toEqual({}); + expect(navChoices[1].name).toEqual('_Booking'); + expect(navChoices[1].value).toEqual({ + navigationPropertyName: '_Booking', + entitySetName: 'Booking' + }); + }); + + test('should return v2 navigation entities', async () => { + const parsedEdmx = parse(metadataV2); + const convertedMetadata = convert(parsedEdmx); + const navChoices = getNavigationEntityChoices(convertedMetadata, OdataVersion.v2, 'SEPMRA_C_PD_Product'); + expect(navChoices[0].name).toEqual('None'); + expect(navChoices[0].value).toEqual({}); + expect(navChoices.length).toEqual(22); + expect(navChoices[1].value).toEqual({ + entitySetName: 'I_DraftAdministrativeData', + navigationPropertyName: 'DraftAdministrativeData' + }); + expect(navChoices).toMatchSnapshot(); + }); + }); + + describe('Test filter entities with aggregate transformation annotation', () => { + test('should filter ALP v4 entities', () => { + const fitleredChoices = [ + { + name: 'SalesOrderItem', + value: { + entitySetName: 'SalesOrderItem', + entitySetType: 'com.c_salesordermanage_sd_aggregate.SalesOrderItem' + } + }, + { + name: 'SalesOrderManage', + value: { + entitySetName: 'SalesOrderManage', + entitySetType: 'com.c_salesordermanage_sd_aggregate.SalesOrderManage' + } + } + ]; + + const filteredEntities = getEntityChoices(metadataV4WithAggregateTransforms, { + useEntityTypeAsName: true, + entitySetFilter: 'filterAggregateTransformationsOnly' + }); + expect(filteredEntities.choices).toEqual(fitleredChoices); + + // Metadata is odata v2 instead of v4, `filterAggregateTransformationsOnly` is ignored + const filteredEntitiesNoEdmx = getEntityChoices(metadataV2, { + entitySetFilter: 'filterAggregateTransformationsOnly' + }); + expect(filteredEntitiesNoEdmx.choices).toMatchSnapshot(); + }); + }); + + describe('Test getEntityChoices', () => { + test('should return all entity sets', () => { + const entityOptions = getEntityChoices(metadataV4WithDraftEntities); + expect(entityOptions.choices.length).toEqual(14); + expect(entityOptions.choices[0].name).toEqual('Airline'); + expect(entityOptions.choices[0].value).toEqual({ + entitySetName: 'Airline', + entitySetType: 'com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.AirlineType' + }); + }); + + test('should return all draft roots', () => { + const entityOptions = getEntityChoices(metadataV2WithDraftRoot); + expect(entityOptions.choices.length).toEqual(23); + expect(entityOptions.choices[7].name).toEqual('SEPMRA_C_PD_Product'); + expect(entityOptions.draftRootIndex).toBe(7); + }); + + test('should preselect the specified main entity', () => { + const entityOptions = getEntityChoices(metadataV4WithDraftEntities, { defaultMainEntityName: 'Airline' }); + expect(entityOptions.choices.length).toEqual(14); + expect(entityOptions.choices[entityOptions.defaultMainEntityIndex!]).toEqual({ + name: 'Airline', + value: { + entitySetName: 'Airline', + entitySetType: 'com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.AirlineType' + } + }); + }); + + test('should return draft enabled entity sets only', async () => { + const entityOptions = getEntityChoices(metadataV4WithDraftEntities, { + useEntityTypeAsName: true, + entitySetFilter: 'filterDraftEnabled' + }); + expect(entityOptions.choices[4].name).toEqual('TravelType'); + expect(entityOptions.choices).toMatchInlineSnapshot(` + [ + { + "name": "BookingType", + "value": { + "entitySetName": "BookingType", + "entitySetType": "com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.BookingType", + }, + }, + { + "name": "BookingSupplementType", + "value": { + "entitySetName": "BookingSupplementType", + "entitySetType": "com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.BookingSupplementType", + }, + }, + { + "name": "SupplementType", + "value": { + "entitySetName": "SupplementType", + "entitySetType": "com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.SupplementType", + }, + }, + { + "name": "SupplementTextType", + "value": { + "entitySetName": "SupplementTextType", + "entitySetType": "com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.SupplementTextType", + }, + }, + { + "name": "TravelType", + "value": { + "entitySetName": "TravelType", + "entitySetType": "com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.TravelType", + }, + }, + ] + `); + }); + + test('should use entity type name as choice name', async () => { + const entityOptions = getEntityChoices(metadataV2, { + useEntityTypeAsName: true + }); + const typeNameChoices = entityOptions.choices; + expect(typeNameChoices).toMatchSnapshot(); + expect(typeNameChoices[0].name).toEqual('I_CurrencyType'); + expect(typeNameChoices[0].value.entitySetName).toEqual('I_CurrencyType'); + expect(typeNameChoices[0].value.entitySetType).toEqual('SEPMRA_PROD_MAN.I_CurrencyType'); + }); + }); +}); diff --git a/packages/odata-service-inquirer/test/unit/prompts/edmx/questions.test.ts b/packages/odata-service-inquirer/test/unit/prompts/edmx/questions.test.ts new file mode 100644 index 0000000000..66d56427d0 --- /dev/null +++ b/packages/odata-service-inquirer/test/unit/prompts/edmx/questions.test.ts @@ -0,0 +1,388 @@ +import { Severity } from '@sap-devx/yeoman-ui-types'; +import { TableType } from '@sap-ux/fiori-elements-writer'; +import type { ConfirmQuestion, ListQuestion } from '@sap-ux/inquirer-common'; +import { OdataVersion } from '@sap-ux/odata-service-writer'; +import type { ConvertedMetadata } from '@sap-ux/vocabularies-types'; +import { readFile } from 'fs/promises'; +import type { ListChoiceOptions, Question } from 'inquirer'; +import { initI18nOdataServiceInquirer, t } from '../../../../src/i18n'; +import type { EntityAnswer } from '../../../../src/prompts/edmx/entity-helper'; +import * as EntityHelper from '../../../../src/prompts/edmx/entity-helper'; +import { getEntitySelectionQuestions } from '../../../../src/prompts/edmx/questions'; +import LoggerHelper from '../../../../src/prompts/logger-helper'; +import type { EntitySelectionAnswers } from '../../../../src/types'; +import * as Types from '../../../../src/types'; +import { EntityPromptNames } from '../../../../src/types'; +import { PromptState } from '../../../../src/utils'; + +describe('Test entity prompts', () => { + let metadataV4WithDraftAndShareAnnot: string; + let metadataV4WithAggregateTransforms: string; + let metadataV2: string; + let metadataV4WithDraftEntities: string; + let metadataV2NoEntities: string; + + beforeAll(async () => { + // Read the test metadata files + metadataV4WithDraftAndShareAnnot = await readFile( + __dirname + '/test-data/metadataV4WithDraftAnnotationAndShareAction.xml', + 'utf8' + ); + metadataV4WithAggregateTransforms = await readFile( + __dirname + '/test-data/metadataV4WithAggregateTransforms.xml', + 'utf8' + ); + metadataV2 = await readFile(__dirname + '/test-data/metadataV2.xml', 'utf8'); + metadataV4WithDraftEntities = await readFile(__dirname + '/test-data/metadataV4WithDraftEntities.xml', 'utf8'); + metadataV2NoEntities = await readFile(__dirname + '/test-data/metadataV2NoEntities.xml', 'utf8'); + // Ensure i18n texts are loaded so we can test localised strings + await initI18nOdataServiceInquirer(); + }); + + test('getEntityQuestions should return the questions when no options specified', () => { + let questions = getEntitySelectionQuestions(metadataV2, 'lrop'); + expect(questions).toMatchSnapshot(); + + // Invalid metadata + const errorLogSpy = jest.spyOn(LoggerHelper.logger, 'log'); + questions = getEntitySelectionQuestions('{}', 'lrop'); + expect(questions).toEqual([]); + expect(errorLogSpy).toBeCalledWith(expect.stringMatching('Unable to parse entities')); + }); + + test('getEntityQuestions should return prompts based options specified', () => { + // Preselect the main entity using option `defaultMainEntityName` + let questions = getEntitySelectionQuestions(metadataV2, 'lrop', false, { + defaultMainEntityName: 'SEPMRA_C_PD_Product' + }); + const mainEntityPrompt = questions.find( + (question) => question.name === EntityPromptNames.mainEntity + ) as ListQuestion; + const mainEntityIndex = (mainEntityPrompt.choices as ListChoiceOptions[]).findIndex( + (choice) => choice.name === 'SEPMRA_C_PD_Product' + ); + expect(mainEntityPrompt?.default).toBe(mainEntityIndex); + + // Hide the table layout prompts using option `hideTableLayoutPrompts` + questions = getEntitySelectionQuestions(metadataV2, 'lrop', false, { + hideTableLayoutPrompts: true + }); + expect(questions.find((question) => question.name === EntityPromptNames.tableType)).toBeUndefined(); + expect(questions.find((question) => question.name === EntityPromptNames.hierarchyQualifier)).toBeUndefined(); + + // Enable autocomplete for specific prompts using option `autoComplete` + questions = getEntitySelectionQuestions(metadataV2, 'lrop', false, { + useAutoComplete: true + }); + const autoCompletePrompts = questions.filter((question) => (question as Question).type === 'autocomplete'); + expect(autoCompletePrompts).toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: EntityPromptNames.mainEntity }), + expect.objectContaining({ name: EntityPromptNames.navigationEntity }) + ]) + ); + }); + + test('getEntityQuestions should return the correct questions: `ovp`', async () => { + const getEntityChoicesSpy = jest.spyOn(EntityHelper, 'getEntityChoices'); + // Analytical List Page + let questions = getEntitySelectionQuestions(metadataV2, 'ovp'); + expect(getEntityChoicesSpy).toHaveBeenCalledWith(metadataV2, { + defaultMainEntityName: undefined, + entitySetFilter: undefined, + useEntityTypeAsName: true + }); + expect(questions).toEqual( + expect.arrayContaining([expect.objectContaining({ name: EntityPromptNames.filterEntityType })]) + ); + const filterEntityPrompt = questions.find( + (question) => question.name === EntityPromptNames.filterEntityType + ) as ListQuestion; + // Specific entity choices should be tested by the entity helper tests + expect((filterEntityPrompt.choices as []).length).toBe(25); + + let validateResult = (filterEntityPrompt!.validate as Function)(); + expect(validateResult).toBe(true); + + // Filter entity type prompt validation message when no choices + getEntityChoicesSpy.mockReturnValueOnce({ + choices: [], + odataVersion: OdataVersion.v2, + convertedMetadata: { version: '2' } as ConvertedMetadata + }); + questions = getEntitySelectionQuestions(metadataV2, 'ovp'); + validateResult = ( + questions.find((question) => question.name === EntityPromptNames.filterEntityType)!.validate as Function + )(); + expect(validateResult).toEqual(t('prompts.filterEntityType.noEntitiesError')); + }); + + test('getEntityQuestions should return the correct questions: `lrop`', async () => { + const getEntityChoicesSpy = jest.spyOn(EntityHelper, 'getEntityChoices'); + // List Report Object Page + let questions = getEntitySelectionQuestions(metadataV4WithAggregateTransforms, 'lrop', false, { + defaultMainEntityName: 'Customer' + }); + expect(getEntityChoicesSpy).toHaveBeenCalledWith(metadataV4WithAggregateTransforms, { + defaultMainEntityName: 'Customer', + entitySetFilter: undefined, + useEntityTypeAsName: false + }); + expect(questions).toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: EntityPromptNames.mainEntity }), + expect.objectContaining({ name: EntityPromptNames.navigationEntity }), + expect.objectContaining({ name: EntityPromptNames.addLineItemAnnotations }), + expect.objectContaining({ name: EntityPromptNames.tableType }), + expect.objectContaining({ name: EntityPromptNames.hierarchyQualifier }) + ]) + ); + let mainEntityPrompt = questions.find( + (question) => question.name === EntityPromptNames.mainEntity + ) as ListQuestion; + // Specific entity choices should be tested by the entity helper tests + expect((mainEntityPrompt.choices as []).length).toBe(38); + const mainEntityIndex = (mainEntityPrompt.choices as ListChoiceOptions[]).findIndex( + (choice) => choice.name === 'Customer' + ); + expect(mainEntityPrompt?.default).toBe(mainEntityIndex); + questions = getEntitySelectionQuestions(metadataV4WithAggregateTransforms, 'lrop', false, { + defaultMainEntityName: 'NotFoundEntity' + }); + mainEntityPrompt = questions.find((question) => question.name === EntityPromptNames.mainEntity) as ListQuestion; + expect(mainEntityPrompt.additionalMessages!()).toEqual({ + message: t('prompts.mainEntitySelection.defaultEntityNameNotFoundWarning'), + severity: Severity.warning + }); + // validate is currently used to warn about no entities, although perhaps we shoudld be using additionalMessages + const validateResult = (mainEntityPrompt.validate as Function)(); + expect(validateResult).toBe(true); + + const navEntityPrompt = questions.find( + (question) => question.name === EntityPromptNames.navigationEntity + ) as ListQuestion; + expect((navEntityPrompt.when as Function)({})).toBe(false); + // Navigation entity prompt should be shown when main entity is selected and the main entity has navigation properties. + // Navigation entity choices are tested fully by the entity helper tests. + expect( + (navEntityPrompt.when as Function)({ + [EntityPromptNames.mainEntity]: { + entitySetName: 'Material', + entitySetType: 'com.c_salesordermanage_sd_aggregate.Material' + } as EntityAnswer + }) + ).toBe(true); + expect((navEntityPrompt.choices as Function)().length).toEqual(2); + }); + + test('`mainEntity` question should conditionally return validation message', async () => { + PromptState.isYUI = true; + // FEOP - error if no draft enabled enities + let questions = getEntitySelectionQuestions(metadataV4WithAggregateTransforms, 'feop', true); + let mainEntityPrompt = questions.find( + (question) => question.name === EntityPromptNames.mainEntity + ) as ListQuestion; + let validateResult = (mainEntityPrompt.validate as Function)(); + expect(validateResult).toBe(t('prompts.mainEntitySelection.noDraftEnabledEntitiesError')); + + // ALP - error if no aggregate transforms + questions = getEntitySelectionQuestions(metadataV4WithDraftEntities, 'alp'); + mainEntityPrompt = questions.find((question) => question.name === EntityPromptNames.mainEntity) as ListQuestion; + validateResult = (mainEntityPrompt.validate as Function)(); + expect(validateResult).toBe(t('prompts.mainEntitySelection.noEntitiesAlpV4Error')); + + // No entities + questions = getEntitySelectionQuestions(metadataV2NoEntities, 'worklist'); + mainEntityPrompt = questions.find((question) => question.name === EntityPromptNames.mainEntity) as ListQuestion; + validateResult = (mainEntityPrompt.validate as Function)(); + expect(validateResult).toBe(t('prompts.mainEntitySelection.noEntitiesError')); + + // CLI exit with error if no entities + PromptState.isYUI = false; + questions = getEntitySelectionQuestions(metadataV2NoEntities, 'worklist'); + mainEntityPrompt = questions.find((question) => question.name === EntityPromptNames.mainEntity) as ListQuestion; + expect(() => (mainEntityPrompt.validate as Function)()).toThrowError(t('errors.exitingGeneration')); + }); + + test('should show line item annotation generation prompt and additional messages', async () => { + // LROP and Worklist templates are the only ones that show the line item annotation prompt + let questions = getEntitySelectionQuestions(metadataV4WithAggregateTransforms, 'lrop', true); + let addLineItemAnnotationsPrompt = questions.find( + (question) => question.name === EntityPromptNames.addLineItemAnnotations + ) as ConfirmQuestion; + + expect(addLineItemAnnotationsPrompt.additionalMessages!()).toBeUndefined(); + expect(addLineItemAnnotationsPrompt.additionalMessages!(true)).toEqual({ + message: t('prompts.addLineItemAnnotations.valueHelpsAnnotationsInfoMessage'), + severity: 2 + }); + // Large edmx processing warning, should be shown when the metadata size exceeds the warning limit + Object.defineProperty(Types, 'MetadataSizeWarningLimitKb', { value: 1 }); + questions = getEntitySelectionQuestions(metadataV4WithAggregateTransforms, 'lrop', true); + addLineItemAnnotationsPrompt = questions.find( + (question) => question.name === EntityPromptNames.addLineItemAnnotations + ) as ConfirmQuestion; + + expect(addLineItemAnnotationsPrompt.additionalMessages!(true)).toEqual({ + message: t('warnings.largeMetadataDocument'), + severity: 1 + }); + + // FEOP template should not show the line item annotation prompt + Object.defineProperty(Types, 'MetadataSizeWarningLimitKb', { value: 1 }); + questions = getEntitySelectionQuestions(metadataV4WithAggregateTransforms, 'feop'); + addLineItemAnnotationsPrompt = questions.find( + (question) => question.name === EntityPromptNames.addLineItemAnnotations + ) as ConfirmQuestion; + expect(addLineItemAnnotationsPrompt).toBeUndefined(); + const addFEOPAnnotations = questions.find( + (question) => question.name === EntityPromptNames.addFEOPAnnotations + ) as ConfirmQuestion; + expect(addFEOPAnnotations.additionalMessages!(true)).toEqual({ + message: t('warnings.largeMetadataDocument'), + severity: 1 + }); + }); + + test('getEntityQuestions should return the questions depending on the specified template type and odata version', () => { + const getEntityChoicesSpy = jest.spyOn(EntityHelper, 'getEntityChoices'); + // Analytical List Page + let questions = getEntitySelectionQuestions(metadataV2, 'alp'); + expect(questions).toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: EntityPromptNames.mainEntity }), + expect.objectContaining({ name: EntityPromptNames.navigationEntity }), + expect.objectContaining({ name: EntityPromptNames.tableType }), + expect.objectContaining({ name: EntityPromptNames.tableMultiSelect }), + expect.objectContaining({ name: EntityPromptNames.tableAutoHide }), + expect.objectContaining({ name: EntityPromptNames.smartVariantManagement }) + ]) + ); + getEntityChoicesSpy.mockClear(); + + // Form Entry Object Page + questions = getEntitySelectionQuestions(metadataV4WithAggregateTransforms, 'feop'); + expect(getEntityChoicesSpy).toHaveBeenCalledWith(metadataV4WithAggregateTransforms, { + defaultMainEntityName: undefined, + entitySetFilter: undefined, + useEntityTypeAsName: false + }); + expect(questions).toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: EntityPromptNames.mainEntity }), + expect.objectContaining({ name: EntityPromptNames.addFEOPAnnotations }) + ]) + ); + // Filter draft enabled entities when the template is Form Entry Object Page and `isCapService` is true + questions = getEntitySelectionQuestions(metadataV4WithAggregateTransforms, 'feop', true); + expect(getEntityChoicesSpy).toHaveBeenCalledWith(metadataV4WithAggregateTransforms, { + defaultMainEntityName: undefined, + entitySetFilter: 'filterDraftEnabled', + useEntityTypeAsName: false + }); + getEntityChoicesSpy.mockClear(); + + // Filter draft enabled entities when the template is Form Entry Object Page and `isCapService` is true + questions = getEntitySelectionQuestions(metadataV4WithAggregateTransforms, 'worklist', true); + expect(getEntityChoicesSpy).toHaveBeenCalledWith(metadataV4WithAggregateTransforms, { + defaultMainEntityName: undefined, + entitySetFilter: undefined, + useEntityTypeAsName: false + }); + expect(questions).toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: EntityPromptNames.mainEntity }), + expect.objectContaining({ name: EntityPromptNames.addLineItemAnnotations }) + ]) + ); + getEntityChoicesSpy.mockClear(); + + // Flexible Page Model aka. Custom Page + questions = getEntitySelectionQuestions(metadataV4WithAggregateTransforms, 'fpm'); + expect(getEntityChoicesSpy).toHaveBeenCalledWith(metadataV4WithAggregateTransforms, { + defaultMainEntityName: undefined, + entitySetFilter: undefined, + useEntityTypeAsName: false + }); + // Note, no navigation entity for FPM + expect(questions).toEqual( + expect.arrayContaining([expect.objectContaining({ name: EntityPromptNames.mainEntity })]) + ); + }); + + test('should prompt for table layout options', async () => { + let questions = getEntitySelectionQuestions(metadataV2, 'lrop', false, { hideTableLayoutPrompts: true }); + + expect(questions).not.toContainEqual(expect.objectContaining({ name: EntityPromptNames.tableType })); + expect(questions).not.toContainEqual(expect.objectContaining({ name: EntityPromptNames.hierarchyQualifier })); + + questions = getEntitySelectionQuestions(metadataV2, 'lrop', false, { hideTableLayoutPrompts: false }); + + expect(questions).toContainEqual(expect.objectContaining({ name: EntityPromptNames.tableType })); + expect(questions).toContainEqual(expect.objectContaining({ name: EntityPromptNames.hierarchyQualifier })); + + let tabelType = questions.find((question) => question.name === EntityPromptNames.tableType) as ListQuestion; + expect( + (tabelType.when as Function)({ + [EntityPromptNames.mainEntity]: { + entitySetName: 'SEPMRA_C_PD_Product', + entitySetType: 'SEPMRA_C_PD_ProductType' + } as EntityAnswer + }) + ).toBe(true); + expect(tabelType.choices as []).toEqual([ + { + name: 'Analytical', + value: 'AnalyticalTable' + }, + { + name: 'Grid', + value: 'GridTable' + }, + { + name: 'Responsive', + value: 'ResponsiveTable' + }, + { + name: 'Tree', + value: 'TreeTable' + } + ]); + expect(tabelType.default).toEqual('ResponsiveTable'); + + questions = getEntitySelectionQuestions(metadataV4WithAggregateTransforms, 'alp', false); + tabelType = questions.find((question) => question.name === EntityPromptNames.tableType) as ListQuestion; + expect(tabelType.choices as []).toEqual([ + { + name: 'Analytical', + value: 'AnalyticalTable' + }, + { + name: 'Grid', + value: 'GridTable' + }, + { + name: 'Responsive', + value: 'ResponsiveTable' + } + ]); + expect(tabelType.default).toEqual('AnalyticalTable'); + + const hierarchyQualifier = questions.find( + (question) => question.name === EntityPromptNames.hierarchyQualifier + ) as ListQuestion; + + Object.values(TableType).forEach((tableTypeValue) => { + expect( + (hierarchyQualifier.when as Function)({ + [EntityPromptNames.tableType]: tableTypeValue + }) + ).toBe(['AnalyticalTable', 'GridTable', 'ResponsiveTable'].includes(tableTypeValue) ? false : true); + }); + + expect((hierarchyQualifier.validate as Function)('Some qualifier value')).toBe(true); + expect((hierarchyQualifier.validate as Function)('')).toEqual( + t('prompts.hierarchyQualifier.qualifierRequiredForV4Warning') + ); + }); +}); diff --git a/packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/annotationsWithPresentationQualifier.xml b/packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/annotationsWithPresentationQualifier.xml new file mode 100644 index 0000000000..b5a7cd1570 --- /dev/null +++ b/packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/annotationsWithPresentationQualifier.xml @@ -0,0 +1,2065 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SoldToParty + + + + + + + + + + + + + NetAmount + + + + + + + + + + + + + + + + + + + + + SalesOrderOverallStatus + + + + + + + + + + + + + NumberOfSalesOrders + + + + + + + + + + + + + + + + + + + + + MainProductCategory + + + + + + + + + + + + + Quantity + + + + + + + + + + + + + + + + + + + + + MainProductCategory + + + + + + + + + + + + + NetAmount + + + + + + + + + + + + + + + + + + + + + ProductCategory + + + + + + + + + + + + + Quantity + + + + + + + + + + + + + + + + + + + + + ProductCategory + + + + + + + + + + + + + NetAmount + + + + + + + + + + + + + + + + + + + + + Product + + + + + + + + + + + + + Quantity + + + + + + + + + + + + + + + + + + + + Product + + + + + + + + + + + + + NetAmount + + + + + + + + + + + + + + + + + + + + + DeliveryCalendarMonth + + + + + + + + + + + + + NetAmount + + + + + + + + + + + + + + + + + + + + + DeliveryCalendarQuarter + + + + + + + + + + + + + NetAmount + + + + + + + + + + + + + + + + + + + + + DeliveryCalendarYear + + + + + + + + + + + + + NetAmount + + + + + + + + + + + + + + + + + + + + Supplier + + + + + + + + + + + + + NetAmount + + + + + + + + + + + + + + + + + + + + SoldToPartyCountry + + + + + + + + + + + + + NetAmount + + + + + + + + + + + + + + + + + + + + + SoldToParty + DeliveryCalendarYear + + + + + + + + + + + + + + + + + NetAmount + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @UI.Chart#FilterRevenueByCustomer + + + + + + + + + + + + + + + + + + + @UI.Chart#FilterNumberOfSalesOrdersByStatus + + + + + + + + + + + + + + + + + + + @UI.Chart#FilterQuantityByMainProductCategory + + + + + + + + + + + + + + + + + + + @UI.Chart#FilterRevenueByMainProductCategory + + + + + + + + + + + + + + + + + + + @UI.Chart#FilterQuantityByProductCategory + + + + + + + + + + + + + + + + + + + @UI.Chart#FilterRevenueByProductCategory + + + + + + + + + + + + + + + + + + + @UI.Chart#FilterQuantityByProduct + + + + + + + + + + + + + + + + + + + @UI.Chart#FilterRevenueByProduct + + + + + + + + + + + + + + + + + + + @UI.Chart#FilterRevenueByCalendarMonth + + + + + + + + + + + + + + + + + + @UI.Chart#FilterRevenueByCalendarQuarter + + + + + + + + + + + + + + + + + + @UI.Chart#FilterRevenueByCalendarYear + + + + + + + + + + + + + + + + + + + @UI.Chart#FilterRevenueBySupplier + + + + + + + + + + + + + + + + + + + @UI.Chart#FilterRevenueByCustomerCountry + + + + + + + + + + + + + + + + + + @UI.LineItem#Default + @UI.Chart#Default + + + + + + + DeliveryCalendarYear + SoldToParty + Product + MainProductCategory + DeliveryCalendarQuarter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CalendarMonth + + + + + + + + + + + + + KPIValue + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @UI.Chart#KPIRevenue + @UI.DataPoint#KPIValue + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @UI.Chart#KPIRevenue + @UI.DataPoint#KPIValue + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/metadataV2.xml b/packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/metadataV2.xml new file mode 100644 index 0000000000..99f2630380 --- /dev/null +++ b/packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/metadataV2.xml @@ -0,0 +1,1806 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SEPMRA_PROD_MAN.SEPMRA_PROD_MAN_Entities/SEPMRA_C_PD_Product + + + + + + + + + + + + + SEPMRA_PROD_MAN.SEPMRA_PROD_MAN_Entities/SEPMRA_C_PD_Product + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Product + + + + + + + Product + + + + + + + Bin + Product + + + + + + + Supplier + + + + + + + ProductCategory + + + + + + + MainProductCategory + + + + + + + + + + to_ProductText + to_ProductTextInOriginalLang + + + + + + + + + + Currency + + + + + to_Currency + to_CurrencyVH + + + + + + + + + + DimensionUnit + + + + + to_DimensionUnit + + + + + + + + + + MainProductCategory + + + + + to_MainProductCategory + to_ProductCategory + + + + + ProductCategory + + + + + + + + + + ProductBaseUnit + + + + + to_ProductBaseUnit + + + + + + + + + + ProductCategory + + + + + to_MainProductCategory + to_ProductCategory + + + + + MainProductCategory + + + + + + + + + + Supplier + + + + + to_Supplier + + + + + + + + + + WeightUnit + + + + + to_WeightUnit + + + + + + + + + + + + + LanguageForEdit + + + + + to_Language + + + + + + + + + + Name + + + + + to_Product/Name + + + + + + + + + + Description + + + + + Description + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/metadataV2NoEntities.xml b/packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/metadataV2NoEntities.xml new file mode 100755 index 0000000000..8d17ec550e --- /dev/null +++ b/packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/metadataV2NoEntities.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/metadataV2WithDraftRoot.xml b/packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/metadataV2WithDraftRoot.xml new file mode 100644 index 0000000000..19bff80c23 --- /dev/null +++ b/packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/metadataV2WithDraftRoot.xml @@ -0,0 +1,1094 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UnitOfMeasure + + + + + + + UnitOfMeasure + + + + + + + MainProductCategory + + + + + + + ActiveProduct + Language + + + + + + + Supplier + + + + + + + ProductCategory + + + + + + + ProductCategory + + + + + + + ProductCategory + + + + + to_ProductCategory + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/metadataV4WithAggregateTransforms.xml b/packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/metadataV4WithAggregateTransforms.xml new file mode 100644 index 0000000000..3d61f419b0 --- /dev/null +++ b/packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/metadataV4WithAggregateTransforms.xml @@ -0,0 +1,4355 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CustomerCreditExposureAmount + + + + + + + + + + + + + + + + + + + + + CustomerCreditExposureAmount + + + + + + + + + + + + + + + + + + + + + CustomerCreditExposureAmount + + + + + + + + + + + + + + + + + + + + + CustomerCreditExposureAmountHidden + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + owner/SalesOrder + + + + + + + ModelYear + + + + + WarrantyYear + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + materialdetail/owner_ID + materialdetail/FabricationCountry + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Custom.concat + + + + + + + aggregate + topcount + bottomcount + identity + concat + groupby + filter + expand + top + skip + orderby + search + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SalesOrder + SalesOrderItem + + + + + + + RequestedQuantity + + + + + NetAmount + + + + + + + + + _MaterialDetails/ModelYear + + + + + _MaterialDetails/WarrantyYear + + + + + + + + + + + SalesOrderItem + + + + + + + + + + + + + + NetAmount + + + + + + + + + + + + + + + + + + + + + NetAmount + + + + + + + + + + + + SalesOrderItem + + + + + + + + + + + + + + NetAmount + + + + + + + + + + + + SalesOrderItem + + + + + + + + + + + + + + NetAmount + + + + + + + + + + + + SalesOrderItem + + + + + + + + + + + + + + + + + + + NetAmount + TargetAmount + + + + + + + + + + + + SalesOrderItem + + + + + + + + + + + + + + + + + + + NetAmountHidden + TargetAmount + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SalesOrderItem + + + + + + + + + + + + + maxAmount + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @UI.Chart + @UI.LineItem + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + min + max + average + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RequestedQuantity + + + + + _it/RequestedQuantity + _it/NetAmount + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Custom.concat + + + + + + + aggregate + topcount + bottomcount + identity + concat + groupby + filter + expand + top + skip + orderby + search + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ID + + + + + + + IncotermsVersion + + + + + IncotermsLocation1 + + + + + + + + + + _Item + + + + + TotalNetAmount + + + + + + + + + _Item/Material + + + + + _Item + + + + + TotalNetAmount + + + + + + + + + _Item/RequestedQuantity + + + + + _Item + + + + + TotalNetAmount + + + + + + + + + _ShipToParty/BusinessPartner + + + + + _ShipToParty + _Partner + + + + + + + + + SoldToParty + + + + + _ShipToParty + _Partner + + + + + SalesOrganization + DistributionChannel + OrganizationDivision + TotalNetAmount + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SalesOrganization + + + + + + + + + + + + + totalPricing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @UI.LineItem + @UI.Chart + + + + + + + SalesOrder + SoldToParty + OverallSDProcessStatus + SalesOrderDate + ShippingCondition + SalesOrganization + DistributionChannel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + min + max + average + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + min + max + average + sum + + + + + + + + + + + + + + + + + + + + + + + _it/editActionIsEnabled + _it/editActionIsDisabled + + + + + + + + + + + + + + + + + + _it/ShippingCondition + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/metadataV4WithDraftAnnotationAndShareAction.xml b/packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/metadataV4WithDraftAnnotationAndShareAction.xml new file mode 100644 index 0000000000..81ea8f8e63 --- /dev/null +++ b/packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/metadataV4WithDraftAnnotationAndShareAction.xml @@ -0,0 +1,2907 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../../../../../../srvd_f4/sap/i_country/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*i_agency.countrycode'/$metadata + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + EMailAddress + WebAddress + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../../../../../../srvd_f4/dmo/i_agency/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*c_travel_mduu.agencyid'/$metadata + + + + + + + + + + + + + + + + + + + + ../../../../../../srvd_f4/dmo/i_customer/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*c_travel_mduu.customerid'/$metadata + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + __CreateByAssociationControl + __EntityControl + __FieldControl + __OperationControl + + + + + + + + + Memo + __CreateByAssociationControl + __EntityControl + __FieldControl + __OperationControl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../../../../srvd_f4/sap/i_currency/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*c_travel_mduu.currencycode'/$metadata + + + + + + + + + + + TravelID + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @SAP__UI.LineItem + + + + + + + TravelID + AgencyID + CustomerID + + + + + + + + + + + + + + + + + + + + ../../../../srvd_f4/sap/i_country/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*i_airport.countrycode'/$metadata + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../../../../srvd_f4/dmo/i_supplement/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*c_booksup_mduu.supplementid'/$metadata + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SAP__self.Container/Travel + + + + + + + + + + + + + + + + + + + + + + __EntityControl + + + + + + + + + SupplementDescription + __EntityControl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../../../../srvd_f4/sap/i_currency/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*c_booksup_mduu.currencycode'/$metadata + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SAP__self.Container/Travel + + + + + + + + + + + + + + + + + + + + + + __EntityControl + + + + + + + + + Description + __EntityControl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + __CreateByAssociationControl + __EntityControl + __OperationControl + + + + + + + + + __CreateByAssociationControl + __EntityControl + __OperationControl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../../../../srvd_f4/dmo/i_carrier/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*i_flight.airlineid'/$metadata + + + + + + + + + + + + + + + + + + ../../../../srvd_f4/dmo/i_connection/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*i_flight.connectionid'/$metadata + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../../../../srvd_f4/sap/i_country/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*i_customer.countrycode'/$metadata + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + EMailAddress + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../../../../srvd_f4/dmo/i_carrier/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*i_connection.airlineid'/$metadata + + + + + + + + + + + + + + + + + + + + + + + + ../../../../srvd_f4/dmo/i_airport/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*i_connection.departureairport'/$metadata + + + + + + + + + + + + + ../../../../srvd_f4/dmo/i_airport/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*i_connection.destinationairport'/$metadata + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + aggregate + groupby + filter + + + + + + + + + eq + ne + gt + ge + lt + le + and + or + contains + startswith + endswith + any + all + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../../../../../../srvd_f4/dmo/i_customer/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*c_booking_mduu.customerid'/$metadata + + + + + + + + + + + + + + + + + + ../../../../srvd_f4/dmo/i_carrier/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*c_booking_mduu.airlineid'/$metadata + + + + + + + + + + + + + + + + + + ../../../../srvd_f4/dmo/i_flight/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*c_booking_mduu.connectionid'/$metadata + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SAP__self.Container/Travel + + + + + + + + + + + + + __CreateByAssociationControl + __EntityControl + + + + + + + + + __CreateByAssociationControl + __EntityControl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../../../../srvd_f4/dmo/i_flight/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*c_booking_mduu.flightdate'/$metadata + + + + + + + + + + + + ../../../../srvd_f4/sap/i_currency/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*c_booking_mduu.currencycode'/$metadata + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/metadataV4WithDraftEntities.xml b/packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/metadataV4WithDraftEntities.xml new file mode 100644 index 0000000000..af883c86ba --- /dev/null +++ b/packages/odata-service-inquirer/test/unit/prompts/edmx/test-data/metadataV4WithDraftEntities.xml @@ -0,0 +1,2905 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../../../../../../srvd_f4/sap/i_country/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*i_agency.countrycode'/$metadata + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + EMailAddress + WebAddress + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../../../../../../srvd_f4/dmo/i_agency/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*c_travel_mduu.agencyid'/$metadata + + + + + + + + + + + + + + + + + + + + ../../../../../../srvd_f4/dmo/i_customer/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*c_travel_mduu.customerid'/$metadata + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + __CreateByAssociationControl + __EntityControl + __FieldControl + __OperationControl + + + + + + + + + Memo + __CreateByAssociationControl + __EntityControl + __FieldControl + __OperationControl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../../../../srvd_f4/sap/i_currency/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*c_travel_mduu.currencycode'/$metadata + + + + + + + + + + + TravelID + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @SAP__UI.LineItem + + + + + + + TravelID + AgencyID + CustomerID + + + + + + + + + + + + + + + + + + + + ../../../../srvd_f4/sap/i_country/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*i_airport.countrycode'/$metadata + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../../../../srvd_f4/dmo/i_supplement/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*c_booksup_mduu.supplementid'/$metadata + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SAP__self.Container/Travel + + + + + + + + + + + + + + + + + + + + + + __EntityControl + + + + + + + + + SupplementDescription + __EntityControl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../../../../srvd_f4/sap/i_currency/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*c_booksup_mduu.currencycode'/$metadata + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SAP__self.Container/Travel + + + + + + + + + + + + + + + + + + + + + + __EntityControl + + + + + + + + + Description + __EntityControl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + __CreateByAssociationControl + __EntityControl + __OperationControl + + + + + + + + + __CreateByAssociationControl + __EntityControl + __OperationControl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../../../../srvd_f4/dmo/i_carrier/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*i_flight.airlineid'/$metadata + + + + + + + + + + + + + + + + + + ../../../../srvd_f4/dmo/i_connection/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*i_flight.connectionid'/$metadata + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../../../../srvd_f4/sap/i_country/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*i_customer.countrycode'/$metadata + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + EMailAddress + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../../../../srvd_f4/dmo/i_carrier/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*i_connection.airlineid'/$metadata + + + + + + + + + + + + + + + + + + + + + + + + ../../../../srvd_f4/dmo/i_airport/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*i_connection.departureairport'/$metadata + + + + + + + + + + + + + ../../../../srvd_f4/dmo/i_airport/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*i_connection.destinationairport'/$metadata + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + aggregate + groupby + filter + + + + + + + + + eq + ne + gt + ge + lt + le + and + or + contains + startswith + endswith + any + all + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../../../../../../srvd_f4/dmo/i_customer/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*c_booking_mduu.customerid'/$metadata + + + + + + + + + + + + + + + + + + ../../../../srvd_f4/dmo/i_carrier/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*c_booking_mduu.airlineid'/$metadata + + + + + + + + + + + + + + + + + + ../../../../srvd_f4/dmo/i_flight/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*c_booking_mduu.connectionid'/$metadata + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SAP__self.Container/Travel + + + + + + + + + + + + + __CreateByAssociationControl + __EntityControl + + + + + + + + + __CreateByAssociationControl + __EntityControl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../../../../srvd_f4/dmo/i_flight/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*c_booking_mduu.flightdate'/$metadata + + + + + + + + + + + + ../../../../srvd_f4/sap/i_currency/0001;ps='srvd-*dmo*sd_travel_mduu-0001';va='com.sap.gateway.srvd.dmo.sd_travel_mduu.v0001.et-*dmo*c_booking_mduu.currencycode'/$metadata + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/odata-service-inquirer/test/unit/prompts/prompts.test.ts b/packages/odata-service-inquirer/test/unit/prompts/prompts.test.ts index 907225291b..485ddae8cd 100644 --- a/packages/odata-service-inquirer/test/unit/prompts/prompts.test.ts +++ b/packages/odata-service-inquirer/test/unit/prompts/prompts.test.ts @@ -54,7 +54,7 @@ describe('getQuestions', () => { // Test that additional choices are added by options: 'includeNone' expect((await getQuestions({ datasourceType: { includeNone: true } }))[0]).toMatchObject({ choices: expect.arrayContaining([ - { name: t('prompts.datasourceType.noneName'), value: DatasourceType.none } + { name: t('prompts.datasourceType.choiceNone'), value: DatasourceType.none } ]) }); }); diff --git a/packages/odata-service-inquirer/test/unit/prompts/sap-system/abap-on-btp/questions.test.ts b/packages/odata-service-inquirer/test/unit/prompts/sap-system/abap-on-btp/questions.test.ts index 39260db642..84cf5fdbea 100644 --- a/packages/odata-service-inquirer/test/unit/prompts/sap-system/abap-on-btp/questions.test.ts +++ b/packages/odata-service-inquirer/test/unit/prompts/sap-system/abap-on-btp/questions.test.ts @@ -101,7 +101,7 @@ describe('questions', () => { }, { "guiOptions": { - "hint": "Select a local file that defines the service connection for an ABAP Environment on SAP Business Technology Platform", + "hint": "Select a local file that defines the service connection for an ABAP Environment on SAP Business Technology Platform.", "mandatory": true, }, "guiType": "file-browser", diff --git a/packages/odata-service-inquirer/test/unit/prompts/sap-system/questions.test.ts b/packages/odata-service-inquirer/test/unit/prompts/sap-system/questions.test.ts index 2cbcab59ba..c11a12953f 100644 --- a/packages/odata-service-inquirer/test/unit/prompts/sap-system/questions.test.ts +++ b/packages/odata-service-inquirer/test/unit/prompts/sap-system/questions.test.ts @@ -142,7 +142,7 @@ describe('questions', () => { }, { "guiOptions": { - "hint": "Select a local file that defines the service connection for an ABAP Environment on SAP Business Technology Platform", + "hint": "Select a local file that defines the service connection for an ABAP Environment on SAP Business Technology Platform.", "mandatory": true, }, "guiType": "file-browser", diff --git a/packages/odata-service-inquirer/test/unit/prompts/sap-system/service-selection/questions.test.ts b/packages/odata-service-inquirer/test/unit/prompts/sap-system/service-selection/questions.test.ts index 0777347c48..c6749b811a 100644 --- a/packages/odata-service-inquirer/test/unit/prompts/sap-system/service-selection/questions.test.ts +++ b/packages/odata-service-inquirer/test/unit/prompts/sap-system/service-selection/questions.test.ts @@ -625,7 +625,6 @@ describe('Test new system prompt', () => { const serviceSelectionPrompt = systemServiceQuestions.find( (question) => question.name === `${promptNamespace}:${promptNames.serviceSelection}` ); - // todo: No check for GA link here??? const choices = await ((serviceSelectionPrompt as ListQuestion)?.choices as Function)(); expect(choices).toEqual([]); const valResult = await ((serviceSelectionPrompt as ListQuestion)?.validate as Function)(); diff --git a/packages/odata-service-inquirer/test/unit/prompts/sap-system/system-selection/__snapshots__/questions.test.ts.snap b/packages/odata-service-inquirer/test/unit/prompts/sap-system/system-selection/__snapshots__/questions.test.ts.snap index d13900d57b..90217c37bd 100644 --- a/packages/odata-service-inquirer/test/unit/prompts/sap-system/system-selection/__snapshots__/questions.test.ts.snap +++ b/packages/odata-service-inquirer/test/unit/prompts/sap-system/system-selection/__snapshots__/questions.test.ts.snap @@ -19,7 +19,7 @@ exports[`Test system selection prompts should return system selection prompts an "default": -1, "guiOptions": { "breadcrumb": true, - "hint": "Select a system configuration", + "hint": "Select a system configuration.", }, "message": "System", "name": "systemSelection", @@ -32,7 +32,7 @@ exports[`Test system selection prompts should return system selection prompts an "guiOptions": { "applyDefaultWhenDirty": true, "breadcrumb": true, - "hint": "Enter the path to the OData service, relative to the selected destination URL", + "hint": "Enter the path to the OData service, relative to the selected destination URL.", "mandatory": true, }, "message": "Service path", @@ -113,7 +113,7 @@ exports[`Test system selection prompts should return system selection prompts an "default": -1, "guiOptions": { "breadcrumb": true, - "hint": "Select a system configuration", + "hint": "Select a system configuration.", }, "message": "System", "name": "systemSelection", @@ -290,7 +290,7 @@ exports[`Test system selection prompts should return system selection prompts an }, { "guiOptions": { - "hint": "Select a local file that defines the service connection for an ABAP Environment on SAP Business Technology Platform", + "hint": "Select a local file that defines the service connection for an ABAP Environment on SAP Business Technology Platform.", "mandatory": true, }, "guiType": "file-browser", diff --git a/packages/odata-service-inquirer/test/unit/prompts/sap-system/system-selection/prompt-helpers.test.ts b/packages/odata-service-inquirer/test/unit/prompts/sap-system/system-selection/prompt-helpers.test.ts index 690dc7689b..72dc927c1f 100644 --- a/packages/odata-service-inquirer/test/unit/prompts/sap-system/system-selection/prompt-helpers.test.ts +++ b/packages/odata-service-inquirer/test/unit/prompts/sap-system/system-selection/prompt-helpers.test.ts @@ -8,7 +8,6 @@ import { getBackendSystemDisplayName, NewSystemChoice } from '../../../../../src/prompts/datasources/sap-system/system-selection/prompt-helpers'; -import type { SystemSelectionPromptOptions } from '../../../../../src/types'; const backendSystemBasic: BackendSystem = { name: 'http://abap.on.prem:1234 (ABAP-on-Prem)', diff --git a/packages/odata-service-inquirer/tsconfig.json b/packages/odata-service-inquirer/tsconfig.json index 31ec473ec9..212b115663 100644 --- a/packages/odata-service-inquirer/tsconfig.json +++ b/packages/odata-service-inquirer/tsconfig.json @@ -18,6 +18,12 @@ { "path": "../feature-toggle" }, + { + "path": "../fiori-elements-writer" + }, + { + "path": "../fiori-freestyle-writer" + }, { "path": "../fiori-generator-shared" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a972173ddb..9bfa31c245 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -168,7 +168,7 @@ importers: version: link:../../packages/system-access yeoman-generator: specifier: 5.10.0 - version: 5.10.0 + version: 5.10.0(mem-fs@2.1.0)(yeoman-environment@3.19.3) devDependencies: '@sap-ux/odata-service-writer': specifier: workspace:* @@ -1327,7 +1327,7 @@ importers: version: 20.6.1 yeoman-generator: specifier: 5.10.0 - version: 5.10.0 + version: 5.10.0(mem-fs@2.1.0)(yeoman-environment@3.19.3) devDependencies: '@sap-ux/axios-extension': specifier: workspace:* @@ -2309,12 +2309,18 @@ importers: packages/odata-service-inquirer: dependencies: + '@sap-ux/annotation-converter': + specifier: 0.9.10 + version: 0.9.10 '@sap-ux/axios-extension': specifier: workspace:* version: link:../axios-extension '@sap-ux/btp-utils': specifier: workspace:* version: link:../btp-utils + '@sap-ux/edmx-parser': + specifier: 0.9.0 + version: 0.9.0 '@sap-ux/fiori-generator-shared': specifier: workspace:* version: link:../fiori-generator-shared @@ -2367,9 +2373,18 @@ importers: '@sap-ux/feature-toggle': specifier: workspace:* version: link:../feature-toggle + '@sap-ux/fiori-elements-writer': + specifier: workspace:* + version: link:../fiori-elements-writer + '@sap-ux/fiori-freestyle-writer': + specifier: workspace:* + version: link:../fiori-freestyle-writer '@sap-ux/odata-service-writer': specifier: workspace:* version: link:../odata-service-writer + '@sap-ux/vocabularies-types': + specifier: 0.11.7 + version: 0.11.7 '@types/inquirer': specifier: 8.2.6 version: 8.2.6 @@ -8178,10 +8193,15 @@ packages: '@sap-ux/vocabularies-types': 0.11.7 dev: false + /@sap-ux/edmx-parser@0.9.0: + resolution: {integrity: sha512-X7yda14CsUqGhEK5/XYhP0W65ueObQxq6Dv5qmJQBvR73V0gNexrY5sWuzCQPcEVVh0sB9A0qesMU9qgp6R37A==} + dependencies: + xml-js: 1.6.11 + dev: false + /@sap-ux/vocabularies-types@0.11.7: resolution: {integrity: sha512-rqRhmPW97dQ8K6nYjL1aP0a4THsUSGK6F5LSOOkSkyTQrBOGu5LNdqi90V2EYKSdIARTvgOnaz1mU+nmkzrsOA==} engines: {node: '>=18.x'} - dev: false /@sap/bas-sdk@3.8.9: resolution: {integrity: sha512-ycYttnSTlEPjDrb0svSv0jBgqEvoekD4bNB4IxuKCu5OKk5FycwsOTgufU8CXYf1ag23wTlurN+N7c4Po6PcJQ==} @@ -22739,37 +22759,6 @@ packages: - bluebird - supports-color - /yeoman-generator@5.10.0: - resolution: {integrity: sha512-iDUKykV7L4nDNzeYSedRmSeJ5eMYFucnKDi6KN1WNASXErgPepKqsQw55TgXPHnmpcyOh2Dd/LAZkyc+f0qaAw==} - engines: {node: '>=12.10.0'} - peerDependencies: - yeoman-environment: ^3.2.0 - peerDependenciesMeta: - yeoman-environment: - optional: true - dependencies: - chalk: 4.1.2 - dargs: 7.0.0 - debug: 4.3.7 - execa: 5.1.1 - github-username: 6.0.0 - lodash: 4.17.21 - mem-fs-editor: 9.4.0(mem-fs@2.1.0) - minimist: 1.2.8 - pacote: 15.2.0 - read-pkg-up: 7.0.1 - run-async: 2.4.1 - semver: 7.5.4 - shelljs: 0.8.5 - sort-keys: 4.2.0 - text-table: 0.2.0 - transitivePeerDependencies: - - bluebird - - encoding - - mem-fs - - supports-color - dev: false - /yeoman-generator@5.10.0(mem-fs@2.1.0)(yeoman-environment@3.19.3): resolution: {integrity: sha512-iDUKykV7L4nDNzeYSedRmSeJ5eMYFucnKDi6KN1WNASXErgPepKqsQw55TgXPHnmpcyOh2Dd/LAZkyc+f0qaAw==} engines: {node: '>=12.10.0'} @@ -22790,7 +22779,7 @@ packages: pacote: 15.2.0 read-pkg-up: 7.0.1 run-async: 2.4.1 - semver: 7.6.3 + semver: 7.5.4 shelljs: 0.8.5 sort-keys: 4.2.0 text-table: 0.2.0 From ab7cc104c9fa25935dc3b902fa967b82a83b7fdc Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 17 Jan 2025 13:41:20 +0000 Subject: [PATCH 2/4] chore: apply latest changesets --- .changeset/clever-eyes-greet.md | 36 ------------------- examples/simple-generator/CHANGELOG.md | 38 ++++++++++++++++++++ examples/simple-generator/package.json | 2 +- packages/fiori-elements-writer/CHANGELOG.md | 34 ++++++++++++++++++ packages/fiori-elements-writer/package.json | 2 +- packages/fiori-freestyle-writer/CHANGELOG.md | 34 ++++++++++++++++++ packages/fiori-freestyle-writer/package.json | 2 +- packages/odata-service-inquirer/CHANGELOG.md | 34 ++++++++++++++++++ packages/odata-service-inquirer/package.json | 2 +- 9 files changed, 144 insertions(+), 40 deletions(-) delete mode 100644 .changeset/clever-eyes-greet.md diff --git a/.changeset/clever-eyes-greet.md b/.changeset/clever-eyes-greet.md deleted file mode 100644 index ac10f061db..0000000000 --- a/.changeset/clever-eyes-greet.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -'@sap-ux/fiori-freestyle-writer': major -'@sap-ux/odata-service-inquirer': major -'@sap-ux/fiori-elements-writer': major -'@sap-ux/generator-simple-fe': patch ---- - -Adds support for entity related prompting. Update some exported types `fiori-elements-writer`, `fiori-freestyle-writer` to remove problematic enum usage. -The major version updates for modules `@sap-ux/fiori-elements-writer` and `@sap-ux/fiori-freestyle-writer` indicates a breaking change of type definitions: - -`@sap-ux/fiori-elements-writer`: - `TemplateType` - `TableType` - `TableSelectionMode` - - `@sap-ux/fiori-freestyle-writer`: - `TemplateType` - -These changes are required to reduce the impact of importing these types by consumers. Previously defined as enums, requiring full dependencies and bloating -consumer code where only these types are required. The new type defintions still allow both uses (type or value) but the `enums` are now defined as `const`. -This change requires updates to consuming code where the type is imported and referenced. -Example where a single enum was used as a value type: - -``` -type Template = { - template: TemplateType.ListReportObjectPage -} -``` - -should now be defined as: - -``` -type Template = { - template: typeof TemplateType.ListReportObjectPage -} -``` diff --git a/examples/simple-generator/CHANGELOG.md b/examples/simple-generator/CHANGELOG.md index ff218537ba..273c605f89 100644 --- a/examples/simple-generator/CHANGELOG.md +++ b/examples/simple-generator/CHANGELOG.md @@ -1,5 +1,43 @@ # @sap-ux/generator-simple-fe +## 1.0.109 + +### Patch Changes + +- 77b0800: Adds support for entity related prompting. Update some exported types `fiori-elements-writer`, `fiori-freestyle-writer` to remove problematic enum usage. + The major version updates for modules `@sap-ux/fiori-elements-writer` and `@sap-ux/fiori-freestyle-writer` indicates a breaking change of type definitions: + + `@sap-ux/fiori-elements-writer`: + `TemplateType` + `TableType` + `TableSelectionMode` + + `@sap-ux/fiori-freestyle-writer`: + `TemplateType` + + These changes are required to reduce the impact of importing these types by consumers. Previously defined as enums, requiring full dependencies and bloating + consumer code where only these types are required. The new type defintions still allow both uses (type or value) but the `enums` are now defined as `const`. + This change requires updates to consuming code where the type is imported and referenced. + Example where a single enum was used as a value type: + + ``` + type Template = { + template: TemplateType.ListReportObjectPage + } + ``` + + should now be defined as: + + ``` + type Template = { + template: typeof TemplateType.ListReportObjectPage + } + ``` + +- Updated dependencies [77b0800] + - @sap-ux/fiori-freestyle-writer@2.0.0 + - @sap-ux/fiori-elements-writer@2.0.0 + ## 1.0.108 ### Patch Changes diff --git a/examples/simple-generator/package.json b/examples/simple-generator/package.json index 79c9981c9a..1fbec344b9 100644 --- a/examples/simple-generator/package.json +++ b/examples/simple-generator/package.json @@ -1,6 +1,6 @@ { "name": "@sap-ux/generator-simple-fe", - "version": "1.0.108", + "version": "1.0.109", "description": "Simple example of a yeoman generator for Fiori elements.", "license": "Apache-2.0", "private": true, diff --git a/packages/fiori-elements-writer/CHANGELOG.md b/packages/fiori-elements-writer/CHANGELOG.md index c9059bb6c3..c26b4d831a 100644 --- a/packages/fiori-elements-writer/CHANGELOG.md +++ b/packages/fiori-elements-writer/CHANGELOG.md @@ -1,5 +1,39 @@ # @sap-ux/fiori-elements-writer +## 2.0.0 + +### Major Changes + +- 77b0800: Adds support for entity related prompting. Update some exported types `fiori-elements-writer`, `fiori-freestyle-writer` to remove problematic enum usage. + The major version updates for modules `@sap-ux/fiori-elements-writer` and `@sap-ux/fiori-freestyle-writer` indicates a breaking change of type definitions: + + `@sap-ux/fiori-elements-writer`: + `TemplateType` + `TableType` + `TableSelectionMode` + + `@sap-ux/fiori-freestyle-writer`: + `TemplateType` + + These changes are required to reduce the impact of importing these types by consumers. Previously defined as enums, requiring full dependencies and bloating + consumer code where only these types are required. The new type defintions still allow both uses (type or value) but the `enums` are now defined as `const`. + This change requires updates to consuming code where the type is imported and referenced. + Example where a single enum was used as a value type: + + ``` + type Template = { + template: TemplateType.ListReportObjectPage + } + ``` + + should now be defined as: + + ``` + type Template = { + template: typeof TemplateType.ListReportObjectPage + } + ``` + ## 1.3.46 ### Patch Changes diff --git a/packages/fiori-elements-writer/package.json b/packages/fiori-elements-writer/package.json index fa34e96cb1..fb6c2a29e5 100644 --- a/packages/fiori-elements-writer/package.json +++ b/packages/fiori-elements-writer/package.json @@ -1,7 +1,7 @@ { "name": "@sap-ux/fiori-elements-writer", "description": "SAP Fiori elements application writer", - "version": "1.3.46", + "version": "2.0.0", "repository": { "type": "git", "url": "https://github.com/SAP/open-ux-tools.git", diff --git a/packages/fiori-freestyle-writer/CHANGELOG.md b/packages/fiori-freestyle-writer/CHANGELOG.md index dc12036264..ce4a910e3d 100644 --- a/packages/fiori-freestyle-writer/CHANGELOG.md +++ b/packages/fiori-freestyle-writer/CHANGELOG.md @@ -1,5 +1,39 @@ # @sap-ux/fiori-freestyle-writer +## 2.0.0 + +### Major Changes + +- 77b0800: Adds support for entity related prompting. Update some exported types `fiori-elements-writer`, `fiori-freestyle-writer` to remove problematic enum usage. + The major version updates for modules `@sap-ux/fiori-elements-writer` and `@sap-ux/fiori-freestyle-writer` indicates a breaking change of type definitions: + + `@sap-ux/fiori-elements-writer`: + `TemplateType` + `TableType` + `TableSelectionMode` + + `@sap-ux/fiori-freestyle-writer`: + `TemplateType` + + These changes are required to reduce the impact of importing these types by consumers. Previously defined as enums, requiring full dependencies and bloating + consumer code where only these types are required. The new type defintions still allow both uses (type or value) but the `enums` are now defined as `const`. + This change requires updates to consuming code where the type is imported and referenced. + Example where a single enum was used as a value type: + + ``` + type Template = { + template: TemplateType.ListReportObjectPage + } + ``` + + should now be defined as: + + ``` + type Template = { + template: typeof TemplateType.ListReportObjectPage + } + ``` + ## 1.2.41 ### Patch Changes diff --git a/packages/fiori-freestyle-writer/package.json b/packages/fiori-freestyle-writer/package.json index 305f73fae5..2576147f36 100644 --- a/packages/fiori-freestyle-writer/package.json +++ b/packages/fiori-freestyle-writer/package.json @@ -1,7 +1,7 @@ { "name": "@sap-ux/fiori-freestyle-writer", "description": "SAP Fiori freestyle application writer", - "version": "1.2.41", + "version": "2.0.0", "repository": { "type": "git", "url": "https://github.com/SAP/open-ux-tools.git", diff --git a/packages/odata-service-inquirer/CHANGELOG.md b/packages/odata-service-inquirer/CHANGELOG.md index 0f5ae64486..d8cd83d32a 100644 --- a/packages/odata-service-inquirer/CHANGELOG.md +++ b/packages/odata-service-inquirer/CHANGELOG.md @@ -1,5 +1,39 @@ # @sap-ux/odata-service-inquirer +## 1.0.0 + +### Major Changes + +- 77b0800: Adds support for entity related prompting. Update some exported types `fiori-elements-writer`, `fiori-freestyle-writer` to remove problematic enum usage. + The major version updates for modules `@sap-ux/fiori-elements-writer` and `@sap-ux/fiori-freestyle-writer` indicates a breaking change of type definitions: + + `@sap-ux/fiori-elements-writer`: + `TemplateType` + `TableType` + `TableSelectionMode` + + `@sap-ux/fiori-freestyle-writer`: + `TemplateType` + + These changes are required to reduce the impact of importing these types by consumers. Previously defined as enums, requiring full dependencies and bloating + consumer code where only these types are required. The new type defintions still allow both uses (type or value) but the `enums` are now defined as `const`. + This change requires updates to consuming code where the type is imported and referenced. + Example where a single enum was used as a value type: + + ``` + type Template = { + template: TemplateType.ListReportObjectPage + } + ``` + + should now be defined as: + + ``` + type Template = { + template: typeof TemplateType.ListReportObjectPage + } + ``` + ## 0.8.8 ### Patch Changes diff --git a/packages/odata-service-inquirer/package.json b/packages/odata-service-inquirer/package.json index 26b8cdeb59..e62e63e2a3 100644 --- a/packages/odata-service-inquirer/package.json +++ b/packages/odata-service-inquirer/package.json @@ -1,7 +1,7 @@ { "name": "@sap-ux/odata-service-inquirer", "description": "Prompts module that can prompt users for inputs required for odata service writing", - "version": "0.8.8", + "version": "1.0.0", "repository": { "type": "git", "url": "https://github.com/SAP/open-ux-tools.git", From fc5916a17310dc87acd4b4b46ca23327af863cb3 Mon Sep 17 00:00:00 2001 From: Cian Morrin <99677697+cianmSAP@users.noreply.github.com> Date: Fri, 17 Jan 2025 14:08:30 +0000 Subject: [PATCH 3/4] fix(abap-deploy): updates for exposing package and transport prompts (#2768) * fix(abap-deploy): updates for exposing package and transport prommpts * feat(abap-deploy): fixes fetching issues * feat(abap-deploy): lint and tidy up * feat(abap-deploy): sonar * feat(abap-deploy): spelling --- .changeset/popular-baboons-sit.md | 5 + .../abap-deploy-config-inquirer/src/index.ts | 4 +- .../src/prompts/helpers.ts | 16 +- .../src/prompts/index.ts | 9 +- .../src/prompts/questions/config/package.ts | 36 +-- .../src/prompts/questions/config/transport.ts | 22 +- .../src/prompts/validators.ts | 99 +++++--- .../abap-service-provider.ts | 236 ++++++++++++------ .../create-transport.ts | 8 +- .../src/service-provider-utils/index.ts | 1 - .../service-provider-utils/list-packages.ts | 13 +- .../transport-config.ts | 18 +- .../service-provider-utils/transport-list.ts | 8 +- .../abap-deploy-config-inquirer/src/utils.ts | 33 ++- .../src/validator-utils.ts | 7 +- .../test/prompts/helpers.test.ts | 19 +- .../prompts/questions/config/package.test.ts | 7 +- .../test/prompts/validators.test.ts | 21 +- .../abap-service-provider.test.ts | 22 +- .../create-transport.test.ts | 14 +- .../list-packages.test.ts | 15 +- .../transport-config.test.ts | 30 +-- .../transport-list.test.ts | 14 +- .../test/utils.test.ts | 14 +- .../test/validator-utils.test.ts | 3 +- 25 files changed, 386 insertions(+), 288 deletions(-) create mode 100644 .changeset/popular-baboons-sit.md diff --git a/.changeset/popular-baboons-sit.md b/.changeset/popular-baboons-sit.md new file mode 100644 index 0000000000..a9a9dbaee8 --- /dev/null +++ b/.changeset/popular-baboons-sit.md @@ -0,0 +1,5 @@ +--- +'@sap-ux/abap-deploy-config-inquirer': minor +--- + +exposes getpackageprompts and gettransportrequest prompts diff --git a/packages/abap-deploy-config-inquirer/src/index.ts b/packages/abap-deploy-config-inquirer/src/index.ts index 0d93f6561c..23db45ff8b 100644 --- a/packages/abap-deploy-config-inquirer/src/index.ts +++ b/packages/abap-deploy-config-inquirer/src/index.ts @@ -1,7 +1,7 @@ import { ToolsLogger, type Logger } from '@sap-ux/logger'; import { initI18n } from './i18n'; import { PromptState } from './prompts/prompt-state'; -import { getAbapDeployConfigQuestions } from './prompts'; +import { getAbapDeployConfigQuestions, getPackagePrompts, getTransportRequestPrompts } from './prompts'; import { getPackageAnswer, getTransportAnswer, reconcileAnswers } from './utils'; import LoggerHelper from './logger-helper'; import type { InquirerAdapter } from '@sap-ux/inquirer-common'; @@ -70,6 +70,8 @@ export { getPackageAnswer, getTransportAnswer, reconcileAnswers, + getPackagePrompts, + getTransportRequestPrompts, TargetSystemType, PackageInputChoices, TransportChoices, diff --git a/packages/abap-deploy-config-inquirer/src/prompts/helpers.ts b/packages/abap-deploy-config-inquirer/src/prompts/helpers.ts index b44000efca..0fb80e5194 100644 --- a/packages/abap-deploy-config-inquirer/src/prompts/helpers.ts +++ b/packages/abap-deploy-config-inquirer/src/prompts/helpers.ts @@ -12,7 +12,8 @@ import { TransportChoices, type AbapSystemChoice, type AbapDeployConfigAnswersInternal, - type BackendTarget + type BackendTarget, + type SystemConfig } from '../types'; import { AuthenticationType, type BackendSystem } from '@sap-ux/store'; import type { ChoiceOptions, ListChoiceOptions } from 'inquirer'; @@ -248,6 +249,7 @@ export function updatePromptStateUrl( * * @param isCli - is running in CLI * @param input - package input + * @param systemConfig - system configuration * @param previousAnswers - previous answers * @param backendTarget - backend target from abap deploy config prompt options * @returns results of query and message based on number of results @@ -255,22 +257,16 @@ export function updatePromptStateUrl( export async function getPackageChoices( isCli: boolean, input: string, + systemConfig: SystemConfig, previousAnswers: AbapDeployConfigAnswersInternal, backendTarget?: BackendTarget ): Promise<{ packages: string[]; morePackageResultsMsg: string }> { let packages; let morePackageResultsMsg = ''; + // For YUI we need to ensure input is provided so the prompt is not re-rendered with no input if (isCli || input) { - packages = await queryPackages( - input, - { - url: PromptState.abapDeployConfig.url, - client: PromptState.abapDeployConfig.client, - destination: PromptState.abapDeployConfig.destination - }, - backendTarget - ); + packages = await queryPackages(input, systemConfig, backendTarget); morePackageResultsMsg = packages && packages.length === ABAP_PACKAGE_SEARCH_MAX_RESULTS diff --git a/packages/abap-deploy-config-inquirer/src/prompts/index.ts b/packages/abap-deploy-config-inquirer/src/prompts/index.ts index 77a8d3c370..4ab6a31253 100644 --- a/packages/abap-deploy-config-inquirer/src/prompts/index.ts +++ b/packages/abap-deploy-config-inquirer/src/prompts/index.ts @@ -6,6 +6,7 @@ import { getTransportRequestPrompts, getConfirmPrompts } from './questions'; +import { PromptState } from './prompt-state'; import type { AbapDeployConfigQuestion, AbapDeployConfigPromptOptions } from '../types'; /** @@ -14,7 +15,7 @@ import type { AbapDeployConfigQuestion, AbapDeployConfigPromptOptions } from '.. * @param options - abap deploy config prompt options * @returns the abap deployment config questions */ -export async function getAbapDeployConfigQuestions( +async function getAbapDeployConfigQuestions( options?: AbapDeployConfigPromptOptions ): Promise { options = options ?? {}; @@ -27,11 +28,13 @@ export async function getAbapDeployConfigQuestions( questions.push(...getAppConfigPrompts(options)); } - const packagePrompts = getPackagePrompts(options); - const transportRequestPrompts = getTransportRequestPrompts(options); + const packagePrompts = getPackagePrompts(options, false, PromptState.isYUI); + const transportRequestPrompts = getTransportRequestPrompts(options, false, PromptState.isYUI); const confirmPrompts = getConfirmPrompts(options); questions.push(...packagePrompts, ...transportRequestPrompts, ...confirmPrompts); return questions as AbapDeployConfigQuestion[]; } + +export { getAbapDeployConfigQuestions, getPackagePrompts, getTransportRequestPrompts }; diff --git a/packages/abap-deploy-config-inquirer/src/prompts/questions/config/package.ts b/packages/abap-deploy-config-inquirer/src/prompts/questions/config/package.ts index f5f5c998e1..ffaaefa444 100644 --- a/packages/abap-deploy-config-inquirer/src/prompts/questions/config/package.ts +++ b/packages/abap-deploy-config-inquirer/src/prompts/questions/config/package.ts @@ -5,6 +5,7 @@ import { showPackageInputChoiceQuestion } from '../../conditions'; import { t } from '../../../i18n'; +import { getSystemConfig } from '../../../utils'; import { getPackageChoices, getPackageInputChoices } from '../../helpers'; import { defaultPackage, defaultPackageChoice } from '../../defaults'; import { validatePackage, validatePackageChoiceInput, validatePackageChoiceInputForCli } from '../../validators'; @@ -21,12 +22,18 @@ import type { AutocompleteQuestionOptions } from 'inquirer-autocomplete-prompt'; * Returns the package prompts. * * @param options - abap deploy config prompt options + * @param useStandalone - whether the prompts are used standalone, defaults to true + * @param isYUI - if true, the prompt is being called from the Yeoman UI extension host * @returns list of list of questions for package prompting */ -export function getPackagePrompts(options: AbapDeployConfigPromptOptions): Question[] { +export function getPackagePrompts( + options: AbapDeployConfigPromptOptions, + useStandalone = true, + isYUI = false +): Question[] { let packageInputChoiceValid: boolean | string; let morePackageResultsMsg = ''; - const isCli = !PromptState.isYUI; + PromptState.isYUI = isYUI; const questions: Question[] = [ { @@ -43,11 +50,7 @@ export function getPackagePrompts(options: AbapDeployConfigPromptOptions): Quest validate: async (input: PackageInputChoices): Promise => { packageInputChoiceValid = await validatePackageChoiceInput( input, - { - url: PromptState.abapDeployConfig.url, - client: PromptState.abapDeployConfig.client, - destination: PromptState.abapDeployConfig.destination - }, + getSystemConfig(useStandalone, PromptState.abapDeployConfig, options.backendTarget), options.backendTarget ); return packageInputChoiceValid; @@ -55,16 +58,13 @@ export function getPackagePrompts(options: AbapDeployConfigPromptOptions): Quest } as ListQuestion, { when: async (previousAnswers: AbapDeployConfigAnswersInternal): Promise => { - if (isCli) { + if (!PromptState.isYUI) { await validatePackageChoiceInputForCli( - { - url: PromptState.abapDeployConfig.url, - client: PromptState.abapDeployConfig.client, - destination: PromptState.abapDeployConfig.destination - }, + getSystemConfig(useStandalone, PromptState.abapDeployConfig, options.backendTarget), previousAnswers.packageInputChoice, options.backendTarget ); + packageInputChoiceValid = true; } return false; @@ -101,7 +101,7 @@ export function getPackagePrompts(options: AbapDeployConfigPromptOptions): Quest type: 'autocomplete', name: promptNames.packageAutocomplete, message: `${t('prompts.config.package.packageAutocomplete.message')}${ - isCli ? t('prompts.config.package.packageAutocomplete.messageTypeFilter') : '' + !PromptState.isYUI ? t('prompts.config.package.packageAutocomplete.messageTypeFilter') : '' }`, guiOptions: { hint: t('prompts.config.package.packageAutocomplete.hint'), @@ -112,7 +112,13 @@ export function getPackagePrompts(options: AbapDeployConfigPromptOptions): Quest previousAnswers: AbapDeployConfigAnswersInternal, input: string ): Promise => { - const results = await getPackageChoices(isCli, input, previousAnswers, options.backendTarget); + const results = await getPackageChoices( + !PromptState.isYUI, + input, + getSystemConfig(useStandalone, PromptState.abapDeployConfig, options.backendTarget), + previousAnswers, + options.backendTarget + ); morePackageResultsMsg = results.morePackageResultsMsg; return results.packages; }, diff --git a/packages/abap-deploy-config-inquirer/src/prompts/questions/config/transport.ts b/packages/abap-deploy-config-inquirer/src/prompts/questions/config/transport.ts index ba519fc16d..24fe74cd6b 100644 --- a/packages/abap-deploy-config-inquirer/src/prompts/questions/config/transport.ts +++ b/packages/abap-deploy-config-inquirer/src/prompts/questions/config/transport.ts @@ -1,5 +1,4 @@ import { t } from '../../../i18n'; - import { defaultOrShowManualTransportQuestion, defaultOrShowTransportCreatedQuestion, @@ -24,12 +23,17 @@ import { useCreateTrDuringDeploy } from '../../../utils'; * Returns the transport prompts. * * @param options - abap deploy config prompt options + * @param useStandalone - whether the prompts are used standalone, defaults to true + * @param isYUI - if true, the prompt is being called from the Yeoman UI extension host * @returns list of questions for transport prompting */ export function getTransportRequestPrompts( - options: AbapDeployConfigPromptOptions + options: AbapDeployConfigPromptOptions, + useStandalone = true, + isYUI = false ): Question[] { let transportInputChoice: TransportChoices; + PromptState.isYUI = isYUI; const questions: Question[] = [ { @@ -50,7 +54,15 @@ export function getTransportRequestPrompts( input: TransportChoices, previousAnswers: AbapDeployConfigAnswersInternal ): Promise => { - const result = validateTransportChoiceInput(input, previousAnswers, true, transportInputChoice); + const result = validateTransportChoiceInput( + useStandalone, + input, + previousAnswers, + true, + transportInputChoice, + options.backendTarget, + options.ui5AbapRepo?.default + ); transportInputChoice = input; return result; } @@ -61,11 +73,13 @@ export function getTransportRequestPrompts( when: async (previousAnswers: AbapDeployConfigAnswersInternal): Promise => { if (!PromptState.isYUI) { const result = await validateTransportChoiceInput( + useStandalone, previousAnswers.transportInputChoice, previousAnswers, false, undefined, - options.backendTarget + options.backendTarget, + options.ui5AbapRepo?.default ); if (result !== true) { throw new Error(result as string); diff --git a/packages/abap-deploy-config-inquirer/src/prompts/validators.ts b/packages/abap-deploy-config-inquirer/src/prompts/validators.ts index 2c23f2ea7c..7b695b044b 100644 --- a/packages/abap-deploy-config-inquirer/src/prompts/validators.ts +++ b/packages/abap-deploy-config-inquirer/src/prompts/validators.ts @@ -11,7 +11,13 @@ import { import { DEFAULT_PACKAGE_ABAP } from '../constants'; import { getTransportListFromService } from '../service-provider-utils'; import { t } from '../i18n'; -import { findBackendSystemByUrl, initTransportConfig, getPackageAnswer, queryPackages } from '../utils'; +import { + findBackendSystemByUrl, + initTransportConfig, + getPackageAnswer, + queryPackages, + getSystemConfig +} from '../utils'; import { handleTransportConfigError } from '../error-handler'; import { AuthenticationType } from '@sap-ux/store'; import { getHelpUrl, HELP_TREE } from '@sap-ux/guided-answers-helper'; @@ -362,37 +368,45 @@ export async function validatePackage( PromptState.transportAnswers.transportRequired = false; return true; } - const systemConfig: SystemConfig = { - url: PromptState.abapDeployConfig.url, - client: PromptState.abapDeployConfig.client, - destination: PromptState.abapDeployConfig.destination - }; + // checks if package is a local package and will update prompt state accordingly - await getTransportListFromService(input.toUpperCase(), answers.ui5AbapRepo ?? '', systemConfig, backendTarget); + await getTransportListFromService(input.toUpperCase(), answers.ui5AbapRepo ?? '', backendTarget); return true; } /** * Handler for creating new transport choices. * - * @param packageAnswer - package name - * @param systemConfig - system configuration - * @param input - transport choice input - * @param previousAnswers - previous answers - * @param validateInputChanged - if the input has changed - * @param prevTransportInputChoice - previous transport input choice - * @param backendTarget - backend target + * @param params - parameters for creating new transports + * @param params.packageAnswer - package name + * @param params.systemConfig - system configuration + * @param params.input - transport choice input + * @param params.previousAnswers - previous answers + * @param params.validateInputChanged - if the input has changed + * @param params.prevTransportInputChoice - previous transport input choice + * @param params.backendTarget - backend target + * @param params.ui5AbapRepoName - ui5 app repository name derived from AbapDeployConfigPromptOptions[ui5AbapRepo] * @returns - boolean or error message as a string */ -async function handleCreateNewTransportChoice( - packageAnswer: string, - systemConfig: SystemConfig, - input?: TransportChoices, - previousAnswers?: AbapDeployConfigAnswersInternal, - validateInputChanged?: boolean, - prevTransportInputChoice?: TransportChoices, - backendTarget?: BackendTarget -): Promise { +async function handleCreateNewTransportChoice({ + packageAnswer, + systemConfig, + input, + previousAnswers, + validateInputChanged, + prevTransportInputChoice, + backendTarget, + ui5AbapRepoName +}: { + packageAnswer: string; + systemConfig: SystemConfig; + input?: TransportChoices; + previousAnswers?: AbapDeployConfigAnswersInternal; + validateInputChanged?: boolean; + prevTransportInputChoice?: TransportChoices; + backendTarget?: BackendTarget; + ui5AbapRepoName?: string; +}): Promise { // Question is re-evaluated triggered by other user changes, // no need to create a new transport number if (validateInputChanged) { @@ -403,7 +417,7 @@ async function handleCreateNewTransportChoice( // take most recent entry in transport list const list = await getTransportList( packageAnswer, - previousAnswers?.ui5AbapRepo ?? '', + previousAnswers?.ui5AbapRepo ?? ui5AbapRepoName ?? '', systemConfig, backendTarget ); @@ -437,24 +451,27 @@ async function handleCreateNewTransportChoice( * @param systemConfig - system configuration * @param previousAnswers - previous answers * @param backendTarget - backend target + * @param ui5AbapRepoName - ui5 app repository name derived from AbapDeployConfigPromptOptions[ui5AbapRepo] * @returns - boolean or error message as a string */ async function handleListExistingTransportChoice( packageAnswer: string, systemConfig: SystemConfig, previousAnswers?: AbapDeployConfigAnswersInternal, - backendTarget?: BackendTarget + backendTarget?: BackendTarget, + ui5AbapRepoName?: string ): Promise { - if (!packageAnswer || !previousAnswers?.ui5AbapRepo) { + if (!packageAnswer || (!previousAnswers?.ui5AbapRepo && !ui5AbapRepoName)) { return t('errors.validators.transportListPreReqs'); } PromptState.transportAnswers.transportList = await getTransportList( packageAnswer, - previousAnswers.ui5AbapRepo, + previousAnswers?.ui5AbapRepo ?? ui5AbapRepoName ?? '', systemConfig, backendTarget ); + if (PromptState.transportAnswers.transportList) { if (PromptState.transportAnswers.transportList.length > 0) { return true; @@ -469,40 +486,48 @@ async function handleListExistingTransportChoice( /** * Validates the transport choice input. * + * @param useStandalone - if the transport prompts are used standalone * @param input - transport choice input * @param previousAnswers - previous answers * @param validateInputChanged - if the input has changed * @param prevTransportInputChoice - previous transport input choice * @param backendTarget - backend target + * @param ui5AbapRepoName - ui5 app repository name derived from AbapDeployConfigPromptOptions[ui5AbapRepo] * @returns boolean or error message as a string */ export async function validateTransportChoiceInput( + useStandalone: boolean, input?: TransportChoices, previousAnswers?: AbapDeployConfigAnswersInternal, validateInputChanged?: boolean, prevTransportInputChoice?: TransportChoices, - backendTarget?: BackendTarget + backendTarget?: BackendTarget, + ui5AbapRepoName?: string ): Promise { const packageAnswer = getPackageAnswer(previousAnswers, PromptState.abapDeployConfig.package); - const systemConfig: SystemConfig = { - url: PromptState.abapDeployConfig.url, - client: PromptState.abapDeployConfig.client, - destination: PromptState.abapDeployConfig.destination - }; + const systemConfig = getSystemConfig(useStandalone, PromptState.abapDeployConfig, backendTarget); switch (input) { case TransportChoices.ListExistingChoice: { - return handleListExistingTransportChoice(packageAnswer, systemConfig, previousAnswers, backendTarget); + return handleListExistingTransportChoice( + packageAnswer, + systemConfig, + previousAnswers, + backendTarget, + ui5AbapRepoName + ); } case TransportChoices.CreateNewChoice: { - return handleCreateNewTransportChoice( + return handleCreateNewTransportChoice({ packageAnswer, systemConfig, input, previousAnswers, validateInputChanged, - prevTransportInputChoice - ); + prevTransportInputChoice, + backendTarget, + ui5AbapRepoName + }); } case TransportChoices.EnterManualChoice: default: diff --git a/packages/abap-deploy-config-inquirer/src/service-provider-utils/abap-service-provider.ts b/packages/abap-deploy-config-inquirer/src/service-provider-utils/abap-service-provider.ts index d958e82481..813e25beab 100644 --- a/packages/abap-deploy-config-inquirer/src/service-provider-utils/abap-service-provider.ts +++ b/packages/abap-deploy-config-inquirer/src/service-provider-utils/abap-service-provider.ts @@ -4,100 +4,180 @@ import { createAbapServiceProvider } from '@sap-ux/system-access'; import { AuthenticationType } from '@sap-ux/store'; import { PromptState } from '../prompts/prompt-state'; import LoggerHelper from '../logger-helper'; -import type { AbapServiceProvider } from '@sap-ux/axios-extension'; +import type { AbapServiceProvider, AxiosRequestConfig, ProviderConfiguration } from '@sap-ux/axios-extension'; import type { DestinationAbapTarget, UrlAbapTarget } from '@sap-ux/system-access'; import type { BackendTarget, Credentials, SystemConfig } from '../types'; import type { AbapTarget } from '@sap-ux/ui5-config'; -let abapServiceProvider: AbapServiceProvider | undefined; -let system: SystemConfig; - /** - * Get or create an abap service provider. - * - * @param systemConfig - system configuration - * @param backendTarget - backend target from prompt options - * @param credentials - user credentials - * @returns abap service provider + * Class to manage the ABAP service provider used during prompting. */ -export async function getOrCreateServiceProvider( - systemConfig: SystemConfig, - backendTarget?: BackendTarget, - credentials?: Credentials -): Promise { - // use cached service provider - if (abapServiceProvider && isSameSystem(systemConfig, system?.url, system?.client, system?.destination)) { - return abapServiceProvider; +export class AbapServiceProviderManager { + private static abapServiceProvider: AbapServiceProvider | undefined; + private static system: SystemConfig; + + /** + * Get or create an ABAP service provider. + * + * @param backendTarget - backend target from prompt options + * @param credentials - user credentials + * @returns abap service provider + */ + public static async getOrCreateServiceProvider( + backendTarget?: BackendTarget, + credentials?: Credentials + ): Promise { + // 1. Use existing service provider + if (this.isExistingServiceProviderValid(backendTarget)) { + return this.abapServiceProvider as AbapServiceProvider; + } + + // 2. Use connected service provider passed in prompt options with backend target + if (this.isBackendTargetServiceProviderValid(backendTarget)) { + this.abapServiceProvider = backendTarget?.serviceProvider as AbapServiceProvider; + return this.abapServiceProvider; + } + + // 3. Create a new service provider + this.abapServiceProvider = await this.createNewServiceProvider(credentials, backendTarget); + return this.abapServiceProvider; } - // use connected service provider - if ( - backendTarget?.serviceProvider && - isSameSystem( - systemConfig, - backendTarget?.abapTarget.url, - backendTarget?.abapTarget.client, - backendTarget?.abapTarget.destination - ) - ) { - abapServiceProvider = backendTarget.serviceProvider as AbapServiceProvider; - system = backendTarget.abapTarget; - return abapServiceProvider; + + /** + * Returns the system config from the prompt state or the backend target. + * + * @param backendTarget - backend target from prompt options + * @returns - system config + */ + private static getSystemConfig(backendTarget?: BackendTarget): SystemConfig { + return { + url: PromptState.abapDeployConfig.url ?? backendTarget?.abapTarget.url, + client: PromptState.abapDeployConfig.client ?? backendTarget?.abapTarget.client, + destination: PromptState.abapDeployConfig.destination ?? backendTarget?.abapTarget.destination + }; } - abapServiceProvider = await createNewServiceProvider(credentials); - return abapServiceProvider; -} -/** - * Create a new abap service provider using @sap-ux/system-access. - * - * @param credentials - user credentials - * @returns abap service provider - */ -async function createNewServiceProvider(credentials?: Credentials): Promise { - let abapTarget: AbapTarget; - if (isAppStudio()) { - abapTarget = { destination: PromptState.abapDeployConfig.destination } as DestinationAbapTarget; - } else { - abapTarget = { - url: PromptState.abapDeployConfig.url, - client: PromptState.abapDeployConfig.client, - scp: PromptState.abapDeployConfig.scp - } as UrlAbapTarget; - if (PromptState.abapDeployConfig.isS4HC) { - abapTarget.authenticationType = AuthenticationType.ReentranceTicket; + /** + * Ensures if there is existing service provider, that it matches with the system configuration used when it was created. + * + * @param backendTarget - backend target from prompt options + * @returns true if existing service provider is valid, otherwise false + */ + private static isExistingServiceProviderValid(backendTarget?: BackendTarget): boolean { + const systemConfig = this.getSystemConfig(backendTarget); + if ( + this.abapServiceProvider && + isSameSystem(systemConfig, this.system?.url, this.system?.client, this.system?.destination) + ) { + this.system = systemConfig; + return true; } + return false; } - let auth; - if (credentials?.username && credentials?.password) { - auth = { - username: credentials.username, - password: credentials.password - }; + /** + * Check if the backend target is valid. There are two scenarios where the backend target is valid: + * 1. If the service provider (created during system selection) is connected to the same system as the backend target. + * 2. The prompt state system configuration is empty, meaning the system prompts have not been used, then the backend target must be deemed valid. + * + * @param backendTarget - backend target from prompt options + * @returns true if service provider passed with the backend target is valid, otherwise false + */ + private static isBackendTargetServiceProviderValid(backendTarget?: BackendTarget): boolean { + if ( + backendTarget?.serviceProvider && + (isSameSystem( + { + url: PromptState.abapDeployConfig.url, + client: PromptState.abapDeployConfig.client, + destination: PromptState.abapDeployConfig.destination + }, + backendTarget?.abapTarget.url, + backendTarget?.abapTarget.client, + backendTarget?.abapTarget.destination + ) || + (!PromptState.abapDeployConfig.url && !PromptState.abapDeployConfig.destination)) + ) { + this.system = backendTarget?.abapTarget; + return true; + } + return false; } - const requestOptions = { - ignoreCertErrors: false, - auth - }; + /** + * Create a new ABAP service provider using @sap-ux/system-access. + * + * @param credentials - user credentials + * @param backendTarget - backend target from prompt options + * @returns abap service provider + */ + private static async createNewServiceProvider( + credentials?: Credentials, + backendTarget?: BackendTarget + ): Promise { + const abapTarget: AbapTarget = this.buildAbapTarget(backendTarget); + const requestOptions = this.buildRequestOptions(credentials); + const serviceProvider = await createAbapServiceProvider(abapTarget, requestOptions, false, LoggerHelper.logger); - const serviceProvider = await createAbapServiceProvider(abapTarget, requestOptions, false, LoggerHelper.logger); + this.system = this.getSystemConfig(backendTarget); - system = { - url: PromptState.abapDeployConfig.url, - client: PromptState.abapDeployConfig.client, - destination: PromptState.abapDeployConfig.destination - }; + return serviceProvider; + } - return serviceProvider; -} + /** + * Build the ABAP target using the prompt state, containing the config assigned during system selection. + * + * @param backendTarget - backend target from prompt options + * @returns abap target + */ + private static buildAbapTarget(backendTarget?: BackendTarget): AbapTarget { + let abapTarget: AbapTarget; + if (isAppStudio()) { + abapTarget = { + destination: PromptState.abapDeployConfig.destination ?? backendTarget?.abapTarget.destination + } as DestinationAbapTarget; + } else { + abapTarget = { + url: PromptState.abapDeployConfig.url ?? backendTarget?.abapTarget.url, + client: PromptState.abapDeployConfig.client ?? backendTarget?.abapTarget.client, + scp: PromptState.abapDeployConfig.scp ?? backendTarget?.abapTarget.scp + } as UrlAbapTarget; -/** - * An abapServiceProvider is cached when it is created. However, we have scenarios such as to test - * query the backend and then show user credential prompts if getting 401 error response. In this case, - * we need to clear the cached service provider, and allow createNewServiceProvider to be called again - * after user provided user credentials. - */ -export function deleteCachedServiceProvider(): void { - abapServiceProvider = undefined; + if ( + PromptState.abapDeployConfig.isS4HC ?? + backendTarget?.abapTarget.authenticationType === AuthenticationType.ReentranceTicket + ) { + abapTarget.authenticationType = AuthenticationType.ReentranceTicket; + } + } + return abapTarget; + } + + /** + * Build the request options. + * + * @param credentials - user credentials + * @returns request options + */ + private static buildRequestOptions(credentials?: Credentials): AxiosRequestConfig & Partial { + let auth; + if (credentials?.username && credentials?.password) { + auth = { + username: credentials.username, + password: credentials.password + }; + } + + return { + ignoreCertErrors: false, + auth + }; + } + + /** + * Clear the cached service provider. + */ + public static deleteExistingServiceProvider(): void { + this.abapServiceProvider = undefined; + } } diff --git a/packages/abap-deploy-config-inquirer/src/service-provider-utils/create-transport.ts b/packages/abap-deploy-config-inquirer/src/service-provider-utils/create-transport.ts index 0ca06adada..c2c29ad1fb 100644 --- a/packages/abap-deploy-config-inquirer/src/service-provider-utils/create-transport.ts +++ b/packages/abap-deploy-config-inquirer/src/service-provider-utils/create-transport.ts @@ -1,26 +1,24 @@ import { TransportRequestService } from '@sap-ux/axios-extension'; import { t } from '../i18n'; -import { getOrCreateServiceProvider } from './abap-service-provider'; +import { AbapServiceProviderManager } from './abap-service-provider'; import LoggerHelper from '../logger-helper'; import type { NewUi5ObjectRequestParams } from '@sap-ux/axios-extension'; -import type { BackendTarget, SystemConfig } from '../types'; +import type { BackendTarget } from '../types'; /** * Create a transport number from the service. * * @param createTransportParams - input parameters for creating a new transport request for an UI5 app object - * @param systemConfig - system configuration * @param backendTarget - backend target * @returns transport number if created successfully, otherwise undefined */ export async function createTransportNumberFromService( createTransportParams: NewUi5ObjectRequestParams, - systemConfig: SystemConfig, backendTarget?: BackendTarget ): Promise { let transportReqNumber: string | undefined; try { - const provider = await getOrCreateServiceProvider(systemConfig, backendTarget); + const provider = await AbapServiceProviderManager.getOrCreateServiceProvider(backendTarget); const adtService = await provider.getAdtService(TransportRequestService); if (adtService) { transportReqNumber = await adtService.createTransportRequest(createTransportParams); diff --git a/packages/abap-deploy-config-inquirer/src/service-provider-utils/index.ts b/packages/abap-deploy-config-inquirer/src/service-provider-utils/index.ts index 331328f16f..c44efdbd49 100644 --- a/packages/abap-deploy-config-inquirer/src/service-provider-utils/index.ts +++ b/packages/abap-deploy-config-inquirer/src/service-provider-utils/index.ts @@ -1,4 +1,3 @@ -export * from './abap-service-provider'; export * from './create-transport'; export * from './list-packages'; export * from './transport-config'; diff --git a/packages/abap-deploy-config-inquirer/src/service-provider-utils/list-packages.ts b/packages/abap-deploy-config-inquirer/src/service-provider-utils/list-packages.ts index 5ebe31c112..0d8db0ce98 100644 --- a/packages/abap-deploy-config-inquirer/src/service-provider-utils/list-packages.ts +++ b/packages/abap-deploy-config-inquirer/src/service-provider-utils/list-packages.ts @@ -1,25 +1,20 @@ import { ListPackageService } from '@sap-ux/axios-extension'; -import { getOrCreateServiceProvider } from './abap-service-provider'; +import { AbapServiceProviderManager } from './abap-service-provider'; import { ABAP_PACKAGE_SEARCH_MAX_RESULTS } from '../constants'; import { t } from '../i18n'; import LoggerHelper from '../logger-helper'; -import type { BackendTarget, SystemConfig } from '../types'; +import type { BackendTarget } from '../types'; /** * List packages from the service. * * @param phrase - search phrase - * @param systemConfig - system configuration * @param backendTarget - backend target from abap deploy config prompt options * @returns list of packages */ -export async function listPackagesFromService( - phrase: string, - systemConfig: SystemConfig, - backendTarget?: BackendTarget -): Promise { +export async function listPackagesFromService(phrase: string, backendTarget?: BackendTarget): Promise { try { - const provider = await getOrCreateServiceProvider(systemConfig, backendTarget); + const provider = await AbapServiceProviderManager.getOrCreateServiceProvider(backendTarget); const adtService = await provider.getAdtService(ListPackageService); if (adtService) { return await adtService.listPackages({ diff --git a/packages/abap-deploy-config-inquirer/src/service-provider-utils/transport-config.ts b/packages/abap-deploy-config-inquirer/src/service-provider-utils/transport-config.ts index 46aece6ca6..3f67a52f9e 100644 --- a/packages/abap-deploy-config-inquirer/src/service-provider-utils/transport-config.ts +++ b/packages/abap-deploy-config-inquirer/src/service-provider-utils/transport-config.ts @@ -1,9 +1,9 @@ import { AtoService } from '@sap-ux/axios-extension'; import { t } from '../i18n'; -import { deleteCachedServiceProvider, getOrCreateServiceProvider } from './abap-service-provider'; +import { AbapServiceProviderManager } from './abap-service-provider'; import LoggerHelper from '../logger-helper'; import type { AtoSettings } from '@sap-ux/axios-extension'; -import type { TransportConfig, InitTransportConfigResult, SystemConfig, Credentials, BackendTarget } from '../types'; +import type { TransportConfig, InitTransportConfigResult, Credentials, BackendTarget } from '../types'; /** * Dummy transport configuration. @@ -111,22 +111,19 @@ class DefaultTransportConfig implements TransportConfig { * * @param initParams - init transport config parameters * @param initParams.backendTarget - backend target from prompt options - * @param initParams.systemConfig - system configuration * @param initParams.credentials - user credentials * @returns init transport config result */ public async init({ backendTarget, - systemConfig, credentials }: { backendTarget?: BackendTarget; - systemConfig: SystemConfig; credentials?: Credentials; }): Promise { const result: InitTransportConfigResult = {}; try { - const provider = await getOrCreateServiceProvider(systemConfig, backendTarget, credentials); + const provider = await AbapServiceProviderManager.getOrCreateServiceProvider(backendTarget, credentials); const atoService = await provider.getAdtService(AtoService); const atoSettings = await atoService?.getAtoInfo(); @@ -134,7 +131,7 @@ class DefaultTransportConfig implements TransportConfig { result.error = this.handleAtoResponse(atoSettings); } } catch (err) { - deleteCachedServiceProvider(); + AbapServiceProviderManager.deleteExistingServiceProvider(); if (err.response?.status === 401) { const auth: string = err.response.headers?.['www-authenticate']; result.transportConfigNeedsCreds = !!auth?.toLowerCase()?.startsWith('basic'); @@ -220,24 +217,21 @@ class DefaultTransportConfig implements TransportConfig { * @param transportConfigOptions - transport configuration options * @param transportConfigOptions.backendTarget - backend target from prompt options * @param transportConfigOptions.scp - scp - * @param transportConfigOptions.systemConfig - system configuration * @param transportConfigOptions.credentials - user credentials * @returns transport configuration instance */ export async function getTransportConfigInstance({ backendTarget, scp, - credentials, - systemConfig + credentials }: { backendTarget?: BackendTarget; scp?: boolean; credentials?: Credentials; - systemConfig: SystemConfig; }): Promise { if (scp) { return { transportConfig: new DummyTransportConfig() }; } - return new DefaultTransportConfig().init({ backendTarget, systemConfig, credentials }); + return new DefaultTransportConfig().init({ backendTarget, credentials }); } diff --git a/packages/abap-deploy-config-inquirer/src/service-provider-utils/transport-list.ts b/packages/abap-deploy-config-inquirer/src/service-provider-utils/transport-list.ts index a92f8faba1..de0882e185 100644 --- a/packages/abap-deploy-config-inquirer/src/service-provider-utils/transport-list.ts +++ b/packages/abap-deploy-config-inquirer/src/service-provider-utils/transport-list.ts @@ -1,9 +1,9 @@ import { TransportChecksService } from '@sap-ux/axios-extension'; import { t } from '../i18n'; -import { getOrCreateServiceProvider } from './abap-service-provider'; +import { AbapServiceProviderManager } from './abap-service-provider'; import LoggerHelper from '../logger-helper'; import { PromptState } from '../prompts/prompt-state'; -import type { BackendTarget, SystemConfig, TransportListItem } from '../types'; +import type { BackendTarget, TransportListItem } from '../types'; import type { ListChoiceOptions } from 'inquirer'; /** @@ -11,19 +11,17 @@ import type { ListChoiceOptions } from 'inquirer'; * * @param packageName - package name * @param appName - app name - * @param systemConfig - system configuration * @param backendTarget - backend target * @returns list of transport numbers. */ export async function getTransportListFromService( packageName: string, appName: string, - systemConfig: SystemConfig, backendTarget?: BackendTarget ): Promise { let transportListItems: TransportListItem[] | undefined; try { - const provider = await getOrCreateServiceProvider(systemConfig, backendTarget); + const provider = await AbapServiceProviderManager.getOrCreateServiceProvider(backendTarget); const adtService = await provider.getAdtService(TransportChecksService); const transportReqList = await adtService?.getTransportRequests(packageName, appName); diff --git a/packages/abap-deploy-config-inquirer/src/utils.ts b/packages/abap-deploy-config-inquirer/src/utils.ts index 1ac8e6edcc..2067603b03 100644 --- a/packages/abap-deploy-config-inquirer/src/utils.ts +++ b/packages/abap-deploy-config-inquirer/src/utils.ts @@ -104,7 +104,6 @@ export async function initTransportConfig({ backendTarget, scp, url, - client, destination, credentials, errorHandler @@ -122,18 +121,11 @@ export async function initTransportConfig({ return result; } - const systemConfig = { - url, - client, - destination - }; - try { result = await getTransportConfigInstance({ backendTarget, scp, - credentials, - systemConfig + credentials }); } catch (e) { result.error = e; @@ -163,6 +155,7 @@ export async function queryPackages( backendTarget?: BackendTarget ): Promise { const uppercaseInput = (input ?? '').toUpperCase(); + return listPackages(uppercaseInput, inputSystemConfig, backendTarget); } @@ -280,3 +273,25 @@ export function reconcileAnswers( return reconciledAnswers; } + +/** + * If the prompts are being use standalone then the system configuration is derived from the prompt options rather than the state. + * + * @param useStandalone - whether the prompts are used standalone + * @param abapDeployConfig - abap deploy config answers derived from the state i.e system selection prompt answers + * @param backendTarget - backend target from abap deploy config prompt options + * @returns system configuration + */ +export function getSystemConfig( + useStandalone: boolean, + abapDeployConfig?: Partial, + backendTarget?: BackendTarget +): SystemConfig { + const configSource = useStandalone ? backendTarget?.abapTarget : abapDeployConfig; + + return { + url: configSource?.url, + client: configSource?.client, + destination: configSource?.destination + }; +} diff --git a/packages/abap-deploy-config-inquirer/src/validator-utils.ts b/packages/abap-deploy-config-inquirer/src/validator-utils.ts index 5135ea2991..415d23b59f 100644 --- a/packages/abap-deploy-config-inquirer/src/validator-utils.ts +++ b/packages/abap-deploy-config-inquirer/src/validator-utils.ts @@ -60,7 +60,7 @@ export async function listPackages( return []; } - return listPackagesFromService(input, systemConfig, backendTarget); + return listPackagesFromService(input, backendTarget); } /** @@ -154,7 +154,7 @@ export async function getTransportList( return undefined; } - const transportList = await getTransportListFromService(packageName, appName, systemConfig, backendTarget); + const transportList = await getTransportListFromService(packageName, appName, backendTarget); return transportList?.length === 1 && transportList[0].transportReqNumber === '' ? [] : transportList; } @@ -181,6 +181,5 @@ export async function createTransportNumber( if (!systemConfig.url && !systemConfig.destination) { return undefined; } - - return createTransportNumberFromService(createTransportParams, systemConfig, backendTarget); + return createTransportNumberFromService(createTransportParams, backendTarget); } diff --git a/packages/abap-deploy-config-inquirer/test/prompts/helpers.test.ts b/packages/abap-deploy-config-inquirer/test/prompts/helpers.test.ts index d929c22de1..702cd06951 100644 --- a/packages/abap-deploy-config-inquirer/test/prompts/helpers.test.ts +++ b/packages/abap-deploy-config-inquirer/test/prompts/helpers.test.ts @@ -194,7 +194,7 @@ describe('helpers', () => { describe('getPackageChoices', () => { it('should return package choices and empty message', async () => { mockQueryPackages.mockResolvedValueOnce(['package1', 'package2']); - const result = await getPackageChoices(true, 'pack', { url: '', package: '' }); + const result = await getPackageChoices(true, 'pack', {}, { url: '', package: '' }); expect(result).toEqual({ packages: ['package1', 'package2'], morePackageResultsMsg: '' @@ -209,7 +209,7 @@ describe('helpers', () => { } mockQueryPackages.mockResolvedValueOnce(packages); - const result = await getPackageChoices(true, 'pack', { url: '', package: '' }); + const result = await getPackageChoices(true, 'pack', {}, { url: '', package: '' }); expect(result).toEqual({ packages, morePackageResultsMsg: t('prompts.config.package.packageAutocomplete.sourceMessage', { @@ -221,11 +221,16 @@ describe('helpers', () => { it('should return package choices and have previous answer to the top', async () => { mockQueryPackages.mockResolvedValueOnce(['package1', 'package2', 'package3']); - const result = await getPackageChoices(true, 'pack', { - url: '', - package: '', - packageAutocomplete: 'package3' - }); + const result = await getPackageChoices( + true, + 'pack', + {}, + { + url: '', + package: '', + packageAutocomplete: 'package3' + } + ); expect(result).toEqual({ packages: ['package3', 'package1', 'package2'], morePackageResultsMsg: '' diff --git a/packages/abap-deploy-config-inquirer/test/prompts/questions/config/package.test.ts b/packages/abap-deploy-config-inquirer/test/prompts/questions/config/package.test.ts index c3deefbb48..bc8292ec11 100644 --- a/packages/abap-deploy-config-inquirer/test/prompts/questions/config/package.test.ts +++ b/packages/abap-deploy-config-inquirer/test/prompts/questions/config/package.test.ts @@ -102,8 +102,7 @@ describe('getPackagePrompts', () => { const validatePackageChoiceInputForCliSpy = jest.spyOn(validators, 'validatePackageChoiceInputForCli'); validatePackageChoiceInputForCliSpy.mockResolvedValueOnce(); // Cli - PromptState.isYUI = false; - const packagePromptsCli = getPackagePrompts({}); + const packagePromptsCli = getPackagePrompts({}, false, false); const packageCliExecutionPromptCli = packagePromptsCli.find( (prompt) => prompt.name === promptNames.packageCliExecution ); @@ -113,9 +112,7 @@ describe('getPackagePrompts', () => { } // Vscode - PromptState.isYUI = true; - - const packagePrompts = getPackagePrompts({}); + const packagePrompts = getPackagePrompts({}, true, true); const packageCliExecutionPrompt = packagePrompts.find( (prompt) => prompt.name === promptNames.packageCliExecution ); diff --git a/packages/abap-deploy-config-inquirer/test/prompts/validators.test.ts b/packages/abap-deploy-config-inquirer/test/prompts/validators.test.ts index c3aadfde32..b76bc95f2a 100644 --- a/packages/abap-deploy-config-inquirer/test/prompts/validators.test.ts +++ b/packages/abap-deploy-config-inquirer/test/prompts/validators.test.ts @@ -309,7 +309,7 @@ describe('Test validators', () => { ui5AbapRepo: 'ZUI5REPO' }); expect(result).toBe(true); - expect(getTransportListFromServiceSpy).toBeCalledWith('ZPACKAGE', 'ZUI5REPO', {}, undefined); + expect(getTransportListFromServiceSpy).toBeCalledWith('ZPACKAGE', 'ZUI5REPO', undefined); }); it('should return error for invalid package input', async () => { const result = await validatePackage(' ', previousAnswers); @@ -329,10 +329,14 @@ describe('Test validators', () => { }); it('should return error for invalid package / ui5 abap repo name', async () => { - let result = await validateTransportChoiceInput(TransportChoices.ListExistingChoice, previousAnswers); + let result = await validateTransportChoiceInput( + false, + TransportChoices.ListExistingChoice, + previousAnswers + ); expect(result).toBe(t('errors.validators.transportListPreReqs')); - result = await validateTransportChoiceInput(TransportChoices.ListExistingChoice, { + result = await validateTransportChoiceInput(false, TransportChoices.ListExistingChoice, { ...previousAnswers, packageManual: 'ZPACKAGE' }); @@ -343,7 +347,7 @@ describe('Test validators', () => { jest.spyOn(validatorUtils, 'getTransportList').mockResolvedValueOnce([ { transportReqNumber: 'K123456', transportReqDescription: 'Mock transport request' } ]); - const result = await validateTransportChoiceInput(TransportChoices.ListExistingChoice, { + const result = await validateTransportChoiceInput(false, TransportChoices.ListExistingChoice, { ...previousAnswers, packageManual: 'ZPACKAGE', ui5AbapRepo: 'ZUI5REPO' @@ -354,7 +358,7 @@ describe('Test validators', () => { it('should return errors messages for listing transport when transport request empty or undefined', async () => { jest.spyOn(validatorUtils, 'getTransportList').mockResolvedValueOnce([]); - let result = await validateTransportChoiceInput(TransportChoices.ListExistingChoice, { + let result = await validateTransportChoiceInput(false, TransportChoices.ListExistingChoice, { ...previousAnswers, packageManual: 'ZPACKAGE', ui5AbapRepo: 'ZUI5REPO' @@ -362,7 +366,7 @@ describe('Test validators', () => { expect(result).toBe(t('warnings.noTransportReqs')); jest.spyOn(validatorUtils, 'getTransportList').mockResolvedValueOnce(undefined); - result = await validateTransportChoiceInput(TransportChoices.ListExistingChoice, { + result = await validateTransportChoiceInput(false, TransportChoices.ListExistingChoice, { ...previousAnswers, packageManual: 'ZPACKAGE', ui5AbapRepo: 'ZUI5REPO' @@ -372,6 +376,7 @@ describe('Test validators', () => { it('should return true if transport request is same as previous', async () => { const result = await validateTransportChoiceInput( + false, TransportChoices.CreateNewChoice, previousAnswers, true, @@ -386,6 +391,7 @@ describe('Test validators', () => { ]); const result = await validateTransportChoiceInput( + false, TransportChoices.CreateNewChoice, previousAnswers, true, @@ -399,6 +405,7 @@ describe('Test validators', () => { jest.spyOn(validatorUtils, 'createTransportNumber').mockResolvedValueOnce('TR1234'); const result = await validateTransportChoiceInput( + false, TransportChoices.CreateNewChoice, previousAnswers, false, @@ -412,6 +419,7 @@ describe('Test validators', () => { jest.spyOn(validatorUtils, 'createTransportNumber').mockResolvedValueOnce(undefined); const result = await validateTransportChoiceInput( + false, TransportChoices.CreateNewChoice, previousAnswers, false, @@ -423,6 +431,7 @@ describe('Test validators', () => { it('should return error if creating a new transport request returns undefined', async () => { const result = await validateTransportChoiceInput( + false, TransportChoices.EnterManualChoice, previousAnswers, false, diff --git a/packages/abap-deploy-config-inquirer/test/service-provider-utils/abap-service-provider.test.ts b/packages/abap-deploy-config-inquirer/test/service-provider-utils/abap-service-provider.test.ts index 87bcbf96f1..32f2b2e33a 100644 --- a/packages/abap-deploy-config-inquirer/test/service-provider-utils/abap-service-provider.test.ts +++ b/packages/abap-deploy-config-inquirer/test/service-provider-utils/abap-service-provider.test.ts @@ -1,7 +1,4 @@ -import { - getOrCreateServiceProvider, - deleteCachedServiceProvider -} from '../../src/service-provider-utils/abap-service-provider'; +import { AbapServiceProviderManager } from '../../src/service-provider-utils/abap-service-provider'; import { isAppStudio } from '@sap-ux/btp-utils'; import { createAbapServiceProvider } from '@sap-ux/system-access'; import { PromptState } from '../../src/prompts/prompt-state'; @@ -23,7 +20,7 @@ const mockIsAppStudio = isAppStudio as jest.Mock; describe('getOrCreateServiceProvider', () => { afterEach(() => { - deleteCachedServiceProvider(); + AbapServiceProviderManager.deleteExistingServiceProvider(); }); it('should return an instance of AbapServiceProvider (VSCode)', async () => { @@ -41,7 +38,7 @@ describe('getOrCreateServiceProvider', () => { password: 'password1' }; - const serviceProvider = await getOrCreateServiceProvider({}, undefined, credentials); + const serviceProvider = await AbapServiceProviderManager.getOrCreateServiceProvider(undefined, credentials); expect(serviceProvider).toBeInstanceOf(AbapServiceProvider); expect(mockCreateAbapServiceProvider).toBeCalledWith( @@ -57,14 +54,7 @@ describe('getOrCreateServiceProvider', () => { ); // use existing provider when called again - const serviceProvider2 = await getOrCreateServiceProvider( - { - url: 'http://target.url', - client: '100' - }, - undefined, - credentials - ); + const serviceProvider2 = await AbapServiceProviderManager.getOrCreateServiceProvider(undefined, credentials); expect(serviceProvider2).toBe(serviceProvider); }); @@ -75,7 +65,7 @@ describe('getOrCreateServiceProvider', () => { destination: 'MOCK_DESTINATION' }; - const serviceProvider = await getOrCreateServiceProvider({}); + const serviceProvider = await AbapServiceProviderManager.getOrCreateServiceProvider(); expect(serviceProvider).toBeInstanceOf(AbapServiceProvider); expect(mockCreateAbapServiceProvider).toBeCalledWith( @@ -100,7 +90,7 @@ describe('getOrCreateServiceProvider', () => { serviceProvider: abapServiceProvider }; - const serviceProvider = await getOrCreateServiceProvider(systemConfig, backendTarget); + const serviceProvider = await AbapServiceProviderManager.getOrCreateServiceProvider(backendTarget); expect(serviceProvider).toBe(abapServiceProvider); }); }); diff --git a/packages/abap-deploy-config-inquirer/test/service-provider-utils/create-transport.test.ts b/packages/abap-deploy-config-inquirer/test/service-provider-utils/create-transport.test.ts index 1cc2c61166..3771c99257 100644 --- a/packages/abap-deploy-config-inquirer/test/service-provider-utils/create-transport.test.ts +++ b/packages/abap-deploy-config-inquirer/test/service-provider-utils/create-transport.test.ts @@ -1,14 +1,14 @@ import { initI18n, t } from '../../src/i18n'; import LoggerHelper from '../../src/logger-helper'; import { createTransportNumberFromService } from '../../src/service-provider-utils'; -import { getOrCreateServiceProvider } from '../../src/service-provider-utils/abap-service-provider'; +import { AbapServiceProviderManager } from '../../src/service-provider-utils/abap-service-provider'; jest.mock('../../src/service-provider-utils/abap-service-provider', () => ({ ...jest.requireActual('../../src/service-provider-utils/abap-service-provider'), - getOrCreateServiceProvider: jest.fn() + AbapServiceProviderManager: { getOrCreateServiceProvider: jest.fn() } })); -const mockGetOrCreateServiceProvider = getOrCreateServiceProvider as jest.Mock; +const mockGetOrCreateServiceProvider = AbapServiceProviderManager.getOrCreateServiceProvider as jest.Mock; describe('Test create transport', () => { beforeAll(async () => { @@ -20,10 +20,6 @@ describe('Test create transport', () => { ui5AppName: 'mockApp', description: 'mockDescription' }; - const systemConfig = { - url: 'https://mock.url.target1.com', - client: '000' - }; it('should return a new transport number', async () => { const mockGetAdtService = { @@ -34,7 +30,7 @@ describe('Test create transport', () => { getAdtService: jest.fn().mockResolvedValueOnce(mockGetAdtService) }); - const transportNumber = await createTransportNumberFromService(createTransportParams, systemConfig); + const transportNumber = await createTransportNumberFromService(createTransportParams); expect(transportNumber).toBe('NEWTR123'); }); @@ -43,7 +39,7 @@ describe('Test create transport', () => { const loggerSpy = jest.spyOn(LoggerHelper.logger, 'debug'); mockGetOrCreateServiceProvider.mockRejectedValueOnce(errorObj); - const transportNumber = await createTransportNumberFromService(createTransportParams, systemConfig); + const transportNumber = await createTransportNumberFromService(createTransportParams); expect(transportNumber).toBe(undefined); expect(loggerSpy).toBeCalledWith( t('errors.debugAbapTargetSystem', { method: 'createTransportNumberFromService', error: errorObj.message }) diff --git a/packages/abap-deploy-config-inquirer/test/service-provider-utils/list-packages.test.ts b/packages/abap-deploy-config-inquirer/test/service-provider-utils/list-packages.test.ts index fa9a383d7d..6bbcd22308 100644 --- a/packages/abap-deploy-config-inquirer/test/service-provider-utils/list-packages.test.ts +++ b/packages/abap-deploy-config-inquirer/test/service-provider-utils/list-packages.test.ts @@ -1,14 +1,14 @@ import { initI18n, t } from '../../src/i18n'; import LoggerHelper from '../../src/logger-helper'; import { listPackagesFromService } from '../../src/service-provider-utils'; -import { getOrCreateServiceProvider } from '../../src/service-provider-utils/abap-service-provider'; +import { AbapServiceProviderManager } from '../../src/service-provider-utils/abap-service-provider'; jest.mock('../../src/service-provider-utils/abap-service-provider', () => ({ ...jest.requireActual('../../src/service-provider-utils/abap-service-provider'), - getOrCreateServiceProvider: jest.fn() + AbapServiceProviderManager: { getOrCreateServiceProvider: jest.fn() } })); -const mockGetOrCreateServiceProvider = getOrCreateServiceProvider as jest.Mock; +const mockGetOrCreateServiceProvider = AbapServiceProviderManager.getOrCreateServiceProvider as jest.Mock; describe('Test list packages', () => { beforeAll(async () => { @@ -17,11 +17,6 @@ describe('Test list packages', () => { const phrase = 'ZPACK'; - const systemConfig = { - url: 'https://mock.url.target1.com', - client: '000' - }; - it('should return a list of packages', async () => { const packages = ['ZPACK1', 'ZPACK2']; const mockGetAdtService = { @@ -32,7 +27,7 @@ describe('Test list packages', () => { getAdtService: jest.fn().mockResolvedValueOnce(mockGetAdtService) }); - const allPackages = await listPackagesFromService(phrase, systemConfig); + const allPackages = await listPackagesFromService(phrase); expect(allPackages).toBe(packages); }); @@ -41,7 +36,7 @@ describe('Test list packages', () => { const loggerSpy = jest.spyOn(LoggerHelper.logger, 'debug'); mockGetOrCreateServiceProvider.mockRejectedValueOnce(errorObj); - const allPackages = await listPackagesFromService(phrase, systemConfig); + const allPackages = await listPackagesFromService(phrase); expect(allPackages).toStrictEqual([]); expect(loggerSpy).toBeCalledWith( t('errors.debugAbapTargetSystem', { method: 'listPackagesFromService', error: errorObj.message }) diff --git a/packages/abap-deploy-config-inquirer/test/service-provider-utils/transport-config.test.ts b/packages/abap-deploy-config-inquirer/test/service-provider-utils/transport-config.test.ts index 5af94c4b11..4db6d054b2 100644 --- a/packages/abap-deploy-config-inquirer/test/service-provider-utils/transport-config.test.ts +++ b/packages/abap-deploy-config-inquirer/test/service-provider-utils/transport-config.test.ts @@ -1,22 +1,21 @@ import { AtoSettings, TenantType } from '@sap-ux/axios-extension'; import { t } from '../../src/i18n'; import { getTransportConfigInstance } from '../../src/service-provider-utils'; -import { getOrCreateServiceProvider } from '../../src/service-provider-utils/abap-service-provider'; +import { AbapServiceProviderManager } from '../../src/service-provider-utils/abap-service-provider'; jest.mock('../../src/service-provider-utils/abap-service-provider', () => ({ ...jest.requireActual('../../src/service-provider-utils/abap-service-provider'), - getOrCreateServiceProvider: jest.fn() + AbapServiceProviderManager: { getOrCreateServiceProvider: jest.fn(), deleteExistingServiceProvider: jest.fn() } })); -const mockGetOrCreateServiceProvider = getOrCreateServiceProvider as jest.Mock; +const mockGetOrCreateServiceProvider = AbapServiceProviderManager.getOrCreateServiceProvider as jest.Mock; describe('getTransportConfigInstance', () => { it('should return the dummy instance of TransportConfig', async () => { const transportConfigResult = await getTransportConfigInstance({ backendTarget: undefined, scp: true, - credentials: {}, - systemConfig: {} + credentials: {} }); expect(transportConfigResult.transportConfig?.getPackage()).toBe(undefined); expect(transportConfigResult.transportConfig?.getApplicationPrefix()).toBe(undefined); @@ -43,8 +42,7 @@ describe('getTransportConfigInstance', () => { const transportConfigResult = await getTransportConfigInstance({ backendTarget: undefined, scp: false, - credentials: {}, - systemConfig: {} + credentials: {} }); expect(transportConfigResult.transportConfig?.getOperationsType()).toBe('P'); @@ -72,8 +70,7 @@ describe('getTransportConfigInstance', () => { const transportConfigResult = await getTransportConfigInstance({ backendTarget: undefined, scp: false, - credentials: {}, - systemConfig: {} + credentials: {} }); expect(transportConfigResult.error).toBe(t('errors.s4SystemNoExtensible')); @@ -91,8 +88,7 @@ describe('getTransportConfigInstance', () => { const transportConfigResult2 = await getTransportConfigInstance({ backendTarget: undefined, scp: false, - credentials: {}, - systemConfig: {} + credentials: {} }); expect(transportConfigResult2.error).toBe(t('errors.incorrectAtoSettings')); @@ -117,8 +113,7 @@ describe('getTransportConfigInstance', () => { const transportConfigResult = await getTransportConfigInstance({ backendTarget: undefined, scp: false, - credentials: {}, - systemConfig: {} + credentials: {} }); expect(transportConfigResult.transportConfig?.isTransportRequired()).toBe(false); @@ -143,8 +138,7 @@ describe('getTransportConfigInstance', () => { const transportConfigResult = await getTransportConfigInstance({ backendTarget: undefined, scp: false, - credentials: {}, - systemConfig: {} + credentials: {} }); expect(transportConfigResult.transportConfigNeedsCreds).toBe(true); @@ -163,8 +157,7 @@ describe('getTransportConfigInstance', () => { const transportConfigResultWithoutHeaders = await getTransportConfigInstance({ backendTarget: undefined, scp: false, - credentials: {}, - systemConfig: {} + credentials: {} }); expect(transportConfigResultWithoutHeaders.transportConfigNeedsCreds).toBe(false); @@ -183,8 +176,7 @@ describe('getTransportConfigInstance', () => { const transportConfigResult2 = await getTransportConfigInstance({ backendTarget: undefined, scp: false, - credentials: {}, - systemConfig: {} + credentials: {} }); expect(transportConfigResult2.transportConfigNeedsCreds).toBe(false); expect(transportConfigResult2.warning).toBe('Failed to get ATO info'); diff --git a/packages/abap-deploy-config-inquirer/test/service-provider-utils/transport-list.test.ts b/packages/abap-deploy-config-inquirer/test/service-provider-utils/transport-list.test.ts index 61cac1feda..186d8c2ea3 100644 --- a/packages/abap-deploy-config-inquirer/test/service-provider-utils/transport-list.test.ts +++ b/packages/abap-deploy-config-inquirer/test/service-provider-utils/transport-list.test.ts @@ -1,14 +1,14 @@ import { initI18n, t } from '../../src/i18n'; import LoggerHelper from '../../src/logger-helper'; import { getTransportListFromService, transportName } from '../../src/service-provider-utils'; -import { getOrCreateServiceProvider } from '../../src/service-provider-utils/abap-service-provider'; +import { AbapServiceProviderManager } from '../../src/service-provider-utils/abap-service-provider'; jest.mock('../../src/service-provider-utils/abap-service-provider', () => ({ ...jest.requireActual('../../src/service-provider-utils/abap-service-provider'), - getOrCreateServiceProvider: jest.fn() + AbapServiceProviderManager: { getOrCreateServiceProvider: jest.fn(), deleteExistingServiceProvider: jest.fn() } })); -const mockGetOrCreateServiceProvider = getOrCreateServiceProvider as jest.Mock; +const mockGetOrCreateServiceProvider = AbapServiceProviderManager.getOrCreateServiceProvider as jest.Mock; describe('Test list transports', () => { beforeAll(async () => { @@ -17,10 +17,6 @@ describe('Test list transports', () => { const packageName = 'ZPACK'; const appName = 'MOCK_APP'; - const systemConfig = { - url: 'https://mock.url.target1.com', - client: '000' - }; it('should return a list of transports', async () => { const transports = [ @@ -35,7 +31,7 @@ describe('Test list transports', () => { getAdtService: jest.fn().mockResolvedValueOnce(mockGetAdtService) }); - const allTransports = await getTransportListFromService(packageName, appName, systemConfig); + const allTransports = await getTransportListFromService(packageName, appName); expect(allTransports).toStrictEqual([ { transportReqNumber: 'TR122C', transportReqDescription: 'TR1 description' }, @@ -48,7 +44,7 @@ describe('Test list transports', () => { const loggerSpy = jest.spyOn(LoggerHelper.logger, 'debug'); mockGetOrCreateServiceProvider.mockRejectedValueOnce(errorObj); - const allTransports = await getTransportListFromService(packageName, appName, systemConfig); + const allTransports = await getTransportListFromService(packageName, appName); expect(allTransports).toStrictEqual(undefined); expect(loggerSpy).toBeCalledWith( t('errors.debugAbapTargetSystem', { method: 'getTransportListFromService', error: errorObj.message }) diff --git a/packages/abap-deploy-config-inquirer/test/utils.test.ts b/packages/abap-deploy-config-inquirer/test/utils.test.ts index f4fedcd0da..a714b0596b 100644 --- a/packages/abap-deploy-config-inquirer/test/utils.test.ts +++ b/packages/abap-deploy-config-inquirer/test/utils.test.ts @@ -129,12 +129,7 @@ describe('Test utils', () => { expect(mockGetTransportConfigInstance).toBeCalledWith({ backendTarget: undefined, scp: false, - credentials: undefined, - systemConfig: { - url: 'https://mocktarget.url', - client: '100', - destination: undefined - } + credentials: undefined }); expect(initTransportConfigResult.transportConfigNeedsCreds).toBe(true); }); @@ -159,12 +154,7 @@ describe('Test utils', () => { expect(mockGetTransportConfigInstance).toBeCalledWith({ backendTarget: undefined, scp: false, - credentials: undefined, - systemConfig: { - url: 'https://mocktarget.url', - client: '100', - destination: undefined - } + credentials: undefined }); expect(initTransportConfigResult.error).toStrictEqual(errorObj); expect(errorHandler).toBeCalledWith(errorObj); diff --git a/packages/abap-deploy-config-inquirer/test/validator-utils.test.ts b/packages/abap-deploy-config-inquirer/test/validator-utils.test.ts index 06d090ba1b..2d7f201576 100644 --- a/packages/abap-deploy-config-inquirer/test/validator-utils.test.ts +++ b/packages/abap-deploy-config-inquirer/test/validator-utils.test.ts @@ -56,9 +56,8 @@ describe('validator-utils', () => { }; expect(await createTransportNumber(createTransportParams, {})).toEqual(undefined); - const systemConfig = { url: 'http://mock.url', client: '123' }; mockCreateTransportNumberFromService.mockResolvedValueOnce('NEWTR1'); - expect(await createTransportNumberFromService(createTransportParams, systemConfig)).toEqual('NEWTR1'); + expect(await createTransportNumberFromService(createTransportParams)).toEqual('NEWTR1'); }); describe('isAppNameValid', () => { From e12ed735ccb6489da6b2543959a34697015a1e5c Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 17 Jan 2025 14:33:24 +0000 Subject: [PATCH 4/4] chore: apply latest changesets --- .changeset/popular-baboons-sit.md | 5 ----- packages/abap-deploy-config-inquirer/CHANGELOG.md | 6 ++++++ packages/abap-deploy-config-inquirer/package.json | 2 +- packages/abap-deploy-config-sub-generator/CHANGELOG.md | 7 +++++++ packages/abap-deploy-config-sub-generator/package.json | 2 +- packages/create/CHANGELOG.md | 7 +++++++ packages/create/package.json | 2 +- 7 files changed, 23 insertions(+), 8 deletions(-) delete mode 100644 .changeset/popular-baboons-sit.md diff --git a/.changeset/popular-baboons-sit.md b/.changeset/popular-baboons-sit.md deleted file mode 100644 index a9a9dbaee8..0000000000 --- a/.changeset/popular-baboons-sit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@sap-ux/abap-deploy-config-inquirer': minor ---- - -exposes getpackageprompts and gettransportrequest prompts diff --git a/packages/abap-deploy-config-inquirer/CHANGELOG.md b/packages/abap-deploy-config-inquirer/CHANGELOG.md index 1a851c8d0b..5617ffdb43 100644 --- a/packages/abap-deploy-config-inquirer/CHANGELOG.md +++ b/packages/abap-deploy-config-inquirer/CHANGELOG.md @@ -1,5 +1,11 @@ # @sap-ux/abap-deploy-config-inquirer +## 1.2.0 + +### Minor Changes + +- fc5916a: exposes getpackageprompts and gettransportrequest prompts + ## 1.1.21 ### Patch Changes diff --git a/packages/abap-deploy-config-inquirer/package.json b/packages/abap-deploy-config-inquirer/package.json index 3339040a57..6fd8d3ba09 100644 --- a/packages/abap-deploy-config-inquirer/package.json +++ b/packages/abap-deploy-config-inquirer/package.json @@ -6,7 +6,7 @@ "url": "https://github.com/SAP/open-ux-tools.git", "directory": "packages/abap-deploy-config-inquirer" }, - "version": "1.1.21", + "version": "1.2.0", "license": "Apache-2.0", "main": "dist/index.js", "scripts": { diff --git a/packages/abap-deploy-config-sub-generator/CHANGELOG.md b/packages/abap-deploy-config-sub-generator/CHANGELOG.md index d27d6aca71..51f61b92d9 100644 --- a/packages/abap-deploy-config-sub-generator/CHANGELOG.md +++ b/packages/abap-deploy-config-sub-generator/CHANGELOG.md @@ -1,5 +1,12 @@ # @sap-ux/abap-deploy-config-sub-generator +## 0.0.14 + +### Patch Changes + +- Updated dependencies [fc5916a] + - @sap-ux/abap-deploy-config-inquirer@1.2.0 + ## 0.0.13 ### Patch Changes diff --git a/packages/abap-deploy-config-sub-generator/package.json b/packages/abap-deploy-config-sub-generator/package.json index 81f65fe437..b9beb2fbd1 100644 --- a/packages/abap-deploy-config-sub-generator/package.json +++ b/packages/abap-deploy-config-sub-generator/package.json @@ -6,7 +6,7 @@ "url": "https://github.com/SAP/open-ux-tools.git", "directory": "packages/abap-deploy-config-sub-generator" }, - "version": "0.0.13", + "version": "0.0.14", "license": "Apache-2.0", "main": "generators/app/index.js", "scripts": { diff --git a/packages/create/CHANGELOG.md b/packages/create/CHANGELOG.md index 82a8e17a00..89bbad1696 100644 --- a/packages/create/CHANGELOG.md +++ b/packages/create/CHANGELOG.md @@ -1,5 +1,12 @@ # @sap-ux/create +## 0.11.32 + +### Patch Changes + +- Updated dependencies [fc5916a] + - @sap-ux/abap-deploy-config-inquirer@1.2.0 + ## 0.11.31 ### Patch Changes diff --git a/packages/create/package.json b/packages/create/package.json index 793f894058..be4b5e636d 100644 --- a/packages/create/package.json +++ b/packages/create/package.json @@ -1,7 +1,7 @@ { "name": "@sap-ux/create", "description": "SAP Fiori tools module to add or remove features", - "version": "0.11.31", + "version": "0.11.32", "repository": { "type": "git", "url": "https://github.com/SAP/open-ux-tools.git",