Skip to content

Commit

Permalink
feat: fetch a single contract agreement by its ID (#1016)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: Kamil Czaja <[email protected]>
  • Loading branch information
ununhexium and kamilczaja authored Aug 1, 2024
1 parent f78ca85 commit 72d0cab
Show file tree
Hide file tree
Showing 9 changed files with 179 additions and 27 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ please see [changelog_updates.md](docs/dev/changelog_updates.md).

#### Minor

- API Wrapper:
- Added wrapper API endpoint to query a single contract agreement

#### Patch

#### Major Changes
Expand Down
29 changes: 25 additions & 4 deletions docs/api/postman_collection.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"info": {
"_postman_id": "14267003-be4b-40ee-a9b6-83b5dd32ba57",
"name": "sovity EDC Community Edition",
"_postman_id": "5f373580-db27-4daf-86e8-84bc8ed933c8",
"name": "sovity EDC Community Edition Copy",
"description": "This is the official postman collection for the sovity EDC Community Edition.\n\nThe Management-API is based on core-edc v0.2.1.\n\nsovity EDC Community Edition: [https://github.com/sovity/edc-ce](https://github.com/sovity/edc-ce)\n\nLicense: [https://github.com/sovity/edc-ce/blob/main/LICENSE](https://github.com/sovity/edc-ce/blob/main/LICENSE)",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "32949497"
Expand Down Expand Up @@ -529,7 +529,7 @@
{
"name": "Get Contract Agreements",
"request": {
"method": "GET",
"method": "POST",
"header": [],
"url": {
"raw": "{{CONSUMER_EDC_MANAGEMENT_URL}}/wrapper/ui/pages/contract-agreement-page",
Expand All @@ -546,6 +546,27 @@
},
"response": []
},
{
"name": "Get Contract Agreements By ID",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{CONSUMER_EDC_MANAGEMENT_URL}}/wrapper/ui/pages/contract-agreement-page/{{CONTRACT_AGREEMENT_ID}}",
"host": [
"{{CONSUMER_EDC_MANAGEMENT_URL}}"
],
"path": [
"wrapper",
"ui",
"pages",
"contract-agreement-page",
"{{CONTRACT_AGREEMENT_ID}}"
]
}
},
"response": []
},
{
"name": "Terminate Contract Agreement",
"event": [
Expand Down Expand Up @@ -2073,4 +2094,4 @@
"type": "default"
}
]
}
}
41 changes: 30 additions & 11 deletions docs/api/sovity-edc-api-wrapper.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,25 @@ paths:
type: array
items:
$ref: '#/components/schemas/UiDataOffer'
/wrapper/ui/pages/contract-agreement-page/{contractAgreementId}:
get:
tags:
- UI
description: Get a single contract agreement card by its identifier
operationId: getContractAgreementCard
parameters:
- name: contractAgreementId
in: path
required: true
schema:
type: string
responses:
default:
description: default response
content:
application/json:
schema:
$ref: '#/components/schemas/ContractAgreementCard'
/wrapper/ui/pages/contract-agreement-page:
post:
tags:
Expand Down Expand Up @@ -1436,17 +1455,6 @@ components:
enum:
- CONSUMING
- PROVIDING
ContractAgreementPage:
required:
- contractAgreements
type: object
properties:
contractAgreements:
type: array
description: Contract Agreement Cards
items:
$ref: '#/components/schemas/ContractAgreementCard'
description: Data as required by the UI's Contract Agreement Page
ContractAgreementTerminationInfo:
required:
- detail
Expand Down Expand Up @@ -1528,6 +1536,17 @@ components:
simplifiedState:
$ref: '#/components/schemas/TransferProcessSimplifiedState'
description: Transfer Process State interpreted
ContractAgreementPage:
required:
- contractAgreements
type: object
properties:
contractAgreements:
type: array
description: Contract Agreement Cards
items:
$ref: '#/components/schemas/ContractAgreementCard'
description: Data as required by the UI's Contract Agreement Page
ContractAgreementPageQuery:
type: object
properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import de.sovity.edc.ext.wrapper.api.common.model.UiAssetCreateRequest;
import de.sovity.edc.ext.wrapper.api.common.model.UiAssetEditRequest;
import de.sovity.edc.ext.wrapper.api.ui.model.AssetPage;
import de.sovity.edc.ext.wrapper.api.ui.model.ContractAgreementCard;
import de.sovity.edc.ext.wrapper.api.ui.model.ContractAgreementPage;
import de.sovity.edc.ext.wrapper.api.ui.model.ContractAgreementPageQuery;
import de.sovity.edc.ext.wrapper.api.ui.model.ContractDefinitionPage;
Expand Down Expand Up @@ -164,6 +165,13 @@ ContractAgreementPage getContractAgreementPage(
@Nullable ContractAgreementPageQuery contractAgreementPageQuery
);

