diff --git a/CHANGELOG.md b/CHANGELOG.md index f93d79620c..75b3e7e9ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ _**For better traceability add the corresponding GitHub issue number in each cha - #1037 extended autocomplete api by contractAgreementId - #985 Added function to save Contracts based on notification contractAgreementIds into the database - #985 Added function to filter notifications for contractAgreementIds +- #786 Added authorization as admin for submodel api & registry api ### Added - #832 added policymanagement list view, creator and editor @@ -25,6 +26,7 @@ _**For better traceability add the corresponding GitHub issue number in each cha - #915 Added section to documentation: EDC-BPN configuration - #1037 Added link from contracts view to the corresponding filtered part table view - #985 Added reference to part/notification under contract +- #786 Added icons on part table to let admin reload registry / sync assets via IRS ### Removed diff --git a/frontend/src/app/modules/page/notifications/detail/edit/notification-edit.component.html b/frontend/src/app/modules/page/notifications/detail/edit/notification-edit.component.html index 10c9e42ba6..d2663d91eb 100644 --- a/frontend/src/app/modules/page/notifications/detail/edit/notification-edit.component.html +++ b/frontend/src/app/modules/page/notifications/detail/edit/notification-edit.component.html @@ -38,6 +38,7 @@ [class.mdc-tooltip--multiline]="true" [matTooltipShowDelay]="1000" [style.cursor]="'not-allowed'" + (mouseenter)="shouldMarkAsTouched.next(true)" > diff --git a/frontend/src/app/modules/page/notifications/detail/edit/notification-edit.component.ts b/frontend/src/app/modules/page/notifications/detail/edit/notification-edit.component.ts index 681d668c78..a2569aae65 100644 --- a/frontend/src/app/modules/page/notifications/detail/edit/notification-edit.component.ts +++ b/frontend/src/app/modules/page/notifications/detail/edit/notification-edit.component.ts @@ -80,6 +80,7 @@ export class NotificationEditComponent implements OnDestroy { public tableAsBuiltSortList: TableHeaderSort[]; private paramSubscription: Subscription; isSaveButtonDisabled: boolean; + shouldMarkAsTouched = new BehaviorSubject(false); constructor( private readonly ownPartsFacade: PartsFacade, diff --git a/frontend/src/app/modules/page/parts/core/parts.facade.ts b/frontend/src/app/modules/page/parts/core/parts.facade.ts index 7033a8beb9..4745fb6629 100644 --- a/frontend/src/app/modules/page/parts/core/parts.facade.ts +++ b/frontend/src/app/modules/page/parts/core/parts.facade.ts @@ -51,6 +51,18 @@ export class PartsFacade { return this.partsState.partsAsPlanned$; } + public reloadRegistry() { + return this.partsService.reloadRegistry(); + } + + public syncPartsAsBuilt(assetIds: string[]) { + return this.partsService.syncPartsAsBuilt(assetIds); + } + + public syncPartsAsPlanned(assetIds: string[]) { + return this.partsService.syncPartsAsPlanned(assetIds); + } + public setPartsAsBuilt(page = 0, pageSize = 50, sorting: TableHeaderSort[] = [], assetAsBuiltFilter?: AssetAsBuiltFilter, isOrSearch?: boolean): void { this.partsAsBuiltSubscription?.unsubscribe(); this.partsAsBuiltSubscription = this.partsService.getPartsAsBuilt(page, pageSize, sorting, assetAsBuiltFilter, isOrSearch).subscribe({ diff --git a/frontend/src/app/modules/page/parts/model/partReloadOperation.enum.ts b/frontend/src/app/modules/page/parts/model/partReloadOperation.enum.ts new file mode 100644 index 0000000000..72ff74f6e6 --- /dev/null +++ b/frontend/src/app/modules/page/parts/model/partReloadOperation.enum.ts @@ -0,0 +1,23 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +export enum PartReloadOperation { + RELOAD_REGISTRY = 'RELOAD_REGISTRY', + SYNC_PARTS_AS_BUILT = 'SYNC_PARTS_AS_BUILT', + SYNC_PARTS_AS_PLANNED = 'SYNC_PARTS_AS_PLANNED' +} diff --git a/frontend/src/app/modules/page/parts/presentation/parts.component.html b/frontend/src/app/modules/page/parts/presentation/parts.component.html index 2379a9d9df..c08a95667c 100644 --- a/frontend/src/app/modules/page/parts/presentation/parts.component.html +++ b/frontend/src/app/modules/page/parts/presentation/parts.component.html @@ -55,6 +55,7 @@ part.id)); + break; + case PartReloadOperation.SYNC_PARTS_AS_PLANNED: + this.syncPartsAsPlanned(this.currentSelectedAsPlannedItems$.value.map(part => part.id)); + break; + } + } + + private reloadRegistry() { + this.partsFacade.reloadRegistry().subscribe({ + next: data => { + this.toastService.success('registryReload.success'); + }, + error: error => { + this.toastService.error(error?.getMessage()); + }, + }); + } + + private syncPartsAsBuilt(assetIds: string[]) { + this.partsFacade.syncPartsAsBuilt(assetIds).subscribe({ + next: data => { + this.toastService.success('partSynchronization.success'); + }, + error: error => { + this.toastService.error(error?.getMessage()); + }, + }); + } + + private syncPartsAsPlanned(assetIds: string[]) { + this.partsFacade.syncPartsAsPlanned(assetIds).subscribe({ + next: data => { + this.toastService.success('partSynchronization.success'); + }, + error: error => { + this.toastService.error(error?.getMessage()); + }, + }); + } + private resetFilterAndShowToast(resetOwner?: boolean) { let filterIsSet = resetMultiSelectionAutoCompleteComponent(this.partsTableComponents, false); if (resetOwner) { diff --git a/frontend/src/app/modules/shared/components/notification-reason/notification-reason.component.scss b/frontend/src/app/modules/shared/components/notification-reason/notification-reason.component.scss index f653f7aeeb..9ad6d0daa3 100644 --- a/frontend/src/app/modules/shared/components/notification-reason/notification-reason.component.scss +++ b/frontend/src/app/modules/shared/components/notification-reason/notification-reason.component.scss @@ -91,7 +91,7 @@ } .message--text__right { - @apply bg-createdLight text-createdDark; + @apply bg-closedLight text-closedDark; float: right; } @@ -102,7 +102,7 @@ right: -7px; height: 25px; border-right: 20px solid; - @apply border-createdLight; + @apply border-closedLight; border-bottom-left-radius: 16px 14px; transform: translate(0, -2px); } @@ -121,7 +121,7 @@ } .message--text__left { - @apply bg-receivedLight text-receivedDark; + @apply bg-createdLight text-createdDark; float: left; } @@ -133,7 +133,7 @@ left: -7px; height: 25px; border-left: 20px solid; - @apply border-receivedLight; + @apply border-createdLight; border-bottom-right-radius: 16px 14px; transform: translate(0, -2px); } @@ -180,11 +180,11 @@ } &--CANCELED { - @apply bg-closedDark text-closedLight before:bg-closedLight; + @apply bg-canceledLight text-canceledDark before:bg-canceledDark; } &--CLOSED { - @apply bg-closedDark text-closedLight before:bg-closedLight; + @apply bg-closedLight text-closedDark before:bg-closedDark; } &--CREATED { @@ -192,11 +192,11 @@ } &--SENT { - @apply bg-pendingLight text-pendingDark before:bg-pendingDark; + @apply bg-receivedLight text-receivedDark before:bg-receivedDark; } &--ACCEPTED { - @apply bg-declinedLight text-declinedDark before:bg-declinedDark; + @apply bg-confirmedLight text-confirmedDark before:bg-confirmedDark; } &--ACKNOWLEDGED { @@ -204,7 +204,7 @@ } &--DECLINED { - @apply bg-confirmedLight text-confirmedDark before:bg-confirmedDark; + @apply bg-declinedLight text-declinedDark before:bg-declinedDark; } } diff --git a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.html b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.html index 80c4249da0..e15034bd85 100644 --- a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.html +++ b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.html @@ -79,24 +79,39 @@
+ [matTooltipDisabled]="isAdmin() && atLeastOneSelected() && !isIllegalSelectionToPublish()">
+
+ +
+

