diff --git a/core/apps/ame-e2e/src/fixtures/valid-yml.yml b/core/apps/ame-e2e/src/fixtures/valid-yml.yml new file mode 100644 index 00000000..3c70be2f --- /dev/null +++ b/core/apps/ame-e2e/src/fixtures/valid-yml.yml @@ -0,0 +1,7 @@ +resourceId: + name: resourceId + in: path + description: An example resource Id. + required: true + schema: + type: string diff --git a/core/apps/ame-e2e/src/integration/editor/generation.cy.ts b/core/apps/ame-e2e/src/integration/editor/generation.cy.ts deleted file mode 100644 index 85b3b83c..00000000 --- a/core/apps/ame-e2e/src/integration/editor/generation.cy.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* eslint-disable cypress/no-unnecessary-waiting */ -/* - * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH - * - * See the AUTHORS file(s) distributed with this work for - * additional information regarding authorship. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * SPDX-License-Identifier: MPL-2.0 - */ - -/// - -import { - GENERATION_downloadFileButton, - GENERATION_tbDownloadDoc, - GENERATION_tbGenerateOpenApiButton, - GENERATION_tbOutputButton, - GENERATION_tbOutputButton_YAML, -} from '../../support/constants'; - -// TODO Change ficture workaround until cy.readFile is fixed by the cypress team -describe('Test generation and download from valid Aspect Model', () => { - it('Can generate valid JSON Open Api Specification', () => { - cy.visitDefault(); - cy.startModelling() - .then(() => cy.openGenerationOpenApiSpec().wait(500)) - .then(() => cy.get(GENERATION_tbGenerateOpenApiButton).click({force: true}).wait(5000)) - .then(() => cy.fixture('cypress/downloads/AspectDefault-open-api.json')); - }); - - it('Can generate and download valid YAML Open Api Specification', () => { - cy.visitDefault(); - cy.startModelling() - .then(() => cy.openGenerationOpenApiSpec().wait(500)) - .then(() => cy.get(GENERATION_tbOutputButton).click()) - .then(() => cy.get(GENERATION_tbOutputButton_YAML).click()) - .then(() => cy.get(GENERATION_tbGenerateOpenApiButton).click({force: true}).wait(5000)) - .then(() => cy.fixture('cypress/downloads/AspectDefault-open-api.yaml')); - }); - - it('Can generate and download valid Aspect Model documentation', () => { - cy.visitDefault(); - cy.startModelling() - .then(() => cy.openGenerationDocumentation().wait(500)) - .then(() => cy.get(GENERATION_tbDownloadDoc).click({force: true}).wait(5000)) - .then(() => cy.fixture('cypress/downloads/AspectDefault-documentation.html')); - }); - - it('Can generate and download valid Json payload', () => { - cy.visitDefault(); - cy.startModelling() - .then(() => cy.openGenerationJsonSample().wait(500)) - .then(() => cy.get(GENERATION_downloadFileButton).click({force: true}).wait(5000)) - .then(() => cy.fixture('cypress/downloads/AspectDefault-sample.json')); - }); - - it('Can generate and download valid Json schema', () => { - cy.visitDefault(); - cy.startModelling() - .then(() => cy.openGenerationJsonSchema().wait(500)) - .then(() => cy.get(GENERATION_downloadFileButton).click({force: true}).wait(5000)) - .then(() => cy.fixture('cypress/downloads/AspectDefault-schema.json')); - }); -}); diff --git a/core/apps/ame-e2e/src/integration/generation/generate-open-api.cy.ts b/core/apps/ame-e2e/src/integration/generation/generate-open-api.cy.ts new file mode 100644 index 00000000..22beae42 --- /dev/null +++ b/core/apps/ame-e2e/src/integration/generation/generate-open-api.cy.ts @@ -0,0 +1,213 @@ +/* eslint-disable cypress/no-unnecessary-waiting */ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for + * additional information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +/// + +import { + GENERATION_accordionTitle, + GENERATION_activateResourcePathCheckbox, + GENERATION_removeUploadFile, + GENERATION_resourcePathInput, + GENERATION_resourcePathPatternError, + GENERATION_resourcePathRequiredError, + GENERATION_resourcePathTitle, + GENERATION_tbBaseUrlInput, + GENERATION_tbBaseUrlInputError, + GENERATION_tbGenerateOpenApiButton, + GENERATION_tbOutputButton, + GENERATION_tbOutputButton_JSON, + GENERATION_tbOutputButton_YAML, + GENERATION_uploadContent, + GENERATION_uploadContentFileInput, + GENERATION_uploadFileRequireError, + GENERATION_uploadTitle, +} from '../../support/constants'; + +describe('Test generation and download of open api specification', () => { + it('Can generate valid JSON Open Api Specification', () => { + cy.visitDefault(); + cy.startModelling() + .then(() => cy.openGenerationOpenApiSpec().wait(500)) + .then(() => cy.get(GENERATION_tbOutputButton).click()) + .then(() => cy.get(GENERATION_tbOutputButton_JSON).click()) + .then(() => cy.get(GENERATION_tbBaseUrlInput).focus().clear().blur()) + .then(() => + cy.get(GENERATION_tbBaseUrlInputError).should('exist').should('be.visible').should('contain.text', 'Please add a valid url'), + ) + .then(() => cy.get(GENERATION_tbBaseUrlInput).focus().type('https://example.com').blur()) + .then(() => cy.get(GENERATION_tbGenerateOpenApiButton).click().wait(5000)) + .then(() => cy.fixture('cypress/downloads/en-open-api.json')); + }); + + it('Can generate valid JSON Open Api Specification with resource path', () => { + cy.visitDefault(); + cy.startModelling() + .then(() => cy.openGenerationOpenApiSpec().wait(500)) + .then(() => cy.get(GENERATION_tbOutputButton).click()) + .then(() => cy.get(GENERATION_tbOutputButton_JSON).click()) + .then(() => cy.get(GENERATION_resourcePathTitle).should('not.exist')) + .then(() => cy.get(GENERATION_activateResourcePathCheckbox).click()) + .then(() => + cy + .get(GENERATION_resourcePathTitle) + .should('exist') + .should('be.visible') + .should('contain.text', 'Resource Path - The resource path for the Aspect API endpoints'), + ) + .then(() => cy.get(GENERATION_resourcePathInput).should('exist').should('be.visible').focus().clear().blur()) + .then(() => + cy + .get(GENERATION_resourcePathRequiredError) + .should('exist') + .should('be.visible') + .should('contain.text', 'Resource path is required'), + ) + .then(() => checkResourcePath()) + .then(() => cy.get(GENERATION_uploadTitle).should('exist').should('be.visible').should('contain.text', 'Upload JSON File')) + .then(() => cy.get(GENERATION_uploadContent).should('exist').should('be.visible')) + .then(() => + cy + .get(GENERATION_uploadFileRequireError) + .should('exist') + .should('be.visible') + .should('contain.text', 'JSON file is required - a variable has been defined in the resource path.'), + ) + .then(() => cy.get(GENERATION_uploadContentFileInput).attachFile('valid-json.json')) + .then(() => cy.get(GENERATION_uploadContent).should('not.exist')) + .then(() => cy.get(GENERATION_accordionTitle).should('exist').should('be.visible').should('contain.text', 'Properties')) + .then(() => cy.get(GENERATION_tbGenerateOpenApiButton).click().wait(5000)) + .then(() => cy.fixture('cypress/downloads/en-open-api.json')); + }); + + it('Can generate and download valid YAML Open Api Specification', () => { + cy.visitDefault(); + cy.startModelling() + .then(() => cy.openGenerationOpenApiSpec().wait(500)) + .then(() => cy.get(GENERATION_tbOutputButton).click()) + .then(() => cy.get(GENERATION_tbOutputButton_YAML).click()) + .then(() => cy.get(GENERATION_tbBaseUrlInput).focus().clear().blur()) + .then(() => + cy.get(GENERATION_tbBaseUrlInputError).should('exist').should('be.visible').should('contain.text', 'Please add a valid url'), + ) + .then(() => cy.get(GENERATION_tbBaseUrlInput).focus().type('https://example.com').blur()) + .then(() => cy.get(GENERATION_tbGenerateOpenApiButton).click({force: true}).wait(5000)) + .then(() => cy.fixture('cypress/downloads/en-open-api.yaml')); + }); + + it('Can generate valid YAML Open Api Specification with resource path', () => { + cy.visitDefault(); + cy.startModelling() + .then(() => cy.openGenerationOpenApiSpec().wait(500)) + .then(() => cy.get(GENERATION_resourcePathTitle).should('not.exist')) + .then(() => cy.get(GENERATION_activateResourcePathCheckbox).click()) + .then(() => + cy + .get(GENERATION_resourcePathTitle) + .should('exist') + .should('be.visible') + .should('contain.text', 'Resource Path - The resource path for the Aspect API endpoints'), + ) + .then(() => cy.get(GENERATION_resourcePathInput).should('exist').should('be.visible').focus().clear().blur()) + .then(() => + cy + .get(GENERATION_resourcePathRequiredError) + .should('exist') + .should('be.visible') + .should('contain.text', 'Resource path is required'), + ) + .then(() => checkResourcePath()) + .then(() => cy.get(GENERATION_uploadTitle).should('exist').should('be.visible').should('contain.text', 'Upload YAML File')) + .then(() => cy.get(GENERATION_uploadContent).should('exist').should('be.visible')) + .then(() => + cy + .get(GENERATION_uploadFileRequireError) + .should('exist') + .should('be.visible') + .should('contain.text', 'YAML file is required - a variable has been defined in the resource path.'), + ) + .then(() => cy.get(GENERATION_uploadContentFileInput).attachFile('valid-yml.yml')) + .then(() => cy.get(GENERATION_uploadContent).should('not.exist')) + .then(() => cy.get(GENERATION_accordionTitle).should('exist').should('be.visible').should('contain.text', 'Properties')) + .then(() => cy.get(GENERATION_tbGenerateOpenApiButton).click().wait(5000)) + .then(() => cy.fixture('cypress/downloads/en-open-api.yaml')); + }); + + it('Test some generate variations', () => { + cy.visitDefault(); + cy.startModelling() + .then(() => cy.openGenerationOpenApiSpec().wait(500)) + .then(() => cy.get(GENERATION_resourcePathTitle).should('not.exist')) + .then(() => { + cy.get(GENERATION_activateResourcePathCheckbox).click(); + cy.get(GENERATION_resourcePathTitle) + .should('exist') + .should('be.visible') + .should('contain.text', 'Resource Path - The resource path for the Aspect API endpoints'); + cy.get(GENERATION_resourcePathInput).should('exist').should('be.visible').scrollIntoView(); + cy.get(GENERATION_uploadTitle).should('exist').should('be.visible').should('contain.text', 'Upload YAML File'); + cy.get(GENERATION_uploadContent).should('exist').should('be.visible'); + cy.get(GENERATION_uploadFileRequireError) + .should('exist') + .should('be.visible') + .should('contain.text', 'YAML file is required - a variable has been defined in the resource path.'); + cy.get(GENERATION_uploadContentFileInput).attachFile('valid-yml.yml'); + cy.get(GENERATION_uploadContent).should('not.exist'); + cy.get(GENERATION_accordionTitle).should('exist').should('be.visible').should('contain.text', 'Properties'); + cy.get(GENERATION_tbGenerateOpenApiButton).should('be.enabled'); + }) + .then(() => { + cy.get(GENERATION_removeUploadFile).click({force: true}); + cy.get(GENERATION_uploadContent).should('exist').should('be.visible'); + cy.get(GENERATION_accordionTitle).should('not.exist'); + }) + .then(() => { + cy.get(GENERATION_uploadContentFileInput).attachFile('valid-yml.yml'); + cy.get(GENERATION_tbOutputButton).click(); + cy.get(GENERATION_tbOutputButton_JSON).click(); + cy.get(GENERATION_uploadContent).should('exist').should('be.visible'); + cy.get(GENERATION_accordionTitle).should('not.exist'); + }) + .then(() => { + cy.get(GENERATION_uploadContentFileInput).attachFile('valid-json.json'); + cy.get(GENERATION_uploadContent).should('not.exist'); + cy.get(GENERATION_accordionTitle).should('exist').should('be.visible'); + cy.get(GENERATION_tbGenerateOpenApiButton).should('be.enabled'); + }) + .then(() => { + cy.get(GENERATION_activateResourcePathCheckbox).click(); + cy.get(GENERATION_uploadContent).should('not.exist'); + cy.get(GENERATION_accordionTitle).should('not.exist'); + cy.get(GENERATION_tbGenerateOpenApiButton).should('be.enabled'); + }); + }); + + function checkResourcePath(): void { + cy.get(GENERATION_resourcePathInput).focus().type('/').blur(); + cy.get(GENERATION_resourcePathPatternError).should('not.exist'); + cy.get(GENERATION_resourcePathInput).focus().clear().type('//').blur(); + cy.get(GENERATION_resourcePathPatternError).should('exist').should('be.visible').should('contain.text', 'Pattern is not matching'); + cy.get(GENERATION_resourcePathInput).focus().clear().type('resource').blur(); + cy.get(GENERATION_resourcePathPatternError).should('exist').should('be.visible').should('contain.text', 'Pattern is not matching'); + cy.get(GENERATION_resourcePathInput).focus().clear().type('/resource').blur(); + cy.get(GENERATION_resourcePathPatternError).should('not.exist'); + cy.get(GENERATION_resourcePathInput).focus().clear().type('/resource/{{').blur(); + cy.get(GENERATION_resourcePathPatternError).should('exist').should('be.visible').should('contain.text', 'Pattern is not matching'); + cy.get(GENERATION_resourcePathInput).focus().clear().type('/resource/}}').blur(); + cy.get(GENERATION_resourcePathPatternError).should('exist').should('be.visible').should('contain.text', 'Pattern is not matching'); + cy.get(GENERATION_resourcePathInput).focus().clear().type('/resource/{}').blur(); + cy.get(GENERATION_resourcePathPatternError).should('not.exist'); + cy.get(GENERATION_resourcePathInput).focus().clear().type('/resource/{resourceId}', {parseSpecialCharSequences: false}).blur(); + cy.get(GENERATION_resourcePathPatternError).should('not.exist'); + } +}); diff --git a/core/apps/ame-e2e/src/integration/generation/generation-documentation.cy.ts b/core/apps/ame-e2e/src/integration/generation/generation-documentation.cy.ts new file mode 100644 index 00000000..45490060 --- /dev/null +++ b/core/apps/ame-e2e/src/integration/generation/generation-documentation.cy.ts @@ -0,0 +1,27 @@ +/* eslint-disable cypress/no-unnecessary-waiting */ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for + * additional information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +/// + +import {GENERATION_tbDownloadDoc} from '../../support/constants'; + +describe('Test generation and download of Aspect Model documentation', () => { + it('Can generate and download valid Aspect Model documentation', () => { + cy.visitDefault(); + cy.startModelling() + .then(() => cy.openGenerationDocumentation().wait(500)) + .then(() => cy.get(GENERATION_tbDownloadDoc).click({force: true}).wait(5000)) + .then(() => cy.fixture('cypress/downloads/AspectDefault-documentation.html')); + }); +}); diff --git a/core/apps/ame-e2e/src/integration/generation/generation-json-payload-and-schema.cy.ts b/core/apps/ame-e2e/src/integration/generation/generation-json-payload-and-schema.cy.ts new file mode 100644 index 00000000..5556b265 --- /dev/null +++ b/core/apps/ame-e2e/src/integration/generation/generation-json-payload-and-schema.cy.ts @@ -0,0 +1,35 @@ +/* eslint-disable cypress/no-unnecessary-waiting */ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for + * additional information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +/// + +import {GENERATION_downloadFileButton} from '../../support/constants'; + +describe('Test generation and download of Json payload/schema', () => { + it('Can generate and download valid Json payload', () => { + cy.visitDefault(); + cy.startModelling() + .then(() => cy.openGenerationJsonSample().wait(500)) + .then(() => cy.get(GENERATION_downloadFileButton).click({force: true}).wait(5000)) + .then(() => cy.fixture('cypress/downloads/AspectDefault-sample.json')); + }); + + it('Can generate and download valid Json schema', () => { + cy.visitDefault(); + cy.startModelling() + .then(() => cy.openGenerationJsonSchema().wait(500)) + .then(() => cy.get(GENERATION_downloadFileButton).click({force: true}).wait(5000)) + .then(() => cy.fixture('cypress/downloads/AspectDefault-schema.json')); + }); +}); diff --git a/core/apps/ame-e2e/src/support/commands.ts b/core/apps/ame-e2e/src/support/commands.ts index 857cf708..4b53cb5b 100644 --- a/core/apps/ame-e2e/src/support/commands.ts +++ b/core/apps/ame-e2e/src/support/commands.ts @@ -23,6 +23,7 @@ import {FileHandlingService, GenerateHandlingService} from '@ame/editor'; import {SearchesStateService} from '@ame/utils'; import {NamespacesManagerService} from '@ame/namespace-manager'; import {Aspect} from '@ame/meta-model'; +import 'cypress-file-upload'; const {mxEventObject, mxEvent} = mxgraphFactory({}); @@ -528,13 +529,25 @@ Cypress.Commands.add('saveAspectModelToWorkspace', () => { Cypress.Commands.add('openGenerationOpenApiSpec', () => { cy.intercept( 'POST', - 'http://localhost:9091/ame/api/generate/open-api-spec?language=en&output=json&baseUrl=https://example.com&includeQueryApi=false&pagingOption=NO_PAGING', + 'http://localhost:9091/ame/api/generate/open-api-spec?language=en&output=json&baseUrl=https://example.com&includeQueryApi=false&pagingOption=NO_PAGING&resourcePath=null&ymlProperties=&jsonProperties=', {fixture: 'valid-open-api.json'}, ); cy.intercept( 'POST', - 'http://localhost:9091/ame/api/generate/open-api-spec?language=en&output=yaml&baseUrl=https://example.com&includeQueryApi=false&pagingOption=NO_PAGING', + 'http://localhost:9091/ame/api/generate/open-api-spec?language=en&output=yaml&baseUrl=https://example.com&includeQueryApi=false&pagingOption=NO_PAGING&resourcePath=null&ymlProperties=&jsonProperties=', + {fixture: 'valid-open-api.yaml'}, + ); + + cy.intercept( + 'POST', + 'http://localhost:9091/ame/api/generate/open-api-spec?language=en&output=json&baseUrl=https://example.com&includeQueryApi=false&pagingOption=NO_PAGING&resourcePath=/resource/%7BresourceId%7D&ymlProperties=&jsonProperties=%7B%0A%20%20%22key%22:%20%22value%22%0A%7D', + {fixture: 'valid-open-api.json'}, + ); + + cy.intercept( + 'POST', + 'http://localhost:9091/ame/api/generate/open-api-spec?language=en&output=yaml&baseUrl=https://example.com&includeQueryApi=false&pagingOption=NO_PAGING&resourcePath=/resource/%7BresourceId%7D&ymlProperties=resourceId:%0A%20%20name:%20resourceId%0A%20%20in:%20path%0A%20%20description:%20An%20example%20resource%20Id.%0A%20%20required:%20true%0A%20%20schema:%0A%20%20%20%20type:%20string%0A&jsonProperties=', {fixture: 'valid-open-api.yaml'}, ); diff --git a/core/apps/ame-e2e/src/support/constants.ts b/core/apps/ame-e2e/src/support/constants.ts index 911a6ac8..01e5bc89 100644 --- a/core/apps/ame-e2e/src/support/constants.ts +++ b/core/apps/ame-e2e/src/support/constants.ts @@ -140,6 +140,21 @@ export enum SettingsDialogSelectors { export const GENERATION_tbGenerateOpenApiButton = '[data-cy="tbGenerateOpenApiButton"]'; export const GENERATION_tbOutputButton = '[data-cy="tbOutputButton"]'; export const GENERATION_tbOutputButton_YAML = '[data-cy="tbOutputButton-yaml"]'; +export const GENERATION_tbOutputButton_JSON = '[data-cy="tbOutputButton-json"]'; +export const GENERATION_tbBaseUrlInput = '[data-cy="tbBaseUrlInput"]'; +export const GENERATION_tbBaseUrlInputError = '[data-cy="tbBaseUrlInputError"]'; +export const GENERATION_activateResourcePathCheckbox = '[data-cy="activateResourcePathCheckbox"]'; +export const GENERATION_resourcePathTitle = '[data-cy="resourcePathTitle"]'; +export const GENERATION_resourcePathInput = '[data-cy="resourcePathInput"]'; +export const GENERATION_resourcePathRequiredError = '[data-cy="resourcePathRequiredError"]'; +export const GENERATION_resourcePathPatternError = '[data-cy="resourcePathPatternError"]'; +export const GENERATION_uploadTitle = '[data-cy="uploadTitle"]'; +export const GENERATION_uploadFileTitle = '[data-cy="uploadFileTitle"]'; +export const GENERATION_uploadContent = '[data-cy="uploadContent"]'; +export const GENERATION_uploadContentFileInput = '[data-cy="uploadContentFileInput"]'; +export const GENERATION_uploadFileRequireError = '[data-cy="uploadFileRequireError"]'; +export const GENERATION_accordionTitle = '[data-cy="accordionTitle"]'; +export const GENERATION_removeUploadFile = '[data-cy="removeUploadFile"]'; export const GENERATION_tbDownloadDoc = '[data-cy="tbDownloadDoc"]'; export const GENERATION_downloadFileButton = '[data-cy="downloadFileButton"]'; diff --git a/core/apps/ame/src/assets/i18n/en.json b/core/apps/ame/src/assets/i18n/en.json index 86366f26..66dbf816 100644 --- a/core/apps/ame/src/assets/i18n/en.json +++ b/core/apps/ame/src/assets/i18n/en.json @@ -252,12 +252,26 @@ "CONFIGURATION": "Configuration", "LANGUAGE": "Language", "OUTPUT_FILE_FORMAT": "Output File format", - "BASEURL": "BaseUrl - the base URL for the Aspect API", + "BASEURL": "Base Url - the base URL for the Aspect API", "PLEASE_ADD_VALID_URL": "Please add a valid url", - "INCLUDE_QUERY_API_TOOLTIP": "if set to true, a path section for the Query API Endpoint of the Aspect API will be included in the specification.", - "INCLUDE_QUERY_API": "IncludeQueryApi", - "USE_SEMANTIC_VERSION": "UseSemanticVersion", - "USE_SEMANTIC_VERSION_TOOLTIP": "if set to true, the complete semantic version of the Aspect Model will be used as the version of the API. Otherwise only the major part of the Aspect Version is used as the version of the API.", + "INCLUDE_QUERY_API_TOOLTIP": "If enabled, a dedicated section for the Query API Endpoint of the Aspect API will be included in the specification.", + "INCLUDE_QUERY_API": "Include Query Api", + "USE_SEMANTIC_VERSION": "Use Semantic Version", + "USE_SEMANTIC_VERSION_TOOLTIP": "If enabled, the complete semantic version of the Aspect Model will be utilized as the API version. Otherwise, only the major part of the Aspect Version will be used as the API version.", + "ACTIVATE_RESOURCE_PATH": "If enabled, the resource path will be activated in the OpenAPI specification.", + "ACTIVATE_RESOURCE_PATH_TOOLTIP": "Activate Resource Path", + "RESOURCE_PATH": "Resource Path - The resource path for the Aspect API endpoints", + "RESOURCE_PATH_REQUIRED": "Resource path is required", + "RESOURCE_PATH_PATTERN": "Pattern is not matching", + "RESOURCE_PATH_ERROR": "Failed to generate Open Api Spec", + "UPLOAD_FILE_TITLE": "Upload {{output}} File", + "UPLOAD_FILE_DESCRIPTION": "For information on the options, please refer to the ", + "UPLOAD_DRAG_AND_DROP_TEXT": "Drag and drop files here or click the icon to upload", + "UPLOADED_FILE_TITLE": "Uploaded {{output}} File", + "UPLOAD_FILE_ACCORDION_TITLE": "Properties", + "UPLOAD_ERROR_TITLE": "Wrong file format", + "UPLOAD_ERROR_MESSAGE": "Please upload a {{output}} file.", + "UPLOAD_FILE_REQUIRED": "{{output}} file is required - a variable has been defined in the resource path.", "PAGING_TYPE_OPTION": "Paging type/option", "NO_PAGING": "No paging", "CURSOR_BASED_PAGING": "Cursor based paging", diff --git a/core/apps/ame/src/assets/i18n/zh.json b/core/apps/ame/src/assets/i18n/zh.json index 950067ee..c0f2e964 100644 --- a/core/apps/ame/src/assets/i18n/zh.json +++ b/core/apps/ame/src/assets/i18n/zh.json @@ -258,6 +258,20 @@ "INCLUDE_QUERY_API": "包括QueryApi", "USE_SEMANTIC_VERSION": "使用Semantic版本", "USE_SEMANTIC_VERSION_TOOLTIP": "如果设置为 true, 则方面模型的完整语义版本将用作 API 的版本。否则,只有方面版本的主要部分用作 API 的版本。", + "ACTIVATE_RESOURCE_PATH": "", + "ACTIVATE_RESOURCE_PATH_TOOLTIP": "", + "RESOURCE_PATH": "", + "RESOURCE_PATH_REQUIRED": "", + "RESOURCE_PATH_PATTERN": "", + "RESOURCE_PATH_ERROR": "", + "UPLOAD_FILE_TITLE": "", + "UPLOAD_FILE_DESCRIPTION": "", + "UPLOAD_DRAG_AND_DROP_TEXT": "", + "UPLOADED_FILE_TITLE": "", + "UPLOAD_FILE_ACCORDION_TITLE": "", + "UPLOAD_ERROR_TITLE": "", + "UPLOAD_ERROR_MESSAGE": "", + "UPLOAD_FILE_REQUIRED": "", "PAGING_TYPE_OPTION": "Paging类型/选项", "NO_PAGING": "无需paging", "CURSOR_BASED_PAGING": "基于光标的paging", diff --git a/core/cypress.config.js b/core/cypress.config.js index aeca3ba7..eac0591e 100644 --- a/core/cypress.config.js +++ b/core/cypress.config.js @@ -40,6 +40,8 @@ module.exports = defineConfig({ 'apps/ame-e2e/src/integration/drag-and-drop/same-namespace/*.ts', 'apps/ame-e2e/src/integration/editor/*.ts', 'apps/ame-e2e/src/integration/export/*.ts', + 'apps/ame-e2e/src/integration/export/lang-string/*.ts', + 'apps/ame-e2e/src/integration/generation/*.ts', 'apps/ame-e2e/src/integration/settings/*.ts', 'apps/ame-e2e/src/integration/*.ts', ], diff --git a/core/libs/api/src/lib/model-api.service.ts b/core/libs/api/src/lib/model-api.service.ts index 99cd9bad..8d57bb03 100644 --- a/core/libs/api/src/lib/model-api.service.ts +++ b/core/libs/api/src/lib/model-api.service.ts @@ -279,12 +279,18 @@ export class ModelApiService { baseUrl: openApi.baseUrl, includeQueryApi: openApi.includeQueryApi, pagingOption: openApi.paging, + resourcePath: openApi.resourcePath, + ymlProperties: openApi.ymlProperties || '', + jsonProperties: openApi.jsonProperties || '', }, responseType: openApi.output === 'yaml' ? ('text' as 'json') : 'json', }) .pipe( timeout(this.requestTimeout), - catchError(res => throwError(() => res)), + catchError(res => { + res.error = openApi.output === 'yaml' ? JSON.parse(res.error)?.error : res.error.error; + return throwError(() => res); + }), ); } diff --git a/core/libs/editor/src/lib/editor-toolbar/components/generate-open-api/generate-open-api.component.html b/core/libs/editor/src/lib/editor-toolbar/components/generate-open-api/generate-open-api.component.html index f322e847..7c7b67b7 100644 --- a/core/libs/editor/src/lib/editor-toolbar/components/generate-open-api/generate-open-api.component.html +++ b/core/libs/editor/src/lib/editor-toolbar/components/generate-open-api/generate-open-api.component.html @@ -24,26 +24,38 @@