@GET
@Path("pages/contract-agreement-page/{contractAgreementId}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Operation(description = "Get a single contract agreement card by its identifier")
ContractAgreementCard getContractAgreementCard(@PathParam("contractAgreementId") String contractAgreementId);

@POST
@Path("pages/contract-agreement-page/transfers")
@Consumes(MediaType.APPLICATION_JSON)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import de.sovity.edc.ext.wrapper.api.common.model.UiAssetCreateRequest;
import de.sovity.edc.ext.wrapper.api.common.model.UiAssetEditRequest;
import de.sovity.edc.ext.wrapper.api.ui.model.AssetPage;
import de.sovity.edc.ext.wrapper.api.ui.model.ContractAgreementCard;
import de.sovity.edc.ext.wrapper.api.ui.model.ContractAgreementPage;
import de.sovity.edc.ext.wrapper.api.ui.model.ContractAgreementPageQuery;
import de.sovity.edc.ext.wrapper.api.ui.model.ContractDefinitionPage;
Expand Down Expand Up @@ -155,6 +156,12 @@ public ContractAgreementPage getContractAgreementPage(@Nullable ContractAgreemen
contractAgreementApiService.contractAgreementPage(dsl, contractAgreementPageQuery));
}

@Override
public ContractAgreementCard getContractAgreementCard(String contractAgreementId) {
return dslContextFactory.transactionResult(dsl ->
contractAgreementApiService.contractAgreement(dsl, contractAgreementId));
}

@Override
public IdResponseDto initiateTransfer(InitiateTransferRequest request) {
return contractAgreementTransferApiService.initiateTransfer(request);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import de.sovity.edc.ext.wrapper.api.ui.pages.contract_agreements.services.ContractAgreementDataFetcher;
import de.sovity.edc.ext.wrapper.api.ui.pages.contract_agreements.services.ContractAgreementPageCardBuilder;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jooq.DSLContext;
Expand Down Expand Up @@ -48,9 +49,20 @@ public ContractAgreementPage contractAgreementPage(DSLContext dsl, @Nullable Con
return new ContractAgreementPage(cards.toList());
} else {
var filtered = cards.filter(card ->
card.getTerminationStatus().equals(contractAgreementPageQuery.getTerminationStatus()))
card.getTerminationStatus().equals(contractAgreementPageQuery.getTerminationStatus()))
.toList();
return new ContractAgreementPage(filtered);
}
}