{ expect(componentInstance.selection.selected).toEqual([]); }); + it('should emit reload registry event on part reload button click when no part selected', async () => { + const { fixture } = await renderPartsTableComponent(1, TableType.AS_BUILT_OWN); + const { componentInstance } = fixture; + + const emitPartReloadSpy = spyOn(componentInstance.partReloadClickedEvent, 'emit'); + componentInstance.partReloadIconClicked(); + + expect(emitPartReloadSpy).toHaveBeenCalledWith(PartReloadOperation.RELOAD_REGISTRY); + }); + + it('should emit sync parts as built event on part reload button click when part selected', async () => { + const { fixture } = await renderPartsTableComponent(1, TableType.AS_BUILT_OWN); + const { componentInstance } = fixture; + componentInstance.mainAspectType = MainAspectType.AS_BUILT; + const emitPartReloadSpy = spyOn(componentInstance.partReloadClickedEvent, 'emit'); + + componentInstance.selection.select({ id: 1 }); + componentInstance.partReloadIconClicked(); + + expect(emitPartReloadSpy).toHaveBeenCalledWith(PartReloadOperation.SYNC_PARTS_AS_BUILT); + }); + + it('should emit sync parts as planned event on part reload button click when part selected', async () => { + const { fixture } = await renderPartsTableComponent(1, TableType.AS_PLANNED_OWN); + const { componentInstance } = fixture; + componentInstance.mainAspectType = MainAspectType.AS_PLANNED; + const emitPartReloadSpy = spyOn(componentInstance.partReloadClickedEvent, 'emit'); + + componentInstance.selection.select({ id: 1 }); + componentInstance.partReloadIconClicked(); + + expect(emitPartReloadSpy).toHaveBeenCalledWith(PartReloadOperation.SYNC_PARTS_AS_PLANNED); + }); }); diff --git a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.ts b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.ts index 673ab802c7..e2830cd84b 100644 --- a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.ts +++ b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.ts @@ -42,6 +42,7 @@ import { UserService } from '@core/user/user.service'; import { MainAspectType } from '@page/parts/model/mainAspectType.enum'; import { Owner } from '@page/parts/model/owner.enum'; import { ImportState, Part } from '@page/parts/model/parts.model'; +import { PartReloadOperation } from '@page/parts/model/partReloadOperation.enum'; import { MultiSelectAutocompleteComponent } from '@shared/components/multi-select-autocomplete/multi-select-autocomplete.component'; import { TableType } from '@shared/components/multi-select-autocomplete/table-type.model'; import { PartsTableConfigUtils } from '@shared/components/parts-table/parts-table-config.utils'; @@ -72,6 +73,7 @@ export class PartsTableComponent implements OnInit { @ViewChildren(MultiSelectAutocompleteComponent) multiSelectAutocompleteComponents: QueryList; @Output() publishIconClickedEvent = new EventEmitter(); + @Output() partReloadClickedEvent = new EventEmitter(); @Input() labelId: string; @Input() noShadow = false; @Input() showHover = true; @@ -184,7 +186,7 @@ export class PartsTableComponent implements OnInit { this.createQualityNotificationClickedEvent.emit(this.notificationType); } - public isAllowedToPublish(): boolean { + public isAdmin(): boolean { return this.roleService.hasAccess([ 'admin' ]); } @@ -200,10 +202,31 @@ export class PartsTableComponent implements OnInit { } } - public publishIconClicked(): void { + handleKeyDownPartReloadIconClicked(event: KeyboardEvent): void { + if (event.key === 'Enter') { + this.partReloadIconClicked(); + } + } + + public publishIconClicked() { this.publishIconClickedEvent.emit(); } + public partReloadIconClicked() { + if (!this.atLeastOneSelected()) { + this.partReloadClickedEvent.emit(PartReloadOperation.RELOAD_REGISTRY); + } else { + switch (this.mainAspectType) { + case MainAspectType.AS_BUILT: + this.partReloadClickedEvent.emit(PartReloadOperation.SYNC_PARTS_AS_BUILT); + break; + case MainAspectType.AS_PLANNED: + this.partReloadClickedEvent.emit(PartReloadOperation.SYNC_PARTS_AS_PLANNED); + break; + } + } + } + public readonly dataSource = new MatTableDataSource(); public readonly selection = new SelectionModel(true, []); diff --git a/frontend/src/app/modules/shared/components/request-notification-new/notification-new-request.component.ts b/frontend/src/app/modules/shared/components/request-notification-new/notification-new-request.component.ts index 5c86d26633..f91bfcb4d8 100644 --- a/frontend/src/app/modules/shared/components/request-notification-new/notification-new-request.component.ts +++ b/frontend/src/app/modules/shared/components/request-notification-new/notification-new-request.component.ts @@ -19,6 +19,7 @@ import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; import { FormControl, FormGroup, ValidationErrors, Validators } from '@angular/forms'; +import { environment } from '@env'; import { bpnRegex } from '@page/admin/presentation/bpn-configuration/bpn-configuration.component'; import { NotificationDetailFacade } from '@page/notifications/core/notification-detail.facade'; import { BaseInputHelper } from '@shared/abstraction/baseInput/baseInput.helper'; @@ -27,7 +28,6 @@ import { Severity } from '@shared/model/severity.model'; import { View } from '@shared/model/view.model'; import { BehaviorSubject, Observable, Subscription } from 'rxjs'; import { filter, tap } from 'rxjs/operators'; -import {environment} from '@env'; @Component({ selector: 'app-notification-new-request', @@ -60,6 +60,7 @@ export class RequestNotificationNewComponent implements OnDestroy, OnInit { public readonly minDate = new Date(); private applicationBPN = environment.bpn; private subscription: Subscription; + @Input() allTouched: Observable; constructor(public readonly notificationDetailFacade: NotificationDetailFacade) { @@ -127,6 +128,13 @@ export class RequestNotificationNewComponent implements OnDestroy, OnInit { .subscribe(); } + this.allTouched?.subscribe(next => { + console.log(next); + if (next === true) { + this.formGroup.markAllAsTouched(); + } + }); + } diff --git a/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.scss b/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.scss index f17e925a6b..fec721e552 100644 --- a/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.scss +++ b/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.scss @@ -39,11 +39,11 @@ } &--CANCELED { - @apply bg-closedDark text-closedLight before:bg-closedLight; + @apply bg-canceledLight text-canceledDark before:bg-canceledDark; } &--CLOSED { - @apply bg-closedDark text-closedLight before:bg-closedLight; + @apply bg-closedLight text-closedDark before:bg-closedDark; } &--CREATED { @@ -51,11 +51,11 @@ } &--SENT { - @apply bg-pendingLight text-pendingDark before:bg-pendingDark; + @apply bg-receivedLight text-receivedDark before:bg-receivedDark; } &--ACCEPTED { - @apply bg-declinedLight text-declinedDark before:bg-declinedDark; + @apply bg-confirmedLight text-confirmedDark before:bg-confirmedDark; } &--ACKNOWLEDGED { @@ -63,6 +63,6 @@ } &--DECLINED { - @apply bg-confirmedLight text-confirmedDark before:bg-confirmedDark; + @apply bg-declinedLight text-declinedDark before:bg-declinedDark; } } diff --git a/frontend/src/app/modules/shared/service/parts.service.ts b/frontend/src/app/modules/shared/service/parts.service.ts index f415672eec..05d16f2f05 100644 --- a/frontend/src/app/modules/shared/service/parts.service.ts +++ b/frontend/src/app/modules/shared/service/parts.service.ts @@ -47,6 +47,18 @@ export class PartsService { constructor(private readonly apiService: ApiService) { } + public reloadRegistry() { + return this.apiService.get(`${ this.url }/registry/reload`); + } + + public syncPartsAsBuilt(assetIds: string[]) { + return this.apiService.post(`${ this.url }/assets/as-built/sync`, { assetIds }); + } + + public syncPartsAsPlanned(assetIds: string[]) { + return this.apiService.post(`${ this.url }/assets/as-planned/sync`, { assetIds }); + } + public getPartsAsBuilt(page: number, pageSize: number, sorting: TableHeaderSort[], assetAsBuiltFilter?: AssetAsBuiltFilter, isOrSearch?: boolean): Observable> { let sort = sorting.map(sortingItem => PartsAssembler.mapSortToApiSort(sortingItem)); diff --git a/frontend/src/assets/locales/de/common.json b/frontend/src/assets/locales/de/common.json index a70b4d9dff..1f0f5f2726 100644 --- a/frontend/src/assets/locales/de/common.json +++ b/frontend/src/assets/locales/de/common.json @@ -19,6 +19,8 @@ "noChildPartsForInvestigation": "Diese Funktion ist für Produkte ohne Bauteile nicht verfügbar.", "noCustomerAsPlannedParts": "Produkte von Kunden im Lebenszyklus \"AsPlanned\" sind nicht verfügbar.", "illegalAssetStateToPublish": "Ein oder mehrere ausgewählte Produkte befinden sich nicht im erforderlichen Importstatus \"TRANSIENT\" oder \"ERROR\".", + "syncSelectedParts" : "Synchronisierung der ausgewählten Parts", + "refreshAllParts" : "Synchronisierung aller Parts", "adminContract": "Verträge", "createIncident": "Mitteilung erstellen", "startInvestigation": "Qualitätsuntersuchung für Bauteile erstellen", @@ -298,6 +300,14 @@ "success": "Anfrage zur Veröffentlichung der Produkte war erfolgreich", "error": "" }, + "registryReload": { + "success": "Aktualisierung aller Parts erfolgreich.", + "error": "" + }, + "partSynchronization": { + "success": "Synchronisierung ausgewählter Parts erfolgreich.", + "error": "" + }, "pagination": { "itemsPerPageLabel": "Artikel pro Seite:", "nextPageLabel": "Nächsten Seite", diff --git a/frontend/src/assets/locales/en/common.json b/frontend/src/assets/locales/en/common.json index c39bded7ea..9d48fdbd4f 100644 --- a/frontend/src/assets/locales/en/common.json +++ b/frontend/src/assets/locales/en/common.json @@ -18,6 +18,8 @@ "noChildPartsForInvestigation": "This function is not available for Parts without components.", "noCustomerAsPlannedParts": "Customer parts in lifecycle \"AsPlanned\" are not available.", "illegalAssetStateToPublish": "One or more selected parts are not in the required import state \"TRANSIENT\" or \"ERROR\".", + "syncSelectedParts" : "Synchronize selected parts", + "refreshAllParts" : "Synchronize all parts", "adminContract": "Contracts", "createIncident": "Create quality notification", "startInvestigation": "Start Quality investigation on subcomponents", @@ -297,6 +299,14 @@ "success": "Request to publish assets was successful", "error": "" }, + "registryReload": { + "success": "Successfully reloaded all parts.", + "error": "" + }, + "partSynchronization": { + "success": "Successfully synchronized selected parts.", + "error": "" + }, "pagination": { "itemsPerPageLabel": "Items per page:", "nextPageLabel": "Next page", diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index e30d48ed49..3585d1dd5a 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -19,24 +19,24 @@ /* eslint-disable no-undef */ module.exports = { - content: ['./src/**/*.html', './src/**/*.scss'], + content: [ './src/**/*.html', './src/**/*.scss' ], theme: { extend: { fontFamily: { - bold: ['Catena-X Bold', 'sans-serif'], - boldItalic: ['Catena-X BoldItalic', 'sans-serif'], + bold: [ 'Catena-X Bold', 'sans-serif' ], + boldItalic: [ 'Catena-X BoldItalic', 'sans-serif' ], - italic: ['Catena-X Italic', 'sans-serif'], - regular: ['Catena-X Regular', 'sans-serif'], + italic: [ 'Catena-X Italic', 'sans-serif' ], + regular: [ 'Catena-X Regular', 'sans-serif' ], - light: ['Catena-X Light', 'sans-serif'], - lightItalic: ['Catena-X LightItalic', 'sans-serif'], + light: [ 'Catena-X Light', 'sans-serif' ], + lightItalic: [ 'Catena-X LightItalic', 'sans-serif' ], - medium: ['Catena-X Medium', 'sans-serif'], - mediumItalic: ['Catena-X MediumItalic', 'sans-serif'], + medium: [ 'Catena-X Medium', 'sans-serif' ], + mediumItalic: [ 'Catena-X MediumItalic', 'sans-serif' ], - semiBold: ['Catena-X SemiBold', 'sans-serif'], - semiBoldItalic: ['Catena-X SemiBoldItalic', 'sans-serif'], + semiBold: [ 'Catena-X SemiBold', 'sans-serif' ], + semiBoldItalic: [ 'Catena-X SemiBoldItalic', 'sans-serif' ], }, minHeight: { 0: '0', @@ -131,23 +131,26 @@ module.exports = { // Status colors for notification badges - createdLight: '#f2f3fb', - createdDark: '#111111', + createdLight: '#d4eaff', + createdDark: '#0a2e4f', - receivedLight: '#e1f1fe', - receivedDark: '#2b4078', + receivedLight: '#fff5d6', + receivedDark: '#8b5e00', - pendingLight: '#FFECBD', - pendingDark: '#975b26', + pendingLight: '#ffe4b2', + pendingDark: '#cc5200', - confirmedLight: '#e2f6c7', - confirmedDark: '#5c8d46', + confirmedLight: '#d9f2d9', + confirmedDark: '#0b5e0b', - declinedLight: '#fee7e2', - declinedDark: '#ff5330', + canceledLight: '#ffa6a6', + canceledDark: '#800000', - closedLight: '#ffffff', - closedDark: '#5d3416', + declinedLight: '#ffd9d9', + declinedDark: '#b30000', + + closedLight: '#f0f0f0', + closedDark: '#4a4a4a', qualityTypeOk: '#3db014', qualityTypeMinor: '#ffd74a', @@ -161,7 +164,7 @@ module.exports = { severityLifeThreatening: '#E5231D', semanticDataModelSerialPart: '#3db014', - semanticDataModelBatch: '#ffd21d' + semanticDataModelBatch: '#ffd21d', }, screens: { sm: '640px', diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/PolicyStartUpConfig.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/PolicyStartUpConfig.java index 67aff58f74..9d7f57c812 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/PolicyStartUpConfig.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/PolicyStartUpConfig.java @@ -76,6 +76,7 @@ public void registerDecentralRegistryPermissions() throws JsonProcessingExceptio ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JavaTimeModule()); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + List acceptedPolicy = buildAcceptedPolicies(); defaultAcceptedPoliciesProvider.addAcceptedPolicies(acceptedPolicy); log.info("Successfully added permission to irs client lib provider: {}", mapper.writeValueAsString(acceptedPolicy)); diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/RestTemplateConfiguration.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/RestTemplateConfiguration.java index 32b316b61e..1c249cad77 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/RestTemplateConfiguration.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/RestTemplateConfiguration.java @@ -21,14 +21,11 @@ package org.eclipse.tractusx.traceability.common.config; import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections4.CollectionUtils; import org.eclipse.tractusx.traceability.common.properties.BpdmProperties; import org.eclipse.tractusx.traceability.common.properties.EdcProperties; import org.eclipse.tractusx.traceability.common.properties.FeignDefaultProperties; @@ -40,10 +37,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import org.springframework.http.client.BufferingClientHttpRequestFactory; -import org.springframework.http.client.ClientHttpRequestFactory; -import org.springframework.http.client.ClientHttpRequestInterceptor; -import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager; @@ -51,11 +44,9 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.web.client.DefaultResponseErrorHandler; import org.springframework.web.client.RestTemplate; import java.time.Duration; -import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; @@ -83,7 +74,6 @@ public class RestTemplateConfiguration { private final ClientRegistrationRepository clientRegistrationRepository; private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX"); - /* RestTemplate used by trace x for the resolution of manufacturer names by BPN.*/ @Bean(BPDM_CLIENT_REST_TEMPLATE) public RestTemplate bpdmClientRestTemplate(@Autowired BpdmProperties bpdmProperties) { @@ -157,7 +147,6 @@ public RestTemplate irsRegularTemplate(@Autowired TraceabilityProperties traceab /* RestTemplate used by trace x for the submodel server*/ @Bean(SUBMODEL_REST_TEMPLATE) public RestTemplate submodelRestTemplate(@Autowired TraceabilityProperties traceabilityProperties, @Autowired FeignDefaultProperties feignDefaultProperties) { - return new RestTemplateBuilder() .rootUri(traceabilityProperties.getSubmodelBase()) .setConnectTimeout(Duration.ofMillis(feignDefaultProperties.getConnectionTimeoutMillis())) diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/SecurityConfig.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/SecurityConfig.java index 317678ea78..ce829cc126 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/SecurityConfig.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/SecurityConfig.java @@ -57,8 +57,6 @@ public class SecurityConfig { "/callback/endpoint-data-reference", "/internal/endpoint-data-reference", "/actuator/**", - "/registry/reload", - "/submodel/**", "/irs/job/callback" }; diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/notification/domain/contract/EdcNotificationContractService.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/notification/domain/contract/EdcNotificationContractService.java index d2acc826a1..6b522f9e3a 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/notification/domain/contract/EdcNotificationContractService.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/notification/domain/contract/EdcNotificationContractService.java @@ -79,6 +79,7 @@ public CreateNotificationContractResponse handle(CreateNotificationContractReque throw new CreateNotificationContractException(e); } + Optional optionalPolicyResponse = policyService.getFirstPolicyMatchingApplicationConstraint(); EdcCreatePolicyDefinitionRequest edcCreatePolicyDefinitionRequest; if (optionalPolicyResponse.isPresent()) { diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/shelldescriptor/application/RegistryController.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/shelldescriptor/application/RegistryController.java index b187dd002c..393a2ea7a5 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/shelldescriptor/application/RegistryController.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/shelldescriptor/application/RegistryController.java @@ -30,12 +30,14 @@ import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import assets.importpoc.ErrorResponse; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @Tag(name = "Registry") +@PreAuthorize("hasAnyRole('ROLE_ADMIN')") @RequestMapping(path = "/registry", produces = "application/json") @RequiredArgsConstructor public class RegistryController { diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/submodel/application/rest/SubmodelController.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/submodel/application/rest/SubmodelController.java index 65e1a54c1c..fa82b07084 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/submodel/application/rest/SubmodelController.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/submodel/application/rest/SubmodelController.java @@ -33,6 +33,7 @@ import org.eclipse.tractusx.traceability.submodel.application.service.SubmodelService; import org.eclipse.tractusx.traceability.submodel.domain.model.Submodel; import org.springframework.http.HttpStatus; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -45,6 +46,7 @@ @Slf4j @RestController @Tag(name = "Submodel") +@PreAuthorize("hasAnyRole('ROLE_ADMIN')") @RequestMapping(path = "/submodel/data") @RequiredArgsConstructor public class SubmodelController { diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/submodel/infrastructure/repository/SubmodelServerClientImpl.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/submodel/infrastructure/repository/SubmodelServerClientImpl.java index 7f0a446554..ffe4a60981 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/submodel/infrastructure/repository/SubmodelServerClientImpl.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/submodel/infrastructure/repository/SubmodelServerClientImpl.java @@ -20,9 +20,10 @@ package org.eclipse.tractusx.traceability.submodel.infrastructure.repository; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.eclipse.tractusx.traceability.submodel.domain.repository.SubmodelServerRepository; import org.springframework.stereotype.Service; - +@Slf4j @Service @RequiredArgsConstructor public class SubmodelServerClientImpl implements SubmodelServerRepository { @@ -32,6 +33,7 @@ public class SubmodelServerClientImpl implements SubmodelServerRepository { @Override public void saveSubmodel(String submodelId, String submodel) { + log.info(submodelId, "submodelId"); submodelClient.createSubmodel(submodelId, submodel); } diff --git a/tx-backend/src/main/resources/application-integration-spring-boot.yml b/tx-backend/src/main/resources/application-integration-spring-boot.yml index 9a7ead9777..814382008d 100644 --- a/tx-backend/src/main/resources/application-integration-spring-boot.yml +++ b/tx-backend/src/main/resources/application-integration-spring-boot.yml @@ -33,7 +33,8 @@ traceability: regularApiKey: testRegularKey irsBase: "http://127.0.0.1" irsPoliciesPath: "/irs/policies" - submodelBase: "http://localhost:${server.port}/api/submodel/data" + submodelBase: localhost:${server.port}/api/submodel/data + registry: urlWithPath: "http://127.0.0.1" @@ -91,7 +92,7 @@ digitalTwinRegistryClient: shellLookupEndpoint: "" # required if type is "central", must contain the placeholder {assetIds} feign: submodelApi: - url: http://localhost:9998 + url: "" irsApi: url: "" adminApiKey: test diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/common/config/RestitoConfig.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/common/config/RestitoConfig.java index 53a14d1c3d..a1c6ec38a5 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/common/config/RestitoConfig.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/common/config/RestitoConfig.java @@ -33,6 +33,7 @@ public class RestitoConfig { static { STUB_SERVER = new StubServer(1025, 65000).run(); STUB_SERVER_PORT = STUB_SERVER.getPort(); + System.out.println(STUB_SERVER_PORT + "PORT"); } public static void clear() { @@ -54,6 +55,7 @@ public void initialize(ConfigurableApplicationContext configurableApplicationCon "spring.security.oauth2.client.provider.OKTA.token-uri=http://127.0.0.1:" + STUB_SERVER_PORT + OAUTH2_TOKEN_PATH, "feign.bpnApi.url=http://127.0.0.1:" + STUB_SERVER_PORT, "traceability.irsBase=http://127.0.0.1:" + STUB_SERVER_PORT, + "traceability.submodelBase=http://127.0.0.1:" + STUB_SERVER_PORT + "/api/submodel/data", "feign.portalApi.url=http://127.0.0.1:" + STUB_SERVER_PORT, "feign.irsApi.globalAssetId=testAssetId", "feign.registryApi.url=http://127.0.0.1:" + STUB_SERVER_PORT, diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/common/support/SubmodelSupport.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/common/support/SubmodelSupport.java new file mode 100644 index 0000000000..0951590f9b --- /dev/null +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/common/support/SubmodelSupport.java @@ -0,0 +1,44 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.traceability.integration.common.support; + +import org.glassfish.grizzly.http.util.HttpStatus; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import static com.xebialabs.restito.builder.stub.StubHttp.whenHttp; +import static com.xebialabs.restito.semantics.Action.status; +import static com.xebialabs.restito.semantics.Condition.startsWithUri; + +@Component +public class SubmodelSupport { + + @Autowired + RestitoProvider restitoProvider; + + public void willCreateSubmodel() { + + whenHttp(restitoProvider.stubServer()).match(startsWithUri("/api/submodel/data") + ).then( + status(HttpStatus.CREATED_201) + ); + } + +} diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/importdata/ImportControllerIT.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/importdata/ImportControllerIT.java index 8cbfbab025..c5e207a48a 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/importdata/ImportControllerIT.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/importdata/ImportControllerIT.java @@ -39,6 +39,7 @@ import org.eclipse.tractusx.traceability.integration.common.support.DtrApiSupport; import org.eclipse.tractusx.traceability.integration.common.support.EdcSupport; import org.eclipse.tractusx.traceability.integration.common.support.IrsApiSupport; +import org.eclipse.tractusx.traceability.integration.common.support.SubmodelSupport; import org.hamcrest.Matchers; import org.jose4j.lang.JoseException; import org.junit.jupiter.api.Test; @@ -71,6 +72,9 @@ class ImportControllerIT extends IntegrationTestSpecification { @Autowired DtrApiSupport dtrApiSupport; + @Autowired + SubmodelSupport submodelSupport; + @Autowired IrsApiSupport irsApiSupport; @@ -130,6 +134,7 @@ void givenValidFileWithAsBuiltOnly_whenImportData_thenValidationShouldPass() thr .multiPart(file) .post("/api/assets/import") .then() + .log().all() .statusCode(200) .extract().as(ImportResponse.class); @@ -376,7 +381,7 @@ void givenInvalidAspect_whenImportData_thenValidationShouldNotPass() throws Jose } @Test - void givenValidFile_whenPublishData_thenStatusShouldChangeToInPublishedToCX() throws JoseException, InterruptedException, IOException { + void givenValidFile_whenPublishData_thenStatusShouldChangeToInPublishedToCX() throws JoseException, InterruptedException { // given String path = getClass().getResource("/testdata/importfiles/validImportFile.json").getFile(); File file = new File(path); @@ -398,6 +403,7 @@ void givenValidFile_whenPublishData_thenStatusShouldChangeToInPublishedToCX() th oAuth2ApiSupport.oauth2ApiReturnsTechnicalUserToken(); oAuth2ApiSupport.oauth2ApiReturnsDtrToken(); dtrApiSupport.dtrWillCreateShell(); + submodelSupport.willCreateSubmodel(); // when given() @@ -444,6 +450,7 @@ void givenValidFile2_whenPublishData_thenStatusShouldChangeToPublishedToCx() thr oAuth2ApiSupport.oauth2ApiReturnsTechnicalUserToken(); oAuth2ApiSupport.oauth2ApiReturnsDtrToken(); dtrApiSupport.dtrWillCreateShell(); + submodelSupport.willCreateSubmodel(); // when given() @@ -492,6 +499,7 @@ void givenValidFile_whenPublishDataFailsOnDtr_thenStatusShouldChangeError() thro oAuth2ApiSupport.oauth2ApiReturnsTechnicalUserToken(); oAuth2ApiSupport.oauth2ApiReturnsDtrToken(); dtrApiSupport.dtrWillFailToCreateShell(); + submodelSupport.willCreateSubmodel(); // when given() diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/submodel/SubmodelControllerIT.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/submodel/SubmodelControllerIT.java index 858038cc38..675928ad74 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/submodel/SubmodelControllerIT.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/submodel/SubmodelControllerIT.java @@ -20,9 +20,11 @@ package org.eclipse.tractusx.traceability.integration.submodel; import io.restassured.http.ContentType; +import org.eclipse.tractusx.traceability.common.security.JwtRole; import org.eclipse.tractusx.traceability.integration.IntegrationTestSpecification; import org.eclipse.tractusx.traceability.submodel.infrastructure.model.SubmodelEntity; import org.eclipse.tractusx.traceability.submodel.infrastructure.repository.JpaSubmodelRepository; +import org.jose4j.lang.JoseException; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -37,7 +39,7 @@ class SubmodelControllerIT extends IntegrationTestSpecification { JpaSubmodelRepository jpaSubmodelRepository; @Test - void givenNoSubmodels_whenDeleteAll_thenDeleteSubmodelsFromDatabase() { + void givenNoSubmodels_whenDeleteAll_thenDeleteSubmodelsFromDatabase() throws JoseException { // given String submodelId = "UUID:Xa123123"; jpaSubmodelRepository.save(SubmodelEntity.builder() @@ -51,6 +53,7 @@ void givenNoSubmodels_whenDeleteAll_thenDeleteSubmodelsFromDatabase() { given() .log().all() .when() + .header(oAuth2Support.jwtAuthorization(JwtRole.ADMIN)) .delete("/api/submodel/data") .then() .log().all() @@ -62,7 +65,7 @@ void givenNoSubmodels_whenDeleteAll_thenDeleteSubmodelsFromDatabase() { } @Test - void givenSubmodel_whenGetById_thenGetIt() { + void givenSubmodel_whenGetById_thenGetIt() throws JoseException { // given String submodelId = "UUID:Xa123123"; String payload = "Payload string"; @@ -75,6 +78,7 @@ void givenSubmodel_whenGetById_thenGetIt() { String responseBody = given() .log().all() .when() + .header(oAuth2Support.jwtAuthorization(JwtRole.ADMIN)) .get("/api/submodel/data/" + submodelId) .then() .log().all() @@ -87,7 +91,7 @@ void givenSubmodel_whenGetById_thenGetIt() { } @Test - void givenNoSubmodels_whenGetById_thenNotFound() { + void givenNoSubmodels_whenGetById_thenNotFound() throws JoseException { // given String submodelId = "UUID:Xa123123"; @@ -95,6 +99,7 @@ void givenNoSubmodels_whenGetById_thenNotFound() { given() .log().all() .when() + .header(oAuth2Support.jwtAuthorization(JwtRole.ADMIN)) .get("/api/submodel/data/" + submodelId) .then() .log().all() @@ -102,13 +107,14 @@ void givenNoSubmodels_whenGetById_thenNotFound() { } @Test - void givenSubmodel_whenSave_thenSaveIntoDatabase() { + void givenSubmodel_whenSave_thenSaveIntoDatabase() throws JoseException { // given String submodelId = "submodelId"; String requestContent = "test request"; // when given() + .header(oAuth2Support.jwtAuthorization(JwtRole.ADMIN)) .contentType(ContentType.JSON) .log().all() .when()