{{ 'GENERATE_OPENAPI_SPEC_DIALOG.TITLE' | translate }}

- - {{ 'GENERATE_OPENAPI_SPEC_DIALOG.OUTPUT_FILE_FORMAT' | translate }} - - JSON - YAML - - +
+ + {{ 'GENERATE_OPENAPI_SPEC_DIALOG.OUTPUT_FILE_FORMAT' | translate }} + + JSON + YAML + + - - {{ 'GENERATE_OPENAPI_SPEC_DIALOG.BASEURL' | translate }} - - {{ 'GENERATE_OPENAPI_SPEC_DIALOG.PLEASE_ADD_VALID_URL' | translate }} - - + + {{ 'GENERATE_OPENAPI_SPEC_DIALOG.BASEURL' | translate }} + + + {{ 'GENERATE_OPENAPI_SPEC_DIALOG.PLEASE_ADD_VALID_URL' | translate }} + + + + + {{ 'GENERATE_OPENAPI_SPEC_DIALOG.PAGING_TYPE_OPTION' | translate }} + + {{ 'GENERATE_OPENAPI_SPEC_DIALOG.NO_PAGING' | translate }} + {{ 'GENERATE_OPENAPI_SPEC_DIALOG.CURSOR_BASED_PAGING' | translate }} + {{ 'GENERATE_OPENAPI_SPEC_DIALOG.OFFSET_BASED_PAGING' | translate }} + {{ 'GENERATE_OPENAPI_SPEC_DIALOG.TIME_BASED_PAGING' | translate }} + + +
{{ 'GENERATE_OPENAPI_SPEC_DIALOG.INCLUDE_QUERY_API' | translate }} @@ -51,26 +63,98 @@

