diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml index 616ce94ffa..e666fdccd7 100644 --- a/.github/codeql/codeql-config.yml +++ b/.github/codeql/codeql-config.yml @@ -22,6 +22,9 @@ query-filters: id: - java/unused-reference-type - java/spring-disabled-csrf-protection + problem.severity: + - warning + - recommendation paths-ignore: - frontend/dist @@ -32,4 +35,4 @@ paths-ignore: - frontend/src/mockServiceWorker.js - tx-backend/src/main/resources/application-integration-spring-boot.yml - docs/src/post-processing - - tx-backend/target/generated-sources/jsonschema2pojo + - tx-backend/target diff --git a/CHANGELOG.md b/CHANGELOG.md index e7a46ca8f4..4bcec084f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,18 +8,25 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). _**For better traceability add the corresponding GitHub issue number in each changelog entry, please.**_ ## [UNRELEASED - DD.MM.YYYY] - ### Added +- #695 OAuth2.0 Client scope configuration - #606 Added error message into notifications on failure +- #596 Added Policy management documentation +- Added overview of the scheduler tasks in documentation ### Changed - - #709 Bumped spring-core from 6.0.17 to 6.1.5 - #606 cucumber tests retry on error - #606 refactored response model to only be used by common model package tx-models - +- #709 Fixed CVE-2024-22257 overriding spring-security-core from 6.1.7 to 6.2.3 +- #596 Policy management has been moved to different module +- #762 updated documentation for release 24.5 ### Removed +- Shedlock, resilence4j, templateResolver as not used anymore + +### Added +- #630 Added Parts extended detailed view ## [10.7.0 - 18.03.2024] diff --git a/charts/traceability-foss/charts/backend/templates/deployment.yaml b/charts/traceability-foss/charts/backend/templates/deployment.yaml index 3c053b3bc2..f1300835f2 100644 --- a/charts/traceability-foss/charts/backend/templates/deployment.yaml +++ b/charts/traceability-foss/charts/backend/templates/deployment.yaml @@ -90,6 +90,8 @@ spec: value: {{ .Values.oauth2.clientId | quote }} - name: OAUTH2_CLIENT_SECRET value: {{ .Values.oauth2.clientSecret | quote }} + - name: OAUTH2_CLIENT_SCOPE + value: {{ .Values.oauth2.clientScope | quote }} - name: OAUTH2_PROVIDER_TOKEN_URI value: {{ .Values.oauth2.clientTokenUri | quote }} - name: OAUTH2_JWK_SET_URI diff --git a/docs/src/docs/concepts/TRACEFOSS-993/dataspace-contract-nego-auditor-sequence.puml b/docs/concept/TRACEFOSS-993/dataspace-contract-nego-auditor-sequence.puml similarity index 100% rename from docs/src/docs/concepts/TRACEFOSS-993/dataspace-contract-nego-auditor-sequence.puml rename to docs/concept/TRACEFOSS-993/dataspace-contract-nego-auditor-sequence.puml diff --git a/docs/src/docs/concepts/TRACEFOSS-993/sending-notifications-collect-artifacts-sequence.puml b/docs/concept/TRACEFOSS-993/sending-notifications-collect-artifacts-sequence.puml similarity index 100% rename from docs/src/docs/concepts/TRACEFOSS-993/sending-notifications-collect-artifacts-sequence.puml rename to docs/concept/TRACEFOSS-993/sending-notifications-collect-artifacts-sequence.puml diff --git a/docs/src/docs/concepts/[TRACEFOSS-1019]-data-import-interface/data-import-interface-squence.puml b/docs/concept/[TRACEFOSS-1019]-data-import-interface/data-import-interface-squence.puml similarity index 100% rename from docs/src/docs/concepts/[TRACEFOSS-1019]-data-import-interface/data-import-interface-squence.puml rename to docs/concept/[TRACEFOSS-1019]-data-import-interface/data-import-interface-squence.puml diff --git a/docs/src/docs/concepts/[TRACEFOSS-1019]-data-import-interface/import-format-input-validation.puml b/docs/concept/[TRACEFOSS-1019]-data-import-interface/import-format-input-validation.puml similarity index 100% rename from docs/src/docs/concepts/[TRACEFOSS-1019]-data-import-interface/import-format-input-validation.puml rename to docs/concept/[TRACEFOSS-1019]-data-import-interface/import-format-input-validation.puml diff --git a/docs/src/docs/concepts/[TRACEFOSS-1019]-data-import-interface/tracex-twin-import-format.puml b/docs/concept/[TRACEFOSS-1019]-data-import-interface/tracex-twin-import-format.puml similarity index 100% rename from docs/src/docs/concepts/[TRACEFOSS-1019]-data-import-interface/tracex-twin-import-format.puml rename to docs/concept/[TRACEFOSS-1019]-data-import-interface/tracex-twin-import-format.puml diff --git a/docs/src/docs/concepts/_concept.template.md b/docs/concept/_concept.template.md similarity index 100% rename from docs/src/docs/concepts/_concept.template.md rename to docs/concept/_concept.template.md diff --git a/docs/src/docs/arc42/building-block-view/whitebox-overall.adoc b/docs/src/docs/arc42/building-block-view/blackbox-overall.adoc similarity index 68% rename from docs/src/docs/arc42/building-block-view/whitebox-overall.adoc rename to docs/src/docs/arc42/building-block-view/blackbox-overall.adoc index 24a87e1406..f1206ab823 100644 --- a/docs/src/docs/arc42/building-block-view/whitebox-overall.adoc +++ b/docs/src/docs/arc42/building-block-view/blackbox-overall.adoc @@ -1,13 +1,13 @@ -= Whitebox overall system += Blackbox overall system -== [Outdated] Component diagram +== Component diagram [plantuml, target=whitebox-overview, format=svg] .... include::../../../uml-diagrams/arc42/building-block-view/whitebox_overall.puml[] .... -== [Outdated] Component description +== Component description |=== |Components |Description @@ -19,11 +19,9 @@ include::../../../uml-diagrams/arc42/building-block-view/whitebox_overall.puml[] |The EDC Consumer Component is there to fulfill the GAIA-X and IDSA-data sovereignty principles. The EDC Consumer consists out of a control plane and a data plane. |EDC Provider -|The EDC Provider Component connects with EDC Consumer component and  forms the end point for the actual exchange of data. It handles automatic contract negotiation and the subsequent exchange of data assets for connected applications. +|The EDC Provider Component connects with EDC Consumer component and forms the endpoint for the actual exchange of data. It handles automatic contract negotiation and the subsequent exchange of data assets for connected applications. |Submodel Server |The Submodel Server offers endpoints for requesting the Submodel aspects. -|IAM/DAPS -|DAPS as central Identity Provider |=== diff --git a/docs/src/docs/arc42/building-block-view/full.adoc b/docs/src/docs/arc42/building-block-view/full.adoc index f6043a4166..df8d91b548 100644 --- a/docs/src/docs/arc42/building-block-view/full.adoc +++ b/docs/src/docs/arc42/building-block-view/full.adoc @@ -1,5 +1,5 @@ = Building block view -include::whitebox-overall.adoc[leveloffset=+1] +include::blackbox-overall.adoc[leveloffset=+1] include::level-1.adoc[leveloffset=+1] diff --git a/docs/src/docs/arc42/building-block-view/index.adoc b/docs/src/docs/arc42/building-block-view/index.adoc index 2b2fa53f62..c63bbf56bf 100644 --- a/docs/src/docs/arc42/building-block-view/index.adoc +++ b/docs/src/docs/arc42/building-block-view/index.adoc @@ -2,6 +2,5 @@ == Chapters -- xref:whitebox-overall.adoc[Whitebox overall system] +- xref:blackbox-overall.adoc[Blackbox overall system] - xref:level-1.adoc[Level 1] -- xref:level-2.adoc[Level 2] diff --git a/docs/src/docs/arc42/runtime-view/assets/return-all-assets.adoc b/docs/src/docs/arc42/runtime-view/assets/return-all-assets.adoc index e554db534e..9e71b73573 100644 --- a/docs/src/docs/arc42/runtime-view/assets/return-all-assets.adoc +++ b/docs/src/docs/arc42/runtime-view/assets/return-all-assets.adoc @@ -9,7 +9,7 @@ The same can be done with as planned assets. include::../../../../uml-diagrams/arc42/runtime-view/assets/return-all-assets.puml[] .... -==== Overview +== Overview When a user requests stored assets, TraceX-FOSS checks if the user has an adequate role ('ROLE_ADMIN', 'ROLE_SUPERVISOR', 'ROLE_USER'). If yes, then the endpoint returns a pageable result of assets. diff --git a/docs/src/docs/arc42/runtime-view/assets/return-specific-assets.adoc b/docs/src/docs/arc42/runtime-view/assets/return-specific-assets.adoc index 87b759715d..10511620d6 100644 --- a/docs/src/docs/arc42/runtime-view/assets/return-specific-assets.adoc +++ b/docs/src/docs/arc42/runtime-view/assets/return-specific-assets.adoc @@ -9,7 +9,7 @@ The same can be done with as planned assets. include::../../../../uml-diagrams/arc42/runtime-view/assets/return-specific-assets.puml[] .... -==== Overview +== Overview When a user requests a specific asset, TraceX-FOSS checks if the user has an adequate role ('ROLE_ADMIN', 'ROLE_SUPERVISOR', 'ROLE_USER'). If yes, then the endpoint returns a precise Asset for the given assetId, if it is found. diff --git a/docs/src/docs/arc42/runtime-view/data-sovereignty/return_asset_contracts.adoc b/docs/src/docs/arc42/runtime-view/data-sovereignty/return_asset_contracts.adoc index afe880541b..078651a62a 100644 --- a/docs/src/docs/arc42/runtime-view/data-sovereignty/return_asset_contracts.adoc +++ b/docs/src/docs/arc42/runtime-view/data-sovereignty/return_asset_contracts.adoc @@ -2,12 +2,12 @@ This section describes functionality and the behavior in case a user requests contract agreements from Trace-X via the Trace-X contracts API (/contracts). -[plantuml,target=return-all-assets,format=svg] +[plantuml,target=return-all-contracts,format=svg] .... include::../../../../uml-diagrams/arc42/runtime-view/data-sovereignty/get-all-contracts-sequenceflow.puml[] .... -==== Overview +== Overview In case a user requests contract agreements, Trace-X checks if the user has required roles ('ROLE_ADMIN', 'ROLE_SUPERVISOR'). If yes, then the requested assets will be mapped to the related contract agreement id. diff --git a/docs/src/docs/arc42/runtime-view/full.adoc b/docs/src/docs/arc42/runtime-view/full.adoc index ebeeeb5ca2..3eab259c87 100644 --- a/docs/src/docs/arc42/runtime-view/full.adoc +++ b/docs/src/docs/arc42/runtime-view/full.adoc @@ -7,3 +7,5 @@ include::notifications.adoc[leveloffset=+1] include::data-consumption.adoc[leveloffset=+1] include::data-provisioning.adoc[leveloffset=+1] include::data-sovereignty.adoc[leveloffset=+1] +include::policies.adoc[leveloffset=+1] +include::scheduler.adoc[leveloffset=+1] diff --git a/docs/src/docs/arc42/runtime-view/policies.adoc b/docs/src/docs/arc42/runtime-view/policies.adoc new file mode 100644 index 0000000000..a6536379a9 --- /dev/null +++ b/docs/src/docs/arc42/runtime-view/policies.adoc @@ -0,0 +1,45 @@ += Policies + +== Overview +=== Scenario 1: Start Up interaction with IRS Policy Store +Trace-X instance define a constraint which is required for data consumption and provisioning. +Trace-X retrieves all policies by IRS and validates if one of the policies contains the required constraint given by Trace-X. +If a policy with the constraint exists and is valid process ends. If the policy is not valid it will create one with the given constraint. + +This sequence diagram describes the process of retrieving or creating policies within the IRS Policy Store based on Trace-X given constraint. + +[plantuml, target=policy-startup-configuration, format=svg] +.... +include::../../../uml-diagrams/arc42/runtime-view/policies/policy-startup-configuration.puml[] +.... + +=== Scenario 2: Start Up interaction with EDC +Trace-X instance uses the policy which includes the defined constraint and transforms it into a valid EDC Policy Request. +The EDC Policy Request will be used for creating a policy for the required notification contracts. + +This sequence diagram describes the process of retrieving the correct policy by IRS Policy Store based on Trace-X given constraint and reuses it for creating an EDC Policy. + +[plantuml, target=policy-startup-notification-contract, format=svg] +.... +include::../../../uml-diagrams/arc42/runtime-view/policies/policy-startup-notification-contract.puml[] +.... + +=== Scenario 3: Provisioning of notifications +Trace-X instance uses the policy which includes the defined constraint and reuses it for validation of catalog offers by the receiver edc. + +This sequence diagram describes the process of how the policy with the defined constraint will be used for validation of catalog offers by the receiver edc, to validate if sending is valid. + +[plantuml, target=policy-notifications, format=svg] +.... +include::../../../uml-diagrams/arc42/runtime-view/policies/policy-notifications.puml[] +.... + +=== Scenario 4: Provisioning of assets +Trace-X instance uses the policy which includes the defined constraint and reuses it for creating edc assets . + +This sequence diagram describes the process of how the policy with the defined constraint will be reused for registering edc data assets. + +[plantuml, target=policy-assets, format=svg] +.... +include::../../../uml-diagrams/arc42/runtime-view/policies/policy-assets.puml[] +.... diff --git a/docs/src/docs/arc42/runtime-view/scheduler.adoc b/docs/src/docs/arc42/runtime-view/scheduler.adoc new file mode 100644 index 0000000000..d3a3f868d2 --- /dev/null +++ b/docs/src/docs/arc42/runtime-view/scheduler.adoc @@ -0,0 +1,4 @@ += Scheduler + +include::scheduler/scheduler.adoc[leveloffset=+1] + diff --git a/docs/src/docs/arc42/runtime-view/scheduler/scheduler.adoc b/docs/src/docs/arc42/runtime-view/scheduler/scheduler.adoc new file mode 100644 index 0000000000..c955419842 --- /dev/null +++ b/docs/src/docs/arc42/runtime-view/scheduler/scheduler.adoc @@ -0,0 +1,8 @@ +An overview of the scheduler tasks configured in the system. + +[options="header"] +|=== +|Scheduler Name | Execution Interval | Description +|PublishAssetsJob | Every hour at 30min | Publishes assets in IN_SYNCHRONIZATION state to core services. The process combines 'as-built' and 'as-planned' assets and initiates their publication for synchronization in the traceability system. +|AssetsRefreshJob | Every 2 hours | Invokes the synchronization of asset shell descriptors with the decentralized registry. It ensures the latest asset information is fetched and updated in the system from external sources. +|=== diff --git a/docs/src/docs/arc42/scope-context/technical-context.adoc b/docs/src/docs/arc42/scope-context/technical-context.adoc index 3bc265f23c..f109612a44 100644 --- a/docs/src/docs/arc42/scope-context/technical-context.adoc +++ b/docs/src/docs/arc42/scope-context/technical-context.adoc @@ -41,8 +41,10 @@ Request contains details required to start IRS fetch job provided by the compone include::../../../uml-diagrams/arc42/scope-context/technical-context/portal-api-view.puml[] .... -The Trace-X acts as a consumer of the Portal component. The Trace-X contains a Restful client (REST template) that build a REST call to the mentioned Portal API based on its known URL (the Portal URL is configurable in the Trace-X). -Request contains "bpns" provided by the component during sending notifications. Like described in the above section, the security aspect is required in order to achieve a REST call against the Portal. As a response, the Trace-X gets the corresponding BPN mappings to EDC urls where a notification should be send over. And as mentioned above, the transport protocol HTTP(S) is used for the REST call communication. +The Trace-X acts as a consumer of the Portal component. +The Trace-X contains a Restful client (REST template) that builds a REST call to the mentioned Portal API based on its known URL (the Portal URL is configurable in the Trace-X). +The Portal is used to authenticate users and requests against the backend. +And as mentioned above, the transport protocol HTTP(S) is used for the REST call communication. === [Outdated] EDC API @@ -51,4 +53,8 @@ Request contains "bpns" provided by the component during sending notifications. include::../../../uml-diagrams/arc42/scope-context/technical-context/edc-api-view.puml[] .... -The Trace-X acts as a consumer of the EDC component. In Trace-X we communicate with EDC directly only for the sake of fulfilling quality-investigation functionality. Specific use cases can be viewed in xref:../runtime-view/index.adoc[Runtime view] section. For this purposes the integrated EDC clients in the Trace-X are responsible for creating restful requests to the EDC component. And as mentioned above, the transport protocol HTTP(S) is used for the REST call communication. +The Trace-X acts as a consumer and provider of the EDC component. +In Trace-X we communicate with EDC directly only for the sake of fulfilling quality-investigation functionality. +Specific use cases can be viewed in xref:../runtime-view/index.adoc[Runtime view] section. +For these purposes the integrated EDC clients in the Trace-X are responsible for creating restful requests to the EDC component. +And as mentioned above, the transport protocol HTTP(S) is used for the REST call communication. diff --git a/docs/src/docs/user/user-manual.adoc b/docs/src/docs/user/user-manual.adoc index 02baa0dc6e..831eb88a7e 100644 --- a/docs/src/docs/user/user-manual.adoc +++ b/docs/src/docs/user/user-manual.adoc @@ -271,6 +271,23 @@ Detailed information on the IDs for the manufactured part/batch. Information about the identifiers at the customer for the respective part/batch. +==== Traction battery code data + +If the asset has the "traction battery code" aspect model, an additional section underneath will be displayed. +In this section there are detailed information about the traction battery and a table with its subcomponents + +==== Creation of a quality incident from detailed view + +By clicking on the "announcement" icon you can create a quality incident from the detailed view, containing the part information in the currently opened detailed view. If this +functionality is disabled, a tooltip will provide information explaining the reason. You can trigger the tooltip by hovering above the button. + +==== Publish asset from detailed view + +By clicking on the "publish" icon, you can publish the currently opened part from the detailed view. If the icon is disabled, +a tooltip will provide information explaining the reason. You can trigger the tooltip by hovering above the button + + + == Other parts List view of the supplied/delivered parts and batches (Supplier parts / Customer parts). diff --git a/docs/src/images/arc42/user-guide/parts-list-detailed-view.png b/docs/src/images/arc42/user-guide/parts-list-detailed-view.png index f98da69000..340d713d8a 100644 Binary files a/docs/src/images/arc42/user-guide/parts-list-detailed-view.png and b/docs/src/images/arc42/user-guide/parts-list-detailed-view.png differ diff --git a/docs/src/uml-diagrams/arc42/building-block-view/whitebox_overall.puml b/docs/src/uml-diagrams/arc42/building-block-view/whitebox_overall.puml index 4721ae072c..9b7c966ec0 100644 --- a/docs/src/uml-diagrams/arc42/building-block-view/whitebox_overall.puml +++ b/docs/src/uml-diagrams/arc42/building-block-view/whitebox_overall.puml @@ -17,11 +17,11 @@ component [**TraceXApplication**] <> { } component [**CatenaX-Network**] <> { - component [**IAM/KeyCloak**] <> as IAM_IRS + component [**Portal**] <> as PORTAL component [**Digital Twin Registry**] <> as DT_REG - component [**IRS**] <> as IAM_DAPS - IAM_IRS --[hidden]> DT_REG - DT_REG --[hidden]> IAM_DAPS + component [**IRS**] <> as IRS + PORTAL --[hidden]> DT_REG + DT_REG --[hidden]> IRS } component [**Tier/OEM**] <> { @@ -38,19 +38,20 @@ component [**Tier/OEM**] <> { actor IrsApiConsumer IrsApiConsumer -right(0- TraceX -IrsApiConsumer <.r.> IAM_IRS +IrsApiConsumer <.r.> PORTAL TraceX <.d.> EDC -TraceX <.l.> IAM_IRS -TraceX <.> DT_REG -EDC <..> IAM_DAPS +TraceX <.l.> PORTAL +TraceX <..> IRS +EDC <..> DT_REG EDC <..> EDCProviderOEM EDC <...> EDCProviderTier1 EDC <....> EDCProviderTier11 -IAM_DAPS <..> EDCProviderOEM -IAM_DAPS <...> EDCProviderTier1 -IAM_DAPS <....> EDCProviderTier11 +IRS <..> EDCProviderOEM +IRS <...> EDCProviderTier1 +IRS <....> EDCProviderTier11 EDCProviderOEM <..> SubmodelServerOEM EDCProviderTier1 <..> SubmodelServerTier1 +EDCProviderTier1 <..> SubmodelServerTier1 EDCProviderTier11 <..> SubmodelServerTier11 @enduml diff --git a/docs/src/uml-diagrams/arc42/runtime-view/policies/policy-assets.puml b/docs/src/uml-diagrams/arc42/runtime-view/policies/policy-assets.puml new file mode 100644 index 0000000000..e30c6d343e --- /dev/null +++ b/docs/src/uml-diagrams/arc42/runtime-view/policies/policy-assets.puml @@ -0,0 +1,18 @@ +@startuml +skinparam monochrome true +skinparam shadowing false +skinparam defaultFontName "Architects daughter" + +title Sequence Diagram: Policy Handling on asset provisioning flow +participant "TraceX" as TraceX +participant "EDC Consumer" as EdcConsumer +participant "EDC Provider (other) " as EdcProvider + +TraceX -> TraceX: Publish Asset to Core Services +TraceX -> EdcConsumer: Register policy +EdcConsumer -> EdcProvider: Register policy +EdcProvider -> EdcConsumer: Return ok +EdcConsumer -> TraceX: Return ok +TraceX -> TraceX: Reuse policy for contract definition creation + +@enduml diff --git a/docs/src/uml-diagrams/arc42/runtime-view/policies/policy-notifications.puml b/docs/src/uml-diagrams/arc42/runtime-view/policies/policy-notifications.puml new file mode 100644 index 0000000000..ea8d38ae70 --- /dev/null +++ b/docs/src/uml-diagrams/arc42/runtime-view/policies/policy-notifications.puml @@ -0,0 +1,49 @@ +@startuml +skinparam monochrome true +skinparam shadowing false +skinparam defaultFontName "Architects daughter" + +title Sequence Diagram: Policy Handling on notification flow +participant "TraceX" as TraceX +participant "IRS " as IRS +participant "EDC Consumer" as EdcConsumer +participant "EDC Provider (other) " as EdcProvider + +TraceX -> TraceX: Approve, Acknowledge, Accept, Decline or Close Notification +TraceX -> EdcConsumer: Get catalog of receiver +EdcConsumer -> EdcProvider: Get catalog of receiver +EdcProvider -> EdcConsumer: Return catalog +EdcConsumer -> TraceX: Return catalog +TraceX -> TraceX: Validate Contract Type, Method and Policy + +alt Notification Type: Alert + TraceX -> TraceX: Filter for method (update, receive) + TraceX -> TraceX: Validate if catalog policy matches the configured policies in IRS Lib (3 Default Policies) + alt Method: Update + TraceX -> EdcConsumer: Send out alert notification (update method) + EdcConsumer -> EdcProvider: Send out alert notification (update method) + EdcProvider -> EdcConsumer: ok + EdcConsumer -> TraceX: .. + else Method: Receive + TraceX -> EdcConsumer: Send out alert notification (receive method) + EdcConsumer -> EdcProvider: Send out alert notification (receive method) + EdcProvider -> EdcConsumer: ok + EdcConsumer -> TraceX: .. + end +else Notification Type: Investigation + TraceX -> TraceX: Filter for method (update, receive) + TraceX -> TraceX: Validate if catalog policy matches the configured policies in IRS Lib (3 Default Policies) + alt Method: Update + TraceX -> EdcConsumer: Send out Investigation notification (update method) + EdcConsumer -> EdcProvider: Send out Investigation notification (update method) + EdcProvider -> EdcConsumer: ok + EdcConsumer -> TraceX: .. + else Method: Receive + TraceX -> EdcConsumer: Send out Investigation notification (receive method) + EdcConsumer -> EdcProvider: Send out Investigation notification (receive method) + EdcProvider -> EdcConsumer: ok + EdcConsumer -> TraceX: .. + end +end + +@enduml diff --git a/docs/src/docs/concepts/#534-policies/policy-handling-tracex.puml b/docs/src/uml-diagrams/arc42/runtime-view/policies/policy-startup-configuration.puml similarity index 93% rename from docs/src/docs/concepts/#534-policies/policy-handling-tracex.puml rename to docs/src/uml-diagrams/arc42/runtime-view/policies/policy-startup-configuration.puml index d1622498c1..6792d50a28 100644 --- a/docs/src/docs/concepts/#534-policies/policy-handling-tracex.puml +++ b/docs/src/uml-diagrams/arc42/runtime-view/policies/policy-startup-configuration.puml @@ -7,9 +7,11 @@ participant "TraceXConfig" as Config participant "TraceX" as TraceX participant "IRS " as IRS -Config -> TraceX : ID 3.0 Trace +Config -> TraceX : Constraint TraceX -> IRS : Get Policies -IRS -> TraceX: Return policies (Default Policies from IRS (C1: Membership, C2: Framework, C3: ID3.0) +IRS -> TraceX: Return policies +TraceX -> TraceX: Check if a policy matches own constraint + TraceX -> IRS : Create(not exists) or update(exists) @enduml diff --git a/docs/src/uml-diagrams/arc42/runtime-view/policies/policy-startup-notification-contract.puml b/docs/src/uml-diagrams/arc42/runtime-view/policies/policy-startup-notification-contract.puml new file mode 100644 index 0000000000..3f56d4cb80 --- /dev/null +++ b/docs/src/uml-diagrams/arc42/runtime-view/policies/policy-startup-notification-contract.puml @@ -0,0 +1,44 @@ +@startuml +skinparam monochrome true +skinparam shadowing false +skinparam defaultFontName "Architects daughter" + +title Sequence Diagram: TraceX Interaction with EDC on startup +participant "TraceX" as TraceX +participant "IRS" as IRS +participant "EDC " as EDC + +TraceX -> IRS: Get Policies +IRS -> TraceX : return Policies +TraceX -> TraceX: Find any policy which matches own constraint + +alt Policy found + TraceX -> TraceX: Use found policy for EdcPolicy creation. + TraceX -> EDC : Create notification asset, policy, definition +else Policy not found + TraceX -> TraceX: Corrupt state of application +end + +@enduml + +@startuml +skinparam monochrome true +skinparam shadowing false +skinparam defaultFontName "Architects daughter" + +title Sequence Diagram: Sending notifications +participant "TraceX" as TraceX +participant "TraceXIRSLib" as TraceXIRSLib +participant "IRS " as IRS +participant "EDC " as EDC + +TraceX -> TraceX: ... +TraceX -> EDC: Get catalog +EDC -> TraceX: -> Return catalog +TraceX -> TraceX: Filter for notification type (alert / investigation) / method(update, receive) +TraceX -> TraceXIRSLib: Validate if catalog policy matches the configured policies in IRS Lib (3 Default Policies) +TraceXIRSLib -> TraceX: Valid +TraceX -> EDC: Send out notification +@enduml + + diff --git a/docs/src/uml-diagrams/arc42/scope-context/technical-context/aas-api-view.puml b/docs/src/uml-diagrams/arc42/scope-context/technical-context/aas-api-view.puml index 4e30a30861..dd2188bf06 100644 --- a/docs/src/uml-diagrams/arc42/scope-context/technical-context/aas-api-view.puml +++ b/docs/src/uml-diagrams/arc42/scope-context/technical-context/aas-api-view.puml @@ -7,6 +7,10 @@ skinparam ranksep 20 actor "User" as User component [**Trace-X**] <> as Trace +node "Eclipse Dataspace Connector" { + [**EDC**] <> as EDC +} + node "Digital Twin Registry" { [**AAS Registry**] <> as AASR } @@ -16,14 +20,17 @@ node "IAM" { } interface "TRACE API" as TAPI +interface "EDC API" as EAPI interface "OIDC" as IAMAPI interface "Registry API" as IRAPI User -( TAPI TAPI - Trace -Trace --( IRAPI +Trace --( EAPI IRAPI -- AASR Trace --down( IAMAPI AASR --( IAMAPI KIAM -- IAMAPI +EAPI -- EDC +EDC --( IRAPI @enduml diff --git a/frontend/cypress/integration/pages/AdminPage.ts b/frontend/cypress/integration/pages/AdminPage.ts index 317548e64e..7f381661dc 100644 --- a/frontend/cypress/integration/pages/AdminPage.ts +++ b/frontend/cypress/integration/pages/AdminPage.ts @@ -39,6 +39,17 @@ export class AdminPage { return cy.get('td').contains(contractId).parent('tr').find('td mat-checkbox').click(); } + static clickCheckBoxForFirstContractInTable() { + return cy.get('td').first().parent('tr').find('td mat-checkbox').click(); + } + + static getContractIdOfFirstContractInTable() { + return cy.get('[data-testid="table-component--cell-data"]').first().then(contractId => { + return contractId.text(); + }); + } + + static clickExportContractsButton() { return cy.get('[data-testid="export-contracts-button"]').click(); } diff --git a/frontend/cypress/support/step_definitions/admin-contracts.ts b/frontend/cypress/support/step_definitions/admin-contracts.ts index b403bc36d3..4dc11a6e56 100644 --- a/frontend/cypress/support/step_definitions/admin-contracts.ts +++ b/frontend/cypress/support/step_definitions/admin-contracts.ts @@ -8,13 +8,13 @@ When('navigate to administration view tab {string}', (tabName: AdminViewTab) => const header = AdminPage.getHeaderOfTabView(tabName); switch (tabName) { case AdminViewTab.BPN_CONFIGURATION_VIEW: - header.contains('BPN - EDC Konfiguration').should('be.visible'); + header.contains('BPN - EDC Konfiguration').should('be.visible') || header.contains('BPN - EDC configuration').should('be.visible'); break; case AdminViewTab.IMPORT_VIEW: - header.contains('Trace-X Datenimport').should('be.visible'); + header.contains('Trace-X Datenimport').should('be.visible') || header.contains('Trace-X Data import').should('be.visible'); break; case AdminViewTab.CONTRACT_VIEW: - header.contains('Verträge').should('be.visible'); + header.contains('Verträge').should('be.visible') || header.contains('Contracts').should('be.visible'); break; default: { throw new Error(`The View Tab header ${ tabName } did not load or is not existing`); @@ -27,6 +27,16 @@ When('select contract with contract-id {string}', function(contractId: string) { AdminPage.clickCheckBoxForContractId(contractId).should('have.class', 'mat-mdc-checkbox-checked'); }); +When('select the first contract in the contracts table', function() { + AdminPage.getContractIdOfFirstContractInTable().then(contractId => { + currentContractId = contractId; + expect(currentContractId).not.to.be.null + }); + cy.wait(1000); + + AdminPage.clickCheckBoxForFirstContractInTable().should('have.class', 'mat-mdc-checkbox-checked'); +}) + When('export selected contracts', function() { AdminPage.clickExportContractsButton().should('be.visible'); }); @@ -40,7 +50,8 @@ Then('exported contracts csv file is existing', function() { Then('exported csv file has correct content', function() { AdminPage.getExportedContractsFileData().then((data) => { - expect(data).to.equal(`contractId,counterpartyAddress,creationDate,endDate,state\n${ currentContractId },https://trace-x-edc-e2e-a.dev.demo.catena-x.net/api/v1/dsp,Thu Mar 07 2024,Thu Jan 01 1970,FINALIZED`); + let expectedData = currentContractId.trim().replace(/\n/g,''); + expect(data).to.contain(expectedData); }); }); diff --git a/frontend/src/app/modules/core/layout/header/user-navigation/user-menu.component.scss b/frontend/src/app/modules/core/layout/header/user-navigation/user-menu.component.scss index c204e04260..52d3cb878e 100644 --- a/frontend/src/app/modules/core/layout/header/user-navigation/user-menu.component.scss +++ b/frontend/src/app/modules/core/layout/header/user-navigation/user-menu.component.scss @@ -49,6 +49,7 @@ right: -5px; transition: opacity; border-radius: 16px; + z-index: 101; } .user-menu-items { diff --git a/frontend/src/app/modules/page/admin/presentation/admin.component.scss b/frontend/src/app/modules/page/admin/presentation/admin.component.scss index 0b2aeaa5d7..ad692e9da4 100644 --- a/frontend/src/app/modules/page/admin/presentation/admin.component.scss +++ b/frontend/src/app/modules/page/admin/presentation/admin.component.scss @@ -26,7 +26,7 @@ position: relative; transition: all 0.5s; max-height: 80vh; - overflow: hidden; + overflow: auto; } .sidenav--expanded { diff --git a/frontend/src/app/modules/page/other-parts/other-parts.module.ts b/frontend/src/app/modules/page/other-parts/other-parts.module.ts index 7c11ee119f..9477a60801 100644 --- a/frontend/src/app/modules/page/other-parts/other-parts.module.ts +++ b/frontend/src/app/modules/page/other-parts/other-parts.module.ts @@ -22,23 +22,23 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { getI18nPageProvider } from '@core/i18n'; -import { PartDetailsModule } from '@shared/modules/part-details/partDetails.module'; +import { SupplierPartsComponent } from '@page/other-parts/presentation/supplier-parts/supplier-parts.component'; +import { PartsDetailModule } from '@page/parts/detail/parts-detail.module'; import { FormatPartSemanticDataModelToCamelCasePipe } from '@shared/pipes/format-part-semantic-data-model-to-camelcase.pipe'; +import { BomLifecycleSettingsService } from '@shared/service/bom-lifecycle-settings.service'; import { SharedModule } from '@shared/shared.module'; import { TemplateModule } from '@shared/template.module'; +import { AngularSplitModule } from 'angular-split'; import { OtherPartsFacade } from './core/other-parts.facade'; import { OtherPartsService } from './core/other-parts.service'; import { OtherPartsState } from './core/other-parts.state'; import { OtherPartsRoutingModule } from './other-parts.routing'; -import { OtherPartsComponent } from './presentation/other-parts.component'; -import { SupplierPartsComponent } from '@page/other-parts/presentation/supplier-parts/supplier-parts.component'; import { CustomerPartsComponent } from './presentation/customer-parts/customer-parts.component'; -import { AngularSplitModule } from 'angular-split'; -import { BomLifecycleSettingsService } from '@shared/service/bom-lifecycle-settings.service'; +import { OtherPartsComponent } from './presentation/other-parts.component'; @NgModule({ declarations: [ OtherPartsComponent, SupplierPartsComponent, CustomerPartsComponent ], - imports: [ CommonModule, TemplateModule, SharedModule, OtherPartsRoutingModule, PartDetailsModule, AngularSplitModule ], + imports: [ CommonModule, TemplateModule, SharedModule, OtherPartsRoutingModule, PartsDetailModule, AngularSplitModule ], providers: [ OtherPartsState, OtherPartsFacade, diff --git a/frontend/src/app/modules/page/other-parts/other-parts.routing.ts b/frontend/src/app/modules/page/other-parts/other-parts.routing.ts index 018739d49c..64d7e470f2 100644 --- a/frontend/src/app/modules/page/other-parts/other-parts.routing.ts +++ b/frontend/src/app/modules/page/other-parts/other-parts.routing.ts @@ -22,6 +22,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { OtherPartsComponent } from '@page/other-parts/presentation/other-parts.component'; +import { PartsDetailComponent } from '@page/parts/detail/parts-detail.component'; import { I18NEXT_NAMESPACE_RESOLVER } from 'angular-i18next'; export /** @type {*} */ @@ -33,6 +34,13 @@ const PARTS_ROUTING: Routes = [ data: { i18nextNamespaces: [ 'page.otherParts', 'partDetail' ] }, resolve: { i18next: I18NEXT_NAMESPACE_RESOLVER }, }, + { + path: ':partId', + pathMatch: 'full', + component: PartsDetailComponent, + data: { i18nextNamespaces: [ 'page.otherParts', 'partDetail' ] }, + resolve: { i18next: I18NEXT_NAMESPACE_RESOLVER }, + }, ]; @NgModule({ diff --git a/frontend/src/app/modules/page/other-parts/presentation/customer-parts/customer-parts.component.ts b/frontend/src/app/modules/page/other-parts/presentation/customer-parts/customer-parts.component.ts index b5a7b59405..fb0e29730f 100644 --- a/frontend/src/app/modules/page/other-parts/presentation/customer-parts/customer-parts.component.ts +++ b/frontend/src/app/modules/page/other-parts/presentation/customer-parts/customer-parts.component.ts @@ -18,21 +18,22 @@ ********************************************************************************/ -import { Component, Input, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'; -import { Pagination } from '@core/model/pagination.model'; -import { OtherPartsFacade } from '@page/other-parts/core/other-parts.facade'; -import { MainAspectType } from '@page/parts/model/mainAspectType.enum'; -import { AssetAsBuiltFilter, AssetAsPlannedFilter, Part } from '@page/parts/model/parts.model'; -import { TableType } from '@shared/components/multi-select-autocomplete/table-type.model'; -import { PartsTableComponent } from '@shared/components/parts-table/parts-table.component'; -import { TableSortingUtil } from '@shared/components/table/table-sorting.util'; -import { TableEventConfig, TableHeaderSort } from '@shared/components/table/table.model'; -import { toAssetFilter, toGlobalSearchAssetFilter } from '@shared/helper/filter-helper'; -import { setMultiSorting } from '@shared/helper/table-helper'; -import { View } from '@shared/model/view.model'; -import { PartDetailsFacade } from '@shared/modules/part-details/core/partDetails.facade'; -import { StaticIdService } from '@shared/service/staticId.service'; -import { Observable } from 'rxjs'; +import {Component, Input, OnDestroy, OnInit, QueryList, ViewChildren} from '@angular/core'; +import {ActivatedRoute, Params, Router} from '@angular/router'; +import {Pagination} from '@core/model/pagination.model'; +import {OtherPartsFacade} from '@page/other-parts/core/other-parts.facade'; +import {MainAspectType} from '@page/parts/model/mainAspectType.enum'; +import {AssetAsBuiltFilter, AssetAsPlannedFilter, Part} from '@page/parts/model/parts.model'; +import {TableType} from '@shared/components/multi-select-autocomplete/table-type.model'; +import {PartsTableComponent} from '@shared/components/parts-table/parts-table.component'; +import {TableSortingUtil} from '@shared/components/table/table-sorting.util'; +import {TableEventConfig, TableHeaderSort} from '@shared/components/table/table.model'; +import {containsAtleastOneFilterEntry, toAssetFilter, toGlobalSearchAssetFilter} from '@shared/helper/filter-helper'; +import {setMultiSorting} from '@shared/helper/table-helper'; +import {View} from '@shared/model/view.model'; +import {PartDetailsFacade} from '@shared/modules/part-details/core/partDetails.facade'; +import {StaticIdService} from '@shared/service/staticId.service'; +import {Observable} from 'rxjs'; @Component({ @@ -47,8 +48,8 @@ export class CustomerPartsComponent implements OnInit, OnDestroy { public readonly customerTabLabelId = this.staticIdService.generateId('OtherParts.customerTabLabel'); - public tableCustomerAsBuiltSortList: TableHeaderSort[]; - public tableCustomerAsPlannedSortList: TableHeaderSort[]; + public tableCustomerAsBuiltSortList: TableHeaderSort[] = []; + public tableCustomerAsPlannedSortList: TableHeaderSort[] = []; private ctrlKeyState = false; @@ -58,10 +59,15 @@ export class CustomerPartsComponent implements OnInit, OnDestroy { assetAsBuiltFilter: AssetAsBuiltFilter; assetsAsPlannedFilter: AssetAsPlannedFilter; + public currentPartTablePage = {AS_BUILT_CUSTOMER_PAGE: 0, AS_PLANNED_CUSTOMER_PAGE: 0} + + constructor( private readonly otherPartsFacade: OtherPartsFacade, private readonly partDetailsFacade: PartDetailsFacade, private readonly staticIdService: StaticIdService, + private readonly router: Router, + private readonly route: ActivatedRoute ) { window.addEventListener('keydown', (event) => { @@ -73,6 +79,8 @@ export class CustomerPartsComponent implements OnInit, OnDestroy { } public ngOnInit(): void { + this.route.queryParams.subscribe(params => this.setupPageByUrlParams(params)); + if (this.bomLifecycle === MainAspectType.AS_BUILT) { this.customerPartsAsBuilt$ = this.otherPartsFacade.customerPartsAsBuilt$; this.tableCustomerAsBuiltSortList = []; @@ -97,10 +105,10 @@ export class CustomerPartsComponent implements OnInit, OnDestroy { filterActivated(isAsBuilt: boolean, assetFilter: any): void { if (isAsBuilt) { this.assetAsBuiltFilter = assetFilter; - this.otherPartsFacade.setCustomerPartsAsBuilt(0, 50, [], toAssetFilter(this.assetAsBuiltFilter, true)); + this.otherPartsFacade.setCustomerPartsAsBuilt(this.currentPartTablePage['AS_BUILT_CUSTOMER_PAGE'] ?? 0, 50, [], toAssetFilter(this.assetAsBuiltFilter, true)); } else { this.assetsAsPlannedFilter = assetFilter; - this.otherPartsFacade.setCustomerPartsAsPlanned(0, 50, [], toAssetFilter(this.assetsAsPlannedFilter, false)); + this.otherPartsFacade.setCustomerPartsAsPlanned(this.currentPartTablePage['AS_PLANNED_CUSTOMER_PAGE'] ?? 0, 50, [], toAssetFilter(this.assetsAsPlannedFilter, false)); } } @@ -110,17 +118,23 @@ export class CustomerPartsComponent implements OnInit, OnDestroy { public onSelectItem(event: Record): void { this.partDetailsFacade.selectedPart = event as unknown as Part; + let tableData = {}; + for(let component of this.partsTableComponents) { + tableData[component.tableType+"_PAGE"] = component.pageIndex; + } + this.router.navigate([`otherParts/${event?.id}`], {queryParams: tableData}) } public onAsBuiltTableConfigChange({ page, pageSize, sorting }: TableEventConfig): void { this.setTableSortingList(sorting, MainAspectType.AS_BUILT); + this.currentPartTablePage['AS_BUILT_CUSTOMER_PAGE'] = page; let pageSizeValue = 50; if (pageSize !== 0) { pageSizeValue = pageSize; } - if (this.assetAsBuiltFilter) { + if (this.assetAsBuiltFilter && containsAtleastOneFilterEntry(this.assetAsBuiltFilter)) { this.otherPartsFacade.setCustomerPartsAsBuilt(0, pageSizeValue, this.tableCustomerAsBuiltSortList, toAssetFilter(this.assetAsBuiltFilter, true)); } else { this.otherPartsFacade.setCustomerPartsAsBuilt(page, pageSizeValue, this.tableCustomerAsBuiltSortList); @@ -129,13 +143,14 @@ export class CustomerPartsComponent implements OnInit, OnDestroy { public onAsPlannedTableConfigChange({ page, pageSize, sorting }: TableEventConfig): void { this.setTableSortingList(sorting, MainAspectType.AS_PLANNED); + this.currentPartTablePage['AS_PLANNED_CUSTOMER_PAGE'] = page; let pageSizeValue = 50; if (pageSize !== 0) { pageSizeValue = pageSize; } - if (this.assetsAsPlannedFilter) { + if (this.assetsAsPlannedFilter && containsAtleastOneFilterEntry(this.assetsAsPlannedFilter)) { this.otherPartsFacade.setCustomerPartsAsPlanned(0, pageSizeValue, this.tableCustomerAsPlannedSortList, toAssetFilter(this.assetsAsPlannedFilter, true)); } else { this.otherPartsFacade.setCustomerPartsAsPlanned(page, pageSizeValue, this.tableCustomerAsPlannedSortList); @@ -147,6 +162,14 @@ export class CustomerPartsComponent implements OnInit, OnDestroy { TableSortingUtil.setTableSortingList(sorting, tableSortList, this.ctrlKeyState); } + private setupPageByUrlParams(params: Params ) { + if(!params) { + return; + } + this.onAsBuiltTableConfigChange({page: params['AS_BUILT_CUSTOMER_PAGE'], pageSize: 50, sorting: null}); + this.onAsPlannedTableConfigChange({page: params['AS_PLANNED_CUSTOMER_PAGE'], pageSize: 50, sorting: null}); + } + protected readonly MainAspectType = MainAspectType; protected readonly TableType = TableType; } diff --git a/frontend/src/app/modules/page/other-parts/presentation/other-parts.component.html b/frontend/src/app/modules/page/other-parts/presentation/other-parts.component.html index a0fba587ba..138d17370d 100644 --- a/frontend/src/app/modules/page/other-parts/presentation/other-parts.component.html +++ b/frontend/src/app/modules/page/other-parts/presentation/other-parts.component.html @@ -108,7 +108,4 @@ - - diff --git a/frontend/src/app/modules/page/other-parts/presentation/supplier-parts/supplier-parts.component.ts b/frontend/src/app/modules/page/other-parts/presentation/supplier-parts/supplier-parts.component.ts index bfaa358b3a..6e28ba314c 100644 --- a/frontend/src/app/modules/page/other-parts/presentation/supplier-parts/supplier-parts.component.ts +++ b/frontend/src/app/modules/page/other-parts/presentation/supplier-parts/supplier-parts.component.ts @@ -18,22 +18,23 @@ ********************************************************************************/ -import { Component, Input, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'; -import { Pagination } from '@core/model/pagination.model'; -import { OtherPartsFacade } from '@page/other-parts/core/other-parts.facade'; -import { MainAspectType } from '@page/parts/model/mainAspectType.enum'; -import { AssetAsBuiltFilter, AssetAsPlannedFilter, Part } from '@page/parts/model/parts.model'; -import { TableType } from '@shared/components/multi-select-autocomplete/table-type.model'; -import { PartsTableComponent } from '@shared/components/parts-table/parts-table.component'; -import { TableSortingUtil } from '@shared/components/table/table-sorting.util'; -import { TableEventConfig, TableHeaderSort } from '@shared/components/table/table.model'; -import { toAssetFilter, toGlobalSearchAssetFilter } from '@shared/helper/filter-helper'; -import { setMultiSorting } from '@shared/helper/table-helper'; -import { NotificationType } from '@shared/model/notification.model'; -import { View } from '@shared/model/view.model'; -import { PartDetailsFacade } from '@shared/modules/part-details/core/partDetails.facade'; -import { StaticIdService } from '@shared/service/staticId.service'; -import { BehaviorSubject, Observable, Subject } from 'rxjs'; +import {Component, Input, OnDestroy, OnInit, QueryList, ViewChildren} from '@angular/core'; +import {ActivatedRoute, Params, Router} from '@angular/router'; +import {Pagination} from '@core/model/pagination.model'; +import {OtherPartsFacade} from '@page/other-parts/core/other-parts.facade'; +import {MainAspectType} from '@page/parts/model/mainAspectType.enum'; +import {AssetAsBuiltFilter, AssetAsPlannedFilter, Part} from '@page/parts/model/parts.model'; +import {TableType} from '@shared/components/multi-select-autocomplete/table-type.model'; +import {PartsTableComponent} from '@shared/components/parts-table/parts-table.component'; +import {TableSortingUtil} from '@shared/components/table/table-sorting.util'; +import {TableEventConfig, TableHeaderSort} from '@shared/components/table/table.model'; +import {containsAtleastOneFilterEntry, toAssetFilter, toGlobalSearchAssetFilter} from '@shared/helper/filter-helper'; +import {setMultiSorting} from '@shared/helper/table-helper'; +import {NotificationType} from '@shared/model/notification.model'; +import {View} from '@shared/model/view.model'; +import {PartDetailsFacade} from '@shared/modules/part-details/core/partDetails.facade'; +import {StaticIdService} from '@shared/service/staticId.service'; +import {BehaviorSubject, Observable, Subject} from 'rxjs'; @Component({ selector: 'app-supplier-parts', @@ -53,8 +54,8 @@ export class SupplierPartsComponent implements OnInit, OnDestroy { public readonly supplierTabLabelId = this.staticIdService.generateId('OtherParts.supplierTabLabel'); - public tableSupplierAsBuiltSortList: TableHeaderSort[]; - public tableSupplierAsPlannedSortList: TableHeaderSort[]; + public tableSupplierAsBuiltSortList: TableHeaderSort[] = []; + public tableSupplierAsPlannedSortList: TableHeaderSort[] = []; private ctrlKeyState = false; @@ -66,10 +67,14 @@ export class SupplierPartsComponent implements OnInit, OnDestroy { assetAsBuiltFilter: AssetAsBuiltFilter; assetsAsPlannedFilter: AssetAsPlannedFilter; + public currentPartTablePage = {AS_BUILT_SUPPLIER_PAGE: 0, AS_PLANNED_SUPPLIER_PAGE: 0} + constructor( private readonly otherPartsFacade: OtherPartsFacade, private readonly partDetailsFacade: PartDetailsFacade, private readonly staticIdService: StaticIdService, + private readonly router: Router, + private readonly route: ActivatedRoute ) { window.addEventListener('keydown', (event) => { @@ -81,6 +86,8 @@ export class SupplierPartsComponent implements OnInit, OnDestroy { } public ngOnInit(): void { + this.route.queryParams.subscribe(params => this.setupPageByUrlParams(params)); + if (this.bomLifecycle === MainAspectType.AS_BUILT) { this.supplierPartsAsBuilt$ = this.otherPartsFacade.supplierPartsAsBuilt$; this.tableSupplierAsBuiltSortList = []; @@ -95,10 +102,10 @@ export class SupplierPartsComponent implements OnInit, OnDestroy { filterActivated(isAsBuilt: boolean, assetFilter: any): void { if (isAsBuilt) { this.assetAsBuiltFilter = assetFilter; - this.otherPartsFacade.setSupplierPartsAsBuilt(0, 50, [], toAssetFilter(this.assetAsBuiltFilter, true)); + this.otherPartsFacade.setSupplierPartsAsBuilt(this.currentPartTablePage?.['AS_BUILT_SUPPLIER_PAGE'] ?? 0, 50, [], toAssetFilter(this.assetAsBuiltFilter, true)); } else { this.assetsAsPlannedFilter = assetFilter; - this.otherPartsFacade.setSupplierPartsAsPlanned(0, 50, [], toAssetFilter(this.assetsAsPlannedFilter, false)); + this.otherPartsFacade.setSupplierPartsAsPlanned(this.currentPartTablePage?.['AS_PLANNED_SUPPLIER_PAGE'] ?? 0, 50, [], toAssetFilter(this.assetsAsPlannedFilter, false)); } } @@ -118,18 +125,23 @@ export class SupplierPartsComponent implements OnInit, OnDestroy { public onSelectItem(event: Record): void { this.partDetailsFacade.selectedPart = event as unknown as Part; + let tableData = {}; + for(let component of this.partsTableComponents) { + tableData[component.tableType+"_PAGE"] = component.pageIndex; + } + this.router.navigate([`otherParts/${event?.id}`], {queryParams: tableData}) } public onAsBuiltTableConfigChange({ page, pageSize, sorting }: TableEventConfig): void { this.setTableSortingList(sorting, MainAspectType.AS_BUILT); + this.currentPartTablePage['AS_BUILT_SUPPLIER_PAGE'] = page; let pageSizeValue = 50; if (pageSize !== 0) { pageSizeValue = pageSize; } - - if (this.assetAsBuiltFilter) { + if (this.assetAsBuiltFilter && containsAtleastOneFilterEntry(this.assetAsBuiltFilter)) { this.otherPartsFacade.setSupplierPartsAsBuilt(0, pageSizeValue, this.tableSupplierAsBuiltSortList, toAssetFilter(this.assetAsBuiltFilter, true)); } else { this.otherPartsFacade.setSupplierPartsAsBuilt(page, pageSizeValue, this.tableSupplierAsBuiltSortList); @@ -139,13 +151,14 @@ export class SupplierPartsComponent implements OnInit, OnDestroy { public onAsPlannedTableConfigChange({ page, pageSize, sorting }: TableEventConfig): void { this.setTableSortingList(sorting, MainAspectType.AS_PLANNED); + this.currentPartTablePage['AS_PLANNED_SUPPLIER_PAGE'] = page; let pageSizeValue = 50; if (pageSize !== 0) { pageSizeValue = pageSize; } - if (this.assetsAsPlannedFilter) { + if (this.assetsAsPlannedFilter && containsAtleastOneFilterEntry(this.assetsAsPlannedFilter)) { this.otherPartsFacade.setSupplierPartsAsPlanned(0, pageSizeValue, this.tableSupplierAsPlannedSortList, toAssetFilter(this.assetsAsPlannedFilter, true)); } else { this.otherPartsFacade.setSupplierPartsAsPlanned(page, pageSizeValue, this.tableSupplierAsPlannedSortList); @@ -159,6 +172,14 @@ export class SupplierPartsComponent implements OnInit, OnDestroy { TableSortingUtil.setTableSortingList(sorting, tableSortList, this.ctrlKeyState); } + private setupPageByUrlParams(params: Params ) { + if(!params) { + return; + } + this.onAsBuiltTableConfigChange({page: params['AS_BUILT_SUPPLIER_PAGE'], pageSize: 50, sorting: null}); + this.onAsPlannedTableConfigChange({page: params['AS_PLANNED_SUPPLIER_PAGE'], pageSize: 50, sorting: null}); + } + protected readonly MainAspectType = MainAspectType; protected readonly TableType = TableType; protected readonly NotificationType = NotificationType; diff --git a/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.html b/frontend/src/app/modules/page/parts/detail/parts-detail.component.html similarity index 59% rename from frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.html rename to frontend/src/app/modules/page/parts/detail/parts-detail.component.html index 37cdfbc044..0a4219f3ec 100644 --- a/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.html +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.html @@ -18,90 +18,124 @@ SPDX-License-Identifier: Apache-2.0 --> +
+
+ +
+ arrow_back + {{ 'actions.goBack' | i18n }} +
+
+
+
+
+ + +
+
+ +
+
+
+
+
+ +
+
+ + +
- - - - - {{ 'partDetail.tab.header' | i18n }} - - {{ 'partDetail.tab.description' | i18n }} - info_circle - - -
-
- -
-
- - -
- -
- - -
+
+ + +
-
- + -
+ > +
-
- -
+
+ +
-
- - -
+
+ + +
-
+ - + + + {{ 'partDetail.investigation.tab.header' | i18n }} + + {{ 'partDetail.investigation.tab.description' | i18n }} + announcement + + - - - {{ 'partDetail.investigation.tab.header' | i18n }} - - {{ 'partDetail.investigation.tab.description' | i18n }} - announcement - - - - - - + + @@ -117,16 +151,16 @@ {{ 'partDetail.tractionBatteryCodeTitle' | i18n }} - - + + {{ 'partDetail.subcomponents' | i18n }} - - + +
- - @@ -165,14 +197,13 @@ {{ 'partDetail.relations' | i18n }} - - + + open_in_new - @@ -242,7 +273,7 @@ (click)="showQualityTypeDropdown = true" (keydown.enter)="showQualityTypeDropdown = true" class="card-list--value card-list--icon" - tabindex="0" + [tabindex]="0" > edit @@ -275,3 +306,21 @@ + + + + + + + + + diff --git a/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.scss b/frontend/src/app/modules/page/parts/detail/parts-detail.component.scss similarity index 74% rename from frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.scss rename to frontend/src/app/modules/page/parts/detail/parts-detail.component.scss index 30780acee4..53a18cd234 100644 --- a/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.scss +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.scss @@ -19,7 +19,20 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ + +:host { + th { + z-index: 4 !important; + } +} + + +mat-card { + height: 100% !important; +} + .part-detail { + &--spinner { display: flex; justify-content: center; @@ -27,15 +40,15 @@ } &--container { - margin-top: 25px; display: grid; grid-template-columns: repeat(4, 1fr); gap: 25px 25px; + margin-bottom: 25px; } @media(max-width: 1023px) { &--container { - grid-template-columns: repeat(1, 1fr); + grid-template-columns: repeat(1, 1fr); } } @@ -155,6 +168,57 @@ } } + &--row { + display: grid; + grid-template-columns: 35% 65%; + grid-template-rows: minmax(23px, auto); + align-items: center; + + > span { + line-height: 1.25rem; + } + + @media (max-width: 1440px) { + grid-template-columns: 40% 60%; + } + + @media (max-width: 1280px) { + grid-template-columns: 50% 50%; + } + + @media (max-width: 1024px) { + grid-template-columns: 30% 70%; + } + + &--textField { + display: grid; + grid-template-columns: 35% 65%; + grid-template-rows: minmax(23px, auto); + align-items: start; + margin-top: 4px; + margin-bottom: 10px; + } + } + + &--key { + padding-right: 10px; + font-weight: bold; + } + + &--value { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + margin-left: 15px; + + &--textField { + max-height: 100px; + overflow-y: auto; + white-space: wrap; + text-overflow: unset; + } + } + &--qualityType { grid-area: c; display: flex; diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts b/frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts new file mode 100644 index 0000000000..5b13842a69 --- /dev/null +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts @@ -0,0 +1,135 @@ +import {LayoutModule} from '@layout/layout.module'; +import {PartsState} from '@page/parts/core/parts.state'; +import {PartsDetailModule} from '@page/parts/detail/parts-detail.module'; +import {MainAspectType} from '@page/parts/model/mainAspectType.enum'; +import {PartsAssembler} from '@shared/assembler/parts.assembler'; +import {PartDetailsFacade} from '@shared/modules/part-details/core/partDetails.facade'; +import {PartDetailsState} from '@shared/modules/part-details/core/partDetails.state'; +import {screen} from '@testing-library/angular'; +import {renderComponent} from '@tests/test-render.utils'; +import {MOCK_part_1} from '../../../../mocks/services/parts-mock/partsAsBuilt/partsAsBuilt.test.model'; + +import {PartsDetailComponent} from './parts-detail.component'; +import {Owner} from "@page/parts/model/owner.enum"; + +let PartsStateMock: PartsState; +let PartDetailsStateMock: PartDetailsState; + +const part = PartsAssembler.assemblePart(MOCK_part_1, MainAspectType.AS_BUILT); +describe('PartsDetailComponent', () => { + beforeEach(() => { + PartDetailsStateMock = new PartDetailsState(); + PartDetailsStateMock.selectedPart = { data: part }; + + PartsStateMock = new PartsState(); + }); + + const renderPartsDetailComponent = async ({ roles = [] } = {}) => { + return await renderComponent(PartsDetailComponent, { + declarations: [ PartsDetailComponent ], + imports: [ PartsDetailModule, LayoutModule ], + providers: [ + PartDetailsFacade, + { provide: PartsState, useFactory: () => PartsStateMock }, + { provide: PartDetailsState, useFactory: () => PartDetailsStateMock }, + ], + roles, + }); + }; + + it('should render part details', async () => { + const {fixture} = await renderPartsDetailComponent(); + const {componentInstance} = fixture; + const spy = spyOn(componentInstance.partDetailsFacade, 'setPartById') + const nameElement = await screen.findByText('BMW AG'); + const productionDateElement = await screen.findByText('2022-02-04T13:48:54'); + + expect(nameElement).toBeInTheDocument(); + expect(productionDateElement).toBeInTheDocument(); + }); + + it('should render child-component table', async () => { + const {fixture} = await renderPartsDetailComponent({roles: ['user']}); + const {componentInstance} = fixture; + + const childTableHeadline = await screen.findByText('partDetail.investigation.headline'); + expect(childTableHeadline).toBeInTheDocument(); + expect(await screen.findByText('partDetail.investigation.noSelection.header')).toBeInTheDocument(); + }); + + + it('should set selected part on null if click on relation page', async () => { + const {fixture} = await renderPartsDetailComponent({roles: ['user']}); + const {componentInstance} = fixture; + + componentInstance.openRelationPage(part); + expect(componentInstance.partDetailsFacade.selectedPart).toEqual(null); + }); + + it('should correctly set restriction keys for actions as user', async () => { + let {fixture} = await renderPartsDetailComponent({roles: ['user']}); + let {componentInstance} = fixture; + + // subcomponent investigation success + componentInstance.isAsPlannedPart = false; + componentInstance.hasChildren = true; + componentInstance.partOwner = Owner.OWN + + expect(componentInstance.setRestrictionMessageKeyForInvestigationOnSubcomponents()).toEqual("routing.startInvestigation"); + + // incident creation success + componentInstance.isPersistentPart = true; + expect(componentInstance.setRestrictionMessageKeyForIncidentCreation()).toEqual("routing.createIncident") + + // incident creation - customer part + componentInstance.partOwner = Owner.CUSTOMER; + expect(componentInstance.setRestrictionMessageKeyForIncidentCreation()).toEqual("routing.notAuthorizedOwner"); + + // publish assets - not admin + expect(componentInstance.setRestrictionMessageKeyForPublishAssets()).toEqual("routing.unauthorized"); + + + }); + + it('should correctly set restriction keys for actions as admin', async () => { + let {fixture} = await renderPartsDetailComponent({roles: ['admin']}); + let {componentInstance} = fixture; + + // publish assets success + componentInstance.partOwner = Owner.OWN; + expect(componentInstance.setRestrictionMessageKeyForPublishAssets()).toEqual("routing.publishAssets") + + // publish assets - not own Part + componentInstance.partOwner = Owner.CUSTOMER; + expect(componentInstance.setRestrictionMessageKeyForPublishAssets()).toEqual("routing.onlyAllowedForOwnParts"); + + componentInstance.partOwner = Owner.OWN + // sucomponent investigation - not as built + componentInstance.isAsPlannedPart = true; + expect(componentInstance.setRestrictionMessageKeyForInvestigationOnSubcomponents()).toEqual("routing.notAllowedForAsPlanned") + + // subcomponent investigation - no child parts + componentInstance.isAsPlannedPart = false; + componentInstance.hasChildren = false; + expect(componentInstance.setRestrictionMessageKeyForInvestigationOnSubcomponents()).toEqual("routing.noChildPartsForInvestigation"); + + // subcomponent investigation - not user role + componentInstance.hasChildren = true; + expect(componentInstance.setRestrictionMessageKeyForInvestigationOnSubcomponents()).toEqual("routing.unauthorized"); + + // incident creation - not as built + componentInstance.isAsPlannedPart = true; + expect(componentInstance.setRestrictionMessageKeyForIncidentCreation()).toEqual("routing.notAllowedForAsPlanned"); + + // incident creation - not persistent part + componentInstance.isAsPlannedPart = false; + componentInstance.isPersistentPart = false; + expect(componentInstance.setRestrictionMessageKeyForIncidentCreation()).toEqual("routing.notAllowedForNonPersistentPart"); + + // incident creation - not user role + componentInstance.isPersistentPart = true; + expect(componentInstance.setRestrictionMessageKeyForIncidentCreation()).toEqual("routing.unauthorized") + + }); + +}); diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts b/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts new file mode 100644 index 0000000000..d077f4b92d --- /dev/null +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts @@ -0,0 +1,221 @@ +import {Location} from '@angular/common'; +import {Component, Input} from '@angular/core'; +import {FormControl} from '@angular/forms'; +import {ActivatedRoute, Router} from '@angular/router'; +import {RoleService} from '@core/user/role.service'; +import {TractionBatteryCode} from '@page/parts/model/aspectModels.model'; +import {Owner} from '@page/parts/model/owner.enum'; + +import {ImportState, Part, QualityType} from '@page/parts/model/parts.model'; +import {PartsAssembler} from '@shared/assembler/parts.assembler'; +import {SelectOption} from '@shared/components/select/select.component'; +import {NotificationType} from '@shared/model/notification.model'; +import {State} from '@shared/model/state'; +import {View} from '@shared/model/view.model'; +import {NotificationAction} from '@shared/modules/notification/notification-action.enum'; + +import {PartDetailsFacade} from '@shared/modules/part-details/core/partDetails.facade'; +import {BehaviorSubject, Observable, Subject, Subscription} from 'rxjs'; +import {filter, tap} from 'rxjs/operators'; + +@Component({ + selector: 'app-parts-detail', + templateUrl: './parts-detail.component.html', + styleUrls: ['./parts-detail.component.scss'] +}) +export class PartsDetailComponent { + @Input() showRelation = true; + @Input() showStartInvestigationOnChildParts = true; + + public shortenPartDetails$: Observable>; + public readonly selectedPartDetails$: Observable>; + public readonly manufacturerDetails$: Observable>; + public readonly customerOrPartSiteDetails$: Observable>; + public readonly tractionBatteryDetails$: Observable>; + public readonly importStateDetails$: Observable>; + public readonly tractionBatterySubcomponents$: Observable>; + + public readonly displayedColumns: string[]; + + public isAsPlannedPart: boolean = false; + public isPersistentPart: boolean = false; + public partOwner: Owner | undefined = Owner.UNKNOWN; + public hasChildren: boolean = false; + + public investigationOnSubcomponentsTooltipMessage: string; + public incidentCreationTooltipMessage: string; + public publishAssetsTooltipMessage: string; + + + public customerOrPartSiteDetailsHeader$: Subscription; + public customerOrPartSiteHeader: string; + + public showQualityTypeDropdown = false; + public qualityTypeOptions: SelectOption[]; + + public qualityTypeControl = new FormControl(null); + + public readonly isPublisherOpen$ = new Subject(); + public readonly isNotificationRequestOpen = new BehaviorSubject(false); + private readonly isStartInvestigationOpen: State = new State(false); + + + public currentPartId: string; + public pageIndexHistory: {AS_BUILT_PAGE: string, AS_PLANNED_PAGE: string} + + constructor(public readonly partDetailsFacade: PartDetailsFacade, private readonly router: Router, private readonly route: ActivatedRoute, public roleService: RoleService, private location: Location) { + + this.currentPartId = this.route.snapshot.params['partId']; + this.partDetailsFacade.setPartById(this.currentPartId); + this.selectedPartDetails$ = this.partDetailsFacade.selectedPart$; + this.shortenPartDetails$ = this.partDetailsFacade.selectedPart$; + + this.shortenPartDetails$ = this.partDetailsFacade.selectedPart$.pipe( + PartsAssembler.mapPartForView(), + tap(({ data }) => { + this.qualityTypeControl.patchValue(data?.qualityType, { emitEvent: false, onlySelf: true }) + }), + ); + + this.manufacturerDetails$ = this.partDetailsFacade.selectedPart$.pipe(PartsAssembler.mapPartForManufacturerView()); + this.customerOrPartSiteDetails$ = this.partDetailsFacade.selectedPart$.pipe(PartsAssembler.mapPartForCustomerOrPartSiteView()); + this.tractionBatteryDetails$ = this.partDetailsFacade.selectedPart$.pipe(PartsAssembler.mapPartForTractionBatteryCodeDetailsView()); + this.tractionBatterySubcomponents$ = this.partDetailsFacade.selectedPart$.pipe(PartsAssembler.mapPartForTractionBatteryCodeSubComponentsView()) as unknown as Observable>; + + this.importStateDetails$ = this.partDetailsFacade.selectedPart$.pipe(PartsAssembler.mapPartForAssetStateDetailsView()); + + this.customerOrPartSiteDetailsHeader$ = this.customerOrPartSiteDetails$?.subscribe(data => { + if (data?.data?.functionValidFrom) { + this.customerOrPartSiteHeader = 'partDetail.partSiteInformationData'; + } else { + this.customerOrPartSiteHeader = 'partDetail.customerData'; + } + }); + + this.qualityTypeOptions = Object.values(QualityType).map(value => ({ + label: value, + value: value, + })); + + this.selectedPartDetails$.subscribe(part => { + const loweredSemanticDataModel = part?.data?.semanticDataModel?.toString()?.toLowerCase(); + if(part?.data?.semanticDataModel) { + this.isAsPlannedPart = loweredSemanticDataModel === 'partasplanned' || loweredSemanticDataModel === 'tombstoneasplanned'|| loweredSemanticDataModel === 'tombstoneasbuilt' || loweredSemanticDataModel === 'unknown'; + } + + if(part?.data?.importState === ImportState.PERSISTENT) { + this.isPersistentPart = true; + } + + this.partOwner = part?.data?.owner; + + if(part?.data?.children?.length > 0 ) { + this.hasChildren = true; + } + + this.incidentCreationTooltipMessage = this.setRestrictionMessageKeyForIncidentCreation(); + this.publishAssetsTooltipMessage = this.setRestrictionMessageKeyForPublishAssets(); + this.investigationOnSubcomponentsTooltipMessage = this.setRestrictionMessageKeyForInvestigationOnSubcomponents() + + + }); + + this.displayedColumns = [ 'position', 'productType', 'tractionBatteryCode' ]; + } + + public ngOnInit(): void { + this.route.queryParams.subscribe((params: {AS_BUILT_PAGE: string, AS_PLANNED_PAGE: string}) => { + this.pageIndexHistory = params; + }) + } + + public ngOnDestroy(): void { + this.partDetailsFacade.selectedPart = null; + } + + public ngAfterViewInit(): void { + this.partDetailsFacade.selectedPart$.pipe(filter(({ data }) => !!data)).subscribe(_ => this.setIsOpen(true)); + } + + public setIsOpen(openState: boolean) { + this.isStartInvestigationOpen.update(openState); + + if (!openState) { + this.partDetailsFacade.selectedPart = null; + } + } + + public openRelationPage(part: Part): void { + this.partDetailsFacade.selectedPart = null; + this.router.navigate([ `parts/relations/${ part.id }` ]).then(_ => window.location.reload()); + } + // valid investigation for subcomponent: + // - is supplier part + // - is as built part + // - has child parts + // - role is not admin + setRestrictionMessageKeyForInvestigationOnSubcomponents(): string { + if(this.isAsPlannedPart) { + return 'routing.notAllowedForAsPlanned'; + } + else if(!this.hasChildren) { + return 'routing.noChildPartsForInvestigation'; + } + else if(this.roleService.isAdmin()) { + return 'routing.unauthorized'; + } else { + return 'routing.startInvestigation'; + } + + } + // valid create quality incident for part (alert/investigation will be handled in create incident view): + // - is not as planned part + // - part owner is own + // - is persistent + // - is not admin role + setRestrictionMessageKeyForIncidentCreation(): string { + if(this.isAsPlannedPart) { + return 'routing.notAllowedForAsPlanned'; + } + else if(!this.isPersistentPart) { + return 'routing.notAllowedForNonPersistentPart'; + } + else if(this.partOwner === Owner.CUSTOMER || this.partOwner === Owner.UNKNOWN) { + return "routing.notAuthorizedOwner"; + } + else if(this.roleService.isAdmin()) { + return 'routing.unauthorized'; + } else { + return 'routing.createIncident'; + } + + } + + // valid publish assets on asset (allowance of state is handled in publish assets view) + // - part owner is own + // - is admin role + setRestrictionMessageKeyForPublishAssets() { + if(!this.roleService.isAdmin()) { + return 'routing.unauthorized'; + } + if(this.partOwner !== Owner.OWN) { + return 'routing.onlyAllowedForOwnParts'; + } else { + return 'routing.publishAssets' + } + + } + + + protected readonly NotificationAction = NotificationAction; + protected readonly Owner = Owner; + + navigateToParentPath() { + const parentPath = this.router.routerState.snapshot.url.split('/')[1]; //otherParts + const navigationExtras = this.pageIndexHistory ? {queryParams: this.pageIndexHistory} : null + this.router.navigate([parentPath], navigationExtras); + } + + + protected readonly NotificationType = NotificationType; +} diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.module.ts b/frontend/src/app/modules/page/parts/detail/parts-detail.module.ts new file mode 100644 index 0000000000..1d25e14035 --- /dev/null +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.module.ts @@ -0,0 +1,27 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { getI18nPageProvider } from '@core/i18n'; +import { PartsDetailComponent } from '@page/parts/detail/parts-detail.component'; +import { PartDetailsFacade } from '@shared/modules/part-details/core/partDetails.facade'; +import { PartDetailsState } from '@shared/modules/part-details/core/partDetails.state'; +import { StartInvestigationComponent } from '@shared/modules/part-details/presentation/start-investigation/start-investigation.component'; +import { LoadedElementsFacade } from '@shared/modules/relations/core/loaded-elements.facade'; +import { LoadedElementsState } from '@shared/modules/relations/core/loaded-elements.state'; +import { RelationsModule } from '@shared/modules/relations/relations.module'; +import { SharedModule } from '@shared/shared.module'; +import { TemplateModule } from '@shared/template.module'; + +@NgModule({ + declarations: [ PartsDetailComponent, StartInvestigationComponent ], + imports: [ CommonModule, TemplateModule, SharedModule, RelationsModule], + providers: [ + PartDetailsState, + PartDetailsFacade, + LoadedElementsFacade, + LoadedElementsState, + ...getI18nPageProvider([ 'page.parts', 'partDetail' ]), + ], + exports: [ PartsDetailComponent ], +}) +export class PartsDetailModule { +} diff --git a/frontend/src/app/modules/page/parts/parts.module.ts b/frontend/src/app/modules/page/parts/parts.module.ts index a25936aa7b..d743a3ad86 100644 --- a/frontend/src/app/modules/page/parts/parts.module.ts +++ b/frontend/src/app/modules/page/parts/parts.module.ts @@ -25,7 +25,7 @@ import { MatDialogModule } from '@angular/material/dialog'; import { getI18nPageProvider } from '@core/i18n'; import { PartsFacade } from '@page/parts/core/parts.facade'; import { PartsState } from '@page/parts/core/parts.state'; -import { PartDetailsModule } from '@shared/modules/part-details/partDetails.module'; +import { PartsDetailModule } from '@page/parts/detail/parts-detail.module'; import { RelationsModule } from '@shared/modules/relations/relations.module'; import { FormatPartSemanticDataModelToCamelCasePipe } from '@shared/pipes/format-part-semantic-data-model-to-camelcase.pipe'; import { BomLifecycleSettingsService } from '@shared/service/bom-lifecycle-settings.service'; @@ -38,7 +38,7 @@ import { RelationComponent } from './presentation/relation/relation.component'; @NgModule({ declarations: [ PartsComponent, RelationComponent ], - imports: [ CommonModule, TemplateModule, SharedModule, PartsRoutingModule, RelationsModule, PartDetailsModule, AngularSplitModule, MatDialogModule ], + imports: [ CommonModule, TemplateModule, SharedModule, PartsRoutingModule, RelationsModule, AngularSplitModule, MatDialogModule, PartsDetailModule ], providers: [ PartsState, BomLifecycleSettingsService, PartsFacade, FormatPartSemanticDataModelToCamelCasePipe, ...getI18nPageProvider([ 'page.parts', 'partDetail' ]) ], }) export class PartsModule { diff --git a/frontend/src/app/modules/page/parts/parts.routing.ts b/frontend/src/app/modules/page/parts/parts.routing.ts index 2e52d74a0a..1e859e61ea 100644 --- a/frontend/src/app/modules/page/parts/parts.routing.ts +++ b/frontend/src/app/modules/page/parts/parts.routing.ts @@ -19,11 +19,12 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; -import { RelationComponent } from '@page/parts/presentation/relation/relation.component'; -import { I18NEXT_NAMESPACE_RESOLVER } from 'angular-i18next'; -import { PartsComponent } from './presentation/parts.component'; +import {NgModule} from '@angular/core'; +import {RouterModule, Routes} from '@angular/router'; +import {PartsDetailComponent} from '@page/parts/detail/parts-detail.component'; +import {RelationComponent} from '@page/parts/presentation/relation/relation.component'; +import {I18NEXT_NAMESPACE_RESOLVER} from 'angular-i18next'; +import {PartsComponent} from './presentation/parts.component'; export /** @type {*} */ const PARTS_ROUTING: Routes = [ @@ -48,6 +49,13 @@ const PARTS_ROUTING: Routes = [ data: { i18nextNamespaces: [ 'page.parts', 'partDetail' ] }, resolve: { i18next: I18NEXT_NAMESPACE_RESOLVER }, }, + { + path: ':partId', + pathMatch: 'full', + component: PartsDetailComponent, + data: { i18nextNamespaces: [ 'page.parts', 'partDetail' ] }, + resolve: { i18next: I18NEXT_NAMESPACE_RESOLVER }, + }, ]; @NgModule({ 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 5621bb1ac1..657e7da9ce 100644 --- a/frontend/src/app/modules/page/parts/presentation/parts.component.html +++ b/frontend/src/app/modules/page/parts/presentation/parts.component.html @@ -126,8 +126,6 @@ - - (); + public currentPartTablePage = {AS_BUILT_OWN_PAGE: 0, AS_PLANNED_OWN_PAGE: 0} @ViewChildren(PartsTableComponent) partsTableComponents: QueryList; @@ -82,7 +84,9 @@ export class PartsComponent implements OnInit, OnDestroy, AfterViewInit { private readonly staticIdService: StaticIdService, private readonly userSettingService: BomLifecycleSettingsService, public toastService: ToastService, - public roleService: RoleService + public roleService: RoleService, + public router: Router, + public route: ActivatedRoute ) { this.partsAsBuilt$ = this.partsFacade.partsAsBuilt$; this.partsAsPlanned$ = this.partsFacade.partsAsPlanned$; @@ -90,10 +94,10 @@ export class PartsComponent implements OnInit, OnDestroy, AfterViewInit { this.tableAsPlannedSortList = []; window.addEventListener('keydown', (event) => { - this.ctrlKeyState = setMultiSorting(event); + this.ctrlKeyState = setMultiSorting(event); }); window.addEventListener('keyup', (event) => { - this.ctrlKeyState = setMultiSorting(event); + this.ctrlKeyState = setMultiSorting(event); }); } @@ -104,21 +108,23 @@ export class PartsComponent implements OnInit, OnDestroy, AfterViewInit { assetAsBuiltFilter: AssetAsBuiltFilter; assetsAsPlannedFilter: AssetAsPlannedFilter; + public ngOnInit(): void { this.partsFacade.setPartsAsBuilt(); this.partsFacade.setPartsAsPlanned(); this.searchFormGroup.addControl('partSearch', new FormControl([])); this.searchControl = this.searchFormGroup.get('partSearch') as unknown as FormControl; + this.route.queryParams.subscribe(params => this.setupPageByUrlParams(params)); } filterActivated(isAsBuilt: boolean, assetFilter: any): void { if (isAsBuilt) { this.assetAsBuiltFilter = assetFilter; - this.partsFacade.setPartsAsBuilt(0, this.DEFAULT_PAGE_SIZE, this.tableAsBuiltSortList, toAssetFilter(this.assetAsBuiltFilter, true)); + this.partsFacade.setPartsAsBuilt(this.currentPartTablePage['AS_BUILT_OWN_PAGE'] ?? 0, this.DEFAULT_PAGE_SIZE, this.tableAsBuiltSortList, toAssetFilter(this.assetAsBuiltFilter, true)); } else { this.assetsAsPlannedFilter = assetFilter; - this.partsFacade.setPartsAsPlanned(0, this.DEFAULT_PAGE_SIZE, this.tableAsPlannedSortList, toAssetFilter(this.assetsAsPlannedFilter, false)); + this.partsFacade.setPartsAsPlanned(this.currentPartTablePage['AS_PLANNED_OWN_PAGE'] ?? 0, this.DEFAULT_PAGE_SIZE, this.tableAsPlannedSortList, toAssetFilter(this.assetsAsPlannedFilter, false)); } } @@ -138,14 +144,14 @@ export class PartsComponent implements OnInit, OnDestroy, AfterViewInit { } refreshPartsOnPublish(message: string) { - if(message) { - this.toastService.error(message); - } else { - this.toastService.success("requestPublishAssets.success") - this.partsFacade.setPartsAsBuilt(); - this.partsFacade.setPartsAsPlanned(); - this.partsTableComponents.map(component => component.clearAllRows()) - } + if (message) { + this.toastService.error(message); + } else { + this.toastService.success("requestPublishAssets.success") + this.partsFacade.setPartsAsBuilt(); + this.partsFacade.setPartsAsPlanned(); + this.partsTableComponents.map(component => component.clearAllRows()) + } } private resetFilterAndShowToast() { @@ -165,17 +171,21 @@ export class PartsComponent implements OnInit, OnDestroy, AfterViewInit { public onSelectItem($event: Record): void { this.partDetailsFacade.selectedPart = $event as unknown as Part; + let tableData = {}; + for (let component of this.partsTableComponents) { + tableData[component.tableType + "_PAGE"] = component.pageIndex; + } + this.router.navigate([`parts/${$event?.id}`], {queryParams: tableData}) } public onAsBuiltTableConfigChange({page, pageSize, sorting}: TableEventConfig): void { this.setTableSortingList(sorting, MainAspectType.AS_BUILT); - + this.currentPartTablePage['AS_BUILT_OWN_PAGE'] = page; let pageSizeValue = this.DEFAULT_PAGE_SIZE; if (pageSize !== 0) { pageSizeValue = pageSize; } - - if (this.assetAsBuiltFilter) { + if (this.assetAsBuiltFilter && containsAtleastOneFilterEntry(this.assetAsBuiltFilter)) { this.partsFacade.setPartsAsBuilt(0, pageSizeValue, this.tableAsBuiltSortList, toAssetFilter(this.assetAsBuiltFilter, true)); } else { this.partsFacade.setPartsAsBuilt(page, pageSizeValue, this.tableAsBuiltSortList); @@ -184,14 +194,15 @@ export class PartsComponent implements OnInit, OnDestroy, AfterViewInit { } public onAsPlannedTableConfigChange({page, pageSize, sorting}: TableEventConfig): void { - this.setTableSortingList(sorting, MainAspectType.AS_PLANNED); + this.setTableSortingList(sorting, MainAspectType.AS_PLANNED); + this.currentPartTablePage['AS_PLANNED_OWN_PAGE'] = page; let pageSizeValue = this.DEFAULT_PAGE_SIZE; if (pageSize !== 0) { pageSizeValue = pageSize; } - if (this.assetsAsPlannedFilter) { + if (this.assetsAsPlannedFilter && containsAtleastOneFilterEntry(this.assetsAsPlannedFilter)) { this.partsFacade.setPartsAsPlanned(0, pageSizeValue, this.tableAsPlannedSortList, toAssetFilter(this.assetsAsPlannedFilter, true)); } else { this.partsFacade.setPartsAsPlanned(page, pageSizeValue, this.tableAsPlannedSortList); @@ -249,6 +260,14 @@ export class PartsComponent implements OnInit, OnDestroy, AfterViewInit { } } + private setupPageByUrlParams(params: Params) { + if (!params) { + return; + } + this.onAsBuiltTableConfigChange({page: params['AS_BUILT_OWN_PAGE'], pageSize: 50, sorting: null}); + this.onAsPlannedTableConfigChange({page: params['AS_PLANNED_OWN_PAGE'], pageSize: 50, sorting: null}); + } + protected readonly UserSettingView = UserSettingView; protected readonly TableType = TableType; protected readonly MainAspectType = MainAspectType; diff --git a/frontend/src/app/modules/page/parts/presentation/relation/relation.component.html b/frontend/src/app/modules/page/parts/presentation/relation/relation.component.html index 26f41dc462..b97455bf8c 100644 --- a/frontend/src/app/modules/page/parts/presentation/relation/relation.component.html +++ b/frontend/src/app/modules/page/parts/presentation/relation/relation.component.html @@ -20,4 +20,3 @@ --> - diff --git a/frontend/src/app/modules/shared/components/asset-publisher/asset-publisher.component.html b/frontend/src/app/modules/shared/components/asset-publisher/asset-publisher.component.html index 0e395920e7..367a315ef0 100644 --- a/frontend/src/app/modules/shared/components/asset-publisher/asset-publisher.component.html +++ b/frontend/src/app/modules/shared/components/asset-publisher/asset-publisher.component.html @@ -5,8 +5,8 @@

{{'publisher.selectedAssets' | i18n}}:

- {{asset.nameAtManufacturer}} - {{asset.id}} + {{asset?.nameAtManufacturer}} + {{asset?.id}} @@ -17,8 +17,8 @@

{{'publisher.policyToApply' | i18n}}:

{{'publisher.selectPolicyLabel' | i18n}} - - {{policy.policyId}} + + {{policy?.policyId}} {{'publisher.selectPolicyError' | i18n}} diff --git a/frontend/src/app/modules/shared/components/asset-publisher/asset-publisher.component.ts b/frontend/src/app/modules/shared/components/asset-publisher/asset-publisher.component.ts index d40443d15e..b5ac0d0ee1 100644 --- a/frontend/src/app/modules/shared/components/asset-publisher/asset-publisher.component.ts +++ b/frontend/src/app/modules/shared/components/asset-publisher/asset-publisher.component.ts @@ -61,6 +61,6 @@ export class AssetPublisherComponent { } checkForIllegalAssetStateToPublish(): boolean { - return this.selectedAssets.some(part => (part.importState !== ImportState.TRANSIENT && part.importState !== ImportState.ERROR)) + return this.selectedAssets.some(part => (part?.importState !== ImportState.TRANSIENT && part?.importState !== ImportState.ERROR)) } } diff --git a/frontend/src/app/modules/shared/components/request-notification/notification-request.component.html b/frontend/src/app/modules/shared/components/request-notification/notification-request.component.html index a064c2e38f..975c3aa3e7 100644 --- a/frontend/src/app/modules/shared/components/request-notification/notification-request.component.html +++ b/frontend/src/app/modules/shared/components/request-notification/notification-request.component.html @@ -26,13 +26,13 @@

{{ this.context + '.headline' - close - - {{ part.nameAtManufacturer || part.id }} - @@ -42,13 +42,13 @@

{{ this.context + '.headline'

{{ 'requestNotification.restoreItem' | i18n }}

restore - {{ removedItemsHistory[0].nameAtManufacturer || removedItemsHistory[0].id }} + {{ removedItemsHistory[0]?.nameAtManufacturer || removedItemsHistory[0]?.id }} diff --git a/frontend/src/app/modules/shared/helper/filter-helper.ts b/frontend/src/app/modules/shared/helper/filter-helper.ts index 3a4b4e8a62..7b3e7ca926 100644 --- a/frontend/src/app/modules/shared/helper/filter-helper.ts +++ b/frontend/src/app/modules/shared/helper/filter-helper.ts @@ -16,14 +16,14 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -import {HttpParams} from '@angular/common/http'; +import { HttpParams } from '@angular/common/http'; import { AssetAsBuiltFilter, AssetAsPlannedFilter, FilterOperator, getFilterOperatorValue, } from '@page/parts/model/parts.model'; -import {NotificationFilter} from '../../../mocks/services/investigations-mock/investigations.model'; +import { NotificationFilter } from '../../../mocks/services/investigations-mock/investigations.model'; export const DATE_FILTER_KEYS = [ 'manufacturingDate', 'functionValidFrom', 'functionValidUntil', 'validityPeriodFrom', 'validityPeriodTo', 'createdDate', 'targetDate', 'creationDate', 'endDate' ]; @@ -200,3 +200,9 @@ export function provideFilterListForNotifications( filter?: NotificationFilter, return filterList; } + +export function containsAtleastOneFilterEntry(filter: AssetAsBuiltFilter | AssetAsPlannedFilter): boolean { + return Object.keys(filter) + .filter(key => filter[key].length) + .length > 0 +} diff --git a/frontend/src/app/modules/shared/index.ts b/frontend/src/app/modules/shared/index.ts index b4720af52c..693a3cb9d6 100644 --- a/frontend/src/app/modules/shared/index.ts +++ b/frontend/src/app/modules/shared/index.ts @@ -22,7 +22,6 @@ export { NotificationModule } from './modules/notification/notification.module'; export { PartDetailsFacade } from './modules/part-details/core/partDetails.facade'; export { PartDetailsState } from './modules/part-details/core/partDetails.state'; -export { PartDetailsModule } from './modules/part-details/partDetails.module'; export { RelationComponentState } from './modules/relations/core/component.state'; export { LoadedElementsFacade } from './modules/relations/core/loaded-elements.facade'; export { LoadedElementsState } from './modules/relations/core/loaded-elements.state'; diff --git a/frontend/src/app/modules/shared/modules/part-details/core/partDetails.facade.ts b/frontend/src/app/modules/shared/modules/part-details/core/partDetails.facade.ts index 54ac31bfe9..50587d9690 100644 --- a/frontend/src/app/modules/shared/modules/part-details/core/partDetails.facade.ts +++ b/frontend/src/app/modules/shared/modules/part-details/core/partDetails.facade.ts @@ -36,6 +36,15 @@ export class PartDetailsFacade { ) { } + public setPartById(urn: string) { + if(!urn || typeof urn !== 'string') { + return; + } + this.partsService.getPart(urn).subscribe(part => { + this.partDetailsState.selectedPart = { data: part} + }) + } + public get selectedPart$(): Observable> { return this.partDetailsState.selectedPart$; } diff --git a/frontend/src/app/modules/shared/modules/part-details/partDetails.module.ts b/frontend/src/app/modules/shared/modules/part-details/partDetails.module.ts deleted file mode 100644 index 03e882342f..0000000000 --- a/frontend/src/app/modules/shared/modules/part-details/partDetails.module.ts +++ /dev/null @@ -1,48 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2022, 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * Copyright (c) 2022, 2023 ZF Friedrichshafen AG - * Copyright (c) 2022, 2023 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 - ********************************************************************************/ - -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { getI18nPageProvider } from '@core/i18n'; -import { SharedModule } from '@shared/shared.module'; -import { TemplateModule } from '@shared/template.module'; -import { LoadedElementsFacade } from '../relations/core/loaded-elements.facade'; -import { LoadedElementsState } from '../relations/core/loaded-elements.state'; -import { RelationsModule } from '../relations/relations.module'; -import { PartDetailsFacade } from './core/partDetails.facade'; -import { PartDetailsState } from './core/partDetails.state'; -import { PartDetailComponent } from './presentation/part-detail.component'; -import { StartInvestigationComponent } from './presentation/start-investigation/start-investigation.component'; - -@NgModule({ - declarations: [ PartDetailComponent, StartInvestigationComponent ], - imports: [ CommonModule, TemplateModule, SharedModule, RelationsModule ], - providers: [ - PartDetailsState, - PartDetailsFacade, - LoadedElementsFacade, - LoadedElementsState, - ...getI18nPageProvider([ 'page.parts', 'partDetail' ]), - ], - exports: [ PartDetailComponent ], -}) -export class PartDetailsModule { -} diff --git a/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.spec.ts b/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.spec.ts deleted file mode 100644 index 63fea82a57..0000000000 --- a/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.spec.ts +++ /dev/null @@ -1,91 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2022, 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * Copyright (c) 2022, 2023 ZF Friedrichshafen AG - * Copyright (c) 2022, 2023 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 - ********************************************************************************/ - -import { LayoutModule } from '@layout/layout.module'; -import { SidenavComponent } from '@layout/sidenav/sidenav.component'; -import { SidenavService } from '@layout/sidenav/sidenav.service'; -import { PartsState } from '@page/parts/core/parts.state'; -import { MainAspectType } from '@page/parts/model/mainAspectType.enum'; -import { PartsAssembler } from '@shared/assembler/parts.assembler'; -import { PartDetailsFacade } from '@shared/modules/part-details/core/partDetails.facade'; -import { PartDetailsState } from '@shared/modules/part-details/core/partDetails.state'; -import { PartDetailsModule } from '@shared/modules/part-details/partDetails.module'; -import { screen, waitFor } from '@testing-library/angular'; -import { renderComponent } from '@tests/test-render.utils'; -import { MOCK_part_1 } from '../../../../../mocks/services/parts-mock/partsAsBuilt/partsAsBuilt.test.model'; -import { PartDetailComponent } from './part-detail.component'; - -let PartsStateMock: PartsState; -let PartDetailsStateMock: PartDetailsState; - -const part = PartsAssembler.assemblePart(MOCK_part_1, MainAspectType.AS_BUILT); - -describe('PartDetailComponent', () => { - beforeEach(() => { - PartDetailsStateMock = new PartDetailsState(); - PartDetailsStateMock.selectedPart = { data: part }; - - PartsStateMock = new PartsState(); - }); - - const renderPartDetailComponent = async ({ roles = [] } = {}) => { - return await renderComponent(``, { - declarations: [ SidenavComponent, PartDetailComponent ], - imports: [ PartDetailsModule, LayoutModule ], - providers: [ - PartDetailsFacade, - { provide: PartsState, useFactory: () => PartsStateMock }, - { provide: PartDetailsState, useFactory: () => PartDetailsStateMock }, - SidenavService, - ], - roles, - }); - }; - - it('should render side nav', async () => { - await renderPartDetailComponent(); - - const sideNavElement = await waitFor(() => screen.getByTestId('sidenav--test-id')); - expect(sideNavElement).toBeInTheDocument(); - }); - - it('should render an open sidenav with part details', async () => { - await renderPartDetailComponent(); - - const sideNavElement = await waitFor(() => screen.getByTestId('sidenav--test-id')); - const nameElement = await screen.findByText('BMW AG'); - const productionDateElement = await screen.findByText('2022-02-04T13:48:54'); - - expect(sideNavElement).toBeInTheDocument(); - await waitFor(() => expect(sideNavElement).toHaveClass('sidenav--container__open')); - - expect(nameElement).toBeInTheDocument(); - expect(productionDateElement).toBeInTheDocument(); - }); - - it('should render child-component table', async () => { - await renderPartDetailComponent({ roles: [ 'user' ] }); - - const childTableHeadline = await screen.findByText('partDetail.investigation.headline'); - expect(childTableHeadline).toBeInTheDocument(); - expect(await screen.findByText('partDetail.investigation.noSelection.header')).toBeInTheDocument(); - }); -}); diff --git a/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.ts b/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.ts deleted file mode 100644 index 6d790b4db4..0000000000 --- a/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.ts +++ /dev/null @@ -1,158 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2022, 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * Copyright (c) 2022, 2023 ZF Friedrichshafen AG - * Copyright (c) 2022, 2023 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 - ********************************************************************************/ - -import { AfterViewInit, Component, Input, OnDestroy } from '@angular/core'; -import { FormControl } from '@angular/forms'; -import { Router } from '@angular/router'; -import { RoleService } from '@core/user/role.service'; -import { TractionBatteryCode } from '@page/parts/model/aspectModels.model'; -import { Owner } from '@page/parts/model/owner.enum'; -import { Part, QualityType } from '@page/parts/model/parts.model'; -import { PartsAssembler } from '@shared/assembler/parts.assembler'; -import { SelectOption } from '@shared/components/select/select.component'; -import { State } from '@shared/model/state'; -import { View } from '@shared/model/view.model'; -import { NotificationAction } from '@shared/modules/notification/notification-action.enum'; -import { PartDetailsFacade } from '@shared/modules/part-details/core/partDetails.facade'; -import { Observable, Subscription } from 'rxjs'; -import { filter, tap } from 'rxjs/operators'; - -@Component({ - selector: 'app-part-detail', - templateUrl: './part-detail.component.html', - styleUrls: [ '../../../components/card-list/card-list.component.scss', './part-detail.component.scss' ], -}) -export class PartDetailComponent implements AfterViewInit, OnDestroy { - @Input() showRelation = true; - @Input() showStartInvestigation = true; - - public readonly shortenPartDetails$: Observable>; - public readonly selectedPartDetails$: Observable>; - public readonly manufacturerDetails$: Observable>; - public readonly customerOrPartSiteDetails$: Observable>; - public readonly tractionBatteryDetails$: Observable>; - public readonly importStateDetails$: Observable>; - public readonly tractionBatterySubcomponents$: Observable>; - - public readonly displayedColumns: string[]; - - public isAsPlannedPart: boolean = false; - - public customerOrPartSiteDetailsHeader$: Subscription; - public customerOrPartSiteHeader: string; - - public showQualityTypeDropdown = false; - public qualityTypeOptions: SelectOption[]; - - public qualityTypeControl = new FormControl(null); - public readonly isOpen$: Observable; - - private readonly isOpenState: State = new State(false); - - public authorizationTooltipMessage: string; - - constructor(private readonly partDetailsFacade: PartDetailsFacade, private readonly router: Router, public roleService: RoleService) { - this.isOpen$ = this.isOpenState.observable; - this.selectedPartDetails$ = this.partDetailsFacade.selectedPart$; - this.shortenPartDetails$ = this.partDetailsFacade.selectedPart$.pipe( - PartsAssembler.mapPartForView(), - tap(({ data }) => { - this.qualityTypeControl.patchValue(data.qualityType, { emitEvent: false, onlySelf: true }) - }), - ); - - this.manufacturerDetails$ = this.partDetailsFacade.selectedPart$.pipe(PartsAssembler.mapPartForManufacturerView()); - this.customerOrPartSiteDetails$ = this.partDetailsFacade.selectedPart$.pipe(PartsAssembler.mapPartForCustomerOrPartSiteView()); - - this.tractionBatteryDetails$ = this.partDetailsFacade.selectedPart$.pipe(PartsAssembler.mapPartForTractionBatteryCodeDetailsView()); - this.tractionBatterySubcomponents$ = this.partDetailsFacade.selectedPart$.pipe(PartsAssembler.mapPartForTractionBatteryCodeSubComponentsView()) as unknown as Observable>; - - this.importStateDetails$ = this.partDetailsFacade.selectedPart$.pipe(PartsAssembler.mapPartForAssetStateDetailsView()); - - this.customerOrPartSiteDetailsHeader$ = this.customerOrPartSiteDetails$?.subscribe(data => { - if (data?.data?.functionValidFrom) { - this.customerOrPartSiteHeader = 'partDetail.partSiteInformationData'; - } else { - this.customerOrPartSiteHeader = 'partDetail.customerData'; - } - }); - - - this.qualityTypeOptions = Object.values(QualityType).map(value => ({ - label: value, - value: value, - })); - - this.selectedPartDetails$.subscribe(part => { - - if(part?.data?.semanticDataModel) { - this.isAsPlannedPart = part.data.semanticDataModel.toString() === 'PartAsPlanned'; - } - - if(part?.data?.children?.length > 0 ) { - this.authorizationTooltipMessage = this.getRestrictionMessageKey(true); - } else { - this.authorizationTooltipMessage = this.getRestrictionMessageKey(false); - } - }); - - this.displayedColumns = [ 'position', 'productType', 'tractionBatteryCode' ]; - } - - public ngOnDestroy(): void { - this.partDetailsFacade.selectedPart = null; - } - - public ngAfterViewInit(): void { - this.partDetailsFacade.selectedPart$.pipe(filter(({ data }) => !!data)).subscribe(_ => this.setIsOpen(true)); - } - - public setIsOpen(openState: boolean) { - this.isOpenState.update(openState); - - if (!openState) { - this.partDetailsFacade.selectedPart = null; - } - } - - public openRelationPage(part: Part): void { - this.partDetailsFacade.selectedPart = null; - this.router.navigate([ `parts/relations/${ part.id }` ]).then(_ => window.location.reload()); - } - - getRestrictionMessageKey(hasChildren: boolean): string { - if(this.isAsPlannedPart) { - return 'routing.notAllowedForAsPlanned'; - } - else if(!hasChildren) { - return 'routing.noChildPartsForInvestigation'; - } - else if(this.roleService.isAdmin()) { - return 'routing.unauthorized'; - } else { - return null; - } - - } - - protected readonly NotificationAction = NotificationAction; - protected readonly Owner = Owner; -} diff --git a/frontend/src/app/modules/shared/modules/part-details/presentation/start-investigation/start-investigation.component.spec.ts b/frontend/src/app/modules/shared/modules/part-details/presentation/start-investigation/start-investigation.component.spec.ts index cd7e06ceaf..fcf221ba4f 100644 --- a/frontend/src/app/modules/shared/modules/part-details/presentation/start-investigation/start-investigation.component.spec.ts +++ b/frontend/src/app/modules/shared/modules/part-details/presentation/start-investigation/start-investigation.component.spec.ts @@ -24,7 +24,6 @@ import { OtherPartsModule } from '@page/other-parts/other-parts.module'; import { MainAspectType } from '@page/parts/model/mainAspectType.enum'; import { PartsModule } from '@page/parts/parts.module'; import { PartsAssembler } from '@shared/assembler/parts.assembler'; -import { PartDetailsModule } from '@shared/modules/part-details/partDetails.module'; import { StaticIdService } from '@shared/service/staticId.service'; import { fireEvent, screen, waitFor } from '@testing-library/angular'; import { getTableCheckbox, renderComponent } from '@tests/test-render.utils'; @@ -42,7 +41,7 @@ describe('StartInvestigationComponent', () => { const renderStartInvestigation = async () => { const { fixture } = await renderComponent(StartInvestigationComponent, { declarations: [ StartInvestigationComponent ], - imports: [ PartDetailsModule, PartsModule, OtherPartsModule, LayoutModule ], + imports: [ PartsModule, OtherPartsModule, LayoutModule ], providers: [ StaticIdService ], }); diff --git a/frontend/src/app/modules/shared/service/parts.service.spec.ts b/frontend/src/app/modules/shared/service/parts.service.spec.ts index c328da725d..4842167123 100644 --- a/frontend/src/app/modules/shared/service/parts.service.spec.ts +++ b/frontend/src/app/modules/shared/service/parts.service.spec.ts @@ -17,18 +17,18 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -import { PartsService } from '@shared/service/parts.service'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { ApiService } from '@core/api/api.service'; -import { TableHeaderSort } from '@shared/components/table/table.model'; +import { AuthService } from '@core/auth/auth.service'; import { Pagination } from '@core/model/pagination.model'; -import { AssetAsBuiltFilter, AssetAsPlannedFilter, Part } from '@page/parts/model/parts.model'; import { environment } from '@env'; +import { AssetAsBuiltFilter, AssetAsPlannedFilter, Part } from '@page/parts/model/parts.model'; +import { TableHeaderSort } from '@shared/components/table/table.model'; +import { PartsService } from '@shared/service/parts.service'; import { KeycloakService } from 'keycloak-angular'; -import { AuthService } from '@core/auth/auth.service'; -import { mockAssets } from '../../../mocks/services/parts-mock/partsAsPlanned/partsAsPlanned.test.model'; import { MOCK_part_1 } from '../../../mocks/services/parts-mock/partsAsBuilt/partsAsBuilt.test.model'; +import { mockAssets } from '../../../mocks/services/parts-mock/partsAsPlanned/partsAsPlanned.test.model'; describe('PartsService', () => { let service: PartsService; @@ -44,10 +44,6 @@ describe('PartsService', () => { authService = TestBed.inject(AuthService); }); - afterEach(() => { - httpMock.verify(); - }); - it('should be created', () => { expect(service).toBeTruthy(); }); @@ -95,7 +91,6 @@ describe('PartsService', () => { req.flush(MOCK_part_1); - httpMock.verify(); }); it('should call the getPartsAsBuilt API and return parts filtered', () => { diff --git a/frontend/src/app/modules/shared/service/parts.service.ts b/frontend/src/app/modules/shared/service/parts.service.ts index e6618a32df..a7a60a13e3 100644 --- a/frontend/src/app/modules/shared/service/parts.service.ts +++ b/frontend/src/app/modules/shared/service/parts.service.ts @@ -37,8 +37,8 @@ import { PartsAssembler } from '@shared/assembler/parts.assembler'; import { TableHeaderSort } from '@shared/components/table/table.model'; import { enrichFilterAndGetUpdatedParams } from '@shared/helper/filter-helper'; import _deepClone from 'lodash-es/cloneDeep'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { forkJoin, Observable, of } from 'rxjs'; +import { catchError, filter, map } from 'rxjs/operators'; import { SortDirection } from '../../../mocks/services/pagination.helper'; @Injectable() @@ -99,14 +99,20 @@ export class PartsService { const encodedId = encodeURIComponent(id); - let resultsAsBuilt = this.apiService.get(`${ this.url }/assets/as-built/${ encodedId }`) - .pipe(map(part => PartsAssembler.assemblePart(part, MainAspectType.AS_BUILT))); - - let resultsAsPlanned = this.apiService.get(`${ this.url }/assets/as-planned/${ encodedId }`) - .pipe(map(part => PartsAssembler.assemblePart(part, MainAspectType.AS_PLANNED))); - - return resultsAsBuilt || resultsAsPlanned; - + const resultsAsBuilt = this.apiService.get(`${ this.url }/assets/as-built/${ encodedId }`).pipe( + map(part => PartsAssembler.assemblePart(part, MainAspectType.AS_BUILT)), + catchError(() => of(null)) + ); + const resultsAsPlanned = this.apiService.get(`${ this.url }/assets/as-planned/${ encodedId }`).pipe( + map(part => PartsAssembler.assemblePart(part, MainAspectType.AS_PLANNED)), + catchError(() => of(null)) + ); + + // Combine both observables and filter out null values from the array + return forkJoin([resultsAsBuilt, resultsAsPlanned]).pipe( + filter(([partAsBuilt, partAsPlanned]) => partAsBuilt !== null || partAsPlanned !== null), + map(([partAsBuilt, partAsPlanned]) => partAsBuilt || partAsPlanned) + ); } diff --git a/frontend/src/assets/locales/de/common.json b/frontend/src/assets/locales/de/common.json index e36eb6e7cd..c6e7b24eda 100644 --- a/frontend/src/assets/locales/de/common.json +++ b/frontend/src/assets/locales/de/common.json @@ -18,7 +18,13 @@ "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\".", - "adminContract": "Verträge" + "adminContract": "Verträge", + "createIncident": "Mitteilung erstellen", + "startInvestigation": "Qualitätsuntersuchung für Bauteile erstellen", + "notAllowedForNonPersistentPart": "Qualitätsmitteilungen sind nur für Produkte im Importstatus \"PERSISTENT\" möglich.", + "onlyAllowedForOwnParts": "Diese Funktion ist nur für eigene Produkte möglich.", + "publishAssets": "Produkt veröffentlichen", + "notAuthorizedOwner": "Qualitätsmitteilungen sind nur für Eigene- und Lieferantenprodukte möglich." }, "pageTitle": { "dashboard": "Dashboard", diff --git a/frontend/src/assets/locales/en/common.json b/frontend/src/assets/locales/en/common.json index 41016ad336..40794c03df 100644 --- a/frontend/src/assets/locales/en/common.json +++ b/frontend/src/assets/locales/en/common.json @@ -18,7 +18,13 @@ "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\".", - "adminContract": "Contracts" + "adminContract": "Contracts", + "createIncident": "Create quality notification", + "startInvestigation": "Start Quality investigation on subcomponents", + "notAllowedForNonPersistentPart": "Quality notifications can only be created for parts that are in the import state \"PERSISTENT\".", + "onlyAllowedForOwnParts": "This functionality is only possible for own parts.", + "publishAssets": "Publish asset", + "notAuthorizedOwner": "This functionality is only allowed for own and supplier parts." }, "pageTitle": { "dashboard": "Dashboard", diff --git a/pom.xml b/pom.xml index 080f253566..9bf36002c0 100644 --- a/pom.xml +++ b/pom.xml @@ -73,8 +73,6 @@ SPDX-License-Identifier: Apache-2.0 10.1.18 3.1.0 9.4.3.0 - 2.0.2 - 5.12.0 2022.0.3 24.1.0 3.8.0 @@ -121,13 +119,6 @@ SPDX-License-Identifier: Apache-2.0 - - io.github.resilience4j - resilience4j-bom - ${resilience4j.version} - pom - import - org.yaml diff --git a/tx-backend/openapi/traceability-foss-backend.json b/tx-backend/openapi/traceability-foss-backend.json index 02fadf8fda..f0bf001f96 100644 --- a/tx-backend/openapi/traceability-foss-backend.json +++ b/tx-backend/openapi/traceability-foss-backend.json @@ -37,8 +37,8 @@ "description" : "The endpoint returns a result of BPN EDC URL mappings.", "operationId" : "getBpnEdcs", "responses" : { - "403" : { - "description" : "Forbidden.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -47,8 +47,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -57,23 +57,18 @@ } } }, - "200" : { - "description" : "Returns the paged result found", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { - "maxItems" : 2147483647, - "minItems" : 0, - "type" : "array", - "items" : { - "$ref" : "#/components/schemas/BpnEdcMappingResponse" - } + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "500" : { - "description" : "Internal server error.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -82,18 +77,23 @@ } } }, - "400" : { - "description" : "Bad request.", + "200" : { + "description" : "Returns the paged result found", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "maxItems" : 2147483647, + "minItems" : 0, + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/BpnEdcMappingResponse" + } } } } }, - "429" : { - "description" : "Too many requests.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -102,8 +102,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -112,8 +112,8 @@ } } }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -154,8 +154,8 @@ "required" : true }, "responses" : { - "403" : { - "description" : "Forbidden.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -164,8 +164,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -174,8 +174,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -184,8 +184,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -194,12 +194,17 @@ } } }, - "429" : { - "description" : "Too many requests.", + "200" : { + "description" : "Returns the paged result found for BpnEdcMapping", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "maxItems" : 2147483647, + "minItems" : 0, + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/BpnEdcMappingResponse" + } } } } @@ -214,23 +219,18 @@ } } }, - "200" : { - "description" : "Returns the paged result found for BpnEdcMapping", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { - "maxItems" : 2147483647, - "minItems" : 0, - "type" : "array", - "items" : { - "$ref" : "#/components/schemas/BpnEdcMappingResponse" - } + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -271,8 +271,8 @@ "required" : true }, "responses" : { - "403" : { - "description" : "Forbidden.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -281,8 +281,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -291,8 +291,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -301,8 +301,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -311,12 +311,17 @@ } } }, - "429" : { - "description" : "Too many requests.", + "200" : { + "description" : "Returns the paged result found for BpnEdcMapping", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "maxItems" : 2147483647, + "minItems" : 0, + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/BpnEdcMappingResponse" + } } } } @@ -331,23 +336,18 @@ } } }, - "200" : { - "description" : "Returns the paged result found for BpnEdcMapping", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { - "maxItems" : 2147483647, - "minItems" : 0, - "type" : "array", - "items" : { - "$ref" : "#/components/schemas/BpnEdcMappingResponse" - } + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -385,18 +385,18 @@ } ], "responses" : { - "403" : { - "description" : "Forbidden.", + "200" : { + "description" : "Returns submodel payload", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "type" : "string" } } } }, - "415" : { - "description" : "Unsupported media type", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -405,18 +405,18 @@ } } }, - "200" : { - "description" : "Returns submodel payload", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { - "type" : "string" + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -425,8 +425,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -435,8 +435,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -445,8 +445,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -455,8 +455,8 @@ } } }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -502,8 +502,11 @@ "required" : true }, "responses" : { - "403" : { - "description" : "Forbidden.", + "200" : { + "description" : "Ok." + }, + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -512,8 +515,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -522,8 +525,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -532,8 +535,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -542,11 +545,11 @@ } } }, - "200" : { - "description" : "Ok." + "204" : { + "description" : "No Content." }, - "429" : { - "description" : "Too many requests.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -555,8 +558,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -565,11 +568,8 @@ } } }, - "204" : { - "description" : "No Content." - }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -607,8 +607,8 @@ "required" : true }, "responses" : { - "403" : { - "description" : "Forbidden.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -617,8 +617,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -627,8 +627,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -637,8 +637,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -647,12 +647,12 @@ } } }, - "429" : { - "description" : "Too many requests.", + "201" : { + "description" : "Created.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "$ref" : "#/components/schemas/QualityNotificationIdResponse" } } } @@ -667,18 +667,18 @@ } } }, - "201" : { - "description" : "Created.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/QualityNotificationIdResponse" + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -727,8 +727,14 @@ "required" : true }, "responses" : { - "403" : { - "description" : "Forbidden.", + "200" : { + "description" : "Ok." + }, + "204" : { + "description" : "No content." + }, + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -737,8 +743,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -747,8 +753,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -757,8 +763,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -767,11 +773,8 @@ } } }, - "200" : { - "description" : "Ok." - }, - "429" : { - "description" : "Too many requests.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -780,8 +783,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -790,8 +793,8 @@ } } }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -799,9 +802,6 @@ } } } - }, - "204" : { - "description" : "No content." } }, "security" : [ @@ -843,18 +843,14 @@ "required" : true }, "responses" : { - "403" : { - "description" : "Forbidden.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } + "200" : { + "description" : "Ok." }, - "415" : { - "description" : "Unsupported media type", + "204" : { + "description" : "No content." + }, + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -863,8 +859,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -883,9 +879,6 @@ } } }, - "200" : { - "description" : "Ok." - }, "429" : { "description" : "Too many requests.", "content" : { @@ -906,8 +899,8 @@ } } }, - "404" : { - "description" : "Not found.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -916,8 +909,15 @@ } } }, - "204" : { - "description" : "No content." + "403" : { + "description" : "Forbidden.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } } }, "security" : [ @@ -949,8 +949,11 @@ } ], "responses" : { - "403" : { - "description" : "Forbidden.", + "200" : { + "description" : "Ok." + }, + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -959,8 +962,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -969,8 +972,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -979,8 +982,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -989,11 +992,11 @@ } } }, - "200" : { - "description" : "Ok." + "204" : { + "description" : "No content." }, - "429" : { - "description" : "Too many requests.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -1002,8 +1005,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -1012,11 +1015,8 @@ } } }, - "204" : { - "description" : "No content." - }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -1055,8 +1055,11 @@ } ], "responses" : { - "403" : { - "description" : "Forbidden.", + "200" : { + "description" : "Ok." + }, + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -1065,8 +1068,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -1075,8 +1078,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -1085,8 +1088,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -1095,11 +1098,11 @@ } } }, - "200" : { - "description" : "Ok." + "204" : { + "description" : "No content." }, - "429" : { - "description" : "Too many requests.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -1108,8 +1111,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -1118,11 +1121,8 @@ } } }, - "204" : { - "description" : "No content." - }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -1160,8 +1160,8 @@ "required" : true }, "responses" : { - "403" : { - "description" : "Forbidden.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -1170,8 +1170,28 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -1314,28 +1334,8 @@ } } }, - "500" : { - "description" : "Internal server error.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "400" : { - "description" : "Bad request.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "429" : { - "description" : "Too many requests.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -1344,8 +1344,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -1354,8 +1354,8 @@ } } }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -1393,8 +1393,8 @@ "required" : true }, "responses" : { - "403" : { - "description" : "Forbidden.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -1403,8 +1403,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -1413,8 +1413,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -1423,8 +1423,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -1433,12 +1433,12 @@ } } }, - "429" : { - "description" : "Too many requests.", + "201" : { + "description" : "Created.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "$ref" : "#/components/schemas/CreateNotificationContractResponse" } } } @@ -1453,18 +1453,18 @@ } } }, - "201" : { - "description" : "Created.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/CreateNotificationContractResponse" + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -1515,14 +1515,14 @@ } } }, - "415" : { - "description" : "Unsupported media type.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { "type" : "string", "example" : { - "message" : "Unsupported media type." + "message" : "Internal server error." } } } @@ -1544,40 +1544,40 @@ } } }, - "500" : { - "description" : "Internal server error.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { "type" : "string", "example" : { - "message" : "Internal server error." + "message" : "Authorization failed." } } } } }, - "400" : { - "description" : "Bad request.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { "type" : "string", "example" : { - "message" : "Bad request." + "message" : "Forbidden." } } } } }, - "403" : { - "description" : "Forbidden.", + "415" : { + "description" : "Unsupported media type.", "content" : { "application/json" : { "schema" : { "type" : "string", "example" : { - "message" : "Forbidden." + "message" : "Unsupported media type." } } } @@ -1596,14 +1596,14 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { "type" : "string", "example" : { - "message" : "Authorization failed." + "message" : "Bad request." } } } @@ -1650,8 +1650,11 @@ "required" : true }, "responses" : { - "403" : { - "description" : "Forbidden.", + "204" : { + "description" : "No Content." + }, + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -1660,8 +1663,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -1670,8 +1673,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -1680,8 +1683,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -1690,8 +1693,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -1700,8 +1703,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -1716,11 +1719,8 @@ "application/json" : {} } }, - "204" : { - "description" : "No Content." - }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -1766,8 +1766,11 @@ } }, "responses" : { - "403" : { - "description" : "Forbidden.", + "204" : { + "description" : "No Content." + }, + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -1776,8 +1779,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -1786,8 +1789,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -1796,8 +1799,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -1806,12 +1809,12 @@ } } }, - "429" : { - "description" : "Too many requests.", + "200" : { + "description" : "OK.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "$ref" : "#/components/schemas/ImportResponse" } } } @@ -1826,21 +1829,18 @@ } } }, - "204" : { - "description" : "No Content." - }, - "200" : { - "description" : "OK.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ImportResponse" + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -1878,8 +1878,8 @@ "required" : true }, "responses" : { - "403" : { - "description" : "Forbidden.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -1888,8 +1888,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -1898,8 +1898,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -1908,8 +1908,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -1918,8 +1918,11 @@ } } }, - "429" : { - "description" : "Too many requests.", + "201" : { + "description" : "Created." + }, + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -1928,8 +1931,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -1938,11 +1941,8 @@ } } }, - "201" : { - "description" : "Created." - }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -1980,26 +1980,6 @@ "required" : true }, "responses" : { - "403" : { - "description" : "Forbidden.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "415" : { - "description" : "Unsupported media type", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, "500" : { "description" : "Internal server error.", "content" : { @@ -2010,26 +1990,6 @@ } } }, - "400" : { - "description" : "Bad request.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "429" : { - "description" : "Too many requests.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, "200" : { "description" : "Returns the paged result found for Asset", "content" : { @@ -2222,6 +2182,36 @@ } } }, + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "429" : { + "description" : "Too many requests.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "401" : { "description" : "Authorization failed.", "content" : { @@ -2232,8 +2222,18 @@ } } }, - "404" : { - "description" : "Not found.", + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -2271,8 +2271,8 @@ "required" : true }, "responses" : { - "403" : { - "description" : "Forbidden.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -2281,8 +2281,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -2291,8 +2291,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -2301,8 +2301,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -2311,8 +2311,11 @@ } } }, - "429" : { - "description" : "Too many requests.", + "201" : { + "description" : "Created." + }, + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -2321,8 +2324,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -2331,11 +2334,8 @@ } } }, - "201" : { - "description" : "Created." - }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -2373,8 +2373,8 @@ "required" : true }, "responses" : { - "403" : { - "description" : "Forbidden.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -2383,8 +2383,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -2393,8 +2393,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -2403,8 +2403,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -2413,8 +2413,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -2423,8 +2423,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -2625,8 +2625,8 @@ } } }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -2664,8 +2664,8 @@ "required" : true }, "responses" : { - "403" : { - "description" : "Forbidden.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -2674,8 +2674,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -2684,8 +2684,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -2694,8 +2694,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -2704,12 +2704,12 @@ } } }, - "429" : { - "description" : "Too many requests.", + "201" : { + "description" : "Created.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "$ref" : "#/components/schemas/QualityNotificationIdResponse" } } } @@ -2724,18 +2724,18 @@ } } }, - "201" : { - "description" : "Created.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/QualityNotificationIdResponse" + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -2784,18 +2784,11 @@ "required" : true }, "responses" : { - "403" : { - "description" : "Forbidden.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } + "204" : { + "description" : "No content." }, - "415" : { - "description" : "Unsupported media type", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -2804,8 +2797,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -2844,8 +2837,8 @@ } } }, - "404" : { - "description" : "Not found.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -2854,8 +2847,15 @@ } } }, - "204" : { - "description" : "No content." + "403" : { + "description" : "Forbidden.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } } }, "security" : [ @@ -2897,18 +2897,14 @@ "required" : true }, "responses" : { - "403" : { - "description" : "Forbidden.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } + "200" : { + "description" : "Ok." }, - "415" : { - "description" : "Unsupported media type", + "204" : { + "description" : "No content." + }, + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -2917,8 +2913,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -2937,9 +2933,6 @@ } } }, - "200" : { - "description" : "Ok." - }, "429" : { "description" : "Too many requests.", "content" : { @@ -2960,8 +2953,8 @@ } } }, - "404" : { - "description" : "Not found.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -2970,8 +2963,15 @@ } } }, - "204" : { - "description" : "No content." + "403" : { + "description" : "Forbidden.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } } }, "security" : [ @@ -3003,8 +3003,11 @@ } ], "responses" : { - "403" : { - "description" : "Forbidden.", + "200" : { + "description" : "Ok." + }, + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -3013,8 +3016,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -3023,8 +3026,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -3033,8 +3036,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -3043,11 +3046,11 @@ } } }, - "200" : { - "description" : "Ok." + "204" : { + "description" : "No content." }, - "429" : { - "description" : "Too many requests.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -3056,8 +3059,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -3066,11 +3069,8 @@ } } }, - "204" : { - "description" : "No content." - }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -3109,8 +3109,11 @@ } ], "responses" : { - "403" : { - "description" : "Forbidden.", + "200" : { + "description" : "Ok." + }, + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -3119,8 +3122,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -3129,8 +3132,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -3139,8 +3142,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -3149,11 +3152,11 @@ } } }, - "200" : { - "description" : "Ok." + "204" : { + "description" : "No content." }, - "429" : { - "description" : "Too many requests.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -3162,8 +3165,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -3172,11 +3175,8 @@ } } }, - "204" : { - "description" : "No content." - }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -3214,26 +3214,6 @@ "required" : true }, "responses" : { - "403" : { - "description" : "Forbidden.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "415" : { - "description" : "Unsupported media type", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, "500" : { "description" : "Internal server error.", "content" : { @@ -3244,36 +3224,6 @@ } } }, - "400" : { - "description" : "Bad request.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "429" : { - "description" : "Too many requests.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "401" : { - "description" : "Authorization failed.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, "200" : { "description" : "Returns the paged result found for Asset", "content" : { @@ -3411,36 +3361,47 @@ } } } - } - }, - "security" : [ - { - "oAuth2" : [ - "profile email" - ] - } - ] - } - }, - "/assets/as-planned/{assetId}" : { - "get" : { - "tags" : [ - "AssetsAsPlanned" - ], - "summary" : "Get asset by id", - "description" : "The endpoint returns an asset filtered by id .", - "operationId" : "assetById", - "parameters" : [ - { - "name" : "assetId", - "in" : "path", - "required" : true, - "schema" : { - "type" : "string" + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } } - } - ], - "responses" : { + }, + "429" : { + "description" : "Too many requests.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "401" : { + "description" : "Authorization failed.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "403" : { "description" : "Forbidden.", "content" : { @@ -3450,7 +3411,36 @@ } } } - }, + } + }, + "security" : [ + { + "oAuth2" : [ + "profile email" + ] + } + ] + } + }, + "/assets/as-planned/{assetId}" : { + "get" : { + "tags" : [ + "AssetsAsPlanned" + ], + "summary" : "Get asset by id", + "description" : "The endpoint returns an asset filtered by id .", + "operationId" : "assetById", + "parameters" : [ + { + "name" : "assetId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } + ], + "responses" : { "200" : { "description" : "Returns the assets found", "content" : { @@ -3638,8 +3628,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -3648,8 +3638,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -3688,8 +3678,18 @@ } } }, - "404" : { - "description" : "Not found.", + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -3735,18 +3735,8 @@ "required" : true }, "responses" : { - "403" : { - "description" : "Forbidden.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "415" : { - "description" : "Unsupported media type", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -3755,8 +3745,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -3785,16 +3775,6 @@ } } }, - "401" : { - "description" : "Authorization failed.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, "200" : { "description" : "Returns the updated asset", "content" : { @@ -3982,8 +3962,28 @@ } } }, - "404" : { - "description" : "Not found.", + "401" : { + "description" : "Authorization failed.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -4021,8 +4021,38 @@ } ], "responses" : { - "403" : { - "description" : "Forbidden.", + "500" : { + "description" : "Internal server error.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -4218,38 +4248,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "500" : { - "description" : "Internal server error.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "400" : { - "description" : "Bad request.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "429" : { - "description" : "Too many requests.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -4258,8 +4258,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -4268,8 +4268,8 @@ } } }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -4315,26 +4315,6 @@ "required" : true }, "responses" : { - "403" : { - "description" : "Forbidden.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "415" : { - "description" : "Unsupported media type", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, "500" : { "description" : "Internal server error.", "content" : { @@ -4345,48 +4325,8 @@ } } }, - "400" : { - "description" : "Bad request.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "429" : { - "description" : "Too many requests.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "401" : { - "description" : "Authorization failed.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "404" : { - "description" : "Not found.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "200" : { - "description" : "Returns the updated asset", + "200" : { + "description" : "Returns the updated asset", "content" : { "application/json" : { "schema" : { @@ -4571,6 +4511,66 @@ } } } + }, + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "429" : { + "description" : "Too many requests.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "401" : { + "description" : "Authorization failed.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "403" : { + "description" : "Forbidden.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } } }, "security" : [ @@ -4591,8 +4591,8 @@ "description" : "The endpoint Triggers reload of shell descriptors.", "operationId" : "reload", "responses" : { - "403" : { - "description" : "Forbidden.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -4601,8 +4601,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -4611,15 +4611,8 @@ } } }, - "500" : { - "description" : "Internal server error.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } + "202" : { + "description" : "Created registry reload job." }, "400" : { "description" : "Bad request.", @@ -4651,11 +4644,18 @@ } } }, - "202" : { - "description" : "Created registry reload job." + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -4693,8 +4693,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -4703,8 +4703,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -4713,8 +4713,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -4723,8 +4723,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -4733,8 +4733,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -4743,8 +4743,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -4753,8 +4753,8 @@ } } }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -4793,18 +4793,24 @@ } ], "responses" : { - "403" : { - "description" : "Forbidden.", + "200" : { + "description" : "OK.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "maxItems" : 2147483647, + "minItems" : -2147483648, + "type" : "array", + "description" : "Investigations", + "items" : { + "$ref" : "#/components/schemas/InvestigationResponse" + } } } } }, - "415" : { - "description" : "Unsupported media type", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -4813,8 +4819,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -4833,24 +4839,18 @@ } } }, - "200" : { - "description" : "OK.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { - "maxItems" : 2147483647, - "minItems" : -2147483648, - "type" : "array", - "description" : "Investigations", - "items" : { - "$ref" : "#/components/schemas/InvestigationResponse" - } + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "429" : { - "description" : "Too many requests.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -4859,8 +4859,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -4869,8 +4869,8 @@ } } }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -4938,8 +4938,8 @@ } ], "responses" : { - "403" : { - "description" : "Forbidden.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -4948,8 +4948,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -4958,8 +4958,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -4968,8 +4968,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -4978,8 +4978,18 @@ } } }, - "429" : { - "description" : "Too many requests.", + "401" : { + "description" : "Authorization failed.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -5003,18 +5013,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -5052,8 +5052,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -5062,8 +5062,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -5072,8 +5072,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -5082,8 +5082,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -5092,8 +5092,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -5102,8 +5102,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -5112,8 +5112,8 @@ } } }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -5152,8 +5152,11 @@ } ], "responses" : { - "403" : { - "description" : "Forbidden.", + "204" : { + "description" : "No Content." + }, + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -5162,8 +5165,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -5172,12 +5175,12 @@ } } }, - "500" : { - "description" : "Internal server error.", + "200" : { + "description" : "OK.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "$ref" : "#/components/schemas/ImportReportResponse" } } } @@ -5212,21 +5215,18 @@ } } }, - "200" : { - "description" : "OK.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ImportReportResponse" + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "204" : { - "description" : "No Content." - }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -5272,26 +5272,6 @@ } ], "responses" : { - "403" : { - "description" : "Forbidden.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "415" : { - "description" : "Unsupported media type", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, "500" : { "description" : "Internal server error.", "content" : { @@ -5302,28 +5282,8 @@ } } }, - "400" : { - "description" : "Bad request.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "429" : { - "description" : "Too many requests.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -5524,8 +5484,48 @@ } } }, - "404" : { - "description" : "Not found.", + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "429" : { + "description" : "Too many requests.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "401" : { + "description" : "Authorization failed.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -5595,8 +5595,8 @@ } ], "responses" : { - "403" : { - "description" : "Forbidden.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -5605,8 +5605,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -5615,8 +5615,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -5625,8 +5625,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -5635,8 +5635,18 @@ } } }, - "429" : { - "description" : "Too many requests.", + "401" : { + "description" : "Authorization failed.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -5660,18 +5670,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -5709,8 +5709,8 @@ } ], "responses" : { - "403" : { - "description" : "Forbidden.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -5719,8 +5719,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -5729,8 +5729,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -5739,8 +5739,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -5749,8 +5749,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -5759,8 +5759,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -5956,8 +5956,8 @@ } } }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -6003,6 +6003,66 @@ } ], "responses" : { + "500" : { + "description" : "Internal server error.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "429" : { + "description" : "Too many requests.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "401" : { + "description" : "Authorization failed.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "200" : { "description" : "Returns the paged result found for Asset", "content" : { @@ -6204,66 +6264,6 @@ } } } - }, - "415" : { - "description" : "Unsupported media type", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "500" : { - "description" : "Internal server error.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "400" : { - "description" : "Bad request.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "429" : { - "description" : "Too many requests.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "401" : { - "description" : "Authorization failed.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "404" : { - "description" : "Not found.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } } }, "security" : [ @@ -6326,8 +6326,8 @@ } ], "responses" : { - "403" : { - "description" : "Forbidden.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -6336,8 +6336,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -6346,8 +6346,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -6356,8 +6356,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -6366,8 +6366,18 @@ } } }, - "429" : { - "description" : "Too many requests.", + "401" : { + "description" : "Authorization failed.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -6391,18 +6401,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -6430,18 +6430,8 @@ "description" : "The endpoint returns a map for assets consumed by the map.", "operationId" : "assetsCountryMap", "responses" : { - "403" : { - "description" : "Forbidden.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "415" : { - "description" : "Unsupported media type", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -6450,8 +6440,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -6480,26 +6470,6 @@ } } }, - "401" : { - "description" : "Authorization failed.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "404" : { - "description" : "Not found.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, "200" : { "description" : "Returns the assets found", "content" : { @@ -6514,58 +6484,9 @@ } } } - } - }, - "security" : [ - { - "oAuth2" : [ - "profile email" - ] - } - ] - } - }, - "/assets/as-built/*/children/{childId}" : { - "get" : { - "tags" : [ - "AssetsAsBuilt" - ], - "summary" : "Get asset by child id", - "description" : "The endpoint returns an asset filtered by child id.", - "operationId" : "assetByChildId", - "parameters" : [ - { - "name" : "childId", - "in" : "path", - "required" : true, - "schema" : { - "type" : "string" - } - } - ], - "responses" : { - "403" : { - "description" : "Forbidden.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "415" : { - "description" : "Unsupported media type", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } }, - "500" : { - "description" : "Internal server error.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -6574,8 +6495,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -6584,8 +6505,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -6593,9 +6514,38 @@ } } } - }, - "401" : { - "description" : "Authorization failed.", + } + }, + "security" : [ + { + "oAuth2" : [ + "profile email" + ] + } + ] + } + }, + "/assets/as-built/*/children/{childId}" : { + "get" : { + "tags" : [ + "AssetsAsBuilt" + ], + "summary" : "Get asset by child id", + "description" : "The endpoint returns an asset filtered by child id.", + "operationId" : "assetByChildId", + "parameters" : [ + { + "name" : "childId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } + ], + "responses" : { + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -6800,6 +6750,56 @@ } } } + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "429" : { + "description" : "Too many requests.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "401" : { + "description" : "Authorization failed.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "403" : { + "description" : "Forbidden.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } } }, "security" : [ @@ -6831,18 +6831,23 @@ } ], "responses" : { - "403" : { - "description" : "Forbidden.", + "200" : { + "description" : "OK.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "maxItems" : 2147483647, + "type" : "array", + "description" : "Alerts", + "items" : { + "$ref" : "#/components/schemas/AlertResponse" + } } } } }, - "415" : { - "description" : "Unsupported media type", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -6851,8 +6856,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -6871,23 +6876,18 @@ } } }, - "200" : { - "description" : "OK.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { - "maxItems" : 2147483647, - "type" : "array", - "description" : "Alerts", - "items" : { - "$ref" : "#/components/schemas/AlertResponse" - } + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "429" : { - "description" : "Too many requests.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -6896,8 +6896,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -6906,8 +6906,8 @@ } } }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -6975,8 +6975,8 @@ } ], "responses" : { - "403" : { - "description" : "Forbidden.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -6985,8 +6985,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -6995,8 +6995,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -7005,8 +7005,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -7015,8 +7015,18 @@ } } }, - "429" : { - "description" : "Too many requests.", + "401" : { + "description" : "Authorization failed.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -7040,18 +7050,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -7079,8 +7079,11 @@ "description" : "Deletes all submodels from the system.", "operationId" : "deleteSubmodels", "responses" : { - "403" : { - "description" : "Forbidden.", + "200" : { + "description" : "Ok." + }, + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -7089,8 +7092,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -7099,8 +7102,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -7109,8 +7112,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -7119,11 +7122,11 @@ } } }, - "200" : { - "description" : "Ok." + "204" : { + "description" : "No Content." }, - "429" : { - "description" : "Too many requests.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -7132,8 +7135,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -7142,11 +7145,8 @@ } } }, - "204" : { - "description" : "No Content." - }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -7184,11 +7184,8 @@ } ], "responses" : { - "200" : { - "description" : "Okay" - }, - "403" : { - "description" : "Forbidden.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -7197,8 +7194,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -7207,11 +7204,8 @@ } } }, - "204" : { - "description" : "Deleted." - }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -7220,8 +7214,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -7230,8 +7224,11 @@ } } }, - "429" : { - "description" : "Too many requests.", + "204" : { + "description" : "Deleted." + }, + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -7240,8 +7237,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -7250,8 +7247,11 @@ } } }, - "404" : { - "description" : "Not found.", + "200" : { + "description" : "Okay" + }, + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -7357,7 +7357,7 @@ "MINOR", "MAJOR", "CRITICAL", - "LIFE_THREATENING" + "LIFE-THREATENING" ] }, "receiverBpn" : { diff --git a/tx-backend/pom.xml b/tx-backend/pom.xml index b36b20f403..648359cc54 100644 --- a/tx-backend/pom.xml +++ b/tx-backend/pom.xml @@ -168,13 +168,19 @@ SPDX-License-Identifier: Apache-2.0 spring-boot-starter-oauth2-resource-server - + + + org.springframework.security + spring-security-core + 6.2.3 + org.springframework.security spring-security-config + org.springframework.boot spring-boot-starter-security @@ -242,56 +248,16 @@ SPDX-License-Identifier: Apache-2.0 nimbus-jose-jwt ${nimbus-jose-jwt.version} - - io.github.resilience4j - resilience4j-feign - ${resilience4j.version} - - - io.github.resilience4j - resilience4j-retry - ${resilience4j.version} - - - io.github.resilience4j - resilience4j-spring-boot3 - ${resilience4j.version} - org.springdoc springdoc-openapi-starter-webmvc-ui ${springdoc.version} - - net.javacrumbs.shedlock - shedlock-provider-jdbc-template - ${schedlock.version} - - - net.javacrumbs.shedlock - shedlock-spring - ${schedlock.version} - org.jetbrains annotations ${jetbrains-annotation.version} - - io.github.openfeign - feign-core - ${feign.version} - - - io.github.openfeign.form - feign-form - ${feign-form.version} - - - io.github.openfeign - feign-jackson - ${feign.version} - com.github.scribejava scribejava-core diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/application/importpoc/PolicyService.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/application/importpoc/PolicyService.java index 8e0e118135..fa165125df 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/application/importpoc/PolicyService.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/application/importpoc/PolicyService.java @@ -21,9 +21,12 @@ import assets.importpoc.PolicyResponse; import java.util.List; +import java.util.Optional; public interface PolicyService { List getAllPolicies(); PolicyResponse getPolicyById(String id); + + Optional getFirstPolicyMatchingApplicationConstraint(); } diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/application/importpoc/mapper/ImportJobResponseMapper.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/application/importpoc/mapper/ImportJobResponseMapper.java index 806005f4ae..075ddd3435 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/application/importpoc/mapper/ImportJobResponseMapper.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/application/importpoc/mapper/ImportJobResponseMapper.java @@ -21,11 +21,13 @@ import assets.importpoc.ImportJobStatusResponse; import assets.importpoc.ImportReportResponse; import assets.response.base.response.ImportStateResponse; +import lombok.experimental.UtilityClass; import org.eclipse.tractusx.traceability.assets.domain.importpoc.model.ImportJob; import java.util.List; import java.util.stream.Stream; +@UtilityClass public class ImportJobResponseMapper { public static ImportReportResponse from(ImportJob importJob) { diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/application/importpoc/mapper/PolicyMapper.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/application/importpoc/mapper/PolicyMapper.java new file mode 100644 index 0000000000..a97c95f107 --- /dev/null +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/application/importpoc/mapper/PolicyMapper.java @@ -0,0 +1,75 @@ +/******************************************************************************** + * 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.assets.application.importpoc.mapper; + +import assets.importpoc.ConstraintResponse; +import assets.importpoc.ConstraintsResponse; +import assets.importpoc.PermissionResponse; +import assets.importpoc.PolicyResponse; +import lombok.experimental.UtilityClass; +import org.eclipse.tractusx.irs.edc.client.asset.model.OdrlContext; +import org.eclipse.tractusx.irs.edc.client.contract.model.EdcOperator; +import org.eclipse.tractusx.irs.edc.client.policy.model.EdcCreatePolicyDefinitionRequest; +import org.eclipse.tractusx.irs.edc.client.policy.model.EdcPolicy; +import org.eclipse.tractusx.irs.edc.client.policy.model.EdcPolicyPermission; +import org.eclipse.tractusx.irs.edc.client.policy.model.EdcPolicyPermissionConstraint; +import org.eclipse.tractusx.irs.edc.client.policy.model.EdcPolicyPermissionConstraintExpression; + +import java.util.List; + +@UtilityClass +public class PolicyMapper { + public static EdcCreatePolicyDefinitionRequest mapToEdcPolicyRequest(PolicyResponse policy) { + OdrlContext odrlContext = OdrlContext.builder().odrl("http://www.w3.org/ns/odrl/2/").build(); + EdcPolicy edcPolicy = EdcPolicy.builder().odrlPermissions(mapToPermissions(policy.permissions())).type("Policy").build(); + return EdcCreatePolicyDefinitionRequest.builder() + .policyDefinitionId(policy.policyId()) + .policy(edcPolicy) + .odrlContext(odrlContext) + .type("PolicyDefinitionRequestDto") + .build(); + } + + private static List mapToPermissions(List permissions) { + return permissions.stream().map(permission -> EdcPolicyPermission.builder() + .action(permission.action().name()) + .edcPolicyPermissionConstraints(mapToConstraint(permission.constraints())) + .build() + ).toList(); + } + + private static EdcPolicyPermissionConstraint mapToConstraint(ConstraintsResponse constraintsResponse) { + return EdcPolicyPermissionConstraint.builder() + .type("AtomicConstraint") + .orExpressions(mapToConstraintExpression(constraintsResponse.or())) + .build(); + } + + private static List mapToConstraintExpression(List constraints) { + return constraints.stream().map(constraint -> EdcPolicyPermissionConstraintExpression.builder() + .type("Constraint") + .leftOperand(constraint.leftOperand()) + .rightOperand(constraint.rightOperand()) + .operator(EdcOperator.builder() + .operatorId("odrl:" + constraint.operatorTypeResponse().getCode()) + .build()) + .build()) + .toList(); + } +} diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/asbuilt/service/AssetAsBuiltServiceImpl.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/asbuilt/service/AssetAsBuiltServiceImpl.java index be2f9f5775..93b8c389f6 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/asbuilt/service/AssetAsBuiltServiceImpl.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/asbuilt/service/AssetAsBuiltServiceImpl.java @@ -24,7 +24,7 @@ import org.eclipse.tractusx.traceability.assets.domain.asbuilt.repository.AssetAsBuiltRepository; import org.eclipse.tractusx.traceability.assets.domain.asbuilt.repository.AssetAsBuiltViewRepository; import org.eclipse.tractusx.traceability.assets.domain.base.AssetRepository; -import org.eclipse.tractusx.traceability.assets.domain.base.IrsRepository; +import org.eclipse.tractusx.traceability.assets.domain.base.JobRepository; import org.eclipse.tractusx.traceability.assets.domain.base.model.AssetBase; import org.eclipse.tractusx.traceability.assets.domain.base.service.AbstractAssetBaseService; import org.eclipse.tractusx.traceability.assets.infrastructure.asbuilt.model.ManufacturingInfo; @@ -48,7 +48,7 @@ public class AssetAsBuiltServiceImpl extends AbstractAssetBaseService { private final AssetAsBuiltViewRepository assetAsBuiltViewRepository; - private final IrsRepository irsRepository; + private final JobRepository jobRepository; @Override protected AssetRepository getAssetRepository() { @@ -70,9 +70,8 @@ protected BomLifecycle getBomLifecycle() { return BomLifecycle.AS_BUILT; } - @Override - protected IrsRepository getIrsRepository() { - return irsRepository; + protected JobRepository getJobRepository() { + return jobRepository; } @Override diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/asplanned/service/AssetAsPlannedServiceImpl.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/asplanned/service/AssetAsPlannedServiceImpl.java index fd73734410..302e2ac429 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/asplanned/service/AssetAsPlannedServiceImpl.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/asplanned/service/AssetAsPlannedServiceImpl.java @@ -23,7 +23,7 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.tractusx.traceability.assets.domain.asplanned.repository.AssetAsPlannedRepository; import org.eclipse.tractusx.traceability.assets.domain.base.AssetRepository; -import org.eclipse.tractusx.traceability.assets.domain.base.IrsRepository; +import org.eclipse.tractusx.traceability.assets.domain.base.JobRepository; import org.eclipse.tractusx.traceability.assets.domain.base.model.AssetBase; import org.eclipse.tractusx.traceability.assets.domain.base.service.AbstractAssetBaseService; import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.request.BomLifecycle; @@ -43,7 +43,7 @@ public class AssetAsPlannedServiceImpl extends AbstractAssetBaseService { private final AssetAsPlannedRepository assetAsPlannedRepository; - private final IrsRepository irsRepository; + private final JobRepository jobRepository; @Override protected AssetRepository getAssetRepository() { @@ -65,9 +65,8 @@ protected BomLifecycle getBomLifecycle() { return BomLifecycle.AS_PLANNED; } - @Override - protected IrsRepository getIrsRepository() { - return irsRepository; + protected JobRepository getJobRepository() { + return jobRepository; } @Override diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/base/IrsRepository.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/base/JobRepository.java similarity index 89% rename from tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/base/IrsRepository.java rename to tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/base/JobRepository.java index d90dd55e2e..79a8196952 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/base/IrsRepository.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/base/JobRepository.java @@ -23,11 +23,10 @@ import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.request.BomLifecycle; import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.Direction; -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.IrsPolicyResponse; import java.util.List; -public interface IrsRepository { +public interface JobRepository { /** * Finds a list of assets with the given global asset ID and direction. * @@ -37,9 +36,7 @@ public interface IrsRepository { */ void createJobToResolveAssets(String globalAssetId, Direction direction, List aspects, BomLifecycle bomLifecycle); - void createIrsPolicyIfMissing(); - void handleJobFinishedCallback(String jobId, String jobState); - List getPolicies(); + } diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/JobRunning.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/base/PolicyRepository.java similarity index 57% rename from tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/JobRunning.java rename to tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/base/PolicyRepository.java index 4a384ca56d..fb9ce4cfe9 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/JobRunning.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/base/PolicyRepository.java @@ -1,7 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022, 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * Copyright (c) 2022, 2023 ZF Friedrichshafen AG - * Copyright (c) 2022, 2023 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -18,17 +16,14 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +package org.eclipse.tractusx.traceability.assets.domain.base; -package org.eclipse.tractusx.traceability.assets.infrastructure.base.irs; +import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.IrsPolicyResponse; -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.IRSResponse; +import java.util.List; -import java.util.function.Predicate; +public interface PolicyRepository { + List getPolicies(); -// TODO is this still needed - as we have changed from async runner for IRS Jobs to callback approach this should be deprecated. (MW) -public class JobRunning implements Predicate { - @Override - public boolean test(IRSResponse jobResponse) { - return IrsRepositoryImpl.jobRunning(jobResponse.jobStatus()); - } + void createIrsPolicyIfMissing(); } diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/base/service/AbstractAssetBaseService.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/base/service/AbstractAssetBaseService.java index b004fc72ad..60cfec21a7 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/base/service/AbstractAssetBaseService.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/base/service/AbstractAssetBaseService.java @@ -21,7 +21,7 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.tractusx.traceability.assets.application.base.service.AssetBaseService; import org.eclipse.tractusx.traceability.assets.domain.base.AssetRepository; -import org.eclipse.tractusx.traceability.assets.domain.base.IrsRepository; +import org.eclipse.tractusx.traceability.assets.domain.base.JobRepository; import org.eclipse.tractusx.traceability.assets.domain.base.model.AssetBase; import org.eclipse.tractusx.traceability.assets.domain.base.model.ImportState; import org.eclipse.tractusx.traceability.assets.domain.base.model.Owner; @@ -47,7 +47,7 @@ public abstract class AbstractAssetBaseService implements AssetBaseService { protected abstract AssetRepository getAssetRepository(); - protected abstract IrsRepository getIrsRepository(); + protected abstract JobRepository getJobRepository(); protected abstract List getDownwardAspects(); @@ -61,13 +61,13 @@ public void synchronizeAssetsAsync(String globalAssetId) { log.info("Synchronizing assets for globalAssetId: {}", globalAssetId); try { if (!getDownwardAspects().isEmpty()) { - getIrsRepository().createJobToResolveAssets(globalAssetId, Direction.DOWNWARD, getDownwardAspects(), getBomLifecycle()); + getJobRepository().createJobToResolveAssets(globalAssetId, Direction.DOWNWARD, getDownwardAspects(), getBomLifecycle()); } if (!getUpwardAspects().isEmpty()) { // TODO: change BomLifecycle.AS_BUILT to getBomLifecycle() - getIrsRepository().createJobToResolveAssets(globalAssetId, Direction.UPWARD, Aspect.upwardAspectsForAssetsAsBuilt(), BomLifecycle.AS_BUILT); + getJobRepository().createJobToResolveAssets(globalAssetId, Direction.UPWARD, Aspect.upwardAspectsForAssetsAsBuilt(), BomLifecycle.AS_BUILT); } } catch (Exception e) { diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/importpoc/service/EdcAssetCreationService.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/importpoc/service/EdcAssetCreationService.java index be715d1a3c..3f64fb8640 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/importpoc/service/EdcAssetCreationService.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/importpoc/service/EdcAssetCreationService.java @@ -19,24 +19,14 @@ package org.eclipse.tractusx.traceability.assets.domain.importpoc.service; -import assets.importpoc.ConstraintResponse; -import assets.importpoc.ConstraintsResponse; -import assets.importpoc.PermissionResponse; import assets.importpoc.PolicyResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.tractusx.irs.edc.client.asset.EdcAssetService; -import org.eclipse.tractusx.irs.edc.client.asset.model.OdrlContext; import org.eclipse.tractusx.irs.edc.client.asset.model.exception.CreateEdcAssetException; import org.eclipse.tractusx.irs.edc.client.asset.model.exception.EdcAssetAlreadyExistsException; -import org.eclipse.tractusx.irs.edc.client.contract.model.EdcOperator; import org.eclipse.tractusx.irs.edc.client.contract.model.exception.CreateEdcContractDefinitionException; import org.eclipse.tractusx.irs.edc.client.contract.service.EdcContractDefinitionService; -import org.eclipse.tractusx.irs.edc.client.policy.model.EdcCreatePolicyDefinitionRequest; -import org.eclipse.tractusx.irs.edc.client.policy.model.EdcPolicy; -import org.eclipse.tractusx.irs.edc.client.policy.model.EdcPolicyPermission; -import org.eclipse.tractusx.irs.edc.client.policy.model.EdcPolicyPermissionConstraint; -import org.eclipse.tractusx.irs.edc.client.policy.model.EdcPolicyPermissionConstraintExpression; import org.eclipse.tractusx.irs.edc.client.policy.model.exception.CreateEdcPolicyDefinitionException; import org.eclipse.tractusx.irs.edc.client.policy.model.exception.EdcPolicyDefinitionAlreadyExists; import org.eclipse.tractusx.irs.edc.client.policy.service.EdcPolicyDefinitionService; @@ -45,9 +35,10 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import java.util.List; import java.util.UUID; +import static org.eclipse.tractusx.traceability.assets.application.importpoc.mapper.PolicyMapper.mapToEdcPolicyRequest; + @Slf4j @Service @RequiredArgsConstructor @@ -110,42 +101,4 @@ public String createEdcContractDefinitionsForDtrAndSubmodel(String policyId) thr return submodelAssetId; } - - private EdcCreatePolicyDefinitionRequest mapToEdcPolicyRequest(PolicyResponse policy) { - OdrlContext odrlContext = OdrlContext.builder().odrl("http://www.w3.org/ns/odrl/2/").build(); - EdcPolicy edcPolicy = EdcPolicy.builder().odrlPermissions(mapToPermissions(policy.permissions())).type("Policy").build(); - return EdcCreatePolicyDefinitionRequest.builder() - .policyDefinitionId(policy.policyId()) - .policy(edcPolicy) - .odrlContext(odrlContext) - .type("PolicyDefinitionRequestDto") - .build(); - } - - private List mapToPermissions(List permissions) { - return permissions.stream().map(permission -> EdcPolicyPermission.builder() - .action(permission.action().name()) - .edcPolicyPermissionConstraints(mapToConstraint(permission.constraints())) - .build() - ).toList(); - } - - private EdcPolicyPermissionConstraint mapToConstraint(ConstraintsResponse constraintsResponse) { - return EdcPolicyPermissionConstraint.builder() - .type("AtomicConstraint") - .orExpressions(mapToConstraintExpression(constraintsResponse.or())) - .build(); - } - - private List mapToConstraintExpression(List constraints) { - return constraints.stream().map(constraint -> EdcPolicyPermissionConstraintExpression.builder() - .type("Constraint") - .leftOperand(constraint.leftOperand()) - .rightOperand(constraint.rightOperand()) - .operator(EdcOperator.builder() - .operatorId("odrl:" + constraint.operatorTypeResponse().getCode()) - .build()) - .build()) - .toList(); - } } diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/importpoc/service/PolicyServiceImpl.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/importpoc/service/PolicyServiceImpl.java index 17d1113b3c..027ae5c14f 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/importpoc/service/PolicyServiceImpl.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/importpoc/service/PolicyServiceImpl.java @@ -22,13 +22,18 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.tractusx.traceability.assets.application.importpoc.PolicyService; -import org.eclipse.tractusx.traceability.assets.domain.base.IrsRepository; +import org.eclipse.tractusx.traceability.assets.domain.base.PolicyRepository; import org.eclipse.tractusx.traceability.assets.domain.importpoc.exception.PolicyNotFoundException; import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.IrsPolicyResponse; +import org.eclipse.tractusx.traceability.common.properties.TraceabilityProperties; import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Service; +import java.util.AbstractMap; import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; import static org.apache.commons.collections4.ListUtils.emptyIfNull; @@ -37,7 +42,8 @@ @Service public class PolicyServiceImpl implements PolicyService { - private final IrsRepository irsRepository; + private final PolicyRepository policyRepository; + private final TraceabilityProperties traceabilityProperties; @Override public List getAllPolicies() { @@ -52,8 +58,23 @@ public PolicyResponse getPolicyById(String id) { .orElseThrow(() -> new PolicyNotFoundException("Policy with id: %s not found.".formatted(id))); } + @Override + public Optional getFirstPolicyMatchingApplicationConstraint() { + Optional policyId = getAllPolicies().stream() + .flatMap(policyResponse -> policyResponse.permissions().stream() + .flatMap(permissionResponse -> Stream.concat( + permissionResponse.constraints().and().stream(), + permissionResponse.constraints().or().stream()) + .map(constraintResponse -> new AbstractMap.SimpleEntry<>(policyResponse.policyId(), constraintResponse)))) + .filter(entry -> entry.getValue().rightOperand().equalsIgnoreCase(traceabilityProperties.getRightOperand())) + .map(Map.Entry::getKey) + .findFirst(); + return policyId.map(this::getPolicyById); + } + + @NotNull private List getAcceptedPoliciesOrEmptyList() { - return emptyIfNull(irsRepository.getPolicies()); + return emptyIfNull(policyRepository.getPolicies()); } } diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/IrsCallbackController.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/IrsCallbackController.java index 3bd3ec8e28..b033326c1c 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/IrsCallbackController.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/IrsCallbackController.java @@ -29,7 +29,7 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.eclipse.tractusx.traceability.assets.domain.base.IrsRepository; +import org.eclipse.tractusx.traceability.assets.domain.base.JobRepository; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -40,7 +40,7 @@ @RequiredArgsConstructor public class IrsCallbackController { - private final IrsRepository irsRepository; + private final JobRepository jobRepository; @Operation(operationId = "irsCallback", summary = "Callback of irs get job details", @@ -95,7 +95,7 @@ public class IrsCallbackController { void handleIrsJobCallback(@RequestParam("id") String jobId, @RequestParam("state") String jobState) { // Security measurment for injection if (jobId.matches("^[a-zA-Z0-9_-]*$")) { - irsRepository.handleJobFinishedCallback(jobId, jobState); + jobRepository.handleJobFinishedCallback(jobId, jobState); } } } diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/IrsRepositoryImpl.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/JobRepositoryImpl.java similarity index 69% rename from tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/IrsRepositoryImpl.java rename to tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/JobRepositoryImpl.java index 341792867e..0998c89568 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/IrsRepositoryImpl.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/JobRepositoryImpl.java @@ -22,8 +22,7 @@ package org.eclipse.tractusx.traceability.assets.infrastructure.base.irs; import lombok.extern.slf4j.Slf4j; -import org.eclipse.tractusx.irs.edc.client.policy.Constraints; -import org.eclipse.tractusx.traceability.assets.domain.base.IrsRepository; +import org.eclipse.tractusx.traceability.assets.domain.base.JobRepository; import org.eclipse.tractusx.traceability.assets.domain.base.model.AssetBase; import org.eclipse.tractusx.traceability.assets.domain.base.model.ImportNote; import org.eclipse.tractusx.traceability.assets.domain.base.model.ImportState; @@ -32,7 +31,6 @@ import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.request.RegisterJobRequest; import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.Direction; import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.IRSResponse; -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.IrsPolicyResponse; import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.JobStatus; import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.factory.AssetMapperFactory; import org.eclipse.tractusx.traceability.bpn.domain.service.BpnRepository; @@ -44,16 +42,14 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.stream.Stream; -import static org.apache.commons.collections4.ListUtils.emptyIfNull; import static org.eclipse.tractusx.irs.component.enums.BomLifecycle.AS_BUILT; import static org.eclipse.tractusx.irs.component.enums.BomLifecycle.AS_PLANNED; import static org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.factory.AssetMapperFactory.extractBpnMap; @Slf4j @Service -public class IrsRepositoryImpl implements IrsRepository { +public class JobRepositoryImpl implements JobRepository { private final BpnRepository bpnRepository; private final TraceabilityProperties traceabilityProperties; @@ -67,7 +63,7 @@ public class IrsRepositoryImpl implements IrsRepository { private final IrsClient irsClient; - public IrsRepositoryImpl( + public JobRepositoryImpl( IrsClient irsClient, BpnRepository bpnRepository, TraceabilityProperties traceabilityProperties, @@ -144,64 +140,7 @@ void saveOrUpdateAssets(AssetCallbackRepository repository, AssetBase asset) { } } - @Override - public void createIrsPolicyIfMissing() { - log.info("Check if irs policy exists"); - final List irsPolicies = this.irsClient.getPolicies(); - final List irsPoliciesIds = irsPolicies.stream().map(policyResponse -> policyResponse.payload().policyId()).toList(); - log.info("Irs has following policies: {}", irsPoliciesIds); - - log.info("Required constraints from application yaml are : {}", traceabilityProperties.getRightOperand()); - - IrsPolicyResponse matchingPolicy = findMatchingPolicy(irsPolicies); - - if (matchingPolicy == null) { - createMissingPolicies(); - } else { - checkAndUpdatePolicy(matchingPolicy); - } - } - - private IrsPolicyResponse findMatchingPolicy(List irsPolicies) { - return irsPolicies.stream() - .filter(irsPolicy -> emptyIfNull(irsPolicy.payload().policy().getPermissions()).stream() - .flatMap(permission -> { - Constraints constraint = permission.getConstraint(); - return constraint != null ? constraint.getAnd().stream() : Stream.empty(); - }) - .anyMatch(constraint -> constraint.getRightOperand().equals(traceabilityProperties.getRightOperand())) - || emptyIfNull(irsPolicy.payload().policy().getPermissions()).stream() - .flatMap(permission -> { - Constraints constraint = permission.getConstraint(); - return constraint != null ? constraint.getOr().stream() : Stream.empty(); - }) - .anyMatch(constraint -> constraint.getRightOperand().equals(traceabilityProperties.getRightOperand()))) - .findFirst() - .orElse(null); - } - - private void createMissingPolicies() { - log.info("Irs policy does not exist creating {}", traceabilityProperties.getRightOperand()); - this.irsClient.registerPolicy(); - } - - private void checkAndUpdatePolicy(IrsPolicyResponse requiredPolicy) { - if (isPolicyExpired(requiredPolicy)) { - log.info("IRS Policy {} has outdated validity updating new ttl", traceabilityProperties.getRightOperand()); - this.irsClient.deletePolicy(); - this.irsClient.registerPolicy(); - } - } - - private boolean isPolicyExpired(IrsPolicyResponse requiredPolicy) { - return traceabilityProperties.getValidUntil().isAfter(requiredPolicy.validUntil()); - } - - @Override - public List getPolicies() { - return irsClient.getPolicies(); - } public static boolean jobCompleted(JobStatus jobStatus) { return JOB_STATUS_COMPLETED.equals(jobStatus.state()); diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/PolicyRepositoryImpl.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/PolicyRepositoryImpl.java new file mode 100644 index 0000000000..8f2636baf2 --- /dev/null +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/PolicyRepositoryImpl.java @@ -0,0 +1,99 @@ +/******************************************************************************** + * 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.assets.infrastructure.base.irs; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.tractusx.irs.edc.client.policy.Constraints; +import org.eclipse.tractusx.traceability.assets.domain.base.PolicyRepository; +import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.IrsPolicyResponse; +import org.eclipse.tractusx.traceability.common.properties.TraceabilityProperties; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Stream; + +import static org.apache.commons.collections4.ListUtils.emptyIfNull; +@Slf4j +@Service +@RequiredArgsConstructor +public class PolicyRepositoryImpl implements PolicyRepository { + + private final IrsClient irsClient; + private final TraceabilityProperties traceabilityProperties; + + @Override + public List getPolicies() { + return irsClient.getPolicies(); + } + @Override + public void createIrsPolicyIfMissing() { + log.info("Check if irs policy exists"); + final List irsPolicies = this.irsClient.getPolicies(); + final List irsPoliciesIds = irsPolicies.stream().map(policyResponse -> policyResponse.payload().policyId()).toList(); + log.info("Irs has following policies: {}", irsPoliciesIds); + + log.info("Required constraints from application yaml are : {}", traceabilityProperties.getRightOperand()); + + IrsPolicyResponse matchingPolicy = findMatchingPolicy(irsPolicies); + + if (matchingPolicy == null) { + createMissingPolicies(); + } else { + checkAndUpdatePolicy(matchingPolicy); + } + } + + private IrsPolicyResponse findMatchingPolicy(List irsPolicies) { + return irsPolicies.stream() + .filter(irsPolicy -> emptyIfNull(irsPolicy.payload().policy().getPermissions()).stream() + .flatMap(permission -> { + Constraints constraint = permission.getConstraint(); + return constraint != null ? constraint.getAnd().stream() : Stream.empty(); + }) + .anyMatch(constraint -> constraint.getRightOperand().equals(traceabilityProperties.getRightOperand())) + || emptyIfNull(irsPolicy.payload().policy().getPermissions()).stream() + .flatMap(permission -> { + Constraints constraint = permission.getConstraint(); + return constraint != null ? constraint.getOr().stream() : Stream.empty(); + }) + .anyMatch(constraint -> constraint.getRightOperand().equals(traceabilityProperties.getRightOperand()))) + .findFirst() + .orElse(null); + } + + + private void createMissingPolicies() { + log.info("Irs policy does not exist creating {}", traceabilityProperties.getRightOperand()); + this.irsClient.registerPolicy(); + } + + private void checkAndUpdatePolicy(IrsPolicyResponse requiredPolicy) { + if (isPolicyExpired(requiredPolicy)) { + log.info("IRS Policy {} has outdated validity updating new ttl", traceabilityProperties.getRightOperand()); + this.irsClient.deletePolicy(); + this.irsClient.registerPolicy(); + } + } + + private boolean isPolicyExpired(IrsPolicyResponse requiredPolicy) { + return traceabilityProperties.getValidUntil().isAfter(requiredPolicy.validUntil()); + } + +} diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/ApplicationConfig.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/ApplicationConfig.java index 364f33316b..54b0809cc9 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/ApplicationConfig.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/ApplicationConfig.java @@ -22,15 +22,8 @@ package org.eclipse.tractusx.traceability.common.config; -import io.github.resilience4j.core.registry.EntryAddedEvent; -import io.github.resilience4j.core.registry.EntryRemovedEvent; -import io.github.resilience4j.core.registry.EntryReplacedEvent; -import io.github.resilience4j.core.registry.RegistryEventConsumer; -import io.github.resilience4j.retry.Retry; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationPropertiesScan; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -42,10 +35,6 @@ import org.springframework.security.task.DelegatingSecurityContextAsyncTaskExecutor; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.view.InternalResourceViewResolver; -import org.thymeleaf.spring6.SpringTemplateEngine; -import org.thymeleaf.templatemode.TemplateMode; -import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver; -import org.thymeleaf.templateresolver.ITemplateResolver; @Configuration @@ -63,13 +52,6 @@ public InternalResourceViewResolver defaultViewResolver() { return new InternalResourceViewResolver(); } - @Bean - public SpringTemplateEngine thymeleafTemplateEngine() { - SpringTemplateEngine templateEngine = new SpringTemplateEngine(); - templateEngine.addTemplateResolver(htmlTemplateResolver()); - templateEngine.addTemplateResolver(textTemplateResolver()); - return templateEngine; - } @Bean(name = "security-context-async") public ThreadPoolTaskExecutor securityContextAsyncExecutor() { @@ -85,42 +67,4 @@ public ThreadPoolTaskExecutor securityContextAsyncExecutor() { public DelegatingSecurityContextAsyncTaskExecutor taskExecutor(@Qualifier("security-context-async") ThreadPoolTaskExecutor threadPoolTaskExecutor) { return new DelegatingSecurityContextAsyncTaskExecutor(threadPoolTaskExecutor); } - - public ITemplateResolver htmlTemplateResolver() { - ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver(); - templateResolver.setSuffix(".html"); - templateResolver.setTemplateMode(TemplateMode.HTML); - templateResolver.setCharacterEncoding("UTF-8"); - return templateResolver; - } - - public ITemplateResolver textTemplateResolver() { - ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver(); - templateResolver.setSuffix(".txt"); - templateResolver.setTemplateMode(TemplateMode.TEXT); - templateResolver.setCharacterEncoding("UTF-8"); - return templateResolver; - } - - - @Bean - public RegistryEventConsumer myRetryRegistryEventConsumer() { - final Logger logger = LoggerFactory.getLogger("RetryLogger"); - - return new RegistryEventConsumer<>() { - @Override - public void onEntryAddedEvent(EntryAddedEvent entryAddedEvent) { - entryAddedEvent.getAddedEntry().getEventPublisher() - .onEvent(event -> logger.info(event.toString())); - } - - @Override - public void onEntryReplacedEvent(EntryReplacedEvent entryReplacedEvent) { - } - - @Override - public void onEntryRemovedEvent(EntryRemovedEvent entryRemoveEvent) { - } - }; - } } diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/ApplicationStartupConfig.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/ApplicationStartupConfig.java index ec58218dc0..6b113d08d5 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/ApplicationStartupConfig.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/ApplicationStartupConfig.java @@ -21,7 +21,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.eclipse.tractusx.traceability.assets.domain.base.IrsRepository; +import org.eclipse.tractusx.traceability.assets.domain.base.PolicyRepository; import org.eclipse.tractusx.traceability.qualitynotification.application.contract.model.CreateNotificationContractRequest; import org.eclipse.tractusx.traceability.qualitynotification.application.contract.model.NotificationMethod; import org.eclipse.tractusx.traceability.qualitynotification.application.contract.model.NotificationType; @@ -43,7 +43,7 @@ @Profile(NOT_INTEGRATION_TESTS) @RequiredArgsConstructor public class ApplicationStartupConfig { - private final IrsRepository irsRepository; + private final PolicyRepository policyRepository; private final EdcNotificationContractService edcNotificationContractService; private static final List NOTIFICATION_CONTRACTS = List.of( new CreateNotificationContractRequest(NotificationType.QUALITY_ALERT, NotificationMethod.UPDATE), @@ -58,7 +58,7 @@ public void registerIrsPolicy() { executor.execute(() -> { try { - irsRepository.createIrsPolicyIfMissing(); + policyRepository.createIrsPolicyIfMissing(); } catch (Exception exception) { log.error("Failed to create Irs Policies: ", exception); } 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 0e56515b02..7e8471a767 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 @@ -33,11 +33,10 @@ import org.eclipse.tractusx.irs.edc.client.policy.Permission; import org.eclipse.tractusx.irs.edc.client.policy.Policy; import org.eclipse.tractusx.irs.edc.client.policy.PolicyType; -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.IrsRepositoryImpl; +import org.eclipse.tractusx.traceability.assets.domain.base.PolicyRepository; import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.IrsPolicyResponse; import org.eclipse.tractusx.traceability.common.properties.TraceabilityProperties; import org.jetbrains.annotations.NotNull; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationPropertiesScan; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -60,18 +59,17 @@ @EnableWebMvc @EnableAsync(proxyTargetClass = true) @EnableConfigurationProperties -@RequiredArgsConstructor @Slf4j @EnableJpaRepositories(basePackages = "org.eclipse.tractusx.traceability.*") +@RequiredArgsConstructor @Profile(NOT_INTEGRATION_TESTS) public class PolicyStartUpConfig { private final AcceptedPoliciesProvider.DefaultAcceptedPoliciesProvider defaultAcceptedPoliciesProvider; - @Autowired - TraceabilityProperties traceabilityProperties; - @Autowired + private final TraceabilityProperties traceabilityProperties; + @Lazy - IrsRepositoryImpl irsRepositoryImpl; + private final PolicyRepository policyRepository; @PostConstruct @ConditionalOnProperty(name = "applicationConfig.registerDecentralRegistryPermissions.enabled", havingValue = "true") @@ -98,7 +96,7 @@ private List buildAcceptedPolicies() { private List createIrsAcceptedPolicies() { - List irsPolicyResponse = irsRepositoryImpl.getPolicies(); + List irsPolicyResponse = policyRepository.getPolicies(); List irsPolicies = irsPolicyResponse.stream().map(response -> { Policy policy = new Policy(response.payload().policyId(), response.payload().policy().getCreatedOn(), response.validUntil(), response.payload().policy().getPermissions()); return new AcceptedPolicy(policy, response.validUntil()); diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/SchedulerConfig.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/SchedulerConfig.java index f2c7e9ab6c..1439710d70 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/SchedulerConfig.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/SchedulerConfig.java @@ -21,15 +21,11 @@ package org.eclipse.tractusx.traceability.common.config; -import net.javacrumbs.shedlock.core.LockProvider; -import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; -import javax.sql.DataSource; - import static org.eclipse.tractusx.traceability.common.config.ApplicationProfiles.NOT_INTEGRATION_TESTS; @Configuration @@ -44,9 +40,4 @@ public ThreadPoolTaskScheduler threadPoolTaskScheduler() { return threadPoolTaskScheduler; } - @Bean - public LockProvider lockProvider(DataSource dataSource) { - return new JdbcTemplateLockProvider(dataSource); - } - } diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/properties/EdcProperties.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/properties/EdcProperties.java index 6ef642c1a0..c7cbfc03b1 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/properties/EdcProperties.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/properties/EdcProperties.java @@ -35,18 +35,6 @@ public class EdcProperties { @Value("${edc.ids.path}") private String idsPath; - @NotBlank - @Value("${edc.contractdefinitions}") - private String contractDefinitionsPath; - - @NotBlank - @Value("${edc.policydefinitions}") - private String policyDefinitionsPath; - - @NotBlank - @Value("${edc.assets}") - private String assetsPath; - @NotBlank @Value("${edc.provider-edc-url}") private String providerEdcUrl; diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/properties/FeignProperties.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/properties/FeignProperties.java index 2e41282501..bb53cbf416 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/properties/FeignProperties.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/properties/FeignProperties.java @@ -21,6 +21,9 @@ package org.eclipse.tractusx.traceability.common.properties; +import lombok.Getter; + +@Getter public class FeignProperties { private final Long connectionTimeoutMillis; @@ -38,19 +41,4 @@ public FeignProperties(Long connectionTimeoutMillis, this.keepAliveDurationMinutes = keepAliveDurationMinutes; } - public Long getConnectionTimeoutMillis() { - return connectionTimeoutMillis; - } - - public Long getReadTimeoutMillis() { - return readTimeoutMillis; - } - - public int getMaxIdleConnections() { - return maxIdleConnections; - } - - public Long getKeepAliveDurationMinutes() { - return keepAliveDurationMinutes; - } } diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/properties/TraceabilityProperties.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/properties/TraceabilityProperties.java index d4bf2e4dc6..06160d5607 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/properties/TraceabilityProperties.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/properties/TraceabilityProperties.java @@ -52,8 +52,4 @@ public class TraceabilityProperties { private String irsBase; private String submodelBase; - public String getIrsJobCallbackUrl() { - return url+"/irs/job/callback?id={id}&state={state}"; - } - } diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/discovery/domain/service/DiscoveryServiceImpl.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/discovery/domain/service/DiscoveryServiceImpl.java index e36ca98051..7f094a4b63 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/discovery/domain/service/DiscoveryServiceImpl.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/discovery/domain/service/DiscoveryServiceImpl.java @@ -64,7 +64,7 @@ public Discovery getDiscoveryByBPN(String bpn) { }); optionalDiscoveryFromDiscoveryService.ifPresent(discoveryList::add); } catch (Exception e) { - throw new DiscoveryFinderException("DiscoverFinder not reachable."); + throw new DiscoveryFinderException("DiscoveryFinder could not determine result."); } Optional optionalDiscoveryFromBpnDatabase = getOptionalDiscoveryFromBpnDatabase(bpn); diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/qualitynotification/domain/base/service/EdcNotificationServiceImpl.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/qualitynotification/domain/base/service/EdcNotificationServiceImpl.java index f7c98a05d5..b54ecc86c5 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/qualitynotification/domain/base/service/EdcNotificationServiceImpl.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/qualitynotification/domain/base/service/EdcNotificationServiceImpl.java @@ -28,6 +28,7 @@ import org.eclipse.tractusx.traceability.discovery.domain.model.Discovery; import org.eclipse.tractusx.traceability.discovery.domain.service.DiscoveryService; import org.eclipse.tractusx.traceability.discovery.infrastructure.exception.DiscoveryFinderException; +import org.eclipse.tractusx.traceability.qualitynotification.domain.base.AlertRepository; import org.eclipse.tractusx.traceability.qualitynotification.domain.base.InvestigationRepository; import org.eclipse.tractusx.traceability.qualitynotification.domain.base.exception.ContractNegotiationException; import org.eclipse.tractusx.traceability.qualitynotification.domain.base.exception.NoCatalogItemException; @@ -57,35 +58,43 @@ public class EdcNotificationServiceImpl implements EdcNotificationService { private final InvestigationsEDCFacade edcFacade; private final DiscoveryService discoveryService; private final InvestigationRepository investigationRepository; + private final AlertRepository alertRepository; @Override @Async(value = AssetsAsyncConfig.UPDATE_NOTIFICATION_EXECUTOR) public CompletableFuture asyncNotificationMessageExecutor(QualityNotificationMessage message) { log.info("::asyncNotificationExecutor::message {}", message); - Discovery discovery = discoveryService.getDiscoveryByBPN(message.getSendTo()); - String senderEdcUrl = discovery.getSenderUrl(); - List receiverUrls = emptyIfNull(discovery.getReceiverUrls()); - List sendResults = List.of(); - - if (message.getType().equals(QualityNotificationType.ALERT)) { - log.info("::asyncNotificationExecutor::isQualityAlert"); - sendResults = receiverUrls - .stream().map(receiverUrl -> handleSendingNotification(message, senderEdcUrl, receiverUrl)).toList(); - } + try { + Discovery discovery = discoveryService.getDiscoveryByBPN(message.getSendTo()); - if (message.getType().equals(QualityNotificationType.INVESTIGATION)) { - log.info("::asyncNotificationExecutor::isQualityInvestigation"); - sendResults = receiverUrls - .stream().map(receiverUrl -> handleSendingNotification(message, senderEdcUrl, receiverUrl)).toList(); - } + String senderEdcUrl = discovery.getSenderUrl(); + List receiverUrls = emptyIfNull(discovery.getReceiverUrls()); + List sendResults = List.of(); - Boolean wasSent = sendResults.stream().anyMatch(Boolean.TRUE::equals); + if (message.getType().equals(QualityNotificationType.ALERT)) { + log.info("::asyncNotificationExecutor::isQualityAlert"); + sendResults = receiverUrls + .stream().map(receiverUrl -> handleSendingNotification(message, senderEdcUrl, receiverUrl)).toList(); + } - if (Boolean.TRUE.equals(wasSent)) { - return CompletableFuture.completedFuture(message); - } + if (message.getType().equals(QualityNotificationType.INVESTIGATION)) { + log.info("::asyncNotificationExecutor::isQualityInvestigation"); + sendResults = receiverUrls + .stream().map(receiverUrl -> handleSendingNotification(message, senderEdcUrl, receiverUrl)).toList(); + } + + Boolean wasSent = sendResults.stream().anyMatch(Boolean.TRUE::equals); + + if (Boolean.TRUE.equals(wasSent)) { + return CompletableFuture.completedFuture(message); + } - return CompletableFuture.completedFuture(null); + return CompletableFuture.completedFuture(null); + + } catch (DiscoveryFinderException discoveryFinderException) { + enrichQualityNotificationByError(discoveryFinderException, message); + return CompletableFuture.completedFuture(null); + } } private boolean handleSendingNotification(QualityNotificationMessage message, String senderEdcUrl, String receiverUrl) { @@ -110,19 +119,27 @@ private boolean handleSendingNotification(QualityNotificationMessage message, St private void enrichQualityNotificationByError(Exception e, QualityNotificationMessage message) { log.info("Retrieving quality notification by message id {}", message.getEdcNotificationId()); + Optional optionalQualityNotificationById; + if (message.getType().equals(QualityNotificationType.INVESTIGATION)) { + optionalQualityNotificationById = investigationRepository.findByEdcNotificationId(message.getEdcNotificationId()); + } else { + optionalQualityNotificationById = alertRepository.findByEdcNotificationId(message.getEdcNotificationId()); + } - Optional optionalQualityNotificationById = investigationRepository.findByNotificationMessageId(message.getEdcNotificationId()); log.info("Successfully executed retrieving quality notification by message id"); if (optionalQualityNotificationById.isPresent()) { log.info("Quality Notification for error message enrichment {}", optionalQualityNotificationById.get()); - optionalQualityNotificationById.get().getNotifications().forEach(message1 -> { - log.info("Message found {}", message1); - }); + optionalQualityNotificationById.get().getNotifications().forEach(message1 -> log.info("Message found {}", message1)); optionalQualityNotificationById.get().secondLatestNotifications().forEach(qmMessage -> { log.info("Message from second latest notification {}", qmMessage); qmMessage.setErrorMessage(e.getMessage()); }); - investigationRepository.updateErrorMessage(optionalQualityNotificationById.get()); + + if (message.getType().equals(QualityNotificationType.INVESTIGATION)) { + investigationRepository.updateErrorMessage(optionalQualityNotificationById.get()); + } else { + alertRepository.updateErrorMessage(optionalQualityNotificationById.get()); + } } else { log.warn("Quality Notification NOT FOUND for error message enrichment notification id {}", message.getId()); } diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/qualitynotification/domain/contract/EdcNotificationContractService.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/qualitynotification/domain/contract/EdcNotificationContractService.java index 7453f1fdd7..24602c362d 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/qualitynotification/domain/contract/EdcNotificationContractService.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/qualitynotification/domain/contract/EdcNotificationContractService.java @@ -20,6 +20,7 @@ ********************************************************************************/ package org.eclipse.tractusx.traceability.qualitynotification.domain.contract; +import assets.importpoc.PolicyResponse; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.tractusx.irs.edc.client.asset.EdcAssetService; @@ -28,9 +29,13 @@ import org.eclipse.tractusx.irs.edc.client.asset.model.exception.DeleteEdcAssetException; import org.eclipse.tractusx.irs.edc.client.contract.model.exception.CreateEdcContractDefinitionException; import org.eclipse.tractusx.irs.edc.client.contract.service.EdcContractDefinitionService; +import org.eclipse.tractusx.irs.edc.client.policy.model.EdcCreatePolicyDefinitionRequest; import org.eclipse.tractusx.irs.edc.client.policy.model.exception.CreateEdcPolicyDefinitionException; import org.eclipse.tractusx.irs.edc.client.policy.model.exception.DeleteEdcPolicyDefinitionException; +import org.eclipse.tractusx.irs.edc.client.policy.model.exception.EdcPolicyDefinitionAlreadyExists; import org.eclipse.tractusx.irs.edc.client.policy.service.EdcPolicyDefinitionService; +import org.eclipse.tractusx.traceability.assets.application.importpoc.PolicyService; +import org.eclipse.tractusx.traceability.assets.application.importpoc.mapper.PolicyMapper; import org.eclipse.tractusx.traceability.common.properties.TraceabilityProperties; import org.eclipse.tractusx.traceability.qualitynotification.application.contract.model.CreateNotificationContractException; import org.eclipse.tractusx.traceability.qualitynotification.application.contract.model.CreateNotificationContractRequest; @@ -38,6 +43,8 @@ import org.eclipse.tractusx.traceability.qualitynotification.application.contract.model.NotificationMethod; import org.springframework.stereotype.Component; +import java.util.Optional; + @Slf4j @Component @AllArgsConstructor @@ -47,6 +54,8 @@ public class EdcNotificationContractService { private final EdcPolicyDefinitionService edcPolicyDefinitionService; private final EdcContractDefinitionService edcContractDefinitionService; private final TraceabilityProperties traceabilityProperties; + private final PolicyService policyService; + private static final String TRACE_FOSS_QUALITY_NOTIFICATION_INVESTIGATION_URL_TEMPLATE = "/api/qualitynotifications/%s"; private static final String TRACE_FOSS_QUALITY_NOTIFICATION_ALERT_URL_TEMPLATE = "/api/qualityalerts/%s"; @@ -69,15 +78,27 @@ public CreateNotificationContractResponse handle(CreateNotificationContractReque throw new CreateNotificationContractException(e); } + Optional optionalPolicyResponse = policyService.getFirstPolicyMatchingApplicationConstraint(); + EdcCreatePolicyDefinitionRequest edcCreatePolicyDefinitionRequest; + if (optionalPolicyResponse.isPresent()) { + edcCreatePolicyDefinitionRequest = + PolicyMapper + .mapToEdcPolicyRequest(optionalPolicyResponse.get()); + } else { + throw new CreateNotificationContractException("Could not find a policy within IRS Policy store which matches the given right operand " + traceabilityProperties.getRightOperand()); + } + String accessPolicyId = ""; try { - accessPolicyId = edcPolicyDefinitionService.createAccessPolicy(traceabilityProperties.getRightOperand()); + accessPolicyId = edcPolicyDefinitionService.createAccessPolicy(edcCreatePolicyDefinitionRequest); } catch (CreateEdcPolicyDefinitionException e) { revertNotificationAsset(notificationAssetId); throw new CreateNotificationContractException(e); + } catch (EdcPolicyDefinitionAlreadyExists alreadyExists) { + accessPolicyId = optionalPolicyResponse.get().policyId(); + log.info("Policy with id " + accessPolicyId + " already exists, using for notification contract."); } - String contractDefinitionId = ""; try { contractDefinitionId = edcContractDefinitionService.createContractDefinition(notificationAssetId, accessPolicyId); diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/qualitynotification/infrastructure/alert/model/AlertNotificationEntity.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/qualitynotification/infrastructure/alert/model/AlertNotificationEntity.java index 6be4d379ed..a300eb38a5 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/qualitynotification/infrastructure/alert/model/AlertNotificationEntity.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/qualitynotification/infrastructure/alert/model/AlertNotificationEntity.java @@ -102,6 +102,7 @@ public static AlertNotificationEntity from(AlertEntity alertEntity, .assets(notificationAssets) .notificationReferenceId(qualityNotificationMessage.getNotificationReferenceId()) .targetDate(qualityNotificationMessage.getTargetDate()) + .errorMessage(qualityNotificationMessage.getErrorMessage()) .severity(qualityNotificationMessage.getSeverity()) .edcNotificationId(qualityNotificationMessage.getEdcNotificationId()) .status(NotificationStatusBaseEntity.fromStringValue(qualityNotificationMessage.getNotificationStatus().name())) diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/shelldescriptor/application/AssetsRefreshJob.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/shelldescriptor/application/AssetsRefreshJob.java index c78aeea461..1cf62ea49d 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/shelldescriptor/application/AssetsRefreshJob.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/shelldescriptor/application/AssetsRefreshJob.java @@ -23,7 +23,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; import org.eclipse.tractusx.traceability.common.config.ApplicationProfiles; import org.springframework.context.annotation.Profile; import org.springframework.scheduling.annotation.EnableScheduling; @@ -40,11 +39,6 @@ public class AssetsRefreshJob { private final DecentralRegistryService decentralRegistryService; @Scheduled(cron = "0 0 */2 * * ?", zone = "Europe/Berlin") - @SchedulerLock( - name = "data-sync-lock", - lockAtLeastFor = "PT5M", - lockAtMostFor = "PT15M" - ) public void refresh() { log.info("Refreshing registry"); decentralRegistryService.synchronizeAssets(); 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 0e89b7222b..a4a7ee649c 100644 --- a/tx-backend/src/main/resources/application-integration-spring-boot.yml +++ b/tx-backend/src/main/resources/application-integration-spring-boot.yml @@ -29,11 +29,14 @@ traceability: adminApiKey: testAdminKey regularApiKey: testRegularKey irsBase: "http://127.0.0.1" + irsPoliciesPath: "/irs/policies" submodelBase: http://localhost:${server.port} registry: urlWithPath: "http://127.0.0.1" edc: + ids: + path: "/api/v1/dsp" api-auth-key: "integration-tests" provider-edc-url: "http://127.0.0.1" provider-dataplane-edc-url: "http://127.0.0.1" @@ -57,19 +60,6 @@ spring: jwt: resource-client: "Integration-Test" - -resilience4j: - retry: - instances: - irs-get: - resultPredicate: 'org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.JobRunning' - maxAttempts: 10 - waitDuration: 10s - enableExponentialBackoff: true - exponentialBackoffMultiplier: 2 - registry: - maxAttempts: 1 - waitDuration: 2s server: port: 9998 management: diff --git a/tx-backend/src/main/resources/application.yml b/tx-backend/src/main/resources/application.yml index fadf74af7b..ab9261e152 100644 --- a/tx-backend/src/main/resources/application.yml +++ b/tx-backend/src/main/resources/application.yml @@ -34,24 +34,10 @@ traceability: edc: ids: path: "/api/v1/dsp" - negotiation: "/management/v2/contractnegotiations" - contractdefinitions: "/management/v2/contractdefinitions" - transfer: "/management/v2/transferprocesses" - catalog: - path: "/management/v2/catalog/request" - cache: - enabled: false # Set to false to disable caching - ttl: P1D # Time after which a cached Item is no longer valid and the real catalog is called instead - maxCachedItems: 64000 # Maximum amount of cached catalog items - policydefinitions: "/management/v2/policydefinitions" - assets: "/management/v2/assets" api-auth-key: ${EDC_API_KEY} - bpn-provider-url-mappings: { } provider-edc-url: ${EDC_PROVIDER_URL} provider-dataplane-edc-url: ${EDC_PROVIDER_DATAPLANE_URL} - callback-urls: ${EDC_CALLBACK_URL} - irs-edc-client: callback-url: ${EDC_CALLBACK_URL_EDC_CLIENT} controlplane: @@ -124,11 +110,12 @@ spring: authorization-grant-type: client_credentials client-id: ${OAUTH2_CLIENT_ID} client-secret: ${OAUTH2_CLIENT_SECRET} + scope: ${OAUTH2_CLIENT_SCOPE:openid} OKTA: client-id: ${OAUTH2_CLIENT_ID} client-secret: ${OAUTH2_CLIENT_SECRET} authorization-grant-type: client_credentials - scope: openid + scope: ${OAUTH2_CLIENT_SCOPE:openid} provider: keycloak: token-uri: ${OAUTH2_PROVIDER_TOKEN_URI} diff --git a/tx-backend/src/main/resources/db/migration/V19__remove_shedlock_table.sql b/tx-backend/src/main/resources/db/migration/V19__remove_shedlock_table.sql new file mode 100644 index 0000000000..4968411cce --- /dev/null +++ b/tx-backend/src/main/resources/db/migration/V19__remove_shedlock_table.sql @@ -0,0 +1 @@ +DROP TABLE public.shedlock; diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/domain/base/service/AbstractAssetBaseServiceTest.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/domain/base/service/AbstractAssetBaseServiceTest.java index 95d63e68e3..a829c36802 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/domain/base/service/AbstractAssetBaseServiceTest.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/domain/base/service/AbstractAssetBaseServiceTest.java @@ -1,7 +1,7 @@ package org.eclipse.tractusx.traceability.assets.domain.base.service; import org.eclipse.tractusx.traceability.assets.domain.base.AssetRepository; -import org.eclipse.tractusx.traceability.assets.domain.base.IrsRepository; +import org.eclipse.tractusx.traceability.assets.domain.base.JobRepository; import org.eclipse.tractusx.traceability.assets.domain.base.model.AssetBase; import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.request.BomLifecycle; import org.eclipse.tractusx.traceability.common.model.PageResult; @@ -55,7 +55,7 @@ protected AssetRepository getAssetRepository() { } @Override - protected IrsRepository getIrsRepository() { + protected JobRepository getJobRepository() { return null; } diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/domain/importpoc/service/PolicyServiceImplTest.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/domain/importpoc/service/PolicyServiceImplTest.java index 7008997821..69abfcec4d 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/domain/importpoc/service/PolicyServiceImplTest.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/domain/importpoc/service/PolicyServiceImplTest.java @@ -20,17 +20,9 @@ import assets.importpoc.PolicyResponse; -import org.eclipse.tractusx.irs.edc.client.policy.Constraint; -import org.eclipse.tractusx.irs.edc.client.policy.Constraints; -import org.eclipse.tractusx.irs.edc.client.policy.Operator; -import org.eclipse.tractusx.irs.edc.client.policy.OperatorType; -import org.eclipse.tractusx.irs.edc.client.policy.Permission; -import org.eclipse.tractusx.irs.edc.client.policy.Policy; -import org.eclipse.tractusx.irs.edc.client.policy.PolicyType; -import org.eclipse.tractusx.traceability.assets.domain.base.IrsRepository; +import org.eclipse.tractusx.traceability.assets.domain.base.PolicyRepository; import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.IrsPolicyResponse; -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.Payload; -import org.junit.jupiter.api.BeforeEach; +import org.eclipse.tractusx.traceability.common.properties.TraceabilityProperties; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -39,7 +31,10 @@ import java.time.OffsetDateTime; import java.util.List; +import java.util.Optional; +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.traceability.testdata.PolicyTestDataFactory.createIrsPolicyResponse; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.Mockito.when; @@ -49,13 +44,10 @@ class PolicyServiceImplTest { @InjectMocks private PolicyServiceImpl policyService; @Mock - private IrsRepository irsRepository; - - @BeforeEach - public void testSetup() { - policyService = new PolicyServiceImpl(irsRepository); - } + private PolicyRepository policyRepository; + @Mock + private TraceabilityProperties traceabilityProperties; @Test void testGetPolicyByID() { @@ -64,29 +56,10 @@ void testGetPolicyByID() { // GIVEN String policyId = "policy123"; OffsetDateTime createdOn = OffsetDateTime.parse("2023-07-03T16:01:05.309Z"); - List acceptedPolicies = List.of( - IrsPolicyResponse.builder() - .validUntil(OffsetDateTime.now()) - .payload( - Payload.builder() - .policyId(policyId) - .policy( - Policy.builder() - .createdOn(createdOn) - .permissions(List.of( - Permission.builder() - .action(PolicyType.USE) - .constraint(Constraints.builder() - .and(List.of(new Constraint("", new Operator(OperatorType.EQ), ""))) - .or(List.of(new Constraint("", new Operator(OperatorType.EQ), ""))) - .build()) - .build())) - .build()) - .build()) - .build()); + List acceptedPolicies = List.of(createIrsPolicyResponse(policyId, createdOn, "", "")); // WHEN - when(irsRepository.getPolicies()).thenReturn(acceptedPolicies); + when(policyRepository.getPolicies()).thenReturn(acceptedPolicies); List allPolicies = policyService.getAllPolicies(); // THEN @@ -95,4 +68,44 @@ void testGetPolicyByID() { assertEquals(createdOn, allPolicies.get(0).createdOn()); } + @Test + void getPolicyByConstraintRightOperand() { + // Given + IrsPolicyResponse firstPolicyResponse = createIrsPolicyResponse("test", OffsetDateTime.now(), "my-constraint1", ""); + IrsPolicyResponse secondPolicyResponse = createIrsPolicyResponse("test2", OffsetDateTime.now(), "my-constraint2", ""); + IrsPolicyResponse thirdPolicyResponse = createIrsPolicyResponse("test3", OffsetDateTime.now(), "my-constraint3", ""); + IrsPolicyResponse fourthPolicyResponse = createIrsPolicyResponse("test4", OffsetDateTime.now(), "my-constraint4", ""); + List policyResponseList = List.of(firstPolicyResponse, secondPolicyResponse, thirdPolicyResponse, fourthPolicyResponse); + when(policyRepository.getPolicies()).thenReturn(policyResponseList); + when(traceabilityProperties.getRightOperand()).thenReturn("my-constraint4"); + // When + + Optional policyResult = policyService.getFirstPolicyMatchingApplicationConstraint(); + + // Then + assertThat(policyResult).isPresent(); + assertThat(policyResult.get().policyId()).isEqualTo("test4"); + + } + + @Test + void getPolicyByConstraintRightOperandNotFound() { + // Given + IrsPolicyResponse firstPolicyResponse = createIrsPolicyResponse("test", OffsetDateTime.now(), "my-constraint1", ""); + IrsPolicyResponse secondPolicyResponse = createIrsPolicyResponse("test2", OffsetDateTime.now(), "my-constraint2", ""); + IrsPolicyResponse thirdPolicyResponse = createIrsPolicyResponse("test3", OffsetDateTime.now(), "my-constraint3", ""); + IrsPolicyResponse fourthPolicyResponse = createIrsPolicyResponse("test4", OffsetDateTime.now(), "my-constraint4", ""); + List policyResponseList = List.of(firstPolicyResponse, secondPolicyResponse, thirdPolicyResponse, fourthPolicyResponse); + when(policyRepository.getPolicies()).thenReturn(policyResponseList); + when(traceabilityProperties.getRightOperand()).thenReturn("not-exists"); + + // When + + Optional policyResult = policyService.getFirstPolicyMatchingApplicationConstraint(); + + // Then + assertThat(policyResult).isEmpty(); + + } + } diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/domain/service/AssetAsBuiltServiceImplTest.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/domain/service/AssetAsBuiltServiceImplTest.java index 6a933a4bd1..d7c585c2d6 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/domain/service/AssetAsBuiltServiceImplTest.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/domain/service/AssetAsBuiltServiceImplTest.java @@ -20,7 +20,7 @@ package org.eclipse.tractusx.traceability.assets.domain.service; import org.eclipse.tractusx.traceability.assets.domain.asbuilt.service.AssetAsBuiltServiceImpl; -import org.eclipse.tractusx.traceability.assets.domain.base.IrsRepository; +import org.eclipse.tractusx.traceability.assets.domain.base.JobRepository; import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.request.BomLifecycle; import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.Direction; import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.relationship.Aspect; @@ -39,7 +39,7 @@ class AssetAsBuiltServiceImplTest { private AssetAsBuiltServiceImpl assetService; @Mock - private IrsRepository irsRepository; + private JobRepository jobRepository; @Test @@ -51,8 +51,8 @@ void synchronizeAssets_shouldSaveCombinedAssets_whenNoException() { assetService.synchronizeAssetsAsync(globalAssetId); // then - verify(irsRepository).createJobToResolveAssets(globalAssetId, Direction.DOWNWARD, Aspect.downwardAspectsForAssetsAsBuilt(), BomLifecycle.AS_BUILT); - verify(irsRepository).createJobToResolveAssets(globalAssetId, Direction.UPWARD, Aspect.upwardAspectsForAssetsAsBuilt(), BomLifecycle.AS_BUILT); + verify(jobRepository).createJobToResolveAssets(globalAssetId, Direction.DOWNWARD, Aspect.downwardAspectsForAssetsAsBuilt(), BomLifecycle.AS_BUILT); + verify(jobRepository).createJobToResolveAssets(globalAssetId, Direction.UPWARD, Aspect.upwardAspectsForAssetsAsBuilt(), BomLifecycle.AS_BUILT); } diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/domain/service/AssetAsPlannedServiceImplTest.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/domain/service/AssetAsPlannedServiceImplTest.java index 19b495a9d0..205442f79e 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/domain/service/AssetAsPlannedServiceImplTest.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/domain/service/AssetAsPlannedServiceImplTest.java @@ -21,7 +21,7 @@ import org.eclipse.tractusx.traceability.assets.domain.asplanned.repository.AssetAsPlannedRepository; import org.eclipse.tractusx.traceability.assets.domain.asplanned.service.AssetAsPlannedServiceImpl; -import org.eclipse.tractusx.traceability.assets.domain.base.IrsRepository; +import org.eclipse.tractusx.traceability.assets.domain.base.JobRepository; import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.request.BomLifecycle; import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.Direction; import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.relationship.Aspect; @@ -40,7 +40,7 @@ class AssetAsPlannedServiceImplTest { private AssetAsPlannedServiceImpl assetService; @Mock - private IrsRepository irsRepository; + private JobRepository jobRepository; @Mock private AssetAsPlannedRepository assetRepository; @@ -54,7 +54,7 @@ void synchronizeAssets_shouldSaveCombinedAssets_whenNoException() { assetService.synchronizeAssetsAsync(globalAssetId); // then - verify(irsRepository).createJobToResolveAssets(globalAssetId, Direction.DOWNWARD, Aspect.downwardAspectsForAssetsAsPlanned(), BomLifecycle.AS_PLANNED); + verify(jobRepository).createJobToResolveAssets(globalAssetId, Direction.DOWNWARD, Aspect.downwardAspectsForAssetsAsPlanned(), BomLifecycle.AS_PLANNED); } } diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/JobRepositoryImplTest.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/JobRepositoryImplTest.java new file mode 100644 index 0000000000..f8c8869d80 --- /dev/null +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/JobRepositoryImplTest.java @@ -0,0 +1,129 @@ +/******************************************************************************** + * Copyright (c) 2023, 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.assets.infrastructure.base.irs; + +import org.eclipse.tractusx.irs.edc.client.policy.Constraint; +import org.eclipse.tractusx.irs.edc.client.policy.Constraints; +import org.eclipse.tractusx.irs.edc.client.policy.Operator; +import org.eclipse.tractusx.irs.edc.client.policy.OperatorType; +import org.eclipse.tractusx.irs.edc.client.policy.Permission; +import org.eclipse.tractusx.irs.edc.client.policy.Policy; +import org.eclipse.tractusx.irs.edc.client.policy.PolicyType; +import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.request.BomLifecycle; +import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.request.RegisterJobRequest; +import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.Direction; +import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.IrsPolicyResponse; +import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.Payload; +import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.factory.AssetMapperFactory; +import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.relationship.Aspect; +import org.eclipse.tractusx.traceability.bpn.domain.service.BpnRepository; +import org.eclipse.tractusx.traceability.common.model.BPN; +import org.eclipse.tractusx.traceability.common.properties.TraceabilityProperties; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.OffsetDateTime; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class JobRepositoryImplTest { + @InjectMocks + private JobRepositoryImpl jobRepositoryImpl; + + @Mock + TraceabilityProperties traceabilityProperties; + + @Mock + AssetCallbackRepository assetAsBuiltCallbackRepository; + + @Mock + AssetCallbackRepository assetAsPlannedCallbackRepository; + + @Mock + private BpnRepository bpnRepository; + + @Mock + private IrsClient irsClient; + + @Mock + private AssetMapperFactory assetMapperFactory; + + @ParameterizedTest + @MethodSource("provideDirections") + void testFindAssets_completedJob_returnsConvertedAssets(Direction direction) { + // Given + when(traceabilityProperties.getBpn()).thenReturn(BPN.of("test")); + + // When + jobRepositoryImpl.createJobToResolveAssets("1", direction, Aspect.downwardAspectsForAssetsAsBuilt(), BomLifecycle.AS_BUILT); + + // Then + verify(irsClient, times(1)).registerJob(any(RegisterJobRequest.class)); + } + + private static Stream provideDirections() { + return Stream.of( + Arguments.of(Direction.DOWNWARD), + Arguments.of(Direction.UPWARD) + ); + } + + + @Test + void test_getPolicyConstraints() { + //GIVEN + + OffsetDateTime validUntil = OffsetDateTime.now(); + OffsetDateTime createdOn = OffsetDateTime.now(); + List andConstraints = List.of(new Constraint("leftOperand", new Operator(OperatorType.EQ), "rightOperand")); + List orConstraints = List.of(new Constraint("leftOperand", new Operator(OperatorType.EQ), "rightOperand")); + Constraints constraints = new Constraints(andConstraints, orConstraints); + + Policy policy = new Policy("test", createdOn, validUntil, List.of(new Permission(PolicyType.USE, constraints))); + Payload payload = new Payload(null, "test", policy); + + final IrsPolicyResponse existingPolicy = new IrsPolicyResponse(validUntil, payload); + + + when(irsClient.getPolicies()).thenReturn(List.of(existingPolicy)); + + //WHEN + List irsPolicyResponse = irsClient.getPolicies(); + + //THEN + assertThat(irsPolicyResponse).hasSize(1); + } +} diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/IrsRepositoryImplTest.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/PolicyRepositoryImplTest.java similarity index 73% rename from tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/IrsRepositoryImplTest.java rename to tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/PolicyRepositoryImplTest.java index 701979a7bf..7434f25523 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/IrsRepositoryImplTest.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/PolicyRepositoryImplTest.java @@ -26,65 +26,39 @@ import org.eclipse.tractusx.irs.edc.client.policy.Permission; import org.eclipse.tractusx.irs.edc.client.policy.Policy; import org.eclipse.tractusx.irs.edc.client.policy.PolicyType; -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.request.BomLifecycle; -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.request.RegisterJobRequest; -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.Direction; import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.IrsPolicyResponse; import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.Payload; import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.factory.AssetMapperFactory; -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.relationship.Aspect; import org.eclipse.tractusx.traceability.bpn.domain.service.BpnRepository; -import org.eclipse.tractusx.traceability.common.model.BPN; import org.eclipse.tractusx.traceability.common.properties.TraceabilityProperties; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.time.OffsetDateTime; import java.util.Collections; import java.util.List; -import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) -class IrsRepositoryImplTest { - private IrsRepositoryImpl irsRepositoryImpl; +class PolicyRepositoryImplTest { + @InjectMocks + private PolicyRepositoryImpl policyRepositoryImpl; @Mock TraceabilityProperties traceabilityProperties; - @Mock - AssetCallbackRepository assetAsBuiltCallbackRepository; - - @Mock - AssetCallbackRepository assetAsPlannedCallbackRepository; - - @Mock - private BpnRepository bpnRepository; @Mock private IrsClient irsClient; - @Mock - private AssetMapperFactory assetMapperFactory; - - - @BeforeEach - void setUp() { - irsRepositoryImpl = new IrsRepositoryImpl(irsClient, bpnRepository, traceabilityProperties, assetAsBuiltCallbackRepository, assetAsPlannedCallbackRepository, assetMapperFactory); - } - @Test void givenNoPolicyExist_whenCreateIrsPolicyIfMissing_thenCreateIt() { @@ -93,7 +67,7 @@ void givenNoPolicyExist_whenCreateIrsPolicyIfMissing_thenCreateIt() { when(traceabilityProperties.getRightOperand()).thenReturn("test"); // when - irsRepositoryImpl.createIrsPolicyIfMissing(); + policyRepositoryImpl.createIrsPolicyIfMissing(); // then verify(irsClient, times(1)) @@ -113,7 +87,7 @@ void givenPolicyExist_whenCreateIrsPolicyIfMissing_thenDoNotCreateIt() { when(traceabilityProperties.getValidUntil()).thenReturn(OffsetDateTime.parse("2023-07-02T16:01:05.309Z")); // when - irsRepositoryImpl.createIrsPolicyIfMissing(); + policyRepositoryImpl.createIrsPolicyIfMissing(); // then verifyNoMoreInteractions(irsClient); @@ -134,33 +108,13 @@ void givenOutdatedPolicyExist_whenCreateIrsPolicyIfMissing_thenUpdateIt() { when(traceabilityProperties.getValidUntil()).thenReturn(OffsetDateTime.parse("2023-07-04T16:01:05.309Z")); // when - irsRepositoryImpl.createIrsPolicyIfMissing(); + policyRepositoryImpl.createIrsPolicyIfMissing(); // then verify(irsClient, times(1)).deletePolicy(); verify(irsClient, times(1)).registerPolicy(); } - @ParameterizedTest - @MethodSource("provideDirections") - void testFindAssets_completedJob_returnsConvertedAssets(Direction direction) { - // Given - when(traceabilityProperties.getBpn()).thenReturn(BPN.of("test")); - - // When - irsRepositoryImpl.createJobToResolveAssets("1", direction, Aspect.downwardAspectsForAssetsAsBuilt(), BomLifecycle.AS_BUILT); - - // Then - verify(irsClient, times(1)).registerJob(any(RegisterJobRequest.class)); - } - - private static Stream provideDirections() { - return Stream.of( - Arguments.of(Direction.DOWNWARD), - Arguments.of(Direction.UPWARD) - ); - } - @Test void test_getPolicyConstraints() { diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/infrastructure/scheduler/AssetsRefreshJobTest.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/infrastructure/scheduler/AssetsRefreshJobTest.java index 080cf55929..33cd234e89 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/infrastructure/scheduler/AssetsRefreshJobTest.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/infrastructure/scheduler/AssetsRefreshJobTest.java @@ -21,7 +21,6 @@ package org.eclipse.tractusx.traceability.assets.infrastructure.scheduler; -import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; import org.eclipse.tractusx.irs.registryclient.exceptions.RegistryServiceException; import org.eclipse.tractusx.traceability.shelldescriptor.domain.service.DecentralRegistryServiceImpl; import org.eclipse.tractusx.traceability.shelldescriptor.application.AssetsRefreshJob; @@ -54,15 +53,4 @@ void refresh_shouldBeScheduledForEveryTwoHours() throws NoSuchMethodException { String cronExpression = scheduledAnnotation.cron(); assertEquals("0 0 */2 * * ?", cronExpression); } - - @Test - void refresh_shouldHaveSchedulerLockAnnotation() throws NoSuchMethodException { - Scheduled scheduledAnnotation = AssetsRefreshJob.class.getDeclaredMethod("refresh").getAnnotation(Scheduled.class); - assertNotNull(scheduledAnnotation); - SchedulerLock schedulerLockAnnotation = AssetsRefreshJob.class.getDeclaredMethod("refresh").getAnnotation(SchedulerLock.class); - assertNotNull(schedulerLockAnnotation); - assertEquals("data-sync-lock", schedulerLockAnnotation.name()); - assertEquals("PT5M", schedulerLockAnnotation.lockAtLeastFor()); - assertEquals("PT15M", schedulerLockAnnotation.lockAtMostFor()); - } } diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/common/config/ApplicationStartupConfigTest.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/common/config/ApplicationStartupConfigTest.java index 82055f0372..7a10dfaec3 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/common/config/ApplicationStartupConfigTest.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/common/config/ApplicationStartupConfigTest.java @@ -19,7 +19,8 @@ package org.eclipse.tractusx.traceability.common.config; -import org.eclipse.tractusx.traceability.assets.domain.base.IrsRepository; +import org.eclipse.tractusx.traceability.assets.domain.base.JobRepository; +import org.eclipse.tractusx.traceability.assets.domain.base.PolicyRepository; import org.eclipse.tractusx.traceability.qualitynotification.domain.contract.EdcNotificationContractService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -43,7 +44,10 @@ class ApplicationStartupConfigTest { @Mock private EdcNotificationContractService edcNotificationContractService; @Mock - private IrsRepository irsRepository; + private JobRepository jobRepository; + + @Mock + private PolicyRepository policyRepository; @Test void whenCallRegisterIrsPolicy_thenCallRepository() { @@ -53,7 +57,7 @@ void whenCallRegisterIrsPolicy_thenCallRepository() { applicationStartupConfig.registerIrsPolicy(); // then - verify(irsRepository, times(1)).createIrsPolicyIfMissing(); + verify(policyRepository, times(1)).createIrsPolicyIfMissing(); }); executor.shutdown(); diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/infrastructure/edc/notificationcontract/service/EdcNotificationContractServiceTest.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/infrastructure/edc/notificationcontract/service/EdcNotificationContractServiceTest.java index e9a6102de2..25a2e44bdf 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/infrastructure/edc/notificationcontract/service/EdcNotificationContractServiceTest.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/infrastructure/edc/notificationcontract/service/EdcNotificationContractServiceTest.java @@ -21,14 +21,18 @@ package org.eclipse.tractusx.traceability.infrastructure.edc.notificationcontract.service; +import assets.importpoc.PolicyResponse; import org.eclipse.tractusx.irs.edc.client.asset.EdcAssetService; import org.eclipse.tractusx.irs.edc.client.asset.model.exception.CreateEdcAssetException; import org.eclipse.tractusx.irs.edc.client.asset.model.exception.DeleteEdcAssetException; import org.eclipse.tractusx.irs.edc.client.contract.model.exception.CreateEdcContractDefinitionException; import org.eclipse.tractusx.irs.edc.client.contract.service.EdcContractDefinitionService; +import org.eclipse.tractusx.irs.edc.client.policy.model.EdcCreatePolicyDefinitionRequest; import org.eclipse.tractusx.irs.edc.client.policy.model.exception.CreateEdcPolicyDefinitionException; import org.eclipse.tractusx.irs.edc.client.policy.model.exception.DeleteEdcPolicyDefinitionException; import org.eclipse.tractusx.irs.edc.client.policy.service.EdcPolicyDefinitionService; +import org.eclipse.tractusx.traceability.assets.application.importpoc.PolicyService; +import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.IrsPolicyResponse; import org.eclipse.tractusx.traceability.common.properties.TraceabilityProperties; import org.eclipse.tractusx.traceability.qualitynotification.application.contract.model.CreateNotificationContractException; import org.eclipse.tractusx.traceability.qualitynotification.application.contract.model.CreateNotificationContractRequest; @@ -36,16 +40,20 @@ import org.eclipse.tractusx.traceability.qualitynotification.application.contract.model.NotificationMethod; import org.eclipse.tractusx.traceability.qualitynotification.application.contract.model.NotificationType; import org.eclipse.tractusx.traceability.qualitynotification.domain.contract.EdcNotificationContractService; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import java.time.OffsetDateTime; +import java.util.List; +import java.util.Optional; + import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.traceability.testdata.PolicyTestDataFactory.createIrsPolicyResponse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -54,16 +62,20 @@ class EdcNotificationContractServiceTest { @Mock - TraceabilityProperties traceabilityProperties; + private TraceabilityProperties traceabilityProperties; + @Mock + private EdcAssetService edcNotificationAssetService; + @Mock - EdcAssetService edcNotificationAssetService; + private EdcPolicyDefinitionService edcPolicyDefinitionService; @Mock - EdcPolicyDefinitionService edcPolicyDefinitionService; + private EdcContractDefinitionService edcContractDefinitionService; @Mock - EdcContractDefinitionService edcContractDefinitionService; + private PolicyService policyService; + @InjectMocks private EdcNotificationContractService edcNotificationContractService; @@ -71,24 +83,17 @@ class EdcNotificationContractServiceTest { private static final String accessPolicyId = "99"; private static final String contractDefinitionId = "999"; - @BeforeEach - void setUp() { - edcNotificationContractService = new EdcNotificationContractService( - edcNotificationAssetService, edcPolicyDefinitionService, edcContractDefinitionService, traceabilityProperties - ); - } - @Test void testHandle() throws CreateEdcAssetException, CreateEdcPolicyDefinitionException, CreateEdcContractDefinitionException { // given - String rightOperand = "trace3"; NotificationType notificationType = NotificationType.QUALITY_INVESTIGATION; NotificationMethod notificationMethod = NotificationMethod.RESOLVE; + List policyResponses = IrsPolicyResponse.toResponse(List.of(createIrsPolicyResponse("test", OffsetDateTime.now(), "or", "and"))); + when(policyService.getFirstPolicyMatchingApplicationConstraint()).thenReturn(Optional.of(policyResponses.get(0))); CreateNotificationContractRequest request = new CreateNotificationContractRequest(notificationType, notificationMethod); when(edcNotificationAssetService.createNotificationAsset(any(), any(), any(), any())).thenReturn(notificationAssetId); when(traceabilityProperties.getUrl()).thenReturn("https://test"); - when(traceabilityProperties.getRightOperand()).thenReturn(rightOperand); - when(edcPolicyDefinitionService.createAccessPolicy(rightOperand)).thenReturn(accessPolicyId); + when(edcPolicyDefinitionService.createAccessPolicy(any(EdcCreatePolicyDefinitionRequest.class))).thenReturn(accessPolicyId); when(edcContractDefinitionService.createContractDefinition(notificationAssetId, accessPolicyId)).thenReturn(contractDefinitionId); // when @@ -121,14 +126,14 @@ void givenService_whenAssetCreationThrowsException_thenThrowException() throws C @Test void givenService_whenPolicyDefinitionServiceThrowsException_thenThrowException() throws CreateEdcAssetException, CreateEdcPolicyDefinitionException, DeleteEdcAssetException { // given - String rightOperand = "trace3"; NotificationType notificationType = NotificationType.QUALITY_INVESTIGATION; NotificationMethod notificationMethod = NotificationMethod.RESOLVE; + List policyResponses = IrsPolicyResponse.toResponse(List.of(createIrsPolicyResponse("test", OffsetDateTime.now(), "or", "and"))); + when(policyService.getFirstPolicyMatchingApplicationConstraint()).thenReturn(Optional.of(policyResponses.get(0))); CreateNotificationContractRequest request = new CreateNotificationContractRequest(notificationType, notificationMethod); when(edcNotificationAssetService.createNotificationAsset(any(), any(), any(), any())).thenReturn(notificationAssetId); when(traceabilityProperties.getUrl()).thenReturn("https://test"); - when(traceabilityProperties.getRightOperand()).thenReturn(rightOperand); - doThrow(CreateEdcPolicyDefinitionException.class).when(edcPolicyDefinitionService).createAccessPolicy(anyString()); + doThrow(CreateEdcPolicyDefinitionException.class).when(edcPolicyDefinitionService).createAccessPolicy(any(EdcCreatePolicyDefinitionRequest.class)); // when/then assertThrows(CreateNotificationContractException.class, () -> edcNotificationContractService.handle(request)); @@ -138,13 +143,14 @@ void givenService_whenPolicyDefinitionServiceThrowsException_thenThrowException( @Test void givenService_whenContractDefinitionServiceThrowsException_thenThrowException() throws CreateEdcAssetException, CreateEdcContractDefinitionException, DeleteEdcAssetException, DeleteEdcPolicyDefinitionException { // given - String rightOperand = "trace3"; NotificationType notificationType = NotificationType.QUALITY_INVESTIGATION; NotificationMethod notificationMethod = NotificationMethod.RESOLVE; CreateNotificationContractRequest request = new CreateNotificationContractRequest(notificationType, notificationMethod); + List policyResponses = IrsPolicyResponse.toResponse(List.of(createIrsPolicyResponse("test", OffsetDateTime.now(), "or", "and"))); + when(policyService.getFirstPolicyMatchingApplicationConstraint()).thenReturn(Optional.of(policyResponses.get(0))); when(edcNotificationAssetService.createNotificationAsset(any(), any(), any(), any())).thenReturn(notificationAssetId); when(traceabilityProperties.getUrl()).thenReturn("https://test"); - when(traceabilityProperties.getRightOperand()).thenReturn(rightOperand); + doThrow(CreateEdcContractDefinitionException.class).when(edcContractDefinitionService).createContractDefinition(any(), any()); // when/then diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/qualitynotification/domain/service/EdcNotificationServiceImplTest.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/qualitynotification/domain/service/EdcNotificationServiceImplTest.java index 34d7fbe6ac..b98753d762 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/qualitynotification/domain/service/EdcNotificationServiceImplTest.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/qualitynotification/domain/service/EdcNotificationServiceImplTest.java @@ -133,8 +133,6 @@ void givenNoCatalogItemException_whenHandleSendingInvestigation_thenHandleIt() { .severity(QualityNotificationSeverity.MINOR) .build(); doThrow(new NoCatalogItemException()).when(edcFacade).startEdcTransfer(notification, edcReceiverUrl, edcSenderUrl); - QualityNotification qualityNotification = QualityNotification.builder().build(); - when(investigationRepository.findByNotificationMessageId(any())).thenReturn(Optional.of(qualityNotification)); // when notificationsService.asyncNotificationMessageExecutor(notification); @@ -158,8 +156,6 @@ void givenSendNotificationException_whenHandleSendingInvestigation_thenHandleIt( .severity(QualityNotificationSeverity.MINOR) .build(); doThrow(new SendNotificationException("message", new RuntimeException())).when(edcFacade).startEdcTransfer(notification, edcReceiverUrl, edcSenderUrl); - QualityNotification qualityNotification = QualityNotification.builder().build(); - when(investigationRepository.findByNotificationMessageId(any())).thenReturn(Optional.of(qualityNotification)); // when notificationsService.asyncNotificationMessageExecutor(notification); @@ -183,8 +179,7 @@ void givenSendNoEndpointDataReferenceException_whenHandleSendingInvestigation_th .severity(QualityNotificationSeverity.MINOR) .build(); doThrow(new NoEndpointDataReferenceException("message")).when(edcFacade).startEdcTransfer(notification, edcReceiverUrl, edcSenderUrl); - QualityNotification qualityNotification = QualityNotification.builder().build(); - when(investigationRepository.findByNotificationMessageId(any())).thenReturn(Optional.of(qualityNotification)); + // when notificationsService.asyncNotificationMessageExecutor(notification); @@ -208,8 +203,7 @@ void givenContractNegotiationException_whenHandleSendingInvestigation_thenHandle .severity(QualityNotificationSeverity.MINOR) .build(); doThrow(new ContractNegotiationException("message")).when(edcFacade).startEdcTransfer(notification, edcReceiverUrl, edcSenderUrl); - QualityNotification qualityNotification = QualityNotification.builder().build(); - when(investigationRepository.findByNotificationMessageId(any())).thenReturn(Optional.of(qualityNotification)); + // when notificationsService.asyncNotificationMessageExecutor(notification); @@ -234,8 +228,6 @@ void givenNoCatalogItemException_whenHandleSendingAlert_thenHandleIt() { .severity(QualityNotificationSeverity.MINOR) .build(); doThrow(new NoCatalogItemException()).when(edcFacade).startEdcTransfer(notification, edcReceiverUrl, edcSenderUrl); - QualityNotification qualityNotification = QualityNotification.builder().build(); - when(investigationRepository.findByNotificationMessageId(any())).thenReturn(Optional.of(qualityNotification)); // when notificationsService.asyncNotificationMessageExecutor(notification); @@ -262,8 +254,6 @@ void givenSendNotificationException_whenHandleSendingAlert_thenHandleIt() { .build(); doThrow(new SendNotificationException("message", new RuntimeException())).when(edcFacade).startEdcTransfer(notification, edcReceiverUrl, edcSenderUrl); - QualityNotification qualityNotification = QualityNotification.builder().build(); - when(investigationRepository.findByNotificationMessageId(any())).thenReturn(Optional.of(qualityNotification)); // when notificationsService.asyncNotificationMessageExecutor(notification); @@ -288,8 +278,7 @@ void givenSendNoEndpointDataReferenceException_whenHandleSendingAlert_thenHandle .severity(QualityNotificationSeverity.MINOR) .build(); doThrow(new NoEndpointDataReferenceException("message")).when(edcFacade).startEdcTransfer(notification, edcReceiverUrl, edcSenderUrl); - QualityNotification qualityNotification = QualityNotification.builder().build(); - when(investigationRepository.findByNotificationMessageId(any())).thenReturn(Optional.of(qualityNotification)); + // when notificationsService.asyncNotificationMessageExecutor(notification); @@ -313,8 +302,7 @@ void givenContractNegotiationException_whenHandleSendingAlert_thenHandleIt() { .severity(QualityNotificationSeverity.MINOR) .build(); doThrow(new ContractNegotiationException("message")).when(edcFacade).startEdcTransfer(notification, edcReceiverUrl, edcSenderUrl); - QualityNotification qualityNotification = QualityNotification.builder().build(); - when(investigationRepository.findByNotificationMessageId(any())).thenReturn(Optional.of(qualityNotification)); + // when notificationsService.asyncNotificationMessageExecutor(notification); diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/testdata/PolicyTestDataFactory.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/testdata/PolicyTestDataFactory.java new file mode 100644 index 0000000000..b10989c2a9 --- /dev/null +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/testdata/PolicyTestDataFactory.java @@ -0,0 +1,61 @@ +/******************************************************************************** + * 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.testdata; + +import org.eclipse.tractusx.irs.edc.client.policy.Constraint; +import org.eclipse.tractusx.irs.edc.client.policy.Constraints; +import org.eclipse.tractusx.irs.edc.client.policy.Operator; +import org.eclipse.tractusx.irs.edc.client.policy.OperatorType; +import org.eclipse.tractusx.irs.edc.client.policy.Permission; +import org.eclipse.tractusx.irs.edc.client.policy.Policy; +import org.eclipse.tractusx.irs.edc.client.policy.PolicyType; +import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.IrsPolicyResponse; +import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.Payload; +import org.jetbrains.annotations.NotNull; + +import java.time.OffsetDateTime; +import java.util.List; + +public class PolicyTestDataFactory { + + @NotNull + public static IrsPolicyResponse createIrsPolicyResponse(String policyId, OffsetDateTime createdOn, String orRightOperand, String andRightOperand) { + return + IrsPolicyResponse.builder() + .validUntil(OffsetDateTime.now()) + .payload( + Payload.builder() + .policyId(policyId) + .policy( + Policy.builder() + .policyId(policyId) + .createdOn(createdOn) + .permissions(List.of( + Permission.builder() + .action(PolicyType.USE) + .constraint(Constraints.builder() + .and(List.of(new Constraint("", new Operator(OperatorType.EQ), andRightOperand))) + .or(List.of(new Constraint("", new Operator(OperatorType.EQ), orRightOperand))) + .build()) + .build())) + .build()) + .build()) + .build(); + } +}