public ContractAgreementCard contractAgreement(DSLContext dsl, String contractAgreementId) {
val agreementData = contractAgreementDataFetcher.getContractAgreement(dsl, contractAgreementId);
return contractAgreementPageCardBuilder.buildContractAgreementCard(
agreementData.agreement(),
agreementData.negotiation(),
agreementData.asset(),
agreementData.transfers(),
agreementData.termination()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import de.sovity.edc.ext.wrapper.api.ServiceException;
import de.sovity.edc.ext.wrapper.utils.MapUtils;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.eclipse.edc.connector.contract.spi.negotiation.store.ContractNegotiationStore;
import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement;
import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation;
Expand All @@ -32,6 +33,7 @@

import java.util.List;
import java.util.Map;
import java.util.function.Function;

import static de.sovity.edc.ext.db.jooq.Tables.SOVITY_CONTRACT_TERMINATION;
import static java.util.function.Function.identity;
Expand Down Expand Up @@ -62,45 +64,78 @@ public List<ContractAgreementData> getContractAgreements(DSLContext dsl) {
var transfers = getAllTransferProcesses().stream()
.collect(groupingBy(it -> it.getDataRequest().getContractId()));

var terminations = fetchTerminations(dsl, agreements);
var agreementIds = agreements.stream().map(ContractAgreement::getId).toList();

var terminations = fetchTerminations(dsl, agreementIds);

// A ContractAgreement has multiple ContractNegotiations when doing a loopback consumption
return agreements.stream()
.flatMap(agreement -> negotiations.getOrDefault(agreement.getId(), List.of())
.stream()
.map(negotiation -> {
var asset = getAsset(agreement, negotiation, assets);
var asset = getAsset(agreement, negotiation, assets::get);
var contractTransfers = transfers.getOrDefault(agreement.getId(), List.of());
return new ContractAgreementData(agreement, negotiation, asset, contractTransfers, terminations.get(agreement.getId()));
}))
.toList();
}

private @NotNull Map<String, SovityContractTerminationRecord> fetchTerminations(DSLContext dsl, List<ContractAgreement> agreements) {
@NotNull
public ContractAgreementData getContractAgreement(DSLContext dsl, String contractAgreementId) {
val agreement = getContractAgreementById(contractAgreementId);

var agreementIds = agreements.stream().map(ContractAgreement::getId).toList();
val negotiationQuery = QuerySpec.max();
val negotiation = contractNegotiationStore.queryNegotiations(negotiationQuery)
.filter(it -> it.getContractAgreement().getId().equals(contractAgreementId))
.findFirst()
.orElseThrow(
() -> new IllegalStateException("Can't find any negotiation for contract agreement id %s".formatted(contractAgreementId)));

val transfers = getAllTransferProcesses().stream().collect(groupingBy(it -> it.getDataRequest().getContractId()));

val terminations = fetchTerminations(dsl, agreement.getId());

val asset = getAsset(agreement, negotiation, (it) -> assetIndex.findById(agreement.getAssetId()));

return new ContractAgreementData(
agreement,
negotiation,
asset,
transfers.getOrDefault(agreement.getId(), List.of()),
terminations.get(agreement.getId())
);
}

private ContractAgreement getContractAgreementById(String id) {
return contractAgreementService.findById(id);
}

@NotNull
private Map<String, SovityContractTerminationRecord> fetchTerminations(DSLContext dsl, String agreementIds) {
return fetchTerminations(dsl, List.of(agreementIds));
}

@NotNull
private Map<String, SovityContractTerminationRecord> fetchTerminations(DSLContext dsl, List<String> agreementIds) {
var t = SOVITY_CONTRACT_TERMINATION;

var terminations = dsl.select()
return dsl.select()
.from(t)
.where(t.CONTRACT_AGREEMENT_ID.in(agreementIds))
.fetch()
.into(t)
.stream()
.collect(toMap(SovityContractTerminationRecord::getContractAgreementId, identity()));

return terminations;
}

private Asset getAsset(ContractAgreement agreement, ContractNegotiation negotiation, Map<String, Asset> assets) {
private Asset getAsset(ContractAgreement agreement, ContractNegotiation negotiation, Function<String, Asset> selector) {
var assetId = agreement.getAssetId();

if (negotiation.getType() == ContractNegotiation.Type.CONSUMER) {
return dummyAsset(assetId);
}

var asset = assets.get(assetId);
var asset = selector.apply(assetId);
return asset == null ? dummyAsset(assetId) : asset;
}

Expand Down
47 changes: 45 additions & 2 deletions tests/src/test/java/de/sovity/edc/e2e/UiApiWrapperTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,8 @@ void editAssetOnLiveContract(
initiateTransfer(consumerClient, negotiation);

// assert
assertThat(consumerClient.uiApi().getCatalogPageDataOffers(providerProtocolEndpoint).get(0).getAsset().getTitle()).isEqualTo("Good Asset Title");
assertThat(consumerClient.uiApi().getCatalogPageDataOffers(providerProtocolEndpoint).get(0).getAsset().getTitle())
.isEqualTo("Good Asset Title");
val firstAsset = providerClient.uiApi().getContractAgreementPage(null).getContractAgreements().get(0).getAsset();
assertThat(firstAsset.getTitle()).isEqualTo("Good Asset Title");
assertThat(firstAsset.getCustomJsonAsString()).isEqualTo("""
Expand All @@ -558,7 +559,8 @@ void editAssetOnLiveContract(
""");
validateDataTransferred(dataAddress.getDataSinkSpyUrl(), data);
validateTransferProcessesOk(consumerClient, providerClient);
assertThat(providerClient.uiApi().getTransferHistoryPage().getTransferEntries().get(0).getAssetName()).isEqualTo("Good Asset Title");
assertThat(providerClient.uiApi().getTransferHistoryPage().getTransferEntries().get(0).getAssetName())
.isEqualTo("Good Asset Title");
}

@Test
Expand Down Expand Up @@ -589,6 +591,47 @@ void checkIdAvailability(E2eScenario scenario, @Provider EdcClient providerClien
assertThat(posContractDefinitionResponse.getAvailable()).isTrue();
}

@Test
void retrieveSingleContractAgreement(
E2eScenario scenario,
@Provider EdcClient providerClient
) {
// arrange
val assetId = scenario.createAsset();

scenario.createContractDefinition(assetId);
val negotiation = scenario.negotiateAssetAndAwait(assetId);

// act
val retrieved = providerClient.uiApi().getContractAgreementCard(negotiation.getContractAgreementId());
val alternative = providerClient.uiApi()
.getContractAgreementPage(null)
.getContractAgreements()
.stream()
.filter(it -> it.getContractAgreementId().equals(negotiation.getContractAgreementId()))
.findFirst()
.orElseThrow();

val retrievedPolicy = retrieved.getContractPolicy();
val alternativePolicy = alternative.getContractPolicy();

retrieved.setContractPolicy(null);
alternative.setContractPolicy(null);

// assert
assertThat(retrieved).usingRecursiveAssertion().isEqualTo(alternative);

// assert separately because the policy ID is re-generated on each query
assertThat(retrievedPolicy)
.usingRecursiveComparison()
.ignoringFields("policyJsonLd")
.isEqualTo(alternativePolicy);

assertThatJson(retrievedPolicy.getPolicyJsonLd())
.whenIgnoringPaths("@id")
.isEqualTo(alternativePolicy.getPolicyJsonLd());
}

private UiContractNegotiation negotiate(
EdcClient consumerClient,
ConnectorRemote consumerConnector,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ public String createAsset(String id, UiDataSource uiDataSource) {
return internalCreateAsset(id, uiDataSource).getId();
}

public String createAsset(UiAssetCreateRequest uiAssetCreateRequest) {
return providerClient.uiApi().createAsset(uiAssetCreateRequest).getId();
}

public MockedAsset createAssetWithMockResource(String id) {

val path = "/assets/" + id;
Expand Down

0 comments on commit 72d0cab

Please sign in to comment.