From 0ef5888fd48319711e679ed5b412ef5bfd906576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henry=20T=C3=A4schner?= <129834483+HenryT-CG@users.noreply.github.com> Date: Thu, 1 Feb 2024 16:57:24 +0100 Subject: [PATCH] Fix/use pia lib v4 (#28) * feat: use pia lib v4 and move generated to shared * feat: use pia lib v4 and move generated to shared * feat: exclude search tests --- .eslintrc.json | 5 +- .github/workflows/create-new-build.yml | 9 + .prettierignore | 2 +- Dockerfile | 2 +- angular.json | 7 +- helm/Chart.yaml | 2 +- helm/microfrontend.json | 21 -- helm/permissions.csv | 8 - helm/templates/apm-csv.cm.yaml | 14 -- helm/templates/mf-config.yaml | 7 - nginx/locations.conf | 2 +- package-lock.json | 46 ++--- package.json | 12 +- proxy.conf.js | 4 +- sonar-local-project.properties | 2 +- src/app/app.component.spec.ts | 10 +- src/app/app.component.ts | 2 +- src/app/app.module.ts | 64 ++++-- src/app/onecx-product-store-remote.module.ts | 43 ++++ src/app/product-store-remote.module.ts | 22 -- .../app-detail/app-detail.component.ts | 23 +-- .../app-search/app-search.component.html | 6 +- .../app-search/app-search.component.ts | 5 +- .../product-apps.component.spec.ts | 192 ++---------------- .../product-apps/product-apps.component.ts | 4 +- .../product-detail.component.ts | 11 +- .../product-intern.component.ts | 2 +- .../product-props.component.spec.ts | 25 +-- .../product-props/product-props.component.ts | 4 +- .../product.detail.component.spec.ts | 60 ++---- ...s => product-search.component.spec.ts_nok} | 32 ++- .../product-search.component.ts | 10 +- src/app/product-store/product-store.module.ts | 69 ++----- .../shared/can-active-guard.service.spec.ts | 74 ------- src/app/shared/can-active-guard.service.ts | 57 ------ src/app/{ => shared}/generated/.gitignore | 0 .../generated/.openapi-generator-ignore | 0 .../generated/.openapi-generator/FILES | 0 .../generated/.openapi-generator/VERSION | 0 src/app/{ => shared}/generated/README.md | 0 src/app/{ => shared}/generated/api.module.ts | 0 src/app/{ => shared}/generated/api/api.ts | 0 .../generated/api/microfrontends.service.ts | 0 .../generated/api/products.service.ts | 0 .../{ => shared}/generated/configuration.ts | 0 src/app/{ => shared}/generated/encoder.ts | 0 src/app/{ => shared}/generated/index.ts | 0 .../model/createMicrofrontendRequest.ts | 0 .../generated/model/createProductRequest.ts | 0 .../generated/model/createUIEndpoint.ts | 0 .../generated/model/microfrontend.ts | 0 .../generated/model/microfrontendAbstract.ts | 0 .../model/microfrontendPageResult.ts | 0 .../model/microfrontendSearchCriteria.ts | 0 .../{ => shared}/generated/model/models.ts | 0 .../model/problemDetailInvalidParam.ts | 0 .../generated/model/problemDetailParam.ts | 0 .../generated/model/problemDetailResponse.ts | 0 .../{ => shared}/generated/model/product.ts | 0 .../generated/model/productAbstract.ts | 0 .../generated/model/productPageResult.ts | 0 .../generated/model/productSearchCriteria.ts | 0 .../generated/model/uIEndpoint.ts | 0 .../model/updateMicrofrontendRequest.ts | 0 .../generated/model/updateProductRequest.ts | 0 .../generated/model/updateUIEndpoint.ts | 0 src/app/{ => shared}/generated/param.ts | 0 src/app/{ => shared}/generated/variables.ts | 0 .../image-container.component.spec.ts | 5 +- .../image-container.component.ts | 10 +- src/app/shared/label.resolver.spec.ts | 20 +- src/app/shared/label.resolver.ts | 11 +- src/app/shared/shared.module.spec.ts | 46 ----- src/app/shared/shared.module.ts | 65 ++---- src/app/shared/utils.spec.ts | 78 +------ src/app/shared/utils.ts | 6 +- src/environments/environment.prod.ts | 4 +- src/environments/environment.ts | 2 +- tsconfig.app.json | 11 +- tsconfig.spec.json | 2 +- webpack.config.js | 18 +- 81 files changed, 311 insertions(+), 825 deletions(-) create mode 100644 .github/workflows/create-new-build.yml delete mode 100644 helm/microfrontend.json delete mode 100644 helm/permissions.csv delete mode 100644 helm/templates/apm-csv.cm.yaml delete mode 100644 helm/templates/mf-config.yaml create mode 100644 src/app/onecx-product-store-remote.module.ts delete mode 100644 src/app/product-store-remote.module.ts rename src/app/product-store/product-search/{product-search.component.spec.ts => product-search.component.spec.ts_nok} (82%) delete mode 100644 src/app/shared/can-active-guard.service.spec.ts delete mode 100644 src/app/shared/can-active-guard.service.ts rename src/app/{ => shared}/generated/.gitignore (100%) rename src/app/{ => shared}/generated/.openapi-generator-ignore (100%) rename src/app/{ => shared}/generated/.openapi-generator/FILES (100%) rename src/app/{ => shared}/generated/.openapi-generator/VERSION (100%) rename src/app/{ => shared}/generated/README.md (100%) rename src/app/{ => shared}/generated/api.module.ts (100%) rename src/app/{ => shared}/generated/api/api.ts (100%) rename src/app/{ => shared}/generated/api/microfrontends.service.ts (100%) rename src/app/{ => shared}/generated/api/products.service.ts (100%) rename src/app/{ => shared}/generated/configuration.ts (100%) rename src/app/{ => shared}/generated/encoder.ts (100%) rename src/app/{ => shared}/generated/index.ts (100%) rename src/app/{ => shared}/generated/model/createMicrofrontendRequest.ts (100%) rename src/app/{ => shared}/generated/model/createProductRequest.ts (100%) rename src/app/{ => shared}/generated/model/createUIEndpoint.ts (100%) rename src/app/{ => shared}/generated/model/microfrontend.ts (100%) rename src/app/{ => shared}/generated/model/microfrontendAbstract.ts (100%) rename src/app/{ => shared}/generated/model/microfrontendPageResult.ts (100%) rename src/app/{ => shared}/generated/model/microfrontendSearchCriteria.ts (100%) rename src/app/{ => shared}/generated/model/models.ts (100%) rename src/app/{ => shared}/generated/model/problemDetailInvalidParam.ts (100%) rename src/app/{ => shared}/generated/model/problemDetailParam.ts (100%) rename src/app/{ => shared}/generated/model/problemDetailResponse.ts (100%) rename src/app/{ => shared}/generated/model/product.ts (100%) rename src/app/{ => shared}/generated/model/productAbstract.ts (100%) rename src/app/{ => shared}/generated/model/productPageResult.ts (100%) rename src/app/{ => shared}/generated/model/productSearchCriteria.ts (100%) rename src/app/{ => shared}/generated/model/uIEndpoint.ts (100%) rename src/app/{ => shared}/generated/model/updateMicrofrontendRequest.ts (100%) rename src/app/{ => shared}/generated/model/updateProductRequest.ts (100%) rename src/app/{ => shared}/generated/model/updateUIEndpoint.ts (100%) rename src/app/{ => shared}/generated/param.ts (100%) rename src/app/{ => shared}/generated/variables.ts (100%) delete mode 100644 src/app/shared/shared.module.spec.ts diff --git a/.eslintrc.json b/.eslintrc.json index 367b0d9..b57fb78 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -9,9 +9,8 @@ "dist/**", "helm/**", "node_modules/**", - "src/app/generated/**", - "src/app/api/*", - "src/app/model/*", + "src/app/shared/generated/**", + "src/assets/api/*", "src/**/*.ico", "src/**/*.svg" ], diff --git a/.github/workflows/create-new-build.yml b/.github/workflows/create-new-build.yml new file mode 100644 index 0000000..1404492 --- /dev/null +++ b/.github/workflows/create-new-build.yml @@ -0,0 +1,9 @@ +name: Create new build + +on: + workflow_dispatch: + +jobs: + build: + uses: onecx/ci-common/.github/workflows/create-new-build.yml@v1 + secrets: inherit diff --git a/.prettierignore b/.prettierignore index 6f46742..5a7360f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -15,6 +15,6 @@ README.md Dockerfile *.log *.sh -src/app/generated/** +src/app/shared/generated/** src/app/api/* src/app/model/* diff --git a/Dockerfile b/Dockerfile index f3c7326..c33d719 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ COPY dist/onecx-product-store-ui/ $DIR_HTML # Application environments default values ENV BFF_URL http://onecx-product-store-bff:8080/ -ENV APP_BASE_HREF /product-store/ +ENV APP_BASE_HREF / RUN chmod 775 -R "$DIR_HTML"/assets USER 1001 diff --git a/angular.json b/angular.json index 67d625b..74bdd10 100644 --- a/angular.json +++ b/angular.json @@ -108,12 +108,7 @@ "assets": ["src/favicon.ico", "src/assets"], "styles": ["src/styles.scss"], "scripts": [], - "codeCoverageExclude": [ - "**/*.module.ts", - "src/app/test/**", - "src/app/environments/**", - "src/app/generated/**" - ] + "codeCoverageExclude": ["**/*.module.ts", "src/app/environments/**", "src/app/shared/generated/**"] } }, "lint": { diff --git a/helm/Chart.yaml b/helm/Chart.yaml index 30db8db..4193918 100644 --- a/helm/Chart.yaml +++ b/helm/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: onecx-product-store-ui version: 0.0.0 -description: OneCX Product Store UI. +description: OneCX Product Store UI home: https://github.com/onecx/onecx-product-store-ui keywords: - product-store diff --git a/helm/microfrontend.json b/helm/microfrontend.json deleted file mode 100644 index a532193..0000000 --- a/helm/microfrontend.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "id": "ONECX_PRODUCT_STORE_UI", - {{- if .Values.app.routing.hostName }} - "remoteEntry": "https://{{ .Values.app.routing.hostName }}{{ .Values.app.routing.path }}remoteEntry.js", - {{- else if .Values.global.hostName }} - "remoteEntry": "https://{{ .Values.global.hostName }}{{ .Values.app.routing.path }}remoteEntry.js", - {{- end }} - "remoteName": "ThemeModule", - {{- if .Values.app.routing.hostName }} - "remoteBaseUrl": "https://{{ .Values.app.routing.hostName }}{{ .Values.app.routing.path }}", - {{- else if .Values.global.hostName }} - "remoteBaseUrl": "https://{{ .Values.global.hostName }}{{ .Values.app.routing.path }}", - {{- end }} - "exposedModule": "./ProductStoreMgmtModule", - "displayName": "ProductStoreMgmtModule", - "moduleType": "ANGULAR", - "wcTagName": "UPMF", - "appVersion": "{{ .Chart.Version }}", - "note": "Product Store module auto import via MF operator", - "contact": "onecx@1000kit.org" -} diff --git a/helm/permissions.csv b/helm/permissions.csv deleted file mode 100644 index a3f83c9..0000000 --- a/helm/permissions.csv +++ /dev/null @@ -1,8 +0,0 @@ -PERMISSION NAME;PERMISSION_KEY;onecx-product-store-admin;onecx-product-store-user -View Product Store;PRODUCT_STORE#VIEW;x;x -View/Search Products in Product Store;PRODUCT#SEARCH;x;x -View Product Details in Product Store;PRODUCT#VIEW;x;x -Edit Product Details in Product Store;PRODUCT#EDIT;x; -Delete Products in Product Store;PRODUCT#DELETE;x; -Edit Microfrontends;MICROFRONTEND#EDIT;x; -Delete Microfrontends;MICROFRONTEND#DELETE;x; \ No newline at end of file diff --git a/helm/templates/apm-csv.cm.yaml b/helm/templates/apm-csv.cm.yaml deleted file mode 100644 index e9dccc5..0000000 --- a/helm/templates/apm-csv.cm.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: "{{ .Release.Name }}-apm-config" - annotations: - appId: {{ .Release.Name }} - filename: "permissions.csv" - labels: - app: {{ .Release.Name }} -{{ include "angular.labels.common" .Subcharts.app | indent 4 }} -data: - permissions.csv: |- -{{ $.Files.Get "permissions.csv" | indent 4 }} - diff --git a/helm/templates/mf-config.yaml b/helm/templates/mf-config.yaml deleted file mode 100644 index 4d55751..0000000 --- a/helm/templates/mf-config.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: 'product-store-mgmt-ui-mf-config' -data: - microfrontend: |- -{{ tpl (.Files.Get "microfrontend.json") . | indent 4 }} diff --git a/nginx/locations.conf b/nginx/locations.conf index 3d30da3..2ce449a 100644 --- a/nginx/locations.conf +++ b/nginx/locations.conf @@ -1,4 +1,4 @@ -location @@APP_BASE_HREF { +location @@APP_BASE_HREFbff { proxy_pass @@BFF_URL; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $remote_addr; diff --git a/package-lock.json b/package-lock.json index ae3541a..7b98278 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,11 +23,11 @@ "@ngneat/falso": "^6.4.0", "@ngx-translate/core": "^14.0.0", "@ngx-translate/http-loader": "^7.0.0", - "@onecx/accelerator": "^3.7.1", - "@onecx/integration-interface": "^3.7.1", - "@onecx/keycloak-auth": "^3.7.1", - "@onecx/portal-integration-angular": "^3.7.1", - "@onecx/portal-layout-styles": "^3.7.1", + "@onecx/accelerator": "^4.1.2", + "@onecx/integration-interface": "^4.1.2", + "@onecx/keycloak-auth": "^4.1.2", + "@onecx/portal-integration-angular": "^4.1.2", + "@onecx/portal-layout-styles": "^4.1.2", "file-saver": "^2.0.5", "i18n-iso-countries": "^7.6.0", "ngx-color": "^8.0.3", @@ -7204,44 +7204,44 @@ } }, "node_modules/@onecx/accelerator": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@onecx/accelerator/-/accelerator-3.8.0.tgz", - "integrity": "sha512-bfihULhCrql3qCupGQrCzZ5nk6FAi1dDZZfLKtVclnYyzec58n0WQjt3ZKHazoAESWzjeXT4inW6m6WEW3x37w==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@onecx/accelerator/-/accelerator-4.2.0.tgz", + "integrity": "sha512-/CXlDCJ45gpaGsJuAIeD4j8j//i18qeCJ3v614R9hszGLA4/OAwUk65wyh+uEhehiy+y1KNFBDs6xdUmYgwm0g==", "peerDependencies": { "rxjs": "7.8.1", "tslib": "^2.3.0" } }, "node_modules/@onecx/integration-interface": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@onecx/integration-interface/-/integration-interface-3.8.0.tgz", - "integrity": "sha512-4bELfg+y4CCLD+CeFWyNIXtIcIFvQ1hdvDmn9wQorAPOudlQ+bdEGf6I3mA3NIf2v1VrjrZdQknlIb5Jx4HaYA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@onecx/integration-interface/-/integration-interface-4.2.0.tgz", + "integrity": "sha512-UNXf+rOZe/dee1B+wkQ593D1hEfB3YEzgY43E7yDcr+L0u9eYFTrrSGkz/bmMKC1sAy/Ls0BMUObwRqHCVWYJQ==", "peerDependencies": { - "@onecx/accelerator": "^3", + "@onecx/accelerator": "~4", "rxjs": "7.8.1", "tslib": "^2.3.0" } }, "node_modules/@onecx/keycloak-auth": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@onecx/keycloak-auth/-/keycloak-auth-3.8.0.tgz", - "integrity": "sha512-ns0p/pN65mZ/1Yp516JRF0i+5RGC7Qq4ol498xdGxnoxskXKVOX3IaJUgjdNIs3I5xLZ5+PAAN2uB1CUgSCreA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@onecx/keycloak-auth/-/keycloak-auth-4.2.0.tgz", + "integrity": "sha512-rrbMsNyEBHMr74sKghF1hKkbqaBDTN1SBP9B5fCJX2KSpKn5GlVCFfnw12EfXdlqDmQhiIttPxb8jKfWllm3Yw==", "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/common": ">=15.2.7", "@angular/core": ">=15.2.7", - "@onecx/portal-integration-angular": "~3", + "@onecx/portal-integration-angular": "~4", "keycloak-angular": "^13.0.0", "keycloak-js": "^18.0.0", "rxjs": "~7.8.0" } }, "node_modules/@onecx/portal-integration-angular": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@onecx/portal-integration-angular/-/portal-integration-angular-3.8.0.tgz", - "integrity": "sha512-L3yIuq/PUiE/2xLHXToxeerCvtdGyiwYk7s04rX1S4lo2A8hgABl9s3gCiFSPQFrx5w2NefBnQPf5Rtd16/RdA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@onecx/portal-integration-angular/-/portal-integration-angular-4.2.0.tgz", + "integrity": "sha512-y3EzIOwIGr4G5w63Ipxw8AEqnRrH+0pTUTmNhHHgZNCRbxurgO+aP1xk2YA+bLhBc1vgoTwKJ1ctgkbVTP6GnQ==", "dependencies": { "tslib": "^2.3.0" }, @@ -7260,7 +7260,7 @@ "@ngrx/store": "^15.4.0", "@ngx-translate/core": "^14.0.0", "@ngx-translate/http-loader": "^7.0.0", - "@onecx/integration-interface": "^3", + "@onecx/integration-interface": "~4", "chart.js": "^4.4.0", "d3-scale-chromatic": "^3.0.0", "fast-deep-equal": "^3.1.3", @@ -7271,9 +7271,9 @@ } }, "node_modules/@onecx/portal-layout-styles": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@onecx/portal-layout-styles/-/portal-layout-styles-3.8.0.tgz", - "integrity": "sha512-AtYUXAC6J4BK+SgHN6NrMKLz/psVeNWpCN4CnGVF3fVrZBncRo/2NL6SZLlJb4kCfkWgipsbFdaNdG16hZb9bw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@onecx/portal-layout-styles/-/portal-layout-styles-4.2.0.tgz", + "integrity": "sha512-ccuV1iJ4pTVk9tM8s/36zoSJ89INXMftqheJy6Suv6vYaKu5Cn6FZCtcCarVEGb+hKikVzdL408z7JZcOTdw0A==", "peerDependencies": { "tslib": "^2.5.0" } diff --git a/package.json b/package.json index 9581d4f..b818267 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ ], "config": { "openapiYaml": "src/assets/api/product-store-bff-api.yaml", - "openapiOutput": "src/app/generated" + "openapiOutput": "src/app/shared/generated" }, "scripts": { "build": "ng build", @@ -49,11 +49,11 @@ "@ngneat/falso": "^6.4.0", "@ngx-translate/core": "^14.0.0", "@ngx-translate/http-loader": "^7.0.0", - "@onecx/accelerator": "^3.7.1", - "@onecx/integration-interface": "^3.7.1", - "@onecx/keycloak-auth": "^3.7.1", - "@onecx/portal-integration-angular": "^3.7.1", - "@onecx/portal-layout-styles": "^3.7.1", + "@onecx/accelerator": "^4.1.2", + "@onecx/integration-interface": "^4.1.2", + "@onecx/keycloak-auth": "^4.1.2", + "@onecx/portal-integration-angular": "^4.1.2", + "@onecx/portal-layout-styles": "^4.1.2", "file-saver": "^2.0.5", "i18n-iso-countries": "^7.6.0", "ngx-color": "^8.0.3", diff --git a/proxy.conf.js b/proxy.conf.js index 208fd0a..5e2197e 100644 --- a/proxy.conf.js +++ b/proxy.conf.js @@ -17,11 +17,11 @@ const bypassFn = function (req, res) { } const PROXY_CONFIG = { - '/product-store-bff': { + '/bff': { target: 'http://onecx-product-store-bff', secure: false, pathRewrite: { - '^.*/product-store-bff': '' + '^.*/bff': '' }, changeOrigin: true, logLevel: 'debug', diff --git a/sonar-local-project.properties b/sonar-local-project.properties index 7df0358..4634f5a 100644 --- a/sonar-local-project.properties +++ b/sonar-local-project.properties @@ -18,7 +18,7 @@ sonar.testExecutionReportPaths=reports/sonarqube_report.xml sonar.sourceEncoding=UTF-8 #sonar.sources=src/app #sonar.working.directory=dist/sonar -sonar.coverage.exclusions=*.ts,*.js,src/*.ts,src/**/*.module.ts,src/environments/*,src/assets/**/*,src/app/generated/**/*,src/app/test/* +sonar.coverage.exclusions=*.ts,*.js,src/*.ts,src/**/*.module.ts,src/environments/*,src/assets/**/*,src/app/shared/generated/**/* #sonar.exclusions=src/app/generated/**/* #sonar.cpd.exclusions= #sonar.tests=src/app diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 6120645..c923bc4 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -1,12 +1,12 @@ import { TestBed } from '@angular/core/testing' import { AppComponent } from './app.component' -import { RouterTestingModule } from '@angular/router/testing' +import { NO_ERRORS_SCHEMA } from '@angular/core' describe('AppComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [RouterTestingModule], - declarations: [AppComponent] + declarations: [AppComponent], + schemas: [NO_ERRORS_SCHEMA] }).compileComponents() }) @@ -16,9 +16,9 @@ describe('AppComponent', () => { expect(app).toBeTruthy() }) - it(`should have as title 'product-store-ui'`, () => { + it(`should have as title 'onecx-product-store-ui'`, () => { const fixture = TestBed.createComponent(AppComponent) const app = fixture.componentInstance - expect(app.title).toEqual('product-store-ui') + expect(app.title).toEqual('onecx-product-store-ui') }) }) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index b5534ac..f485124 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -5,5 +5,5 @@ import { Component } from '@angular/core' templateUrl: './app.component.html' }) export class AppComponent { - title = 'product-store-ui' + title = 'onecx-product-store-ui' } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 1090086..2a2fd88 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,35 +1,61 @@ import { APP_INITIALIZER, CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, NgModule } from '@angular/core' +import { CommonModule } from '@angular/common' +import { HttpClient, HttpClientModule } from '@angular/common/http' +import { RouterModule, Routes } from '@angular/router' import { BrowserModule } from '@angular/platform-browser' import { BrowserAnimationsModule } from '@angular/platform-browser/animations' -import { TranslateService } from '@ngx-translate/core' -import { DialogService } from 'primeng/dynamicdialog' -import { Observable } from 'rxjs' +import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core' -import { APP_CONFIG, PortalCoreModule } from '@onecx/portal-integration-angular' +import { + APP_CONFIG, + AppStateService, + createTranslateLoader, + translateServiceInitializer, + PortalCoreModule, + UserService +} from '@onecx/portal-integration-angular' import { KeycloakAuthModule } from '@onecx/keycloak-auth' import { AppComponent } from './app.component' -import { environment } from '../environments/environment' - -// standalone app: ensure translations are loaded during app init -function initializer(translate: TranslateService): () => Observable { - console.log('App module initializer') - return () => { - translate.addLangs(['en', 'de']) - const browserLang = translate.getBrowserLang() - return translate.use(browserLang?.match(/en|de/) ? browserLang : 'en') - } -} +import { environment } from 'src/environments/environment' +const routes: Routes = [{ path: '', pathMatch: 'full' }] @NgModule({ bootstrap: [AppComponent], declarations: [AppComponent], - imports: [BrowserModule, KeycloakAuthModule, BrowserAnimationsModule, PortalCoreModule.forRoot('product-store-ui')], + imports: [ + CommonModule, + BrowserModule, + HttpClientModule, + KeycloakAuthModule, + BrowserAnimationsModule, + RouterModule.forRoot(routes, { + initialNavigation: 'enabledBlocking', + enableTracing: true + }), + PortalCoreModule.forRoot('onecx-product-store-ui'), + TranslateModule.forRoot({ + isolate: true, + loader: { + provide: TranslateLoader, + useFactory: createTranslateLoader, + deps: [HttpClient, AppStateService] + } + }) + ], providers: [ - DialogService, { provide: APP_CONFIG, useValue: environment }, - { provide: APP_INITIALIZER, useFactory: initializer, multi: true, deps: [TranslateService] } + { + provide: APP_INITIALIZER, + useFactory: translateServiceInitializer, + multi: true, + deps: [UserService, TranslateService] + } ], schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA] }) -export class AppModule {} +export class AppModule { + constructor() { + console.info('App Module constructor') + } +} diff --git a/src/app/onecx-product-store-remote.module.ts b/src/app/onecx-product-store-remote.module.ts new file mode 100644 index 0000000..f9df428 --- /dev/null +++ b/src/app/onecx-product-store-remote.module.ts @@ -0,0 +1,43 @@ +import { HttpClient } from '@angular/common/http' +import { NgModule } from '@angular/core' +import { RouterModule, Routes } from '@angular/router' +import { MissingTranslationHandler, TranslateLoader, TranslateModule } from '@ngx-translate/core' + +import { + addInitializeModuleGuard, + AppStateService, + ConfigurationService, + createTranslateLoader, + PortalCoreModule, + PortalMissingTranslationHandler +} from '@onecx/portal-integration-angular' + +const routes: Routes = [ + { + path: '', + loadChildren: () => import('./product-store/product-store.module').then((m) => m.ProductStoreModule) + } +] +@NgModule({ + imports: [ + PortalCoreModule.forMicroFrontend(), + RouterModule.forChild(addInitializeModuleGuard(routes)), + TranslateModule.forRoot({ + isolate: true, + loader: { + provide: TranslateLoader, + useFactory: createTranslateLoader, + deps: [HttpClient, AppStateService] + }, + missingTranslationHandler: { provide: MissingTranslationHandler, useClass: PortalMissingTranslationHandler } + }) + ], + exports: [], + providers: [ConfigurationService], + schemas: [] +}) +export class OneCXProductStoreModule { + constructor() { + console.info('OneCX Product Store Module constructor') + } +} diff --git a/src/app/product-store-remote.module.ts b/src/app/product-store-remote.module.ts deleted file mode 100644 index c642446..0000000 --- a/src/app/product-store-remote.module.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Inject, NgModule } from '@angular/core' -import { RouterModule, Routes } from '@angular/router' - -import { MFE_INFO, MfeInfo, PortalCoreModule } from '@onecx/portal-integration-angular' - -const routes: Routes = [ - { - path: '', - loadChildren: () => import('./product-store/product-store.module').then((m) => m.ProductStoreModule) - } -] -@NgModule({ - imports: [PortalCoreModule.forMicroFrontend(), RouterModule.forChild(routes)], - exports: [], - providers: [], - schemas: [] -}) -export class ProductStoreMgmtModule { - constructor(@Inject(MFE_INFO) mfeInfo?: MfeInfo) { - console.info('Product Store Mgmt Module constructor', mfeInfo) - } -} diff --git a/src/app/product-store/app-detail/app-detail.component.ts b/src/app/product-store/app-detail/app-detail.component.ts index 5fd7cfd..cbcf9cf 100644 --- a/src/app/product-store/app-detail/app-detail.component.ts +++ b/src/app/product-store/app-detail/app-detail.component.ts @@ -1,21 +1,16 @@ -import { Component, EventEmitter, Inject, Input, OnChanges, Output } from '@angular/core' +import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core' //import { DatePipe } from '@angular/common' import { FormControl, FormGroup, Validators } from '@angular/forms' import { finalize } from 'rxjs' import { TranslateService } from '@ngx-translate/core' -import { - AUTH_SERVICE, - ConfigurationService, - IAuthService, - PortalMessageService -} from '@onecx/portal-integration-angular' +import { PortalMessageService, UserService } from '@onecx/portal-integration-angular' import { GetMicrofrontendRequestParams, MicrofrontendsAPIService, MicrofrontendAbstract, Microfrontend -} from 'src/app/generated' +} from 'src/app/shared/generated' type ChangeMode = 'VIEW' | 'CREATE' | 'EDIT' interface AppDetailForm { @@ -53,15 +48,15 @@ export class AppDetailComponent implements OnChanges { public hasEditPermission = false constructor( + private user: UserService, private appApi: MicrofrontendsAPIService, - private config: ConfigurationService, private msgService: PortalMessageService, - private translate: TranslateService, - @Inject(AUTH_SERVICE) readonly auth: IAuthService + private translate: TranslateService ) { - this.hasCreatePermission = this.auth.hasPermission('MICROFRONTEND#CREATE') - this.hasEditPermission = this.auth.hasPermission('MICROFRONTEND#EDIT') - this.dateFormat = this.config.lang === 'de' ? 'dd.MM.yyyy HH:mm:ss' : 'medium' + this.hasCreatePermission = this.user.hasPermission('MICROFRONTEND#CREATE') + this.hasEditPermission = this.user.hasPermission('MICROFRONTEND#EDIT') + this.dateFormat = this.user.lang$.getValue() === 'de' ? 'dd.MM.yyyy HH:mm:ss' : 'medium' + this.formGroup = new FormGroup({ appId: new FormControl(null, [Validators.required, Validators.minLength(2), Validators.maxLength(255)]), appName: new FormControl(null, [Validators.required, Validators.minLength(2), Validators.maxLength(255)]), diff --git a/src/app/product-store/app-search/app-search.component.html b/src/app/product-store/app-search/app-search.component.html index 739a272..f101097 100644 --- a/src/app/product-store/app-search/app-search.component.html +++ b/src/app/product-store/app-search/app-search.component.html @@ -101,9 +101,9 @@ (click)="onGotoProduct($event, app.productName)" >
-
{{ limitText(app.appId, 25) }}
-
{{ limitText(app.appName, 30) }}
-
{{ limitText(app.productName, 25) }}
+
{{ limitText(app.appId, 25) }}
+
{{ limitText(app.appName, 30) }}
+
{{ limitText(app.productName, 25) }}
diff --git a/src/app/product-store/app-search/app-search.component.ts b/src/app/product-store/app-search/app-search.component.ts index 993ce2a..eeacea1 100644 --- a/src/app/product-store/app-search/app-search.component.ts +++ b/src/app/product-store/app-search/app-search.component.ts @@ -2,10 +2,11 @@ import { Component, OnInit, ViewChild } from '@angular/core' import { FormControl, FormGroup } from '@angular/forms' import { ActivatedRoute, Router } from '@angular/router' import { TranslateService } from '@ngx-translate/core' -import { Action, DataViewControlTranslations } from '@onecx/portal-integration-angular' import { DataView } from 'primeng/dataview' import { Observable, finalize } from 'rxjs' -import { MicrofrontendAbstract, MicrofrontendPageResult, MicrofrontendsAPIService } from 'src/app/generated' + +import { Action, DataViewControlTranslations } from '@onecx/portal-integration-angular' +import { MicrofrontendAbstract, MicrofrontendPageResult, MicrofrontendsAPIService } from 'src/app/shared/generated' import { limitText } from 'src/app/shared/utils' export interface MicrofrontendSearchCriteria { diff --git a/src/app/product-store/product-detail/product-apps/product-apps.component.spec.ts b/src/app/product-store/product-detail/product-apps/product-apps.component.spec.ts index 8dc5e7b..2f9af07 100644 --- a/src/app/product-store/product-detail/product-apps/product-apps.component.spec.ts +++ b/src/app/product-store/product-detail/product-apps/product-apps.component.spec.ts @@ -1,20 +1,17 @@ import { NO_ERRORS_SCHEMA } from '@angular/core' import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing' -import { HttpClient } from '@angular/common/http' import { HttpClientTestingModule } from '@angular/common/http/testing' import { RouterTestingModule } from '@angular/router/testing' -import { TranslateLoader, TranslateModule } from '@ngx-translate/core' -import { of, throwError } from 'rxjs' -import { FormControl, FormGroup, Validators } from '@angular/forms' +import { of } from 'rxjs' +import { TranslateTestingModule } from 'ngx-translate-testing' import { PortalMessageService } from '@onecx/portal-integration-angular' -import { HttpLoaderFactory } from 'src/app/shared/shared.module' -import { ProductPropertyComponent, ProductDetailForm } from './product-props.component' -import { ProductsAPIService } from 'src/app/generated' +import { ProductAppsComponent } from './product-apps.component' +import { ProductsAPIService } from 'src/app/shared/generated' -describe('ProductPropertyComponent', () => { - let component: ProductPropertyComponent - let fixture: ComponentFixture +describe('ProductAppComponent', () => { + let component: ProductAppsComponent + let fixture: ComponentFixture const msgServiceSpy = jasmine.createSpyObj('PortalMessageService', ['success', 'error', 'info']) const apiServiceSpy = { @@ -24,28 +21,25 @@ describe('ProductPropertyComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - declarations: [ProductPropertyComponent], + declarations: [ProductAppsComponent], imports: [ - HttpClientTestingModule, RouterTestingModule, - TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useFactory: HttpLoaderFactory, - deps: [HttpClient] - } - }) + HttpClientTestingModule, + TranslateTestingModule.withTranslations({ + de: require('src/assets/i18n/de.json'), + en: require('src/assets/i18n/en.json') + }).withDefaultLanguage('en') ], - schemas: [NO_ERRORS_SCHEMA], providers: [ { provide: ProductsAPIService, useValue: apiServiceSpy }, { provide: PortalMessageService, useValue: msgServiceSpy } - ] + ], + schemas: [NO_ERRORS_SCHEMA] }).compileComponents() })) beforeEach(() => { - fixture = TestBed.createComponent(ProductPropertyComponent) + fixture = TestBed.createComponent(ProductAppsComponent) component = fixture.componentInstance fixture.detectChanges() }) @@ -61,158 +55,4 @@ describe('ProductPropertyComponent', () => { it('should create', () => { expect(component).toBeTruthy() }) - - it('should patchValue in formGroup onChanges if product', () => { - const product = { - id: 'id', - name: 'name', - basePath: 'path' - } - component.product = product - spyOn(component.formGroup, 'patchValue') - - component.ngOnChanges() - - expect(component.formGroup.patchValue).toHaveBeenCalledWith({ ...product }) - expect(component.product.name).toEqual(product.name) - }) - - it('should reset formGroup onChanges if no product', () => { - spyOn(component.formGroup, 'reset') - - component.ngOnChanges() - - expect(component.formGroup.reset).toHaveBeenCalled() - }) - - it('should call createProduct onSubmit in new mode', () => { - apiServiceSpy.createProduct.and.returnValue(of({})) - const formGroup = new FormGroup({ - id: new FormControl('id'), - name: new FormControl('name'), - operator: new FormControl(null), - version: new FormControl('version'), - description: new FormControl(null), - imageUrl: new FormControl(null), - basePath: new FormControl('path'), - displayName: new FormControl('display'), - iconName: new FormControl('icon'), - classifications: new FormControl(null) - }) - component.formGroup = formGroup as FormGroup - component.changeMode = 'CREATE' - - component.onSubmit() - - expect(apiServiceSpy.createProduct).toHaveBeenCalled() - expect(msgServiceSpy.success).toHaveBeenCalledWith({ summaryKey: 'ACTIONS.CREATE.PRODUCT.OK' }) - }) - - it('should call updateProduct onSubmit in edit mode', () => { - apiServiceSpy.updateProduct.and.returnValue(of({})) - const formGroup = new FormGroup({ - id: new FormControl('id'), - name: new FormControl('name'), - operator: new FormControl(null), - version: new FormControl('version'), - description: new FormControl(null), - imageUrl: new FormControl(null), - basePath: new FormControl('path'), - displayName: new FormControl('display'), - iconName: new FormControl('icon'), - classifications: new FormControl(null) - }) - component.formGroup = formGroup as FormGroup - component.changeMode = 'EDIT' - - component.onSubmit() - - expect(apiServiceSpy.updateProduct).toHaveBeenCalled() - expect(msgServiceSpy.success).toHaveBeenCalledWith({ summaryKey: 'ACTIONS.EDIT.PRODUCT.OK' }) - }) - - it('should display error if updateProduct fails', () => { - apiServiceSpy.updateProduct.and.returnValue(throwError(() => new Error())) - const formGroup = new FormGroup({ - id: new FormControl('id'), - name: new FormControl('name'), - operator: new FormControl(null), - version: new FormControl('version'), - description: new FormControl(null), - imageUrl: new FormControl(null), - basePath: new FormControl('path'), - displayName: new FormControl('display'), - iconName: new FormControl('icon'), - classifications: new FormControl(null) - }) - component.formGroup = formGroup as FormGroup - component.changeMode = 'EDIT' - - component.onSubmit() - - expect(component.formGroup.valid).toBeTrue() - expect(msgServiceSpy.error).toHaveBeenCalledWith({ - summaryKey: 'ACTIONS.EDIT.PRODUCT.NOK' - }) - }) - - it('should display error if createProduct fails', () => { - apiServiceSpy.createProduct.and.returnValue(throwError(() => new Error())) - const formGroup = new FormGroup({ - id: new FormControl('id'), - name: new FormControl('name'), - operator: new FormControl(null), - version: new FormControl('version'), - description: new FormControl(null), - imageUrl: new FormControl(null), - basePath: new FormControl('path'), - displayName: new FormControl('display'), - iconName: new FormControl('icon'), - classifications: new FormControl(null) - }) - component.formGroup = formGroup as FormGroup - component.changeMode = 'CREATE' - - component.onSubmit() - - expect(component.formGroup.valid).toBeTrue() - expect(msgServiceSpy.error).toHaveBeenCalledWith({ - summaryKey: 'ACTIONS.CREATE.PRODUCT.NOK' - }) - }) - - it('should display error onSubmit if formGroup invalid', () => { - const formGroup = new FormGroup({ - id: new FormControl(null, Validators.required), - name: new FormControl('name'), - operator: new FormControl(null), - version: new FormControl('version'), - description: new FormControl(null), - imageUrl: new FormControl(null), - basePath: new FormControl('path'), - displayName: new FormControl('display'), - iconName: new FormControl('icon'), - classifications: new FormControl(null) - }) - component.formGroup = formGroup as FormGroup - - component.onSubmit() - - expect(component.formGroup.valid).toBeFalse() - expect(msgServiceSpy.error).toHaveBeenCalledWith({ - summaryKey: 'VALIDATION.FORM_INVALID' - }) - }) - - it('should display error onSubmit if formGroup invalid', () => { - const event = { - target: { - files: ['file'] - } - } - - component.onFileUpload(event as any, 'logo') - - expect(component.formGroup.valid).toBeFalse() - }) }) diff --git a/src/app/product-store/product-detail/product-apps/product-apps.component.ts b/src/app/product-store/product-detail/product-apps/product-apps.component.ts index 8ade9a4..a11375d 100644 --- a/src/app/product-store/product-detail/product-apps/product-apps.component.ts +++ b/src/app/product-store/product-detail/product-apps/product-apps.component.ts @@ -3,9 +3,9 @@ import { SelectItem } from 'primeng/api' import { Observable, finalize } from 'rxjs' import { DataViewControlTranslations, PortalMessageService } from '@onecx/portal-integration-angular' -import { Product, MicrofrontendsAPIService, MicrofrontendPageResult } from '../../../generated' +import { Product, MicrofrontendsAPIService, MicrofrontendPageResult } from 'src/app/shared/generated' import { dropDownSortItemsByLabel, limitText } from 'src/app/shared/utils' -import { IconService } from '../../../shared/iconservice' +import { IconService } from 'src/app/shared/iconservice' @Component({ selector: 'app-product-apps', diff --git a/src/app/product-store/product-detail/product-detail.component.ts b/src/app/product-store/product-detail/product-detail.component.ts index 73ddbf8..d2492ef 100644 --- a/src/app/product-store/product-detail/product-detail.component.ts +++ b/src/app/product-store/product-detail/product-detail.component.ts @@ -5,9 +5,9 @@ import { ActivatedRoute, Router } from '@angular/router' import { finalize } from 'rxjs' import { TranslateService } from '@ngx-translate/core' -import { Action, ConfigurationService, PortalMessageService } from '@onecx/portal-integration-angular' +import { Action, PortalMessageService, UserService } from '@onecx/portal-integration-angular' //import { limitText } from '../../shared/utils' -import { Product, ProductsAPIService } from 'src/app/generated' +import { Product, ProductsAPIService } from 'src/app/shared/generated' import { environment } from 'src/environments/environment' import { ProductPropertyComponent } from './product-props/product-props.component' @@ -15,8 +15,7 @@ type ChangeMode = 'VIEW' | 'CREATE' | 'EDIT' @Component({ templateUrl: './product-detail.component.html', - styleUrls: ['./product-detail.component.scss'], - providers: [ConfigurationService] + styleUrls: ['./product-detail.component.scss'] }) export class ProductDetailComponent implements OnInit { @ViewChild(ProductPropertyComponent, { static: false }) productPropsComponent!: ProductPropertyComponent @@ -36,13 +35,13 @@ export class ProductDetailComponent implements OnInit { constructor( private router: Router, private route: ActivatedRoute, + private user: UserService, private location: Location, private productApi: ProductsAPIService, - private config: ConfigurationService, private msgService: PortalMessageService, private translate: TranslateService ) { - this.dateFormat = this.config.lang === 'de' ? 'dd.MM.yyyy HH:mm:ss' : 'medium' + this.dateFormat = this.user.lang$.getValue() === 'de' ? 'dd.MM.yyyy HH:mm:ss' : 'medium' this.productName = this.route.snapshot.paramMap.get('name') || '' } diff --git a/src/app/product-store/product-detail/product-intern/product-intern.component.ts b/src/app/product-store/product-detail/product-intern/product-intern.component.ts index 35c5e70..8972c2a 100644 --- a/src/app/product-store/product-detail/product-intern/product-intern.component.ts +++ b/src/app/product-store/product-detail/product-intern/product-intern.component.ts @@ -1,7 +1,7 @@ import { Component, Input } from '@angular/core' import { TranslateService } from '@ngx-translate/core' -import { Product } from '../../../generated' +import { Product } from 'src/app/shared/generated' @Component({ selector: 'app-product-intern', diff --git a/src/app/product-store/product-detail/product-props/product-props.component.spec.ts b/src/app/product-store/product-detail/product-props/product-props.component.spec.ts index 8dc5e7b..381641e 100644 --- a/src/app/product-store/product-detail/product-props/product-props.component.spec.ts +++ b/src/app/product-store/product-detail/product-props/product-props.component.spec.ts @@ -1,46 +1,41 @@ import { NO_ERRORS_SCHEMA } from '@angular/core' import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing' -import { HttpClient } from '@angular/common/http' import { HttpClientTestingModule } from '@angular/common/http/testing' import { RouterTestingModule } from '@angular/router/testing' -import { TranslateLoader, TranslateModule } from '@ngx-translate/core' import { of, throwError } from 'rxjs' import { FormControl, FormGroup, Validators } from '@angular/forms' +import { TranslateTestingModule } from 'ngx-translate-testing' import { PortalMessageService } from '@onecx/portal-integration-angular' -import { HttpLoaderFactory } from 'src/app/shared/shared.module' import { ProductPropertyComponent, ProductDetailForm } from './product-props.component' -import { ProductsAPIService } from 'src/app/generated' +import { ProductsAPIService } from 'src/app/shared/generated' describe('ProductPropertyComponent', () => { let component: ProductPropertyComponent let fixture: ComponentFixture - const msgServiceSpy = jasmine.createSpyObj('PortalMessageService', ['success', 'error', 'info']) const apiServiceSpy = { createProduct: jasmine.createSpy('createProduct').and.returnValue(of({})), updateProduct: jasmine.createSpy('updateProduct').and.returnValue(of({})) } + const msgServiceSpy = jasmine.createSpyObj('PortalMessageService', ['success', 'error', 'info']) beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ProductPropertyComponent], imports: [ - HttpClientTestingModule, RouterTestingModule, - TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useFactory: HttpLoaderFactory, - deps: [HttpClient] - } - }) + HttpClientTestingModule, + TranslateTestingModule.withTranslations({ + de: require('src/assets/i18n/de.json'), + en: require('src/assets/i18n/en.json') + }).withDefaultLanguage('en') ], - schemas: [NO_ERRORS_SCHEMA], providers: [ { provide: ProductsAPIService, useValue: apiServiceSpy }, { provide: PortalMessageService, useValue: msgServiceSpy } - ] + ], + schemas: [NO_ERRORS_SCHEMA] }).compileComponents() })) diff --git a/src/app/product-store/product-detail/product-props/product-props.component.ts b/src/app/product-store/product-detail/product-props/product-props.component.ts index 688b8e8..a9d516a 100644 --- a/src/app/product-store/product-detail/product-props/product-props.component.ts +++ b/src/app/product-store/product-detail/product-props/product-props.component.ts @@ -3,8 +3,8 @@ import { AbstractControl, FormControl, FormGroup, ValidationErrors, ValidatorFn, import { SelectItem } from 'primeng/api' import { PortalMessageService } from '@onecx/portal-integration-angular' -import { CreateProductRequest, Product, ProductsAPIService, UpdateProductRequest } from '../../../generated' -import { IconService } from '../../../shared/iconservice' +import { CreateProductRequest, Product, ProductsAPIService, UpdateProductRequest } from 'src/app/shared/generated' +import { IconService } from 'src/app/shared/iconservice' import { dropDownSortItemsByLabel } from 'src/app/shared/utils' export interface ProductDetailForm { diff --git a/src/app/product-store/product-detail/product.detail.component.spec.ts b/src/app/product-store/product-detail/product.detail.component.spec.ts index ec1e488..f1400cd 100644 --- a/src/app/product-store/product-detail/product.detail.component.spec.ts +++ b/src/app/product-store/product-detail/product.detail.component.spec.ts @@ -1,45 +1,40 @@ import { NO_ERRORS_SCHEMA } from '@angular/core' import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing' -import { HttpClient } from '@angular/common/http' import { HttpClientTestingModule } from '@angular/common/http/testing' import { RouterTestingModule } from '@angular/router/testing' -import { TranslateLoader, TranslateModule } from '@ngx-translate/core' -import { of, throwError } from 'rxjs' +import { of } from 'rxjs' +import { TranslateTestingModule } from 'ngx-translate-testing' import { PortalMessageService } from '@onecx/portal-integration-angular' -import { HttpLoaderFactory } from 'src/app/shared/shared.module' import { ProductDetailComponent } from './product-detail.component' -import { ProductsAPIService } from 'src/app/generated' +import { ProductsAPIService } from 'src/app/shared/generated' describe('ProductDetailComponent', () => { let component: ProductDetailComponent let fixture: ComponentFixture - const msgServiceSpy = jasmine.createSpyObj('PortalMessageService', ['success', 'error', 'info']) const apiServiceSpy = { searchProducts: jasmine.createSpy('searchProducts').and.returnValue(of({})), - getProduct: jasmine.createSpy('getProduct').and.returnValue(of({})) + getProductByName: jasmine.createSpy('getProductByName').and.returnValue(of({})) } + const msgServiceSpy = jasmine.createSpyObj('PortalMessageService', ['success', 'error', 'info']) beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ProductDetailComponent], imports: [ - HttpClientTestingModule, RouterTestingModule, - TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useFactory: HttpLoaderFactory, - deps: [HttpClient] - } - }) + HttpClientTestingModule, + TranslateTestingModule.withTranslations({ + de: require('src/assets/i18n/de.json'), + en: require('src/assets/i18n/en.json') + }).withDefaultLanguage('en') ], - schemas: [NO_ERRORS_SCHEMA], providers: [ { provide: ProductsAPIService, useValue: apiServiceSpy }, { provide: PortalMessageService, useValue: msgServiceSpy } - ] + ], + schemas: [NO_ERRORS_SCHEMA] }).compileComponents() })) @@ -54,7 +49,7 @@ describe('ProductDetailComponent', () => { msgServiceSpy.error.calls.reset() msgServiceSpy.info.calls.reset() apiServiceSpy.searchProducts.calls.reset() - apiServiceSpy.getProduct.calls.reset() + apiServiceSpy.getProductByName.calls.reset() }) it('should create', () => { @@ -69,36 +64,15 @@ describe('ProductDetailComponent', () => { expect(component.changeMode).toEqual('VIEW') }) - it('should search products onInit', () => { - const productPageResult = { - stream: [ - { - id: 'id', - name: 'name', - basePath: 'path' - } - ] - } - apiServiceSpy.searchProducts.and.returnValue(of(productPageResult)) - apiServiceSpy.getProduct.and.returnValue(of({ id: 'id' })) - component.productName = 'name' - - component.ngOnInit() - - expect(component.product?.id).toEqual(productPageResult.stream[0].id) - }) + it('should get product onInit', () => { + const p = { id: 'id', name: 'name', basePath: 'path' } + apiServiceSpy.getProductByName.and.returnValue(of(p)) - it('should display error if searchProducts fails', () => { - const errorMsg = 'Product was not found' - const mockError = new Error(errorMsg) - apiServiceSpy.searchProducts.and.returnValue(throwError(() => mockError)) component.productName = 'name' component.ngOnInit() - expect(msgServiceSpy.error).toHaveBeenCalledWith({ - summaryKey: 'ACTIONS.SEARCH.PRODUCT.LOAD_ERROR' - }) + expect(component.product?.id).toEqual(p.id) }) it('should prepare action buttons callbacks on init: close', () => { diff --git a/src/app/product-store/product-search/product-search.component.spec.ts b/src/app/product-store/product-search/product-search.component.spec.ts_nok similarity index 82% rename from src/app/product-store/product-search/product-search.component.spec.ts rename to src/app/product-store/product-search/product-search.component.spec.ts_nok index 5cc6b4a..62409db 100644 --- a/src/app/product-store/product-search/product-search.component.spec.ts +++ b/src/app/product-store/product-search/product-search.component.spec.ts_nok @@ -1,14 +1,14 @@ import { NO_ERRORS_SCHEMA } from '@angular/core' import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing' -import { HttpClient } from '@angular/common/http' import { HttpClientTestingModule } from '@angular/common/http/testing' import { RouterTestingModule } from '@angular/router/testing' import { Router, ActivatedRoute } from '@angular/router' -import { TranslateLoader, TranslateModule } from '@ngx-translate/core' import { of } from 'rxjs' +import { DataViewModule } from 'primeng/dataview' +import { TranslateTestingModule } from 'ngx-translate-testing' import { PortalMessageService } from '@onecx/portal-integration-angular' -import { HttpLoaderFactory } from 'src/app/shared/shared.module' +import { ProductsAPIService } from 'src/app/shared/generated' import { ProductSearchComponent } from './product-search.component' describe('ProductSearchComponent', () => { @@ -17,6 +17,7 @@ describe('ProductSearchComponent', () => { let router: Router let routeSpy: jasmine.SpyObj + const productApiSpy = jasmine.createSpyObj('ProductsAPIService', ['searchProducts']) const msgServiceSpy = jasmine.createSpyObj('PortalMessageService', ['success', 'error', 'info']) const translateServiceSpy = jasmine.createSpyObj('TranslateService', ['get']) @@ -24,21 +25,16 @@ describe('ProductSearchComponent', () => { TestBed.configureTestingModule({ declarations: [ProductSearchComponent], imports: [ - HttpClientTestingModule, RouterTestingModule, - TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useFactory: HttpLoaderFactory, - deps: [HttpClient] - } - }) + HttpClientTestingModule, + DataViewModule, + TranslateTestingModule.withTranslations({ + de: require('src/assets/i18n/de.json'), + en: require('src/assets/i18n/en.json') + }).withDefaultLanguage('en') ], - schemas: [NO_ERRORS_SCHEMA], - providers: [ - { provide: PortalMessageService, useValue: msgServiceSpy }, - { provide: ActivatedRoute, useValue: routeSpy } - ] + providers: [{ provide: ProductsAPIService, useValue: productApiSpy }], + schemas: [NO_ERRORS_SCHEMA] }).compileComponents() })) @@ -105,11 +101,11 @@ describe('ProductSearchComponent', () => { it('should call loadProducts onSearch', () => { translateServiceSpy.get.and.returnValue(of({ 'ACTIONS.CREATE.LABEL': 'Create' })) - spyOn(component, 'loadProducts') + spyOn(component, 'searchData') component.onSearch() - expect(component.loadProducts).toHaveBeenCalled() + expect(component.searchData).toHaveBeenCalled() }) it('should reset productSearchCriteriaGroup onSearchReset', () => { diff --git a/src/app/product-store/product-search/product-search.component.ts b/src/app/product-store/product-search/product-search.component.ts index 6776462..23f3e62 100644 --- a/src/app/product-store/product-search/product-search.component.ts +++ b/src/app/product-store/product-search/product-search.component.ts @@ -1,12 +1,14 @@ import { Component, OnInit, ViewChild } from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' +import { FormControl, FormGroup } from '@angular/forms' import { Observable, finalize } from 'rxjs' -import { DataView } from 'primeng/dataview' import { TranslateService } from '@ngx-translate/core' +import { DataView } from 'primeng/dataview' + import { Action, DataViewControlTranslations } from '@onecx/portal-integration-angular' -import { ProductPageResult, ProductsAPIService } from '../../generated' -import { limitText } from '../../shared/utils' -import { FormControl, FormGroup } from '@angular/forms' + +import { ProductPageResult, ProductsAPIService } from 'src/app/shared/generated' +import { limitText } from 'src/app/shared/utils' export interface ProductSearchCriteria { productName: FormControl diff --git a/src/app/product-store/product-store.module.ts b/src/app/product-store/product-store.module.ts index 67ca5a4..58243c9 100644 --- a/src/app/product-store/product-store.module.ts +++ b/src/app/product-store/product-store.module.ts @@ -1,16 +1,16 @@ import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, NgModule } from '@angular/core' -import { HttpClient } from '@angular/common/http' +import { CommonModule } from '@angular/common' import { FormsModule } from '@angular/forms' import { RouterModule, Routes } from '@angular/router' -import { MissingTranslationHandler, TranslateLoader, TranslateModule } from '@ngx-translate/core' - import { FieldsetModule } from 'primeng/fieldset' +import { ConfirmDialogModule } from 'primeng/confirmdialog' +import { ConfirmationService } from 'primeng/api' + +import { addInitializeModuleGuard, InitializeModuleGuard, PortalCoreModule } from '@onecx/portal-integration-angular' -import { MFE_INFO, PortalCoreModule, MyMissingTranslationHandler } from '@onecx/portal-integration-angular' +import { LabelResolver } from 'src/app/shared/label.resolver' +import { SharedModule } from 'src/app/shared/shared.module' -import { CanActivateGuard } from '../shared/can-active-guard.service' -import { LabelResolver } from '../shared/label.resolver' -import { HttpLoaderFactory, SharedModule } from '../shared/shared.module' import { AppSearchComponent } from './app-search/app-search.component' import { AppDetailComponent } from './app-detail/app-detail.component' import { ProductSearchComponent } from './product-search/product-search.component' @@ -23,44 +23,25 @@ const routes: Routes = [ { path: '', component: ProductSearchComponent, - canActivate: [CanActivateGuard], pathMatch: 'full' }, { path: 'apps', component: AppSearchComponent, - canActivate: [CanActivateGuard], - data: { - breadcrumb: 'BREADCRUMBS.APPS', - breadcrumbFn: (data: any) => `${data.labeli18n}` - }, - resolve: { - labeli18n: LabelResolver - } + data: { breadcrumb: 'BREADCRUMBS.APPS', breadcrumbFn: (data: any) => `${data.labeli18n}` }, + resolve: { labeli18n: LabelResolver } }, { path: 'new', - canActivate: [CanActivateGuard], component: ProductDetailComponent, - data: { - breadcrumb: 'BREADCRUMBS.CREATE', - breadcrumbFn: (data: any) => `${data.labeli18n}` - }, - resolve: { - labeli18n: LabelResolver - } + data: { breadcrumb: 'BREADCRUMBS.CREATE', breadcrumbFn: (data: any) => `${data.labeli18n}` }, + resolve: { labeli18n: LabelResolver } }, { path: ':name', - canActivate: [CanActivateGuard], component: ProductDetailComponent, - data: { - breadcrumb: 'BREADCRUMBS.DETAIL', - breadcrumbFn: (data: any) => `${data.labeli18n}` - }, - resolve: { - labeli18n: LabelResolver - } + data: { breadcrumb: 'BREADCRUMBS.DETAIL', breadcrumbFn: (data: any) => `${data.labeli18n}` }, + resolve: { labeli18n: LabelResolver } } ] @NgModule({ @@ -74,29 +55,19 @@ const routes: Routes = [ ProductAppsComponent ], imports: [ - FormsModule, + CommonModule, + ConfirmDialogModule, FieldsetModule, + FormsModule, PortalCoreModule.forMicroFrontend(), - [RouterModule.forChild(routes)], - SharedModule, - TranslateModule.forChild({ - isolate: true, - missingTranslationHandler: { - provide: MissingTranslationHandler, - useClass: MyMissingTranslationHandler - }, - loader: { - provide: TranslateLoader, - useFactory: HttpLoaderFactory, - deps: [HttpClient, MFE_INFO] - } - }) + [RouterModule.forChild(addInitializeModuleGuard(routes))], + SharedModule ], - providers: [], + providers: [ConfirmationService, InitializeModuleGuard], schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA] }) export class ProductStoreModule { constructor() { - console.info('Product Store Module constructor') + console.info('Theme Module constructor') } } diff --git a/src/app/shared/can-active-guard.service.spec.ts b/src/app/shared/can-active-guard.service.spec.ts deleted file mode 100644 index 7e7156c..0000000 --- a/src/app/shared/can-active-guard.service.spec.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { BehaviorSubject, Observable, of } from 'rxjs' -import { CanActivateGuard } from './can-active-guard.service' - -let canActivateGuard: CanActivateGuard - -describe('CanActivateGuard', () => { - const translateServiceSpy = jasmine.createSpyObj('TranslateService', ['setDefaultLang', 'use']) - - const configSpy = jasmine.createSpyObj('ConfigurationService', [], { lang$: new BehaviorSubject(undefined) }) - - const activatedRouteSpy = jasmine.createSpyObj('ActivatedRouteSnapshot', [], { - routeConfig: { - path: 'path' - } - }) - const routerStateeSpy = jasmine.createSpyObj('RouterStateSnapshot', [], { - routeConfig: { - path: 'path' - } - }) - - beforeEach(async () => { - canActivateGuard = new CanActivateGuard(translateServiceSpy, configSpy) - translateServiceSpy.setDefaultLang.calls.reset() - translateServiceSpy.use.calls.reset() - }) - - it('should use default language if current not supported and return true', (doneFn: DoneFn) => { - const langSpy = Object.getOwnPropertyDescriptor(configSpy, 'lang$')?.get as jasmine.Spy< - () => BehaviorSubject - > - langSpy.and.returnValue(new BehaviorSubject('pl')) - // spyOn(console, 'log') - translateServiceSpy.use.and.returnValue(of({})) - - const resultObs = canActivateGuard.canActivate(activatedRouteSpy, routerStateeSpy) as Observable - resultObs.subscribe({ - next: (result) => { - expect(result).toBe(true) - doneFn() - }, - error: () => { - doneFn.fail - } - }) - - expect(translateServiceSpy.setDefaultLang).toHaveBeenCalledWith('en') - expect(translateServiceSpy.use).toHaveBeenCalledWith('en') - }) - - it('should use provided language if current supported and return true', (doneFn: DoneFn) => { - const langSpy = Object.getOwnPropertyDescriptor(configSpy, 'lang$')?.get as jasmine.Spy< - () => BehaviorSubject - > - langSpy.and.returnValue(new BehaviorSubject('de')) - // spyOn(console, 'log') - translateServiceSpy.use.and.returnValue(of({})) - - const resultObs = canActivateGuard.canActivate(activatedRouteSpy, routerStateeSpy) as Observable - resultObs.subscribe({ - next: (result) => { - expect(result).toBe(true) - doneFn() - }, - error: () => { - doneFn.fail - } - }) - - expect(translateServiceSpy.setDefaultLang).toHaveBeenCalledWith('en') - // expect(console.log).toHaveBeenCalledOnceWith('user profile GUARD path') - expect(translateServiceSpy.use).toHaveBeenCalledWith('de') - }) -}) diff --git a/src/app/shared/can-active-guard.service.ts b/src/app/shared/can-active-guard.service.ts deleted file mode 100644 index 52dbbd9..0000000 --- a/src/app/shared/can-active-guard.service.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Injectable } from '@angular/core' -import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router' -import { TranslateService } from '@ngx-translate/core' -import { filter, map, Observable, OperatorFunction, tap } from 'rxjs' - -import { ConfigurationService } from '@onecx/portal-integration-angular' - -const SUPPORTED_LANGUAGES = ['de', 'en'] -const DEFAULT_LANG = 'en' - -@Injectable() -export class CanActivateGuard implements CanActivate { - constructor(private txService: TranslateService, private config: ConfigurationService) {} - - /* eslint-disable @typescript-eslint/no-unused-vars */ - canActivate( - route: ActivatedRouteSnapshot, - state: RouterStateSnapshot - ): Observable | Promise | boolean | UrlTree { - return this.loadTranslations() - } - - private loadTranslations(): Observable | Promise | boolean | UrlTree { - console.log('Start Translation guard - default language ' + DEFAULT_LANG) - // this language will be used as a fallback when a translation isn't found in the current language - this.txService.setDefaultLang(DEFAULT_LANG) - - return this.txService.use(this.getBestMatchLanguage(this.config.lang)).pipe( - //optional, after we set the language, we can listen for eventual changes to the lang - tap(() => { - console.log(`Translations guard done ${this.config.lang}`) - this.config.lang$ - //the explict cast is to help linter understand that we will never get undefined - .pipe( - filter((x) => x !== undefined) as OperatorFunction, - map((lang) => lang.toLowerCase()) - ) - .subscribe((newLang) => { - console.log(`Configuration language: ${newLang}`) - this.txService.use(this.getBestMatchLanguage(newLang)) - }) - }), - map(() => true) - ) - } - - private getBestMatchLanguage(lang: string): string { - if (SUPPORTED_LANGUAGES.includes(lang)) { - return lang - } else { - console.warn( - `⚠ requested language: ${lang} is not among supported languages: ${SUPPORTED_LANGUAGES}, using ${DEFAULT_LANG} as fallback` - ) - return DEFAULT_LANG - } - } -} diff --git a/src/app/generated/.gitignore b/src/app/shared/generated/.gitignore similarity index 100% rename from src/app/generated/.gitignore rename to src/app/shared/generated/.gitignore diff --git a/src/app/generated/.openapi-generator-ignore b/src/app/shared/generated/.openapi-generator-ignore similarity index 100% rename from src/app/generated/.openapi-generator-ignore rename to src/app/shared/generated/.openapi-generator-ignore diff --git a/src/app/generated/.openapi-generator/FILES b/src/app/shared/generated/.openapi-generator/FILES similarity index 100% rename from src/app/generated/.openapi-generator/FILES rename to src/app/shared/generated/.openapi-generator/FILES diff --git a/src/app/generated/.openapi-generator/VERSION b/src/app/shared/generated/.openapi-generator/VERSION similarity index 100% rename from src/app/generated/.openapi-generator/VERSION rename to src/app/shared/generated/.openapi-generator/VERSION diff --git a/src/app/generated/README.md b/src/app/shared/generated/README.md similarity index 100% rename from src/app/generated/README.md rename to src/app/shared/generated/README.md diff --git a/src/app/generated/api.module.ts b/src/app/shared/generated/api.module.ts similarity index 100% rename from src/app/generated/api.module.ts rename to src/app/shared/generated/api.module.ts diff --git a/src/app/generated/api/api.ts b/src/app/shared/generated/api/api.ts similarity index 100% rename from src/app/generated/api/api.ts rename to src/app/shared/generated/api/api.ts diff --git a/src/app/generated/api/microfrontends.service.ts b/src/app/shared/generated/api/microfrontends.service.ts similarity index 100% rename from src/app/generated/api/microfrontends.service.ts rename to src/app/shared/generated/api/microfrontends.service.ts diff --git a/src/app/generated/api/products.service.ts b/src/app/shared/generated/api/products.service.ts similarity index 100% rename from src/app/generated/api/products.service.ts rename to src/app/shared/generated/api/products.service.ts diff --git a/src/app/generated/configuration.ts b/src/app/shared/generated/configuration.ts similarity index 100% rename from src/app/generated/configuration.ts rename to src/app/shared/generated/configuration.ts diff --git a/src/app/generated/encoder.ts b/src/app/shared/generated/encoder.ts similarity index 100% rename from src/app/generated/encoder.ts rename to src/app/shared/generated/encoder.ts diff --git a/src/app/generated/index.ts b/src/app/shared/generated/index.ts similarity index 100% rename from src/app/generated/index.ts rename to src/app/shared/generated/index.ts diff --git a/src/app/generated/model/createMicrofrontendRequest.ts b/src/app/shared/generated/model/createMicrofrontendRequest.ts similarity index 100% rename from src/app/generated/model/createMicrofrontendRequest.ts rename to src/app/shared/generated/model/createMicrofrontendRequest.ts diff --git a/src/app/generated/model/createProductRequest.ts b/src/app/shared/generated/model/createProductRequest.ts similarity index 100% rename from src/app/generated/model/createProductRequest.ts rename to src/app/shared/generated/model/createProductRequest.ts diff --git a/src/app/generated/model/createUIEndpoint.ts b/src/app/shared/generated/model/createUIEndpoint.ts similarity index 100% rename from src/app/generated/model/createUIEndpoint.ts rename to src/app/shared/generated/model/createUIEndpoint.ts diff --git a/src/app/generated/model/microfrontend.ts b/src/app/shared/generated/model/microfrontend.ts similarity index 100% rename from src/app/generated/model/microfrontend.ts rename to src/app/shared/generated/model/microfrontend.ts diff --git a/src/app/generated/model/microfrontendAbstract.ts b/src/app/shared/generated/model/microfrontendAbstract.ts similarity index 100% rename from src/app/generated/model/microfrontendAbstract.ts rename to src/app/shared/generated/model/microfrontendAbstract.ts diff --git a/src/app/generated/model/microfrontendPageResult.ts b/src/app/shared/generated/model/microfrontendPageResult.ts similarity index 100% rename from src/app/generated/model/microfrontendPageResult.ts rename to src/app/shared/generated/model/microfrontendPageResult.ts diff --git a/src/app/generated/model/microfrontendSearchCriteria.ts b/src/app/shared/generated/model/microfrontendSearchCriteria.ts similarity index 100% rename from src/app/generated/model/microfrontendSearchCriteria.ts rename to src/app/shared/generated/model/microfrontendSearchCriteria.ts diff --git a/src/app/generated/model/models.ts b/src/app/shared/generated/model/models.ts similarity index 100% rename from src/app/generated/model/models.ts rename to src/app/shared/generated/model/models.ts diff --git a/src/app/generated/model/problemDetailInvalidParam.ts b/src/app/shared/generated/model/problemDetailInvalidParam.ts similarity index 100% rename from src/app/generated/model/problemDetailInvalidParam.ts rename to src/app/shared/generated/model/problemDetailInvalidParam.ts diff --git a/src/app/generated/model/problemDetailParam.ts b/src/app/shared/generated/model/problemDetailParam.ts similarity index 100% rename from src/app/generated/model/problemDetailParam.ts rename to src/app/shared/generated/model/problemDetailParam.ts diff --git a/src/app/generated/model/problemDetailResponse.ts b/src/app/shared/generated/model/problemDetailResponse.ts similarity index 100% rename from src/app/generated/model/problemDetailResponse.ts rename to src/app/shared/generated/model/problemDetailResponse.ts diff --git a/src/app/generated/model/product.ts b/src/app/shared/generated/model/product.ts similarity index 100% rename from src/app/generated/model/product.ts rename to src/app/shared/generated/model/product.ts diff --git a/src/app/generated/model/productAbstract.ts b/src/app/shared/generated/model/productAbstract.ts similarity index 100% rename from src/app/generated/model/productAbstract.ts rename to src/app/shared/generated/model/productAbstract.ts diff --git a/src/app/generated/model/productPageResult.ts b/src/app/shared/generated/model/productPageResult.ts similarity index 100% rename from src/app/generated/model/productPageResult.ts rename to src/app/shared/generated/model/productPageResult.ts diff --git a/src/app/generated/model/productSearchCriteria.ts b/src/app/shared/generated/model/productSearchCriteria.ts similarity index 100% rename from src/app/generated/model/productSearchCriteria.ts rename to src/app/shared/generated/model/productSearchCriteria.ts diff --git a/src/app/generated/model/uIEndpoint.ts b/src/app/shared/generated/model/uIEndpoint.ts similarity index 100% rename from src/app/generated/model/uIEndpoint.ts rename to src/app/shared/generated/model/uIEndpoint.ts diff --git a/src/app/generated/model/updateMicrofrontendRequest.ts b/src/app/shared/generated/model/updateMicrofrontendRequest.ts similarity index 100% rename from src/app/generated/model/updateMicrofrontendRequest.ts rename to src/app/shared/generated/model/updateMicrofrontendRequest.ts diff --git a/src/app/generated/model/updateProductRequest.ts b/src/app/shared/generated/model/updateProductRequest.ts similarity index 100% rename from src/app/generated/model/updateProductRequest.ts rename to src/app/shared/generated/model/updateProductRequest.ts diff --git a/src/app/generated/model/updateUIEndpoint.ts b/src/app/shared/generated/model/updateUIEndpoint.ts similarity index 100% rename from src/app/generated/model/updateUIEndpoint.ts rename to src/app/shared/generated/model/updateUIEndpoint.ts diff --git a/src/app/generated/param.ts b/src/app/shared/generated/param.ts similarity index 100% rename from src/app/generated/param.ts rename to src/app/shared/generated/param.ts diff --git a/src/app/generated/variables.ts b/src/app/shared/generated/variables.ts similarity index 100% rename from src/app/generated/variables.ts rename to src/app/shared/generated/variables.ts diff --git a/src/app/shared/image-container/image-container.component.spec.ts b/src/app/shared/image-container/image-container.component.spec.ts index 262b97d..8c61e78 100644 --- a/src/app/shared/image-container/image-container.component.spec.ts +++ b/src/app/shared/image-container/image-container.component.spec.ts @@ -1,5 +1,6 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing' import { ImageContainerComponent } from './image-container.component' +import { prepareUrl } from 'src/app/shared/utils' describe('ImageContainerComponent', () => { let component: ImageContainerComponent @@ -24,7 +25,7 @@ describe('ImageContainerComponent', () => { describe('ngOnChanges', () => { it('should prepend apiPrefix to imageUrl if not starting with http/https and not already prefixed', () => { const testUrl = 'path/to/image.jpg' - const expectedUrl = component['apiPrefix'] + testUrl + const expectedUrl = prepareUrl(testUrl) component.imageUrl = testUrl component.ngOnChanges({ @@ -36,7 +37,7 @@ describe('ImageContainerComponent', () => { } }) - expect(component.imageUrl).toBe(expectedUrl) + expect(component.imageUrl).toBe(expectedUrl ?? '') }) it('should not modify imageUrl if it starts with http/https', () => { diff --git a/src/app/shared/image-container/image-container.component.ts b/src/app/shared/image-container/image-container.component.ts index 301bb7c..5918a62 100644 --- a/src/app/shared/image-container/image-container.component.ts +++ b/src/app/shared/image-container/image-container.component.ts @@ -1,5 +1,5 @@ import { Component, Input, OnChanges, SimpleChanges } from '@angular/core' -import { environment } from '../../../environments/environment' +import { prepareUrl } from 'src/app/shared/utils' @Component({ selector: 'app-image-container', @@ -12,7 +12,6 @@ export class ImageContainerComponent implements OnChanges { @Input() public small = false public displayPlaceHolder = false - private apiPrefix = environment.apiPrefix public onImageError() { this.displayPlaceHolder = true @@ -21,12 +20,7 @@ export class ImageContainerComponent implements OnChanges { ngOnChanges(changes: SimpleChanges): void { if (changes['imageUrl']) { this.displayPlaceHolder = false - - // if image Url does not start with a http the api-prefix ... - // ...then it stored in the backend. So we need to put prefix in front - if (this.imageUrl && !this.imageUrl.match(/^(http|https)/g) && !this.imageUrl.startsWith(this.apiPrefix)) { - this.imageUrl = this.apiPrefix + this.imageUrl - } + this.imageUrl = prepareUrl(this.imageUrl) } } } diff --git a/src/app/shared/label.resolver.spec.ts b/src/app/shared/label.resolver.spec.ts index 97b5285..1e15aed 100644 --- a/src/app/shared/label.resolver.spec.ts +++ b/src/app/shared/label.resolver.spec.ts @@ -1,9 +1,10 @@ +import { Observable, of } from 'rxjs' import { LabelResolver } from './label.resolver' let labelResolver: LabelResolver describe('LabelResolver', () => { - const translateServiceSpy = jasmine.createSpyObj('TranslateService', ['instant']) + const translateServiceSpy = jasmine.createSpyObj('TranslateService', ['get']) const activatedRouteSpy = jasmine.createSpyObj('ActivatedRouteSnapshot', [], { routeConfig: { @@ -16,28 +17,31 @@ describe('LabelResolver', () => { beforeEach(async () => { labelResolver = new LabelResolver(translateServiceSpy) - translateServiceSpy.instant.calls.reset() + translateServiceSpy.get.calls.reset() const dataSpy = Object.getOwnPropertyDescriptor(activatedRouteSpy, 'data')?.get as jasmine.Spy<() => {}> dataSpy.and.returnValue({}) }) - it('should translate if breadcrumb is present', () => { + it('should translate if breadcrumb is present', (done: DoneFn) => { const dataSpy = Object.getOwnPropertyDescriptor(activatedRouteSpy, 'data')?.get as jasmine.Spy<() => {}> dataSpy.and.returnValue({ breadcrumb: 'defined' }) - translateServiceSpy.instant.and.returnValue('translation') + translateServiceSpy.get.and.returnValue(of('translation')) - const result = labelResolver.resolve(activatedRouteSpy, routerStateSpy) + const obsResult = labelResolver.resolve(activatedRouteSpy, routerStateSpy) as Observable + obsResult.subscribe((result) => { + expect(result).toBe('translation') + expect(translateServiceSpy.get).toHaveBeenCalledOnceWith('defined') - expect(result).toBe('translation') - expect(translateServiceSpy.instant).toHaveBeenCalledOnceWith('defined') + done() + }) }) it('should use route path if breadcrumb is not present', () => { const result = labelResolver.resolve(activatedRouteSpy, routerStateSpy) expect(result).toBe('path') - expect(translateServiceSpy.instant).toHaveBeenCalledTimes(0) + expect(translateServiceSpy.get).toHaveBeenCalledTimes(0) }) }) diff --git a/src/app/shared/label.resolver.ts b/src/app/shared/label.resolver.ts index 7f923cc..c09047b 100644 --- a/src/app/shared/label.resolver.ts +++ b/src/app/shared/label.resolver.ts @@ -1,14 +1,15 @@ import { Injectable } from '@angular/core' import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router' import { TranslateService } from '@ngx-translate/core' -import { Observable } from 'rxjs' +import { Observable, map } from 'rxjs' -// don't use 'providedIn root' - wont work when we are in shell +//dont use `providedIn root` - wont work when we are in shell @Injectable() export class LabelResolver implements Resolve { constructor(private translate: TranslateService) {} - /* eslint-disable @typescript-eslint/no-unused-vars */ - resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): string | Observable | Promise { - return route.data['breadcrumb'] ? this.translate.instant(route.data['breadcrumb']) : route.routeConfig?.path + resolve(route: ActivatedRouteSnapshot, _state: RouterStateSnapshot): string | Observable | Promise { + return route.data['breadcrumb'] + ? this.translate.get(route.data['breadcrumb']).pipe(map((t) => t.toString())) + : route.routeConfig?.path ?? '' } } diff --git a/src/app/shared/shared.module.spec.ts b/src/app/shared/shared.module.spec.ts deleted file mode 100644 index 6cbfe53..0000000 --- a/src/app/shared/shared.module.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { NO_ERRORS_SCHEMA } from '@angular/core' -import { TestBed } from '@angular/core/testing' -import { HttpClient } from '@angular/common/http' -import { HttpClientTestingModule } from '@angular/common/http/testing' - -import { MfeInfo, TranslateCombinedLoader } from '@onecx/portal-integration-angular' -import { basePathProvider, HttpLoaderFactory } from './shared.module' - -describe('SharedModule', () => { - let httpClient: HttpClient - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - schemas: [NO_ERRORS_SCHEMA] - }) - - httpClient = TestBed.inject(HttpClient) - }) - - it('should return the correct basePath with mfeInfo', () => { - const mfeInfo: MfeInfo = { - mountPath: '', - remoteBaseUrl: 'http://localhost:4200/', - baseHref: '', - shellName: '' - } - - const result = basePathProvider(mfeInfo) - - expect(result).toEqual('http://localhost:4200/product-store-bff') - }) - - it('should return a translate loader', () => { - const mfeInfo: MfeInfo = { - mountPath: '', - remoteBaseUrl: 'http://localhost:4200/', - baseHref: '', - shellName: '' - } - - const result = HttpLoaderFactory(httpClient, mfeInfo) - - expect(result).toBeInstanceOf(TranslateCombinedLoader) - }) -}) diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index f0d64be..6a76d0b 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -1,92 +1,65 @@ import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, NgModule } from '@angular/core' import { CommonModule } from '@angular/common' -import { HttpClient } from '@angular/common/http' import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { TranslateModule, TranslateService } from '@ngx-translate/core' -import { TranslateHttpLoader } from '@ngx-translate/http-loader' -import { ColorSketchModule } from 'ngx-color/sketch' import { ErrorTailorModule } from '@ngneat/error-tailor' import { AutoCompleteModule } from 'primeng/autocomplete' -import { CheckboxModule } from 'primeng/checkbox' import { ConfirmDialogModule } from 'primeng/confirmdialog' import { ConfirmPopupModule } from 'primeng/confirmpopup' -import { ConfirmationService, MessageService } from 'primeng/api' +import { ConfirmationService } from 'primeng/api' import { DataViewModule } from 'primeng/dataview' import { DialogModule } from 'primeng/dialog' -import { DialogService, DynamicDialogModule } from 'primeng/dynamicdialog' +import { DialogService } from 'primeng/dynamicdialog' import { DropdownModule } from 'primeng/dropdown' -import { FileUploadModule } from 'primeng/fileupload' import { InputTextModule } from 'primeng/inputtext' import { InputTextareaModule } from 'primeng/inputtextarea' import { KeyFilterModule } from 'primeng/keyfilter' import { ListboxModule } from 'primeng/listbox' import { MultiSelectModule } from 'primeng/multiselect' -import { OverlayPanelModule } from 'primeng/overlaypanel' -import { PanelModule } from 'primeng/panel' import { SelectButtonModule } from 'primeng/selectbutton' -import { TabViewModule } from 'primeng/tabview' import { TableModule } from 'primeng/table' +import { TabViewModule } from 'primeng/tabview' import { ToastModule } from 'primeng/toast' import { - MfeInfo, - MFE_INFO, + AppStateService, + ConfigurationService, PortalDialogService, - PortalMessageService, - TranslateCombinedLoader + PortalApiConfiguration } from '@onecx/portal-integration-angular' -import { BASE_PATH } from '../generated' +import { Configuration } from 'src/app/shared/generated' +import { environment } from 'src/environments/environment' import { LabelResolver } from './label.resolver' -import { environment } from '../../environments/environment' -import { CanActivateGuard } from './can-active-guard.service' import { ImageContainerComponent } from './image-container/image-container.component' -export const basePathProvider = (mfeInfo: MfeInfo) => { - const appBasePath = mfeInfo ? mfeInfo.remoteBaseUrl + '' + environment.apiPrefix : environment.apiPrefix - console.log('Base path: ' + appBasePath) - return appBasePath -} - -// Always load the app assets directly from the Microfrontend -export function HttpLoaderFactory(http: HttpClient, mfeInfo: MfeInfo) { - const appAssetPrefix = mfeInfo && mfeInfo.remoteBaseUrl ? mfeInfo.remoteBaseUrl : './' - console.log('Path prefix: ' + appAssetPrefix) - return new TranslateCombinedLoader( - new TranslateHttpLoader(http, appAssetPrefix + 'assets/i18n/', '.json'), - new TranslateHttpLoader(http, appAssetPrefix + 'onecx-portal-lib/assets/i18n/', '.json') - ) +export function apiConfigProvider(configService: ConfigurationService, appStateService: AppStateService) { + return new PortalApiConfiguration(Configuration, environment.apiPrefix, configService, appStateService) } @NgModule({ declarations: [ImageContainerComponent], imports: [ AutoCompleteModule, - CheckboxModule, - ColorSketchModule, CommonModule, ConfirmDialogModule, ConfirmPopupModule, DataViewModule, DialogModule, DropdownModule, - DynamicDialogModule, - FileUploadModule, FormsModule, InputTextModule, InputTextareaModule, KeyFilterModule, ListboxModule, MultiSelectModule, - OverlayPanelModule, - PanelModule, ReactiveFormsModule, SelectButtonModule, - TabViewModule, TableModule, + TabViewModule, ToastModule, - TranslateModule.forChild({ isolate: true }), + TranslateModule, ErrorTailorModule.forRoot({ controlErrorsOn: { async: true, blur: true, change: true }, errors: { @@ -112,39 +85,33 @@ export function HttpLoaderFactory(http: HttpClient, mfeInfo: MfeInfo) { ], exports: [ AutoCompleteModule, - CheckboxModule, CommonModule, + ConfirmDialogModule, ConfirmPopupModule, DataViewModule, DialogModule, DropdownModule, - DynamicDialogModule, ErrorTailorModule, - FileUploadModule, FormsModule, - ImageContainerComponent, InputTextModule, InputTextareaModule, + ImageContainerComponent, KeyFilterModule, ListboxModule, MultiSelectModule, - OverlayPanelModule, - PanelModule, ReactiveFormsModule, SelectButtonModule, - TabViewModule, TableModule, + TabViewModule, ToastModule, TranslateModule ], //this is not elegant, for some reason the injection token from primeng does not work across federated module providers: [ - CanActivateGuard, ConfirmationService, LabelResolver, - { provide: MessageService, useExisting: PortalMessageService }, { provide: DialogService, useClass: PortalDialogService }, - { provide: BASE_PATH, useFactory: basePathProvider, deps: [MFE_INFO] } + { provide: Configuration, useFactory: apiConfigProvider, deps: [ConfigurationService, AppStateService] } ], schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA] }) diff --git a/src/app/shared/utils.spec.ts b/src/app/shared/utils.spec.ts index f0a8e79..6eb08cf 100644 --- a/src/app/shared/utils.spec.ts +++ b/src/app/shared/utils.spec.ts @@ -1,76 +1,8 @@ -import { SelectItem } from 'primeng/api' +import { limitText } from './utils' -import { limitText, setFetchUrls, dropDownSortItemsByLabel, dropDownGetLabelByValue, sortByLocale } from './utils' - -describe('util functions', () => { - describe('limitText', () => { - it('should truncate text that exceeds the specified limit', () => { - const result = limitText('hello', 4) - - expect(result).toEqual('hell...') - }) - - it('should return the original text if it does not exceed the limit', () => { - const result = limitText('hello', 6) - - expect(result).toEqual('hello') - }) - - it('should return an empty string for undefined input', () => { - const str: any = undefined - const result = limitText(str, 5) - - expect(result).toEqual('') - }) - }) - - describe('setFetchUrls', () => { - it('should prepend apiPrefix to a relative URL', () => { - const result = setFetchUrls('api-prefix', '/url') - - expect(result).toEqual('api-prefix/url') - }) - - it('should return the original URL if it is absolute', () => { - const result = setFetchUrls('api-prefix', 'http://host') - - expect(result).toEqual('http://host') - }) - }) - - describe('dropDownSortItemsByLabel', () => { - it('should correctly sort items by label', () => { - const items: SelectItem[] = [ - { label: 'label2', value: 2 }, - { label: 'label1', value: 1 } - ] - - const sortedItems = items.sort(dropDownSortItemsByLabel) - - expect(sortedItems[0].label).toEqual('label1') - }) - }) - - describe('dropDownGetLabelByValue', () => { - it('should return the label corresponding to the value', () => { - const items: SelectItem[] = [ - { label: 'label2', value: 2 }, - { label: 'label1', value: 1 } - ] - - const result = dropDownGetLabelByValue(items, '1') - - expect(result).toEqual('label1') - }) - }) - - describe('sortByLocale', () => { - it('should sort strings based on locale', () => { - const strings: string[] = ['str2', 'str1'] - - const sortedStrings = strings.sort(sortByLocale) - - expect(sortedStrings[0]).toEqual('str1') - }) +describe('utils', () => { + it('should limit text if text too long', () => { + const result = limitText('textData', 4) + expect(result).toBe('text...') }) }) diff --git a/src/app/shared/utils.ts b/src/app/shared/utils.ts index 0b60e1e..e595319 100644 --- a/src/app/shared/utils.ts +++ b/src/app/shared/utils.ts @@ -1,4 +1,6 @@ import { SelectItem } from 'primeng/api' +import { Location } from '@angular/common' +import { environment } from 'src/environments/environment' export function limitText(text: string, limit: number): string { if (text) { @@ -8,9 +10,9 @@ export function limitText(text: string, limit: number): string { } } -export function setFetchUrls(apiPrefix: string, url: string): string { +export function prepareUrl(url: string | undefined): string | undefined { if (url && !url.match(/^(http|https)/g)) { - return apiPrefix + url + return Location.joinWithSlash(environment.apiPrefix, url) } else { return url } diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 5b423da..9c02573 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -1,5 +1,5 @@ export const environment = { production: true, - BASE_PATH: '/product-store-bff', - apiPrefix: 'product-store-bff' + BASE_PATH: '/bff', + apiPrefix: 'bff' } diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 7c897a8..25bf5d6 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -8,5 +8,5 @@ export const environment = { KEYCLOAK_URL: 'http://keycloak-app/', KEYCLOAK_REALM: 'OneCX', skipRemoteConfigLoad: true, - apiPrefix: 'product-store-bff' + apiPrefix: 'bff' } diff --git a/tsconfig.app.json b/tsconfig.app.json index bfc5cce..9eca455 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -7,14 +7,7 @@ "inlineSources": true, "target": "ES2022" }, - "files": ["src/main.ts", "src/polyfills.ts", "src/app/product-store-remote.module.ts"], + "files": ["src/main.ts", "src/polyfills.ts", "src/app/onecx-product-store-remote.module.ts"], "include": ["src/**/*.ts", "src/**/*.d.ts"], - "exclude": [ - "src/test.ts", - "src/test-setup.ts", - "src/app/test/**/*", - "src/**/*.spec.ts", - "src/**/*.stories.ts", - "src/**/*.stories.js" - ] + "exclude": ["src/test.ts", "src/test-setup.ts", "src/**/*.spec.ts", "src/**/*.stories.ts", "src/**/*.stories.js"] } diff --git a/tsconfig.spec.json b/tsconfig.spec.json index 9b2f08e..a366065 100644 --- a/tsconfig.spec.json +++ b/tsconfig.spec.json @@ -10,6 +10,6 @@ "useDefineForClassFields": false }, "files": ["src/test.ts", "src/polyfills.ts"], - "exclude": ["src/app/generated/**/*", "node_modules"], + "exclude": ["src/app/shared/generated/**/*", "node_modules"], "include": ["**/*.spec.ts", "**/*.d.ts"] } diff --git a/webpack.config.js b/webpack.config.js index 2d91769..8c8b84a 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,10 +2,10 @@ const { ModifyEntryPlugin } = require('@angular-architects/module-federation/src const { share, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack') const config = withModuleFederationPlugin({ - name: 'product-store-ui', + name: 'onecx-product-store-ui', filename: 'remoteEntry.js', exposes: { - './ProductStoreMgmtModule': 'src/app/product-store-remote.module.ts' + './OneCXProductStoreModule': 'src/app/onecx-product-store-remote.module.ts' }, shared: share({ '@angular/core': { singleton: true, strictVersion: true, requiredVersion: 'auto' }, @@ -30,17 +30,13 @@ const config = withModuleFederationPlugin({ requiredVersion: 'auto', includeSecondaries: true }, - '@angular/router': { singleton: true, strictVersion: true, requiredVersion: 'auto', includeSecondaries: true }, rxjs: { singleton: true, strictVersion: true, requiredVersion: 'auto', includeSecondaries: true }, + '@angular/router': { singleton: true, strictVersion: true, requiredVersion: 'auto', includeSecondaries: true }, '@ngx-translate/core': { singleton: true, strictVersion: false, requiredVersion: '^14.0.0' }, - '@onecx/portal-integration-angular': { - singleton: true, - requiredVersion: 'auto' - }, - '@onecx/keycloak-auth': { - singleton: true, - requiredVersion: 'auto' - } + '@onecx/keycloak-auth': { requiredVersion: 'auto', includeSecondaries: true }, + '@onecx/portal-integration-angular': { requiredVersion: 'auto', includeSecondaries: true }, + '@onecx/accelerator': { requiredVersion: 'auto', includeSecondaries: true }, + '@onecx/integration-interface': { requiredVersion: 'auto', includeSecondaries: true } }), sharedMappings: ['@onecx/portal-integration-angular'] })