{{ 'GENERATE_OPENAPI_SPEC_DIALOG.TITLE' | translate }}

{{ 'GENERATE_OPENAPI_SPEC_DIALOG.USE_SEMANTIC_VERSION' | translate }} + + + {{ 'GENERATE_OPENAPI_SPEC_DIALOG.ACTIVATE_RESOURCE_PATH_TOOLTIP' | translate }} +
- - {{ 'GENERATE_OPENAPI_SPEC_DIALOG.PAGING_TYPE_OPTION' | translate }} - - {{ 'GENERATE_OPENAPI_SPEC_DIALOG.NO_PAGING' | translate }} - {{ 'GENERATE_OPENAPI_SPEC_DIALOG.CURSOR_BASED_PAGING' | translate }} - {{ 'GENERATE_OPENAPI_SPEC_DIALOG.OFFSET_BASED_PAGING' | translate }} - {{ 'GENERATE_OPENAPI_SPEC_DIALOG.TIME_BASED_PAGING' | translate }} - - +
+ + {{ 'GENERATE_OPENAPI_SPEC_DIALOG.RESOURCE_PATH' | translate }} + + + {{ 'GENERATE_OPENAPI_SPEC_DIALOG.RESOURCE_PATH_REQUIRED' | translate }} + + + {{ 'GENERATE_OPENAPI_SPEC_DIALOG.RESOURCE_PATH_PATTERN' | translate }} + + +

+ {{ 'GENERATE_OPENAPI_SPEC_DIALOG.UPLOAD_FILE_TITLE' | translate: {output: output.value.toUpperCase()} }}: +

+

+ {{ 'GENERATE_OPENAPI_SPEC_DIALOG.UPLOAD_FILE_DESCRIPTION' | translate: {output: output.value.toUpperCase()} }} + {{ output.value.toUpperCase() }} file properties +

+ +
+

+ {{ 'GENERATE_OPENAPI_SPEC_DIALOG.UPLOADED_FILE_TITLE' | translate: {output: output.value.toUpperCase()} }}: +

+ {{ uploadedFile.name }} + +
+ + +
+ +
+ +

{{ 'GENERATE_OPENAPI_SPEC_DIALOG.UPLOAD_DRAG_AND_DROP_TEXT' | translate }}

+
+
+ + {{ 'GENERATE_OPENAPI_SPEC_DIALOG.UPLOAD_FILE_REQUIRED' | translate: {output: output.value.toUpperCase()} }} +
+ + + + + + + {{ 'GENERATE_OPENAPI_SPEC_DIALOG.UPLOAD_FILE_ACCORDION_TITLE' | translate }} + + + + + + +
-
+

{{ 'GENERATE_OPENAPI_SPEC_DIALOG.GENERATING' | translate }}

@@ -88,6 +172,6 @@

{{ 'GENERATE_OPENAPI_SPEC_DIALOG.GENERATING' | translate }}

data-cy="tbGenerateOpenApiButton" > {{ 'GENERATE_OPENAPI_SPEC_DIALOG.BUTTON.GENERATE' | translate }} - - + diff --git a/core/libs/editor/src/lib/editor-toolbar/components/generate-open-api/generate-open-api.component.scss b/core/libs/editor/src/lib/editor-toolbar/components/generate-open-api/generate-open-api.component.scss index 388b151a..c2d21b29 100644 --- a/core/libs/editor/src/lib/editor-toolbar/components/generate-open-api/generate-open-api.component.scss +++ b/core/libs/editor/src/lib/editor-toolbar/components/generate-open-api/generate-open-api.component.scss @@ -11,22 +11,85 @@ * SPDX-License-Identifier: MPL-2.0 */ -:host { +.dialog { display: flex; flex-direction: column; min-width: 500px; -} -.checkbox-margin { - margin: 12px 12px; -} + &__checkbox { + margin: 12px 12px; + } -.loading { - display: flex; - flex-direction: column; - align-items: center; -} + &__loading { + display: flex; + flex-direction: column; + align-items: center; + } + + &__content { + padding-bottom: 0; + } + + &__border { + &--dashed { + border: 2px dashed #000; + } + &--error { + border: 2px dashed red; + } + } + + &__title { + font-size: 1.5em; + margin-bottom: 0.5em; + } + + &__description { + margin-top: 0; + } + + &__upload-input { + display: none; + } + + &__upload-content { + padding: 10px 0; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; + + button { + order: 1; + } + + span { + order: 2; + font-size: 10px; + } + } + + &__textarea { + background-color: lightgray; + padding-top: 10px; + padding-left: 6px; + width: 100%; + height: 150px; + resize: none; + border: none; + } + + &__file-name { + padding-left: 10px; + } + + &__file-info { + display: flex; + align-items: center; -mat-dialog-content { - padding-bottom: 0; + &__name { + margin-left: 10px; + } + } } diff --git a/core/libs/editor/src/lib/editor-toolbar/components/generate-open-api/generate-open-api.component.ts b/core/libs/editor/src/lib/editor-toolbar/components/generate-open-api/generate-open-api.component.ts index 3954686c..50c1c98c 100644 --- a/core/libs/editor/src/lib/editor-toolbar/components/generate-open-api/generate-open-api.component.ts +++ b/core/libs/editor/src/lib/editor-toolbar/components/generate-open-api/generate-open-api.component.ts @@ -11,18 +11,19 @@ * SPDX-License-Identifier: MPL-2.0 */ -import {Component, OnInit} from '@angular/core'; +import {Component, ElementRef, HostListener, OnDestroy, OnInit, ViewChild} from '@angular/core'; import {MatDialogRef} from '@angular/material/dialog'; -import {FormControl, FormGroup} from '@angular/forms'; +import {FormControl, FormGroup, Validators} from '@angular/forms'; import * as locale from 'locale-codes'; import {SammLanguageSettingsService} from '@ame/settings-dialog'; import {EditorDialogValidators} from '../../../editor-dialog'; import {finalize, first} from 'rxjs/operators'; -import {map} from 'rxjs'; +import {map, Subscription} from 'rxjs'; import {saveAs} from 'file-saver'; import {EditorService} from '../../../editor.service'; import {ModelService} from '@ame/rdf/services'; -import {NamespacesCacheService} from '@ame/cache'; +import {LanguageTranslationService} from '@ame/translation'; +import {NotificationsService} from '@ame/shared'; export interface OpenApi { language: string; @@ -31,6 +32,9 @@ export interface OpenApi { includeQueryApi: boolean; useSemanticVersion: boolean; paging: string; + resourcePath: string; + ymlProperties: string; + jsonProperties: string; } @Component({ @@ -38,81 +42,215 @@ export interface OpenApi { templateUrl: './generate-open-api.component.html', styleUrls: ['./generate-open-api.component.scss'], }) -export class GenerateOpenApiComponent implements OnInit { +export class GenerateOpenApiComponent implements OnInit, OnDestroy { + @ViewChild('dropArea') dropArea: ElementRef; + form: FormGroup; languages: locale.ILocale[]; isGenerating = false; + linkToSpecification = 'https://eclipse-esmf.github.io/ame-guide/generate/generate-openapi-doc.html'; + uploadedFile: File = undefined; + subscriptions = new Subscription(); + + private resourcePathValidators = [ + Validators.required, + Validators.pattern(/^\/[a-zA-Z{}/]*$/), + Validators.pattern(/^(?!.*\/\/)(?!.*{{)(?!.*}}).*$/), + Validators.pattern(/.*({.*})?.*$/), + ]; + + public get output(): FormControl { + return this.form.get('output') as FormControl; + } + + public get activateResourcePath(): FormControl { + return this.form.get('activateResourcePath') as FormControl; + } + + public get resourcePath(): FormControl { + return this.form.get('resourcePath') as FormControl; + } + + public get file(): FormControl { + return this.form.get('file') as FormControl; + } - private get currentCachedFile() { - return this.namespaceCacheService.currentCachedFile; + public get ymlProperties(): FormControl { + return this.form.get('ymlProperties') as FormControl; + } + + public get jsonProperties(): FormControl { + return this.form.get('jsonProperties') as FormControl; } constructor( private dialogRef: MatDialogRef, private languageService: SammLanguageSettingsService, - private namespaceCacheService: NamespacesCacheService, private modelService: ModelService, private editorService: EditorService, + private notificationsService: NotificationsService, + private translate: LanguageTranslationService, ) {} ngOnInit(): void { + this.initializeForm(); + this.setupFormListeners(); + } + + ngOnDestroy(): void { + this.subscriptions.unsubscribe(); + } + + private initializeForm(): void { this.languages = this.languageService.getSammLanguageCodes().map(tag => locale.getByTag(tag)); this.form = new FormGroup({ - baseUrl: new FormControl('https://example.com', { - validators: [EditorDialogValidators.baseUrl], - updateOn: 'blur', - }), + baseUrl: new FormControl('https://example.com', Validators.compose([Validators.required, EditorDialogValidators.baseUrl])), language: new FormControl(this.languages[0].tag), includeQueryApi: new FormControl(false), useSemanticVersion: new FormControl(false), - output: new FormControl('json'), + activateResourcePath: new FormControl(false), + output: new FormControl('yaml'), paging: new FormControl('NO_PAGING'), + resourcePath: new FormControl(null), + file: new FormControl(null), + ymlProperties: new FormControl(null), + jsonProperties: new FormControl(null), }); } - generateOpenApiSpec(): void { - const openApi: OpenApi = { - output: this.getControlValue('output') as string, - baseUrl: this.getControlValue('baseUrl') as string, - includeQueryApi: this.getControlValue('includeQueryApi') as boolean, - useSemanticVersion: this.getControlValue('useSemanticVersion') as boolean, - paging: this.getControlValue('paging') as string, - language: this.getControlValue('language') as string, - }; + private setupFormListeners(): void { + this.subscriptions.add(this.output?.valueChanges.subscribe(() => this.removeUploadedFile())); + + this.subscriptions.add( + this.activateResourcePath?.valueChanges.subscribe(activateResourcePath => { + const resourcePathControl = this.form.get('resourcePath'); + + if (activateResourcePath) { + resourcePathControl?.setValue('/resource/{resourceId}'); + resourcePathControl?.setValidators(this.resourcePathValidators); + } else { + resourcePathControl?.setValue(null); + resourcePathControl?.setValidators(null); + } + + resourcePathControl?.updateValueAndValidity(); + }), + ); + + this.subscriptions.add( + this.resourcePath?.valueChanges.subscribe(resourcePath => { + const fileControl = this.form.get('file'); + const hasBrackets = /{.*}/.test(resourcePath); + hasBrackets ? fileControl?.setValidators(Validators.required) : fileControl?.setValidators(null); + fileControl?.updateValueAndValidity(); + }), + ); + } + + @HostListener('dragover', ['$event']) + preventDragDefaults(event: Event): void { + event.preventDefault(); + } + + @HostListener('drop', ['$event']) + handleFileDrop(event: DragEvent): void { + event.preventDefault(); + if (!this.dropArea.nativeElement.contains(event.target)) return; + const files = event.dataTransfer?.files; + if (files && files.length) { + const file = files[0]; + if (this.validateFile(file)) { + this.processFile(file); + } else { + this.showError(); + } + } + } + + private validateFile(file: File): boolean { + const fileType = this.form.value.output; + return fileType === 'json' ? file.name.endsWith('.json') : file.name.endsWith('.yaml') || file.name.endsWith('.yml'); + } + + private processFile(file: File): void { + this.uploadedFile = file; + this.form.patchValue({file: file}); + this.readFileContent(file); + } + + private readFileContent(file: File): void { + const reader = new FileReader(); + reader.onload = () => this.handleFileContent(file, reader.result as string); + reader.readAsText(file); + } + + private handleFileContent(file: File, content: string): void { + const fileType = this.getFileType(file); + const propertyName = fileType === 'json' ? 'jsonProperties' : 'ymlProperties'; + this.form.patchValue({[propertyName]: content}); + } + + private getFileType(file: File): 'json' | 'yml' { + if (file.name.endsWith('.json')) { + return 'json'; + } else if (file.name.endsWith('.yaml') || file.name.endsWith('.yml')) { + return 'yml'; + } + + throw new Error('Unsupported file type'); + } + + private showError(): void { + this.notificationsService.error({ + title: this.translate.translateService.instant('GENERATE_OPENAPI_SPEC_DIALOG.UPLOAD_ERROR_TITLE'), + message: this.translate.translateService.instant('GENERATE_OPENAPI_SPEC_DIALOG.UPLOAD_ERROR_MESSAGE', { + output: this.form.value.output.toUpperCase(), + }), + }); + } + + generateOpenApiSpec(): void { this.isGenerating = true; + const openApiSpec = this.form.value as OpenApi; + this.subscriptions.add( + this.editorService + .generateOpenApiSpec(this.modelService.currentRdfModel, openApiSpec) + .pipe( + first(), + map(data => this.handleGeneratedSpec(data, openApiSpec)), + finalize(() => { + this.isGenerating = false; + this.dialogRef.close(); + }), + ) + .subscribe(), + ); + } - this.editorService - .generateOpenApiSpec(this.modelService.currentRdfModel, openApi) - .pipe( - first(), - map(data => { - if (openApi.output === 'yaml') { - saveAs( - new Blob([data], { - type: 'text/yaml', - }), - `${this.modelService.loadedAspect.name}-open-api.yaml`, - ); - } else { - saveAs( - new Blob([JSON.stringify(data, null, 2)], { - type: 'application/json;charset=utf-8', - }), - !this.modelService.loadedAspect ? this.currentCachedFile.fileName : `${this.modelService.loadedAspect.name}-open-api.json`, - ); - } - }), - finalize(() => { - this.isGenerating = false; - this.dialogRef.close(); - }), - ) - .subscribe(); + private handleGeneratedSpec(data: any, spec: OpenApi): void { + const fileType = spec.output === 'yaml' ? 'text/yaml' : 'application/json;charset=utf-8'; + const fileData = spec.output === 'yaml' ? data : JSON.stringify(data, null, 2); + const fileName = `${spec.language}-open-api.${spec.output}`; + saveAs(new Blob([fileData], {type: fileType}), fileName); } - close(): void { - this.dialogRef.close(); + onFileBrowseHandler($event: Event): void { + const files = ($event.target as HTMLInputElement).files; + + if (files.length) { + const file = files[0]; + this.uploadedFile = file; + this.readFileContent(file); + this.file.patchValue(file); + } + } + + removeUploadedFile(): void { + this.uploadedFile = null; + this.ymlProperties?.reset(); + this.jsonProperties?.reset(); + this.file?.reset(); } getControl(path: string): FormControl { @@ -122,4 +260,8 @@ export class GenerateOpenApiComponent implements OnInit { getControlValue(path: string): string | boolean { return this.getControl(path).value; } + + close(): void { + this.dialogRef.close(); + } } diff --git a/core/libs/editor/src/lib/editor-toolbar/services/file-handling.service.ts b/core/libs/editor/src/lib/editor-toolbar/services/file-handling.service.ts index eccbbe46..eebda9ff 100644 --- a/core/libs/editor/src/lib/editor-toolbar/services/file-handling.service.ts +++ b/core/libs/editor/src/lib/editor-toolbar/services/file-handling.service.ts @@ -91,7 +91,6 @@ export class FileHandlingService { private namespaceCacheService: NamespacesCacheService, private migratorService: MigratorService, private sidebarService: SidebarStateService, - private titleService: Title, private translate: LanguageTranslationService, private electronSignalsService: ElectronSignalsService, private configurationService: ConfigurationService, diff --git a/core/libs/editor/src/lib/editor.service.ts b/core/libs/editor/src/lib/editor.service.ts index ca0eeaa8..ffe3602c 100644 --- a/core/libs/editor/src/lib/editor.service.ts +++ b/core/libs/editor/src/lib/editor.service.ts @@ -332,7 +332,16 @@ export class EditorService { generateOpenApiSpec(rdfModel: RdfModel, openApi: OpenApi): Observable { const serializedModel = this.rdfService.serializeModel(rdfModel); - return this.modelApiService.generateOpenApiSpec(serializedModel, openApi); + return this.modelApiService.generateOpenApiSpec(serializedModel, openApi).pipe( + catchError(err => { + this.notificationsService.error({ + title: this.translate.language.GENERATE_OPENAPI_SPEC_DIALOG.RESOURCE_PATH_ERROR, + message: err.error.message, + timeout: 5000, + }); + return throwError(() => err.error); + }), + ); } private loadCurrentModel(loadedRdfModel: RdfModel, rdfAspectModel: string, namespaceFileName: string, editElementUrn?: string): void { diff --git a/core/libs/translation/src/lib/models/language.interface.ts b/core/libs/translation/src/lib/models/language.interface.ts index 6eaa522d..1942b798 100644 --- a/core/libs/translation/src/lib/models/language.interface.ts +++ b/core/libs/translation/src/lib/models/language.interface.ts @@ -367,6 +367,20 @@ export interface GenerateOpenapiSpecDialog { INCLUDE_QUERY_API: string; USE_SEMANTIC_VERSION: string; USE_SEMANTIC_VERSION_TOOLTIP: string; + ACTIVATE_RESOURCE_PATH: string; + ACTIVATE_RESOURCE_PATH_TOOLTIP: string; + RESOURCE_PATH: string; + RESOURCE_PATH_REQUIRED: string; + RESOURCE_PATH_PATTERN: string; + RESOURCE_PATH_ERROR: string; + UPLOAD_FILE_TITLE: string; + UPLOAD_FILE_DESCRIPTION: string; + UPLOAD_DRAG_AND_DROP_TEXT: string; + UPLOADED_FILE_TITLE: string; + UPLOAD_FILE_ACCORDION_TITLE: string; + UPLOAD_ERROR_TITLE: string; + UPLOAD_ERROR_MESSAGE: string; + UPLOAD_FILE_REQUIRED: string; PAGING_TYPE_OPTION: string; NO_PAGING: string; CURSOR_BASED_PAGING: string; diff --git a/core/package-lock.json b/core/package-lock.json index e398a709..3f1d721a 100644 --- a/core/package-lock.json +++ b/core/package-lock.json @@ -77,6 +77,7 @@ "@typescript-eslint/parser": "5.60.1", "coverage-istanbul-loader": "^3.0.5", "cypress": "^13.6.6", + "cypress-file-upload": "^5.0.8", "cypress-real-events": "^1.12.0", "electron": "28.2.1", "electron-builder": "^24.13.3", @@ -16632,6 +16633,18 @@ "node": "^16.0.0 || ^18.0.0 || >=20.0.0" } }, + "node_modules/cypress-file-upload": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/cypress-file-upload/-/cypress-file-upload-5.0.8.tgz", + "integrity": "sha512-+8VzNabRk3zG6x8f8BWArF/xA/W0VK4IZNx3MV0jFWrJS/qKn8eHfa5nU73P9fOQAgwHFJx7zjg4lwOnljMO8g==", + "dev": true, + "engines": { + "node": ">=8.2.1" + }, + "peerDependencies": { + "cypress": ">3.0.0" + } + }, "node_modules/cypress-real-events": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/cypress-real-events/-/cypress-real-events-1.12.0.tgz", diff --git a/core/package.json b/core/package.json index 090c31b2..96c61a5c 100644 --- a/core/package.json +++ b/core/package.json @@ -256,6 +256,7 @@ "@typescript-eslint/parser": "5.60.1", "coverage-istanbul-loader": "^3.0.5", "cypress": "^13.6.6", + "cypress-file-upload": "^5.0.8", "cypress-real-events": "^1.12.0", "electron": "28.2.1", "electron-builder": "^24.13.3", diff --git a/documentation/ame-guide/modules/ROOT/images/generation/open-api-dialog.png b/documentation/ame-guide/modules/ROOT/images/generation/open-api-dialog.png new file mode 100644 index 00000000..0dfa475d Binary files /dev/null and b/documentation/ame-guide/modules/ROOT/images/generation/open-api-dialog.png differ diff --git a/documentation/ame-guide/modules/ROOT/images/generate-api-spec.png b/documentation/ame-guide/modules/ROOT/images/generation/open-api-menu.png similarity index 100% rename from documentation/ame-guide/modules/ROOT/images/generate-api-spec.png rename to documentation/ame-guide/modules/ROOT/images/generation/open-api-menu.png diff --git a/documentation/ame-guide/modules/ROOT/pages/generate/generate-openapi-doc.adoc b/documentation/ame-guide/modules/ROOT/pages/generate/generate-openapi-doc.adoc index 8223e85d..b736946c 100644 --- a/documentation/ame-guide/modules/ROOT/pages/generate/generate-openapi-doc.adoc +++ b/documentation/ame-guide/modules/ROOT/pages/generate/generate-openapi-doc.adoc @@ -7,17 +7,36 @@ A menu offers one option to generate the OpenAPI specification. +: ** *OpenAPI specification* -- continue with this one + -image::generate-api-spec.png[Generate document, width=100%, align="left"] +image::generation/open-api-menu.png[Generate document, width=100%, align="left"] + * In case your model supports multiple languages, select the specific language for the documentation. * Select *JSON* or *YAML* as the desired file format. * Set the *Base URL* were the Aspect API will be served from. -* Check the *IncludeQueryAPI* checkbox, if you need a path section for the Query API Endpoint of the Aspect API to be included in the specification. -* Check the *UseSemanticVersion* checkbox, if you need the complete semantic version of the Aspect Model to be used as the version of the API. Otherwise, only the major part of the Aspect Version is used as the version of the API. * Select the preferred option for paging: ** Cursor-based paging ** Offset-based paging ** Time-based paging +* Check the *Include Query API* checkbox, if you need a path section for the Query API Endpoint of the Aspect API to be included in the specification. +* Check the *Use Semantic Version* checkbox, if you need the complete semantic version of the Aspect Model to be used as the version of the API. Otherwise, only the major part of the Aspect Version is used as the version of the API. +* Check the *Active Resource Path* checkbox, if you need the active resource path to be included in the specification. * Click *Generate* and store the Open API specification file locally. +[[resource-path]] +== Configuring Resource Path in the OpenAPI Generation Dialog + +When the Resource Path is activated, additional fields are introduced in the OpenAPI Generation Dialog to allow for its configuration. +The inclusion of certain properties in the output depends on the selected output file format: + +*YAML*: A property file in YAML format is required when YAML is chosen as the output file format. +*JSON*: A property file in JSON format is needed if JSON is selected. + +It is crucial to ensure that the properties in the file match those specified in the resource path. +Failure to do so will result in an error prompting a review of the mismatches. + +For additional details on configuring the Resource Path and generating OpenAPI specifications using the SAMM CLI, please consult our comprehensive guide: +https://eclipse-esmf.github.io/esmf-developer-guide/2.6.1/tooling-guide/samm-cli.html#using-the-cli-to-create-a-json-openapi-specification)[Generating OpenAPI Specifications with SAMM CLI]. + +image::generation/open-api-dialog.png[Generate document, width=100%, align="left"] + TIP: If you run into errors, please make sure that the graph is valid. + If the invalid element is not highlighted with a red border, click the Validate icon to get a hint.