From 10b6f53bcbb5c2c1b70ffae5646fb889d4b543cb Mon Sep 17 00:00:00 2001
From: Christophe Loiseau <116@lab0.net>
Date: Fri, 16 Feb 2024 15:23:32 +0100
Subject: [PATCH 01/24] feat: stable contract offer id in DspCatalogService
(#795)
---
CHANGELOG.md | 1 +
.../ContractAgreementPageCardBuilder.java | 2 -
.../catalog/mapper/DspContractOfferUtils.java | 78 +++++++++++++++++++
.../catalog/mapper/DspDataOfferBuilder.java | 6 +-
.../utils/catalog/DspCatalogServiceTest.java | 5 +-
.../mapper/DspContractOfferUtilsTest.java | 24 ++++++
.../edc/utils/catalog/catalogResponse.json | 2 +-
7 files changed, 112 insertions(+), 6 deletions(-)
create mode 100644 utils/catalog-parser/src/main/java/de/sovity/edc/utils/catalog/mapper/DspContractOfferUtils.java
create mode 100644 utils/catalog-parser/src/test/java/de/sovity/edc/utils/catalog/mapper/DspContractOfferUtilsTest.java
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 411e43531..f382d4a2f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@ please see [changelog_updates.md](docs/dev/changelog_updates.md).
#### Minor Changes
#### Patch Changes
+- DspCatalogService: Contract Offer IDs are now stable
### Deployment Migration Notes
diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_agreements/services/ContractAgreementPageCardBuilder.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_agreements/services/ContractAgreementPageCardBuilder.java
index bc8fa989b..d090c4401 100644
--- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_agreements/services/ContractAgreementPageCardBuilder.java
+++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_agreements/services/ContractAgreementPageCardBuilder.java
@@ -22,7 +22,6 @@
import de.sovity.edc.ext.wrapper.api.ui.pages.transferhistory.TransferProcessStateService;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement;
import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation;
import org.eclipse.edc.connector.transfer.spi.types.TransferProcess;
@@ -35,7 +34,6 @@
import static de.sovity.edc.ext.wrapper.utils.EdcDateUtils.utcMillisToOffsetDateTime;
import static de.sovity.edc.ext.wrapper.utils.EdcDateUtils.utcSecondsToOffsetDateTime;
-@Slf4j
@RequiredArgsConstructor
public class ContractAgreementPageCardBuilder {
private final PolicyMapper policyMapper;
diff --git a/utils/catalog-parser/src/main/java/de/sovity/edc/utils/catalog/mapper/DspContractOfferUtils.java b/utils/catalog-parser/src/main/java/de/sovity/edc/utils/catalog/mapper/DspContractOfferUtils.java
new file mode 100644
index 000000000..e7cbc18a3
--- /dev/null
+++ b/utils/catalog-parser/src/main/java/de/sovity/edc/utils/catalog/mapper/DspContractOfferUtils.java
@@ -0,0 +1,78 @@
+package de.sovity.edc.utils.catalog.mapper;
+
+import de.sovity.edc.utils.JsonUtils;
+import de.sovity.edc.utils.jsonld.JsonLdUtils;
+import de.sovity.edc.utils.jsonld.vocab.Prop;
+import jakarta.json.Json;
+import jakarta.json.JsonObject;
+import lombok.val;
+import org.eclipse.edc.connector.contract.spi.ContractId;
+import org.jetbrains.annotations.NotNull;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
+
+public class DspContractOfferUtils {
+
+ /**
+ * /!\ Workaround
+ *
+ * The Eclipse EDC uses a new random UUID for each policy that it returns and in turn a new contract ID.
+ * This Eclipse ID can't be used as such.
+ * As a workaround, we must introduce our own ID.
+ * For a first iteration, we will assume that the content of the policy remains the same (same content, same order)
+ * and hash it to use it as a key.
+ *
+ * @param contract The contract to compute an ID from
+ * @return A base64 string that can be used as an id for the {@code contract}
+ */
+ public static String buildStableId(JsonObject contract) {
+ // FIXME: This doesn't enforce any property order and may cause trouble if the returned policy schema is not consistent.
+ // Use canonical form if needed later.
+ val noId = Json.createObjectBuilder(contract).remove(Prop.ID).build();
+ val policyId = hash(noId);
+
+ val currentId = ContractId.parseId(JsonLdUtils.string(contract, Prop.ID))
+ .orElseThrow((failure) -> {
+ throw new RuntimeException("Failed to parse the contract id: " + failure.getFailureDetail());
+ });
+
+ return currentId.definitionPart() + ":" + currentId.assetIdPart() + ":" + policyId;
+ }
+
+ @NotNull
+ private static String hash(JsonObject noId) {
+ val policyJsonString = JsonUtils.toJson(noId);
+ val sha1 = sha1(policyJsonString);
+ // encoding with base16 to make the hash readable to humans (similarly to how the random UUID would have been readable)
+ val base16 = toBase16(sha1);
+ return toBase64(base16);
+ }
+
+ @NotNull
+ private static String toBase64(String string) {
+ byte[] stringBytes = string.getBytes(StandardCharsets.UTF_8);
+ byte[] bytes = Base64.getEncoder().encode(stringBytes);
+ return new String(bytes);
+ }
+
+ @NotNull
+ private static String toBase16(byte[] bytes) {
+ val sb = new StringBuilder();
+ for (byte b : bytes) {
+ sb.append(Character.forDigit(b >> 4 & 0xf, 16));
+ sb.append(Character.forDigit(b & 0xf, 16));
+ }
+ return sb.toString();
+ }
+
+ private static byte[] sha1(String string) {
+ try {
+ return MessageDigest.getInstance("sha-1").digest(string.getBytes());
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/utils/catalog-parser/src/main/java/de/sovity/edc/utils/catalog/mapper/DspDataOfferBuilder.java b/utils/catalog-parser/src/main/java/de/sovity/edc/utils/catalog/mapper/DspDataOfferBuilder.java
index 119d9d102..f5ef31736 100644
--- a/utils/catalog-parser/src/main/java/de/sovity/edc/utils/catalog/mapper/DspDataOfferBuilder.java
+++ b/utils/catalog-parser/src/main/java/de/sovity/edc/utils/catalog/mapper/DspDataOfferBuilder.java
@@ -22,6 +22,7 @@
import jakarta.json.Json;
import jakarta.json.JsonObject;
import lombok.RequiredArgsConstructor;
+import lombok.val;
import org.eclipse.edc.jsonld.spi.JsonLd;
import org.jetbrains.annotations.NotNull;
@@ -66,6 +67,9 @@ private DspDataOffer buildDataOffer(JsonObject dataset) {
@NotNull
private DspContractOffer buildContractOffer(JsonObject json) {
- return new DspContractOffer(JsonLdUtils.id(json), json);
+ val stableId = DspContractOfferUtils.buildStableId(json);
+ val withStableId = Json.createObjectBuilder(json).remove(Prop.ID).add(Prop.ID, stableId).build();
+
+ return new DspContractOffer(stableId, withStableId);
}
}
diff --git a/utils/catalog-parser/src/test/java/de/sovity/edc/utils/catalog/DspCatalogServiceTest.java b/utils/catalog-parser/src/test/java/de/sovity/edc/utils/catalog/DspCatalogServiceTest.java
index 1124f8405..4a34cb5a1 100644
--- a/utils/catalog-parser/src/test/java/de/sovity/edc/utils/catalog/DspCatalogServiceTest.java
+++ b/utils/catalog-parser/src/test/java/de/sovity/edc/utils/catalog/DspCatalogServiceTest.java
@@ -45,7 +45,8 @@ private DspCatalogService newDspCatalogService(String resultJsonFilename) {
var result = CompletableFuture.completedFuture(StatusResult.success(catalogJson.getBytes(StandardCharsets.UTF_8)));
when(catalogService.requestCatalog(eq(endpoint), eq("dataspace-protocol-http"), eq(QuerySpec.max()))).thenReturn(result);
- var dataOfferBuilder = new DspDataOfferBuilder(new TitaniumJsonLd(mock(Monitor.class)));
+ var monitor = mock(Monitor.class);
+ var dataOfferBuilder = new DspDataOfferBuilder(new TitaniumJsonLd(monitor));
return new DspCatalogService(catalogService, dataOfferBuilder);
}
@@ -71,7 +72,7 @@ void testCatalogMapping() {
assertThat(offer.getContractOffers()).hasSize(1);
var co = offer.getContractOffers().get(0);
- assertThat(co.getContractOfferId()).isEqualTo("policy-1");
+ assertThat(co.getContractOfferId()).isEqualTo("contract-id:asset-id:ODJjMDNjMmM4YzQ5NmE0OTg4ZjVjOTY4OGU2MmMyN2UxYjIxZDAwZQ==");
assertThat(toJson(co.getPolicyJsonLd())).contains("ALWAYS_TRUE");
assertThat(offer.getDistributions()).hasSize(1);
diff --git a/utils/catalog-parser/src/test/java/de/sovity/edc/utils/catalog/mapper/DspContractOfferUtilsTest.java b/utils/catalog-parser/src/test/java/de/sovity/edc/utils/catalog/mapper/DspContractOfferUtilsTest.java
new file mode 100644
index 000000000..21ceeed8d
--- /dev/null
+++ b/utils/catalog-parser/src/test/java/de/sovity/edc/utils/catalog/mapper/DspContractOfferUtilsTest.java
@@ -0,0 +1,24 @@
+package de.sovity.edc.utils.catalog.mapper;
+
+import jakarta.json.Json;
+import lombok.val;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+class DspContractOfferUtilsTest {
+ @Test
+ void testCanConvertTheRandomIdToStableId() {
+ // arrange
+ val contractOffer = Json.createObjectBuilder()
+ .add("@id", "part1:part2:part3")
+ .add("somefield", "somevalue")
+ .build();
+
+ // act
+ val result = DspContractOfferUtils.buildStableId(contractOffer);
+
+ // assert
+ assertThat(result).isEqualTo("part1:part2:MjliNzcwMjdjMzA2YzE3ZGYyZWNhZjY2OGI3OTYxY2U5OTY1YmExNw==");
+ }
+}
diff --git a/utils/catalog-parser/src/test/resources/de/sovity/edc/utils/catalog/catalogResponse.json b/utils/catalog-parser/src/test/resources/de/sovity/edc/utils/catalog/catalogResponse.json
index d069f7302..613b7ac49 100644
--- a/utils/catalog-parser/src/test/resources/de/sovity/edc/utils/catalog/catalogResponse.json
+++ b/utils/catalog-parser/src/test/resources/de/sovity/edc/utils/catalog/catalogResponse.json
@@ -5,7 +5,7 @@
"@id": "test-1.0",
"@type": "dcat:Dataset",
"odrl:hasPolicy": {
- "@id": "policy-1",
+ "@id": "contract-id:asset-id:policy-id",
"@type": "odrl:Set",
"odrl:permission": {
"odrl:target": "test-1.0",
From 700cdb915ede57582c3a2845b49141558852d19e Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 20 Feb 2024 08:41:01 +0100
Subject: [PATCH 02/24] build(deps-dev): bump vite (#735)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.2.3 to 4.5.2.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.5.2/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.2/packages/vite)
---
updated-dependencies:
- dependency-name: vite
dependency-type: direct:development
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.../typescript-client/package-lock.json | 640 +++++++++++++++---
1 file changed, 564 insertions(+), 76 deletions(-)
diff --git a/extensions/wrapper/clients/typescript-client/package-lock.json b/extensions/wrapper/clients/typescript-client/package-lock.json
index 07afecf3e..c068adf0b 100644
--- a/extensions/wrapper/clients/typescript-client/package-lock.json
+++ b/extensions/wrapper/clients/typescript-client/package-lock.json
@@ -276,10 +276,346 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
+ "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
+ "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
+ "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
+ "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
+ "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
+ "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
+ "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
+ "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
+ "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
+ "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
+ "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
+ "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
+ "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
+ "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
+ "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
+ "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
+ "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
+ "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
+ "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
+ "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
+ "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/@esbuild/win32-x64": {
- "version": "0.17.15",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.15.tgz",
- "integrity": "sha512-DjDa9ywLUUmjhV2Y9wUTIF+1XsmuFGvZoCmOWkli1XcNAh5t25cc7fgsCx4Zi/Uurep3TTLyDiKATgGEg61pkA==",
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
+ "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
"cpu": [
"x64"
],
@@ -741,9 +1077,9 @@
}
},
"node_modules/esbuild": {
- "version": "0.17.15",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.15.tgz",
- "integrity": "sha512-LBUV2VsUIc/iD9ME75qhT4aJj0r75abCVS0jakhFzOtR7TQsqQA5w0tZ+KTKnwl3kXE0MhskNdHDh/I5aCR1Zw==",
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
+ "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
"dev": true,
"hasInstallScript": true,
"bin": {
@@ -753,28 +1089,28 @@
"node": ">=12"
},
"optionalDependencies": {
- "@esbuild/android-arm": "0.17.15",
- "@esbuild/android-arm64": "0.17.15",
- "@esbuild/android-x64": "0.17.15",
- "@esbuild/darwin-arm64": "0.17.15",
- "@esbuild/darwin-x64": "0.17.15",
- "@esbuild/freebsd-arm64": "0.17.15",
- "@esbuild/freebsd-x64": "0.17.15",
- "@esbuild/linux-arm": "0.17.15",
- "@esbuild/linux-arm64": "0.17.15",
- "@esbuild/linux-ia32": "0.17.15",
- "@esbuild/linux-loong64": "0.17.15",
- "@esbuild/linux-mips64el": "0.17.15",
- "@esbuild/linux-ppc64": "0.17.15",
- "@esbuild/linux-riscv64": "0.17.15",
- "@esbuild/linux-s390x": "0.17.15",
- "@esbuild/linux-x64": "0.17.15",
- "@esbuild/netbsd-x64": "0.17.15",
- "@esbuild/openbsd-x64": "0.17.15",
- "@esbuild/sunos-x64": "0.17.15",
- "@esbuild/win32-arm64": "0.17.15",
- "@esbuild/win32-ia32": "0.17.15",
- "@esbuild/win32-x64": "0.17.15"
+ "@esbuild/android-arm": "0.18.20",
+ "@esbuild/android-arm64": "0.18.20",
+ "@esbuild/android-x64": "0.18.20",
+ "@esbuild/darwin-arm64": "0.18.20",
+ "@esbuild/darwin-x64": "0.18.20",
+ "@esbuild/freebsd-arm64": "0.18.20",
+ "@esbuild/freebsd-x64": "0.18.20",
+ "@esbuild/linux-arm": "0.18.20",
+ "@esbuild/linux-arm64": "0.18.20",
+ "@esbuild/linux-ia32": "0.18.20",
+ "@esbuild/linux-loong64": "0.18.20",
+ "@esbuild/linux-mips64el": "0.18.20",
+ "@esbuild/linux-ppc64": "0.18.20",
+ "@esbuild/linux-riscv64": "0.18.20",
+ "@esbuild/linux-s390x": "0.18.20",
+ "@esbuild/linux-x64": "0.18.20",
+ "@esbuild/netbsd-x64": "0.18.20",
+ "@esbuild/openbsd-x64": "0.18.20",
+ "@esbuild/sunos-x64": "0.18.20",
+ "@esbuild/win32-arm64": "0.18.20",
+ "@esbuild/win32-ia32": "0.18.20",
+ "@esbuild/win32-x64": "0.18.20"
}
},
"node_modules/escape-string-regexp": {
@@ -1270,9 +1606,9 @@
}
},
"node_modules/rollup": {
- "version": "3.20.2",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.20.2.tgz",
- "integrity": "sha512-3zwkBQl7Ai7MFYQE0y1MeQ15+9jsi7XxfrqwTb/9EK8D9C9+//EBR4M+CuA1KODRaNbFez/lWxA5vhEGZp4MUg==",
+ "version": "3.29.4",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
+ "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==",
"dev": true,
"bin": {
"rollup": "dist/bin/rollup"
@@ -1464,15 +1800,14 @@
}
},
"node_modules/vite": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/vite/-/vite-4.2.3.tgz",
- "integrity": "sha512-kLU+m2q0Y434Y1kCy3TchefAdtFso0ILi0dLyFV8Us3InXTU11H/B5ZTqCKIQHzSKNxVG/yEx813EA9f1imQ9A==",
+ "version": "4.5.2",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz",
+ "integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==",
"dev": true,
"dependencies": {
- "esbuild": "^0.17.5",
- "postcss": "^8.4.21",
- "resolve": "^1.22.1",
- "rollup": "^3.18.0"
+ "esbuild": "^0.18.10",
+ "postcss": "^8.4.27",
+ "rollup": "^3.27.1"
},
"bin": {
"vite": "bin/vite.js"
@@ -1480,12 +1815,16 @@
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
"optionalDependencies": {
"fsevents": "~2.3.2"
},
"peerDependencies": {
"@types/node": ">= 14",
"less": "*",
+ "lightningcss": "^1.21.0",
"sass": "*",
"stylus": "*",
"sugarss": "*",
@@ -1498,6 +1837,9 @@
"less": {
"optional": true
},
+ "lightningcss": {
+ "optional": true
+ },
"sass": {
"optional": true
},
@@ -1772,10 +2114,157 @@
"to-fast-properties": "^2.0.0"
}
},
+ "@esbuild/android-arm": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
+ "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/android-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
+ "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/android-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
+ "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/darwin-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
+ "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/darwin-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
+ "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/freebsd-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
+ "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/freebsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
+ "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-arm": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
+ "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
+ "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-ia32": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
+ "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-loong64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
+ "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-mips64el": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
+ "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-ppc64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
+ "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-riscv64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
+ "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-s390x": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
+ "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
+ "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/netbsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
+ "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/openbsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
+ "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/sunos-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
+ "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/win32-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
+ "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/win32-ia32": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
+ "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
+ "dev": true,
+ "optional": true
+ },
"@esbuild/win32-x64": {
- "version": "0.17.15",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.15.tgz",
- "integrity": "sha512-DjDa9ywLUUmjhV2Y9wUTIF+1XsmuFGvZoCmOWkli1XcNAh5t25cc7fgsCx4Zi/Uurep3TTLyDiKATgGEg61pkA==",
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
+ "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
"dev": true,
"optional": true
},
@@ -2142,33 +2631,33 @@
}
},
"esbuild": {
- "version": "0.17.15",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.15.tgz",
- "integrity": "sha512-LBUV2VsUIc/iD9ME75qhT4aJj0r75abCVS0jakhFzOtR7TQsqQA5w0tZ+KTKnwl3kXE0MhskNdHDh/I5aCR1Zw==",
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
+ "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
"dev": true,
"requires": {
- "@esbuild/android-arm": "0.17.15",
- "@esbuild/android-arm64": "0.17.15",
- "@esbuild/android-x64": "0.17.15",
- "@esbuild/darwin-arm64": "0.17.15",
- "@esbuild/darwin-x64": "0.17.15",
- "@esbuild/freebsd-arm64": "0.17.15",
- "@esbuild/freebsd-x64": "0.17.15",
- "@esbuild/linux-arm": "0.17.15",
- "@esbuild/linux-arm64": "0.17.15",
- "@esbuild/linux-ia32": "0.17.15",
- "@esbuild/linux-loong64": "0.17.15",
- "@esbuild/linux-mips64el": "0.17.15",
- "@esbuild/linux-ppc64": "0.17.15",
- "@esbuild/linux-riscv64": "0.17.15",
- "@esbuild/linux-s390x": "0.17.15",
- "@esbuild/linux-x64": "0.17.15",
- "@esbuild/netbsd-x64": "0.17.15",
- "@esbuild/openbsd-x64": "0.17.15",
- "@esbuild/sunos-x64": "0.17.15",
- "@esbuild/win32-arm64": "0.17.15",
- "@esbuild/win32-ia32": "0.17.15",
- "@esbuild/win32-x64": "0.17.15"
+ "@esbuild/android-arm": "0.18.20",
+ "@esbuild/android-arm64": "0.18.20",
+ "@esbuild/android-x64": "0.18.20",
+ "@esbuild/darwin-arm64": "0.18.20",
+ "@esbuild/darwin-x64": "0.18.20",
+ "@esbuild/freebsd-arm64": "0.18.20",
+ "@esbuild/freebsd-x64": "0.18.20",
+ "@esbuild/linux-arm": "0.18.20",
+ "@esbuild/linux-arm64": "0.18.20",
+ "@esbuild/linux-ia32": "0.18.20",
+ "@esbuild/linux-loong64": "0.18.20",
+ "@esbuild/linux-mips64el": "0.18.20",
+ "@esbuild/linux-ppc64": "0.18.20",
+ "@esbuild/linux-riscv64": "0.18.20",
+ "@esbuild/linux-s390x": "0.18.20",
+ "@esbuild/linux-x64": "0.18.20",
+ "@esbuild/netbsd-x64": "0.18.20",
+ "@esbuild/openbsd-x64": "0.18.20",
+ "@esbuild/sunos-x64": "0.18.20",
+ "@esbuild/win32-arm64": "0.18.20",
+ "@esbuild/win32-ia32": "0.18.20",
+ "@esbuild/win32-x64": "0.18.20"
}
},
"escape-string-regexp": {
@@ -2518,9 +3007,9 @@
"dev": true
},
"rollup": {
- "version": "3.20.2",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.20.2.tgz",
- "integrity": "sha512-3zwkBQl7Ai7MFYQE0y1MeQ15+9jsi7XxfrqwTb/9EK8D9C9+//EBR4M+CuA1KODRaNbFez/lWxA5vhEGZp4MUg==",
+ "version": "3.29.4",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
+ "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==",
"dev": true,
"requires": {
"fsevents": "~2.3.2"
@@ -2642,16 +3131,15 @@
"dev": true
},
"vite": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/vite/-/vite-4.2.3.tgz",
- "integrity": "sha512-kLU+m2q0Y434Y1kCy3TchefAdtFso0ILi0dLyFV8Us3InXTU11H/B5ZTqCKIQHzSKNxVG/yEx813EA9f1imQ9A==",
+ "version": "4.5.2",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz",
+ "integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==",
"dev": true,
"requires": {
- "esbuild": "^0.17.5",
+ "esbuild": "^0.18.10",
"fsevents": "~2.3.2",
- "postcss": "^8.4.21",
- "resolve": "^1.22.1",
- "rollup": "^3.18.0"
+ "postcss": "^8.4.27",
+ "rollup": "^3.27.1"
}
},
"vite-plugin-dts": {
From 0efa9e79057d98ea38a3a17e1d9201e5edd8041a Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 20 Feb 2024 08:57:07 +0100
Subject: [PATCH 03/24] build(deps): bump the npm_and_yarn group across 1
directories with 2 updates (#810)
Bumps the npm_and_yarn group with 2 updates in the /extensions/wrapper/clients/typescript-client-example directory: [undici](https://github.com/nodejs/undici) and [@sveltejs/kit](https://github.com/sveltejs/kit/tree/HEAD/packages/kit).
Updates `undici` from 5.26.5 to 5.28.3
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v5.26.5...v5.28.3)
Updates `@sveltejs/kit` from 1.27.6 to 1.30.4
- [Release notes](https://github.com/sveltejs/kit/releases)
- [Changelog](https://github.com/sveltejs/kit/blob/@sveltejs/kit@1.30.4/packages/kit/CHANGELOG.md)
- [Commits](https://github.com/sveltejs/kit/commits/@sveltejs/kit@1.30.4/packages/kit)
---
updated-dependencies:
- dependency-name: undici
dependency-type: indirect
dependency-group: npm_and_yarn-security-group
- dependency-name: "@sveltejs/kit"
dependency-type: direct:development
dependency-group: npm_and_yarn-security-group
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.../package-lock.json | 30 +++++++++----------
.../typescript-client-example/package.json | 2 +-
2 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/extensions/wrapper/clients/typescript-client-example/package-lock.json b/extensions/wrapper/clients/typescript-client-example/package-lock.json
index 407c91412..1910384ec 100644
--- a/extensions/wrapper/clients/typescript-client-example/package-lock.json
+++ b/extensions/wrapper/clients/typescript-client-example/package-lock.json
@@ -12,7 +12,7 @@
},
"devDependencies": {
"@sveltejs/adapter-auto": "^2.0.0",
- "@sveltejs/kit": "^1.27.6",
+ "@sveltejs/kit": "^1.30.4",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.21",
"prettier": "^2.8.0",
@@ -514,9 +514,9 @@
}
},
"node_modules/@sveltejs/kit": {
- "version": "1.27.6",
- "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.27.6.tgz",
- "integrity": "sha512-GsjTkMbKzXdbeRg0tk8S7HNShQ4879ftRr0ZHaZfjbig1xQwG57Bvcm9U9/mpLJtCapLbLWUnygKrgcLISLC8A==",
+ "version": "1.30.4",
+ "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.30.4.tgz",
+ "integrity": "sha512-JSQIQT6XvdchCRQEm7BABxPC56WP5RYVONAi+09S8tmzeP43fBsRlr95bFmsTQM2RHBldfgQk+jgdnsKI75daA==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
@@ -532,7 +532,7 @@
"set-cookie-parser": "^2.6.0",
"sirv": "^2.0.2",
"tiny-glob": "^0.2.9",
- "undici": "~5.26.2"
+ "undici": "^5.28.3"
},
"bin": {
"svelte-kit": "svelte-kit.js"
@@ -2141,9 +2141,9 @@
}
},
"node_modules/undici": {
- "version": "5.26.5",
- "resolved": "https://registry.npmjs.org/undici/-/undici-5.26.5.tgz",
- "integrity": "sha512-cSb4bPFd5qgR7qr2jYAi0hlX9n5YKK2ONKkLFkxl+v/9BvC0sOpZjBHDBSXc5lWAf5ty9oZdRXytBIHzgUcerw==",
+ "version": "5.28.3",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.3.tgz",
+ "integrity": "sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==",
"dev": true,
"dependencies": {
"@fastify/busboy": "^2.0.0"
@@ -2524,9 +2524,9 @@
}
},
"@sveltejs/kit": {
- "version": "1.27.6",
- "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.27.6.tgz",
- "integrity": "sha512-GsjTkMbKzXdbeRg0tk8S7HNShQ4879ftRr0ZHaZfjbig1xQwG57Bvcm9U9/mpLJtCapLbLWUnygKrgcLISLC8A==",
+ "version": "1.30.4",
+ "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.30.4.tgz",
+ "integrity": "sha512-JSQIQT6XvdchCRQEm7BABxPC56WP5RYVONAi+09S8tmzeP43fBsRlr95bFmsTQM2RHBldfgQk+jgdnsKI75daA==",
"dev": true,
"requires": {
"@sveltejs/vite-plugin-svelte": "^2.5.0",
@@ -2541,7 +2541,7 @@
"set-cookie-parser": "^2.6.0",
"sirv": "^2.0.2",
"tiny-glob": "^0.2.9",
- "undici": "~5.26.2"
+ "undici": "^5.28.3"
}
},
"@sveltejs/vite-plugin-svelte": {
@@ -3616,9 +3616,9 @@
"dev": true
},
"undici": {
- "version": "5.26.5",
- "resolved": "https://registry.npmjs.org/undici/-/undici-5.26.5.tgz",
- "integrity": "sha512-cSb4bPFd5qgR7qr2jYAi0hlX9n5YKK2ONKkLFkxl+v/9BvC0sOpZjBHDBSXc5lWAf5ty9oZdRXytBIHzgUcerw==",
+ "version": "5.28.3",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.3.tgz",
+ "integrity": "sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==",
"dev": true,
"requires": {
"@fastify/busboy": "^2.0.0"
diff --git a/extensions/wrapper/clients/typescript-client-example/package.json b/extensions/wrapper/clients/typescript-client-example/package.json
index 532f3486c..b20b7ea96 100644
--- a/extensions/wrapper/clients/typescript-client-example/package.json
+++ b/extensions/wrapper/clients/typescript-client-example/package.json
@@ -17,7 +17,7 @@
},
"devDependencies": {
"@sveltejs/adapter-auto": "^2.0.0",
- "@sveltejs/kit": "^1.27.6",
+ "@sveltejs/kit": "^1.30.4",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.21",
"prettier": "^2.8.0",
From 496d837e47f7d926d539929d7b3c3bb9b11052cf Mon Sep 17 00:00:00 2001
From: Tim Berthold <75306992+tmberthold@users.noreply.github.com>
Date: Wed, 21 Feb 2024 11:25:16 +0100
Subject: [PATCH 04/24] Update security_scan.yml
---
.github/workflows/security_scan.yml | 39 +++++++++++++++++++----------
1 file changed, 26 insertions(+), 13 deletions(-)
diff --git a/.github/workflows/security_scan.yml b/.github/workflows/security_scan.yml
index c43bd288b..6a5076180 100644
--- a/.github/workflows/security_scan.yml
+++ b/.github/workflows/security_scan.yml
@@ -1,30 +1,43 @@
name: Trivy Security Scan
on:
- pull_request:
- branches: ["main"]
+ push:
+ workflow_dispatch:
jobs:
- security_scan:
- name: security_scan
+ security_scan_rootfs:
+ name: security_scan_rootfs
runs-on: ubuntu-latest
steps:
- name: Checkout code
- uses: actions/checkout@v3
-
- - name: Run static analysis
+ uses: actions/checkout@v4
+ - name: Run static analysis (rootfs)
uses: aquasecurity/trivy-action@master
with:
- scan-type: "fs"
- scanners: "vuln,config"
+ scan-type: "rootfs"
+ scanners: "vuln,misconfig"
ignore-unfixed: true
format: "sarif"
- output: "trivy-results.sarif"
+ output: "trivy-results-rootfs.sarif"
severity: "CRITICAL,HIGH"
-
- - name: Upload Trivy scan results to GitHub Security tab
+ security_scan_repo:
+ name: security_scan_repo
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ - name: Run static analysis (repo)
+ uses: aquasecurity/trivy-action@master
+ with:
+ scan-type: "repo"
+ scanners: "vuln,misconfig"
+ ignore-unfixed: true
+ format: "sarif"
+ output: "trivy-results-repo.sarif"
+ severity: "CRITICAL,HIGH"
+ - name: Upload Trivy scan results to GitHub Security tab (repo)
uses: github/codeql-action/upload-sarif@v2
continue-on-error: true
with:
- sarif_file: "trivy-results.sarif"
+ sarif_file: "trivy-results-repo.sarif"
category: "code"
From 315735f075c33a586ef6a1249e18ba96eef6372c Mon Sep 17 00:00:00 2001
From: sovitybot <107936402+sovitybot@users.noreply.github.com>
Date: Wed, 21 Feb 2024 12:20:05 +0100
Subject: [PATCH 05/24] Templates: synced file(s) with sovity/PMO-Software
(#755)
---
.github/ISSUE_TEMPLATE/bug_report.md | 26 +++++-----
.github/ISSUE_TEMPLATE/documentation.md | 30 ++++++++++++
.github/ISSUE_TEMPLATE/epic_template.md | 41 ++++++++++++++++
.github/ISSUE_TEMPLATE/feature_request.md | 32 +++++--------
.github/ISSUE_TEMPLATE/process.md | 24 ++++++++++
.github/PULL_REQUEST_TEMPLATE.md | 47 +++++++++++++++----
.../workflows/add_pullrequest_to_project.yml | 1 +
7 files changed, 160 insertions(+), 41 deletions(-)
create mode 100644 .github/ISSUE_TEMPLATE/documentation.md
create mode 100644 .github/ISSUE_TEMPLATE/epic_template.md
create mode 100644 .github/ISSUE_TEMPLATE/process.md
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 0b6ebc34e..55705dfff 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -2,37 +2,33 @@
name: Bug Report
about: Create a report to help us improve
title: ""
-labels: ["kind/bug", "task/analyze", "scope/ce"]
+labels: "kind/bug"
assignees: ""
---
# Bug Report
## Description
-
-_A clear and concise description of the bug._
-_If applicable, add screenshots or other information to help explain your problem._
+
+
### Expected Behavior
-
-_A clear and concise description of what you expected to happen._
+
### Observed Behavior
-
-_A clear and concise description of what happened instead._
+
## Steps to Reproduce
-
-Steps to reproduce the behavior:
+
## Context Information
-
-_Add any other context about the problem here._
+
## Possible Implementation and Work Breakdown
+
```[tasklist]
-- [ ] Fix the GitHub Projects Labels, Sprint and other Metadata
-- [ ] Refine a Solution Proposal / Work Breakdown
+- [ ] adjust the labels, sprint and other metadata
+- [ ] refine a solution proposal / work breakdown
```
diff --git a/.github/ISSUE_TEMPLATE/documentation.md b/.github/ISSUE_TEMPLATE/documentation.md
new file mode 100644
index 000000000..4ca8c166d
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/documentation.md
@@ -0,0 +1,30 @@
+---
+name: Documentation Update Request
+about: Create a report to help us improve our documentation
+title: ""
+labels: "task/documentation"
+assignees: ""
+---
+
+# Documentation Update Request
+
+## Description
+
+
+## Current Documentation
+
+
+## Proposed Changes
+
+
+## Justification
+
+
+## Additional Context
+
+
+## Deadline
+
+
+## Notes
+
diff --git a/.github/ISSUE_TEMPLATE/epic_template.md b/.github/ISSUE_TEMPLATE/epic_template.md
new file mode 100644
index 000000000..24edb0b59
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/epic_template.md
@@ -0,0 +1,41 @@
+---
+name: Epic
+about: Help us with new ideas
+title: ""
+labels: "kind/epic"
+assignees: ""
+---
+
+# Epic
+
+## Description
+
+
+### Requirements
+
+
+## Work Breakdown
+
+
+```[tasklist]
+### Stories
+- [ ] Create Stories which can be converted into issues
+```
+
+## Initiative / goal
+
+
+### Hypothesis
+
+
+## Acceptance criteria and must have scope
+
+
+## Stakeholders
+
+
+## Timeline
+
+
+## Need for refinement
+
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 866963cb3..2c9a8820f 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -1,39 +1,33 @@
---
name: Feature Request
-about: Help us improve the sovity CE EDC Connector product experience with new features suggestions
+about: Help us with new features
title: ""
-labels: ["kind/enhancement", "task/analyze", "status/blocked/needs-product", "scope/ce"]
-assignees: ["AbdullahMuk"]
+labels: "kind/enhancement"
+assignees: ""
---
# Feature Request
## Description
-
-_A clear and concise description of what the customer wants to happen._
-
+
- As a USER who PRECONDITIONS, I want to DO_THING, so I can ACCOMPLISH_GOAL.
## Which Areas Would Be Affected?
-
-_e.g., DPF, CI, build, transfer, etc._
+
## Why Is the Feature Desired?
+
-_What problems does that user face that existing functionalities do solve?_
-
-## How does this tie into the current product?
+## How does this tie into our current product?
+
-_Describe whether this request is related to an existing workflow, feature, or otherwise something in the product today. Or, does this open us up to new innovative ideas?_
+## Stakeholders
+
-## (For sovity Team to complete) Stakeholders
-
-_Add more on who asked for this, i.e. company, person, how much they pay us, what their tier is, are they a strategic account, etc. Who needs to be kept up-to-date about this feature?_
-
-## (For sovity Team to complete) Solution Proposal and Work Breakdown
+## Solution Proposal and Work Breakdown
+
```[tasklist]
- [ ] Fix the GitHub Projects Labels, Sprint and other Metadata
-- [ ] Refine further action items for this feature request
+- [ ] Refine a Solution Proposal / Work Breakdown
```
-
diff --git a/.github/ISSUE_TEMPLATE/process.md b/.github/ISSUE_TEMPLATE/process.md
new file mode 100644
index 000000000..4957c23cb
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/process.md
@@ -0,0 +1,24 @@
+---
+name: Refine Process Request
+about: Existing processes must be adapted or new ones created
+title: ""
+labels: "task/documentation"
+assignees: ""
+---
+
+# Process Refinement Request
+
+## Description
+
+
+## Current State
+
+
+## Proposed Changes
+
+
+## Related Issues or PRs
+
+
+## Additional Information
+
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index f23f70347..fb93cb530 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,12 +1,43 @@
+# Pull Request
-_What issues does this PR close?_
+_Briefly describe WHAT your PR changes, which features it adds/modifies._
+## How Has This Been Tested?
-```[tasklist]
-### Checklist
-- [ ] The PR title is short and expressive.
-- [ ] I have updated the CHANGELOG.md. See [changelog_update.md](https://github.com/sovity/edc-extensions/tree/main/docs/dev/changelog_updates.md) for more information.
-- [ ] I have updated the Deployment Migration Notes Section in the CHANGELOG.md for any configuration / external API changes.
-- [ ] I have performed a **self-review**
-```
+Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
+- Test A
+- Test B
+- ...
+
+**Test Configuration**:
+
+- Firmware version:
+- Hardware:
+- Toolchain:
+- SDK:
+
+## Linked Issue(s)
+
+_Use keywords to automate: https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword_
+
+- fixes # (issue)
+- closes # (issue)
+- ...
+
+## PR is blocked by
+
+- [ ] blocked by # (issue)
+
+# Checklist
+
+- [ ] I have **formatted the title** correctly and precisely
+- [ ] My code follows the **style guidelines** of this project
+- [ ] I have performed a **self-review** of my own code
+- [ ] I have **commented** my code, particularly in hard-to-understand areas and public classes/methods
+- [ ] I have made corresponding changes to the **documentation**
+- [ ] My changes generate **no new warnings** (performed checkstyle check locally)
+- [ ] I have added **tests that prove my fix** is effective or that my feature works
+- [ ] New and existing unit **tests pass locally** with my changes
+- [ ] Any dependent **changes have been merged** and published in downstream modules
+- [ ] I have added/updated **copyright headers**
diff --git a/.github/workflows/add_pullrequest_to_project.yml b/.github/workflows/add_pullrequest_to_project.yml
index 9ca64829c..08fc53826 100644
--- a/.github/workflows/add_pullrequest_to_project.yml
+++ b/.github/workflows/add_pullrequest_to_project.yml
@@ -5,6 +5,7 @@ on:
jobs:
add_pullrequest_to_project:
+ if: github.actor != 'dependabot' && github.actor != 'sovitybot' # ignore PRs from bots
name: add_pullrequest_to_project
runs-on: ubuntu-latest
steps:
From 18d062c00a270a7ff15fc34c2fa1ab95d35baa83 Mon Sep 17 00:00:00 2001
From: Christophe Loiseau <116@lab0.net>
Date: Wed, 21 Feb 2024 15:26:19 +0100
Subject: [PATCH 06/24] Remove unnecessary dependency
:data-plane-instance-store-sql (#812)
* Remove unnecessary dependency :data-plane-instance-store-sql and the associated service
---------
Co-authored-by: efiege <105237007+efiege@users.noreply.github.com>
---
CHANGELOG.md | 1 +
extensions/postgres-flyway/build.gradle.kts | 1 -
...neInstanceStatementsProviderExtension.java | 34 -------------------
...rg.eclipse.edc.spi.system.ServiceExtension | 1 -
4 files changed, 1 insertion(+), 36 deletions(-)
delete mode 100644 extensions/postgres-flyway/src/main/java/de/sovity/edc/extension/postgresql/DataPlaneInstanceStatementsProviderExtension.java
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f382d4a2f..ab5af6129 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ please see [changelog_updates.md](docs/dev/changelog_updates.md).
#### Patch Changes
- DspCatalogService: Contract Offer IDs are now stable
+- Fixed some requests' timeouts by removing the data-plane-instance-store-sql Extension
### Deployment Migration Notes
diff --git a/extensions/postgres-flyway/build.gradle.kts b/extensions/postgres-flyway/build.gradle.kts
index d2d94b3f7..203782838 100644
--- a/extensions/postgres-flyway/build.gradle.kts
+++ b/extensions/postgres-flyway/build.gradle.kts
@@ -20,7 +20,6 @@ dependencies {
// Adds Database-Related EDC-Extensions (EDC-SQL-Stores, JDBC-Driver, Pool and Transactions)
implementation("${edcGroup}:control-plane-sql:${edcVersion}")
- implementation("${edcGroup}:data-plane-instance-store-sql:${edcVersion}")
implementation("${tractusGroup}:sql-pool:${tractusVersion}")
implementation("${edcGroup}:transaction-local:${edcVersion}")
diff --git a/extensions/postgres-flyway/src/main/java/de/sovity/edc/extension/postgresql/DataPlaneInstanceStatementsProviderExtension.java b/extensions/postgres-flyway/src/main/java/de/sovity/edc/extension/postgresql/DataPlaneInstanceStatementsProviderExtension.java
deleted file mode 100644
index bb5b71ec1..000000000
--- a/extensions/postgres-flyway/src/main/java/de/sovity/edc/extension/postgresql/DataPlaneInstanceStatementsProviderExtension.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (c) 2023 sovity GmbH
- *
- * 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
- *
- * SPDX-License-Identifier: Apache-2.0
- *
- * Contributors:
- * sovity GmbH - initial implementation
- *
- */
-
-package de.sovity.edc.extension.postgresql;
-
-import org.eclipse.edc.connector.dataplane.selector.store.sql.schema.DataPlaneInstanceStatements;
-import org.eclipse.edc.connector.dataplane.selector.store.sql.schema.postgres.PostgresDataPlaneInstanceStatements;
-import org.eclipse.edc.runtime.metamodel.annotation.Provider;
-import org.eclipse.edc.spi.system.ServiceExtension;
-
-public class DataPlaneInstanceStatementsProviderExtension implements ServiceExtension {
-
-
- @Override
- public String name() {
- return "DataPlaneInstance Statements Provider";
- }
-
- @Provider
- public DataPlaneInstanceStatements dataPlaneInstanceStatements() {
- return new PostgresDataPlaneInstanceStatements();
- }
-}
\ No newline at end of file
diff --git a/extensions/postgres-flyway/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/extensions/postgres-flyway/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension
index 2662cff7e..b25d94ffc 100644
--- a/extensions/postgres-flyway/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension
+++ b/extensions/postgres-flyway/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension
@@ -1,2 +1 @@
de.sovity.edc.extension.postgresql.PostgresFlywayExtension
-de.sovity.edc.extension.postgresql.DataPlaneInstanceStatementsProviderExtension
From e8db3409fa2ec80710aaec9e5d213024fca527ed Mon Sep 17 00:00:00 2001
From: Christophe Loiseau <116@lab0.net>
Date: Wed, 21 Feb 2024 16:11:29 +0100
Subject: [PATCH 07/24] chore: prepare release (7.2.1, #815)
---
.env | 4 ++--
.github/ISSUE_TEMPLATE/release.md | 9 +++++----
CHANGELOG.md | 26 ++++++++++++++++++++++++++
3 files changed, 33 insertions(+), 6 deletions(-)
diff --git a/.env b/.env
index 2d381cc4d..54dae6834 100644
--- a/.env
+++ b/.env
@@ -1,5 +1,5 @@
# Env variables for docker-compose.yaml
-EDC_IMAGE=ghcr.io/sovity/edc-dev:7.2.0
-TEST_BACKEND_IMAGE=ghcr.io/sovity/test-backend:7.2.0
+EDC_IMAGE=ghcr.io/sovity/edc-dev:7.2.1
+TEST_BACKEND_IMAGE=ghcr.io/sovity/test-backend:7.2.1
EDC_UI_IMAGE=ghcr.io/sovity/edc-ui:2.4.0
EDC_UI_ACTIVE_PROFILE=sovity-open-source
diff --git a/.github/ISSUE_TEMPLATE/release.md b/.github/ISSUE_TEMPLATE/release.md
index 12278adf8..4565df1c7 100644
--- a/.github/ISSUE_TEMPLATE/release.md
+++ b/.github/ISSUE_TEMPLATE/release.md
@@ -12,7 +12,7 @@ assignees: ""
Feel free to edit this release checklist in-progress depending on what tasks need to be done:
-- [ ] Release [edc-ui](https://github.com/sovity/edc-ui), this might require several steps: _Link to EDC UI Release here_
+- [ ] Release [edc-ui](https://github.com/sovity/edc-ui), this might require several steps, first of which is to [create a new `Release` issue](https://github.com/sovity/edc-ui/issues/new/choose)
- [ ] Decide a release version depending on major/minor/patch changes in the CHANGELOG.md.
- [ ] Update this issue's title to the new version
- [ ] `release-prep` PR:
@@ -48,7 +48,8 @@ Feel free to edit this release checklist in-progress depending on what tasks nee
the [docker-compose's .env file](https://github.com/sovity/edc-extensions/blob/main/.env).
- [ ] Set the UI release version for `EDC_UI_IMAGE` of
the [docker-compose's .env file](https://github.com/sovity/edc-extensions/blob/main/.env).
- - [ ] If the core EDC version changed, update the `openapi.yaml`.
+ - [ ] If the Eclipse EDC version changed, update
+ the [eclipse-edc-management-api.yaml file](https://github.com/sovity/edc-extensions/blob/main/docs/eclipse-edc-management-api.yaml).
- [ ] Update the Postman Collection if required.
- [ ] Merge the `release-prep` PR.
- [ ] Wait for the main branch to be green.
@@ -59,6 +60,6 @@ Feel free to edit this release checklist in-progress depending on what tasks nee
- [ ] Check if the pipeline built the release versions in the Actions-Section (or you won't see it).
- [ ] Revisit the changed list of tasks and compare it
with [.github/ISSUE_TEMPLATE/release.md](https://github.com/sovity/edc-extensions/blob/main/.github/ISSUE_TEMPLATE/release.md).
- Propose changes where it
- makes sense.
+ Propose changes where it makes sense.
- [ ] Close this issue.
+- [ ] Inform the Product Manager of this new release
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ab5af6129..035c423b3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,13 +15,39 @@ please see [changelog_updates.md](docs/dev/changelog_updates.md).
#### Minor Changes
+#### Patch Changes
+
+### Deployment Migration Notes
+
+#### Compatible Versions
+
+
+## [7.2.1] - 2024-02-21
+
+### Overview
+
+Bugfixes
+
+### EDC UI
+
+https://github.com/sovity/edc-ui/releases/tag/v2.4.0
+
+### EDC Extensions
+
#### Patch Changes
- DspCatalogService: Contract Offer IDs are now stable
- Fixed some requests' timeouts by removing the data-plane-instance-store-sql Extension
### Deployment Migration Notes
+_No special deployment migration steps required_
+
#### Compatible Versions
+- Connector Backend Docker Images:
+ - Dev EDC: `ghcr.io/sovity/edc-dev:7.2.1`
+ - sovity EDC CE: `ghcr.io/sovity/edc-ce:7.2.1`
+ - MDS EDC CE: `ghcr.io/sovity/edc-ce-mds:7.2.1`
+- Connector UI Docker Image: `ghcr.io/sovity/edc-ui:2.4.0`
## [7.2.0] - 2024-02-14
From 3af5416da71fcce6456a0ced3bb2d9c7af684cca Mon Sep 17 00:00:00 2001
From: Christophe Loiseau <116@lab0.net>
Date: Wed, 21 Feb 2024 17:27:22 +0100
Subject: [PATCH 08/24] chore: release process improvements (#816)
---
.github/ISSUE_TEMPLATE/release.md | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/.github/ISSUE_TEMPLATE/release.md b/.github/ISSUE_TEMPLATE/release.md
index 4565df1c7..0849c09d6 100644
--- a/.github/ISSUE_TEMPLATE/release.md
+++ b/.github/ISSUE_TEMPLATE/release.md
@@ -52,11 +52,16 @@ Feel free to edit this release checklist in-progress depending on what tasks nee
the [eclipse-edc-management-api.yaml file](https://github.com/sovity/edc-extensions/blob/main/docs/eclipse-edc-management-api.yaml).
- [ ] Update the Postman Collection if required.
- [ ] Merge the `release-prep` PR.
-- [ ] Wait for the main branch to be green.
-- [ ] Test the release `docker-compose.yaml` with `RELEASE_EDC_IMAGE=ghcr.io/sovity/edc-dev:latest`.
- - [ ] Ensure with a `docker ps -a` that all containers are healthy, and not `healthy: starting` or `healthy: unhealthy`.
+- [ ] Wait for the main branch to be green. You can check the status in GH [actions](https://github.com/sovity/edc-extensions/actions).
+- [ ] Validate the image
+ - [ ] Pull the latest latest edc-dev image: `docker image pull ghcr.io/sovity/edc-dev:latest`.
+ - [ ] Check that your image was built recently `docker image ls | grep ghcr.io/sovity/edc-dev`.
+ - [ ] Test the release `docker-compose.yaml` with `EDC_IMAGE=ghcr.io/sovity/edc-dev:latest`.
+ - [ ] Ensure with a `docker ps -a` that all containers are healthy, and not `healthy: starting` or `healthy: unhealthy`.
- [ ] Test the postman collection against that running docker-compose.
-- [ ] Create a release and re-use the changelog section as release description, and the version as title.
+- [ ] [Create a release](https://github.com/sovity/edc-extensions/releases/new)
+ - [ ] In `Choose the tag`, type your new release version in the format `vx.y.z` (for instance `v1.2.3`) then click `+Create new tag vx.y.z on release`.
+ - [ ] Re-use the changelog section as release description, and the version as title.
- [ ] Check if the pipeline built the release versions in the Actions-Section (or you won't see it).
- [ ] Revisit the changed list of tasks and compare it
with [.github/ISSUE_TEMPLATE/release.md](https://github.com/sovity/edc-extensions/blob/main/.github/ISSUE_TEMPLATE/release.md).
From 3b42ce9a566c3adb46adaf44a68b587129945911 Mon Sep 17 00:00:00 2001
From: sovitybot <107936402+sovitybot@users.noreply.github.com>
Date: Thu, 22 Feb 2024 07:54:22 +0100
Subject: [PATCH 09/24] =?UTF-8?q?=F0=9F=94=84=20Templates:=20synced=20loca?=
=?UTF-8?q?l=20'.github/PULL=5FREQUEST=5FTEMPLATE.md'=20with=20remote=20'.?=
=?UTF-8?q?github/PULL=5FREQUEST=5FTEMPLATE.md'=20(#819)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/PULL_REQUEST_TEMPLATE.md | 49 ++++++--------------------------
1 file changed, 8 insertions(+), 41 deletions(-)
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index fb93cb530..c777e39aa 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,43 +1,10 @@
-# Pull Request
+_What issues does this PR close?_
-_Briefly describe WHAT your PR changes, which features it adds/modifies._
-## How Has This Been Tested?
-
-Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
-
-- Test A
-- Test B
-- ...
-
-**Test Configuration**:
-
-- Firmware version:
-- Hardware:
-- Toolchain:
-- SDK:
-
-## Linked Issue(s)
-
-_Use keywords to automate: https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword_
-
-- fixes # (issue)
-- closes # (issue)
-- ...
-
-## PR is blocked by
-
-- [ ] blocked by # (issue)
-
-# Checklist
-
-- [ ] I have **formatted the title** correctly and precisely
-- [ ] My code follows the **style guidelines** of this project
-- [ ] I have performed a **self-review** of my own code
-- [ ] I have **commented** my code, particularly in hard-to-understand areas and public classes/methods
-- [ ] I have made corresponding changes to the **documentation**
-- [ ] My changes generate **no new warnings** (performed checkstyle check locally)
-- [ ] I have added **tests that prove my fix** is effective or that my feature works
-- [ ] New and existing unit **tests pass locally** with my changes
-- [ ] Any dependent **changes have been merged** and published in downstream modules
-- [ ] I have added/updated **copyright headers**
+```[tasklist]
+### Checklist
+- [ ] The PR title is short and expressive.
+- [ ] I have updated the CHANGELOG.md. See [changelog_update.md](https://github.com/sovity/authority-portal/tree/main/docs/dev/changelog_updates.md) for more information.
+- [ ] I have updated the Deployment Migration Notes Section in the CHANGELOG.md for any configuration / external API changes.
+- [ ] I have performed a **self-review**
+```
From e2702224a43f2f188856d71815a6c442d899ad42 Mon Sep 17 00:00:00 2001
From: Tim Berthold <75306992+tmberthold@users.noreply.github.com>
Date: Thu, 22 Feb 2024 11:20:38 +0100
Subject: [PATCH 10/24] Delete .github/workflows/add_pullrequest_to_project.yml
Decision was done in PMO-Software.
---
.../workflows/add_pullrequest_to_project.yml | 17 -----------------
1 file changed, 17 deletions(-)
delete mode 100644 .github/workflows/add_pullrequest_to_project.yml
diff --git a/.github/workflows/add_pullrequest_to_project.yml b/.github/workflows/add_pullrequest_to_project.yml
deleted file mode 100644
index 08fc53826..000000000
--- a/.github/workflows/add_pullrequest_to_project.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-name: Add pull request to project action
-
-on:
- pull_request:
-
-jobs:
- add_pullrequest_to_project:
- if: github.actor != 'dependabot' && github.actor != 'sovitybot' # ignore PRs from bots
- name: add_pullrequest_to_project
- runs-on: ubuntu-latest
- steps:
- - uses: actions/add-to-project@v0.5.0
- with:
- project-url: https://github.com/orgs/sovity/projects/9
- github-token: ${{ secrets.ADD_ISSUE_TO_PROJECT_PAT }}
- labeled: area/dependency
- label-operator: NOT
From bbb26558e16a20110b2843332ece29bd45a410f3 Mon Sep 17 00:00:00 2001
From: sovitybot <107936402+sovitybot@users.noreply.github.com>
Date: Mon, 26 Feb 2024 15:42:54 +0100
Subject: [PATCH 11/24] =?UTF-8?q?=F0=9F=94=84=20Templates:=20synced=20file?=
=?UTF-8?q?(s)=20with=20sovity/PMO-Software=20(#826)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/ISSUE_TEMPLATE/bug_report.md | 48 --------------------
.github/ISSUE_TEMPLATE/bug_report.yaml | 62 ++++++++++++++++++++++++++
.github/ISSUE_TEMPLATE/config.yml | 1 +
3 files changed, 63 insertions(+), 48 deletions(-)
delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md
create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yaml
create mode 100644 .github/ISSUE_TEMPLATE/config.yml
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
deleted file mode 100644
index 55705dfff..000000000
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ /dev/null
@@ -1,48 +0,0 @@
----
-name: Bug Report
-about: Create a report to help us improve
-title: ""
-labels: "kind/bug"
-assignees: ""
----
-
-# Bug Report
-
-## Description
-
-
-
-### Expected Behavior
-
-
-### Observed Behavior
-
-
-## Steps to Reproduce
-
-
-## Context Information
-
-
-## Possible Implementation and Work Breakdown
-
-
-```[tasklist]
-- [ ] adjust the labels, sprint and other metadata
-- [ ] refine a solution proposal / work breakdown
-```
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml
new file mode 100644
index 000000000..ac0ab99fe
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yaml
@@ -0,0 +1,62 @@
+name: Bug Report Template
+description: Create a report to help us improve
+labels: ["kind/bug"]
+body:
+ - type: textarea
+ id: description
+ attributes:
+ label: Description - What happened? *
+ description: A clear and concise description of the bug.
+ placeholder: Tell us what you see!
+ validations:
+ required: true
+ - type: textarea
+ id: expected
+ attributes:
+ label: Expected Behavior *
+ description: A clear and concise description of what you expected to happen.
+ placeholder: Tell us what you expected!
+ validations:
+ required: true
+ - type: textarea
+ id: observed
+ attributes:
+ label: Observed Behavior *
+ description: A clear and concise description of what happened instead.
+ placeholder: Tell us what you observed!
+ validations:
+ required: true
+ - type: textarea
+ id: steps
+ attributes:
+ label: Steps to Reproduce
+ description: Steps to reproduce the behavior.
+ placeholder: Tell us how to reproduce the issue!
+ validations:
+ required: false
+ - type: textarea
+ id: context
+ attributes:
+ label: Context Information
+ description: Add any other context about the problem here.
+ validations:
+ required: false
+ - type: textarea
+ id: logs
+ attributes:
+ label: Relevant log output
+ description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
+ render: shell
+ validations:
+ required: false
+ - type: textarea
+ id: screenshots
+ attributes:
+ label: Screenshots
+ description: If applicable, add screenshots or other information to help explain your problem.
+ validations:
+ required: false
+ - type: markdown
+ attributes:
+ value: |
+ _* These fields are mandatory, without filling them it is not possible to create the issue._
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 000000000..0086358db
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1 @@
+blank_issues_enabled: true
From 91aaa8abe3da8437d93d637adab04abc3cdd9203 Mon Sep 17 00:00:00 2001
From: sovitybot <107936402+sovitybot@users.noreply.github.com>
Date: Tue, 27 Feb 2024 14:21:08 +0100
Subject: [PATCH 12/24] =?UTF-8?q?=F0=9F=94=84=20Templates:=20synced=20loca?=
=?UTF-8?q?l=20'.github/ISSUE=5FTEMPLATE/feature=5Frequest.md'=20with=20re?=
=?UTF-8?q?mote=20'.github/ISSUE=5FTEMPLATE/feature=5Frequest.md'=20(#829)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/ISSUE_TEMPLATE/feature_request.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 2c9a8820f..5ff7afa21 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -30,4 +30,5 @@ assignees: ""
```[tasklist]
- [ ] Fix the GitHub Projects Labels, Sprint and other Metadata
- [ ] Refine a Solution Proposal / Work Breakdown
+- [ ] (For Tech Team): Include acceptance criteria for the sub-tasks of the work breakdown
```
From d7d87c6b0a3f1f6015d986efcd7bd08c3a88517d Mon Sep 17 00:00:00 2001
From: sovitybot <107936402+sovitybot@users.noreply.github.com>
Date: Thu, 29 Feb 2024 09:29:31 +0100
Subject: [PATCH 13/24] =?UTF-8?q?=F0=9F=94=84=20Templates:=20synced=20loca?=
=?UTF-8?q?l=20'.github/ISSUE=5FTEMPLATE/bug=5Freport.yaml'=20with=20remot?=
=?UTF-8?q?e=20'.github/ISSUE=5FTEMPLATE/bug=5Freport.yaml'=20(#835)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/ISSUE_TEMPLATE/bug_report.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml
index ac0ab99fe..93c91f3fd 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yaml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yaml
@@ -1,5 +1,5 @@
name: Bug Report Template
-description: Create a report to help us improve
+description: Report a bug to help us improve
labels: ["kind/bug"]
body:
- type: textarea
From 93d47f65bbccf3e5d246f0c33707fd3e30f29ed5 Mon Sep 17 00:00:00 2001
From: Christophe Loiseau
Date: Tue, 5 Mar 2024 12:08:45 +0100
Subject: [PATCH 14/24] feat: add custom Json and JsonLd fields (#820)
---
CHANGELOG.md | 2 +
UPDATES.md | 32 ++++
docs/sovity-edc-api-wrapper.yaml | 135 ++++++--------
.../ext/wrapper/api/common/model/UiAsset.java | 32 ++--
.../common/model/UiAssetCreateRequest.java | 30 +--
.../model/UiAssetEditMetadataRequest.java | 30 +--
.../wrapper-common-mappers/build.gradle.kts | 8 +-
.../mappers/utils/JsonBuilderUtils.java | 9 +
.../common/mappers/utils/UiAssetMapper.java | 92 ++++++----
.../api/common/mappers/AssetMapperTest.java | 64 +++++--
.../wrapper/api/common/mappers/TestUtils.java | 15 ++
.../src/test/resources/example-asset.jsonld | 19 +-
extensions/wrapper/wrapper/build.gradle.kts | 12 +-
.../api/ui/pages/asset/AssetApiService.java | 17 +-
.../api/ui/pages/asset/AssetBuilder.java | 34 ++--
.../ui/pages/asset/AssetApiServiceTest.java | 72 +++++++-
.../ContractAgreementPageTest.java | 2 +-
gradle.properties | 1 +
tests/build.gradle.kts | 7 +-
.../edc/e2e/Ms8ConnectorMigrationTest.java | 10 -
.../de/sovity/edc/e2e/UiApiWrapperTest.java | 172 ++++++++++++++++--
.../V1_9__ms8-test-contract-provider.sql | 3 -
.../catalog/mapper/DspContractOfferUtils.java | 2 +-
.../sovity/edc/utils/jsonld/vocab/Prop.java | 2 +
24 files changed, 564 insertions(+), 238 deletions(-)
create mode 100644 UPDATES.md
create mode 100644 extensions/wrapper/wrapper-common-mappers/src/test/java/de/sovity/edc/ext/wrapper/api/common/mappers/TestUtils.java
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 035c423b3..0490db0ab 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,8 @@ please see [changelog_updates.md](docs/dev/changelog_updates.md).
#### Minor Changes
+- UIAsset: Replaced unsafe additional and private properties with safer alternative fields `customJsonAsString` (**not** affected by Json LD manipulation) and `customJsonLdAsString` (affected by Json LD manipulation), along with their private counterparts.
+
#### Patch Changes
### Deployment Migration Notes
diff --git a/UPDATES.md b/UPDATES.md
new file mode 100644
index 000000000..62e413204
--- /dev/null
+++ b/UPDATES.md
@@ -0,0 +1,32 @@
+
+# Updates checklist
+
+This is a checklist about the workarounds that we had to use and may cause trouble in the future.
+These are not easily testable and require a manual check.
+
+---
+
+After each EDC version update
+
+- [ ] Check if `org.eclipse.edc.spi.types.domain.asset.Asset.toBuilder` added a new
+ field and adjust the builder in `de.sovity.edc.ext.wrapper.api.ui.pages.asset.AssetBuilder.fromEditMetadataRequest` accordingly
+
+## Context
+
+### Asset.toBuilder
+
+A list of the element that may break when updating the EDC version.
+
+In `de.sovity.edc.ext.wrapper.api.ui.pages.asset.AssetBuilder.fromEditMetadataRequest`
+
+When re-creating the asset, we can't re-use the `Asset.toBuilder()` as it doesn't allow us to remove properties.
+
+We must therefore re-build the asset using the same content as that `.toBuilder()`.
+
+If the Eclipse EDC adds a field in this builder, we will miss it and any write to the JsonLd via the web API
+will remove that hypothetical new field.
+
+#### Workaround
+
+On the EDC version update, check that `org.eclipse.edc.spi.types.domain.asset.Asset.toBuilder` doesn't set more
+fields than what we set. If a new field was added, add it to this function too.
diff --git a/docs/sovity-edc-api-wrapper.yaml b/docs/sovity-edc-api-wrapper.yaml
index 1f6471ec2..c8ae99553 100644
--- a/docs/sovity-edc-api-wrapper.yaml
+++ b/docs/sovity-edc-api-wrapper.yaml
@@ -558,34 +558,23 @@ components:
type: string
description: Data Address
description: Data Address
- additionalProperties:
- type: object
- additionalProperties:
- type: string
- description: Custom Asset Properties (that are strings)
- description: Custom Asset Properties (that are strings)
- additionalJsonProperties:
- type: object
- additionalProperties:
- type: string
- description: Custom Asset Properties (that are not strings but other JSON
- values)
- description: Custom Asset Properties (that are not strings but other JSON
- values)
- privateProperties:
- type: object
- additionalProperties:
- type: string
- description: Private Asset Properties (that are strings)
- description: Private Asset Properties (that are strings)
- privateJsonProperties:
- type: object
- additionalProperties:
- type: string
- description: Private Asset Properties (that are not strings but other
- JSON values)
- description: Private Asset Properties (that are not strings but other JSON
- values)
+ customJsonAsString:
+ type: string
+ description: Contains serialized custom properties in the JSON format.
+ customJsonLdAsString:
+ type: string
+ description: "Contains serialized custom properties in the JSON LD format.\
+ \ Contrary to the customJsonAsString field, this string must represent\
+ \ a JSON LD object and will be affected by JSON LD compaction and expansion.\
+ \ Due to a technical limitation, the properties can't be booleans."
+ privateCustomJsonAsString:
+ type: string
+ description: Same as customJsonAsString but the data will be stored in the
+ private properties.
+ privateCustomJsonLdAsString:
+ type: string
+ description: Same as customJsonLdAsString but the data will be stored in
+ the private properties. The same limitations apply.
description: Type-Safe OpenAPI generator friendly Asset Create DTO that supports
an opinionated subset of the original EDC Asset Entity.
IdResponseDto:
@@ -839,34 +828,23 @@ components:
type: string
description: Temporal coverage end date (inclusive)
format: date
- additionalProperties:
- type: object
- additionalProperties:
- type: string
- description: Custom Asset Properties (that are strings)
- description: Custom Asset Properties (that are strings)
- additionalJsonProperties:
- type: object
- additionalProperties:
- type: string
- description: Custom Asset Properties (that are not strings but other JSON
- values)
- description: Custom Asset Properties (that are not strings but other JSON
- values)
- privateProperties:
- type: object
- additionalProperties:
- type: string
- description: Private Asset Properties (that are strings)
- description: Private Asset Properties (that are strings)
- privateJsonProperties:
- type: object
- additionalProperties:
- type: string
- description: Private Asset Properties (that are not strings but other
- JSON values)
- description: Private Asset Properties (that are not strings but other JSON
- values)
+ customJsonAsString:
+ type: string
+ description: Contains serialized custom properties in the JSON format.
+ customJsonLdAsString:
+ type: string
+ description: "Contains serialized custom properties in the JSON LD format.\
+ \ Contrary to the customJsonAsString field, this string must represent\
+ \ a JSON LD object and will be affected by JSON LD compaction and expansion.\
+ \ Due to a technical limitation, the properties can't be booleans."
+ privateCustomJsonAsString:
+ type: string
+ description: Same as customJsonAsString but the data will be stored in the
+ private properties.
+ privateCustomJsonLdAsString:
+ type: string
+ description: Same as customJsonLdAsString but the data will be stored in
+ the private properties. The same limitations apply.
description: Data for editing an asset.
AssetPage:
required:
@@ -1010,37 +988,26 @@ components:
type: string
description: Temporal coverage end date (inclusive)
format: date
- additionalProperties:
- type: object
- additionalProperties:
- type: string
- description: Unhandled Asset Properties (that were strings)
- description: Unhandled Asset Properties (that were strings)
- additionalJsonProperties:
- type: object
- additionalProperties:
- type: string
- description: Unhandled Asset Properties (that were not strings but other
- JSON values)
- description: Unhandled Asset Properties (that were not strings but other
- JSON values)
- privateProperties:
- type: object
- additionalProperties:
- type: string
- description: Private Asset Properties (that were strings)
- description: Private Asset Properties (that were strings)
- privateJsonProperties:
- type: object
- additionalProperties:
- type: string
- description: Private Asset Properties (that were not strings but other
- JSON values)
- description: Private Asset Properties (that were not strings but other JSON
- values)
assetJsonLd:
type: string
description: Contains the entire asset in the JSON-LD format
+ customJsonAsString:
+ type: string
+ description: Contains serialized custom properties in the JSON format.
+ customJsonLdAsString:
+ type: string
+ description: "Contains serialized custom properties in the JSON LD format.\
+ \ Contrary to the customJsonAsString field, this string must represent\
+ \ a JSON LD object and will be affected by JSON LD compaction and expansion.\
+ \ Due to a technical limitation, the properties can't be booleans."
+ privateCustomJsonAsString:
+ type: string
+ description: Same as customJsonAsString but the data will be stored in the
+ private properties.
+ privateCustomJsonLdAsString:
+ type: string
+ description: Same as customJsonLdAsString but the data will be stored in
+ the private properties. The same limitations apply.
description: Type-Safe Asset Metadata as needed by our UI
UiContractOffer:
required:
diff --git a/extensions/wrapper/wrapper-common-api/src/main/java/de/sovity/edc/ext/wrapper/api/common/model/UiAsset.java b/extensions/wrapper/wrapper-common-api/src/main/java/de/sovity/edc/ext/wrapper/api/common/model/UiAsset.java
index 8af27b0ab..f75dbd6f1 100644
--- a/extensions/wrapper/wrapper-common-api/src/main/java/de/sovity/edc/ext/wrapper/api/common/model/UiAsset.java
+++ b/extensions/wrapper/wrapper-common-api/src/main/java/de/sovity/edc/ext/wrapper/api/common/model/UiAsset.java
@@ -138,18 +138,26 @@ public class UiAsset {
@Schema(description = "Temporal coverage end date (inclusive)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private LocalDate temporalCoverageToInclusive;
- @Schema(description = "Unhandled Asset Properties (that were strings)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
- private Map additionalProperties;
-
- @Schema(description = "Unhandled Asset Properties (that were not strings but other JSON values)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
- private Map additionalJsonProperties;
-
- @Schema(description = "Private Asset Properties (that were strings)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
- private Map privateProperties;
-
- @Schema(description = "Private Asset Properties (that were not strings but other JSON values)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
- private Map privateJsonProperties;
-
@Schema(description = "Contains the entire asset in the JSON-LD format", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private String assetJsonLd;
+
+ @Schema(description = "Contains serialized custom properties in the JSON format.",
+ requiredMode = Schema.RequiredMode.NOT_REQUIRED)
+ private String customJsonAsString;
+
+ @Schema(description = "Contains serialized custom properties in the JSON LD format. " +
+ "Contrary to the customJsonAsString field, this string must represent a JSON LD object " +
+ "and will be affected by JSON LD compaction and expansion. " +
+ "Due to a technical limitation, the properties can't be booleans.",
+ requiredMode = Schema.RequiredMode.NOT_REQUIRED)
+ private String customJsonLdAsString;
+
+ @Schema(description = "Same as customJsonAsString but the data will be stored in the private properties.",
+ requiredMode = Schema.RequiredMode.NOT_REQUIRED)
+ private String privateCustomJsonAsString;
+
+ @Schema(description = "Same as customJsonLdAsString but the data will be stored in the private properties. " +
+ "The same limitations apply.",
+ requiredMode = Schema.RequiredMode.NOT_REQUIRED)
+ private String privateCustomJsonLdAsString;
}
diff --git a/extensions/wrapper/wrapper-common-api/src/main/java/de/sovity/edc/ext/wrapper/api/common/model/UiAssetCreateRequest.java b/extensions/wrapper/wrapper-common-api/src/main/java/de/sovity/edc/ext/wrapper/api/common/model/UiAssetCreateRequest.java
index 69c2de0b4..ac1930880 100644
--- a/extensions/wrapper/wrapper-common-api/src/main/java/de/sovity/edc/ext/wrapper/api/common/model/UiAssetCreateRequest.java
+++ b/extensions/wrapper/wrapper-common-api/src/main/java/de/sovity/edc/ext/wrapper/api/common/model/UiAssetCreateRequest.java
@@ -108,15 +108,23 @@ public class UiAssetCreateRequest {
@Schema(description = "Data Address", requiredMode = Schema.RequiredMode.REQUIRED)
private Map dataAddressProperties;
- @Schema(description = "Custom Asset Properties (that are strings)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
- private Map additionalProperties;
-
- @Schema(description = "Custom Asset Properties (that are not strings but other JSON values)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
- private Map additionalJsonProperties;
-
- @Schema(description = "Private Asset Properties (that are strings)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
- private Map privateProperties;
-
- @Schema(description = "Private Asset Properties (that are not strings but other JSON values)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
- private Map privateJsonProperties;
+ @Schema(description = "Contains serialized custom properties in the JSON format.",
+ requiredMode = Schema.RequiredMode.NOT_REQUIRED)
+ private String customJsonAsString;
+
+ @Schema(description = "Contains serialized custom properties in the JSON LD format. " +
+ "Contrary to the customJsonAsString field, this string must represent a JSON LD object " +
+ "and will be affected by JSON LD compaction and expansion. " +
+ "Due to a technical limitation, the properties can't be booleans.",
+ requiredMode = Schema.RequiredMode.NOT_REQUIRED)
+ private String customJsonLdAsString;
+
+ @Schema(description = "Same as customJsonAsString but the data will be stored in the private properties.",
+ requiredMode = Schema.RequiredMode.NOT_REQUIRED)
+ private String privateCustomJsonAsString;
+
+ @Schema(description = "Same as customJsonLdAsString but the data will be stored in the private properties. " +
+ "The same limitations apply.",
+ requiredMode = Schema.RequiredMode.NOT_REQUIRED)
+ private String privateCustomJsonLdAsString;
}
diff --git a/extensions/wrapper/wrapper-common-api/src/main/java/de/sovity/edc/ext/wrapper/api/common/model/UiAssetEditMetadataRequest.java b/extensions/wrapper/wrapper-common-api/src/main/java/de/sovity/edc/ext/wrapper/api/common/model/UiAssetEditMetadataRequest.java
index f321bae58..53ea64c10 100644
--- a/extensions/wrapper/wrapper-common-api/src/main/java/de/sovity/edc/ext/wrapper/api/common/model/UiAssetEditMetadataRequest.java
+++ b/extensions/wrapper/wrapper-common-api/src/main/java/de/sovity/edc/ext/wrapper/api/common/model/UiAssetEditMetadataRequest.java
@@ -102,15 +102,23 @@ public class UiAssetEditMetadataRequest {
@Schema(description = "Temporal coverage end date (inclusive)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private LocalDate temporalCoverageToInclusive;
- @Schema(description = "Custom Asset Properties (that are strings)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
- private Map additionalProperties;
-
- @Schema(description = "Custom Asset Properties (that are not strings but other JSON values)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
- private Map additionalJsonProperties;
-
- @Schema(description = "Private Asset Properties (that are strings)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
- private Map privateProperties;
-
- @Schema(description = "Private Asset Properties (that are not strings but other JSON values)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
- private Map privateJsonProperties;
+ @Schema(description = "Contains serialized custom properties in the JSON format.",
+ requiredMode = Schema.RequiredMode.NOT_REQUIRED)
+ private String customJsonAsString;
+
+ @Schema(description = "Contains serialized custom properties in the JSON LD format. " +
+ "Contrary to the customJsonAsString field, this string must represent a JSON LD object " +
+ "and will be affected by JSON LD compaction and expansion. " +
+ "Due to a technical limitation, the properties can't be booleans.",
+ requiredMode = Schema.RequiredMode.NOT_REQUIRED)
+ private String customJsonLdAsString;
+
+ @Schema(description = "Same as customJsonAsString but the data will be stored in the private properties.",
+ requiredMode = Schema.RequiredMode.NOT_REQUIRED)
+ private String privateCustomJsonAsString;
+
+ @Schema(description = "Same as customJsonLdAsString but the data will be stored in the private properties. " +
+ "The same limitations apply.",
+ requiredMode = Schema.RequiredMode.NOT_REQUIRED)
+ private String privateCustomJsonLdAsString;
}
diff --git a/extensions/wrapper/wrapper-common-mappers/build.gradle.kts b/extensions/wrapper/wrapper-common-mappers/build.gradle.kts
index e6a15314d..1b5893174 100644
--- a/extensions/wrapper/wrapper-common-mappers/build.gradle.kts
+++ b/extensions/wrapper/wrapper-common-mappers/build.gradle.kts
@@ -1,8 +1,9 @@
val lombokVersion: String by project
+val assertj: String by project
val edcGroup: String by project
val edcVersion: String by project
-val assertj: String by project
+val jsonUnit: String by project
val mockitoVersion: String by project
plugins {
@@ -27,11 +28,12 @@ dependencies {
testAnnotationProcessor("org.projectlombok:lombok:${lombokVersion}")
testCompileOnly("org.projectlombok:lombok:${lombokVersion}")
testImplementation("${edcGroup}:json-ld:${edcVersion}")
+ testImplementation("net.javacrumbs.json-unit:json-unit-assertj:${jsonUnit}")
+ testImplementation("org.assertj:assertj-core:${assertj}")
+ testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
testImplementation("org.mockito:mockito-core:${mockitoVersion}")
testImplementation("org.mockito:mockito-inline:${mockitoVersion}")
testImplementation("org.mockito:mockito-junit-jupiter:${mockitoVersion}")
- testImplementation("org.assertj:assertj-core:${assertj}")
- testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.0")
}
diff --git a/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/utils/JsonBuilderUtils.java b/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/utils/JsonBuilderUtils.java
index a390ede30..3fbee875a 100644
--- a/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/utils/JsonBuilderUtils.java
+++ b/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/utils/JsonBuilderUtils.java
@@ -60,4 +60,13 @@ protected static JsonObjectBuilder addNonNullJsonValue(JsonObjectBuilder builder
builder.add(key, value);
return builder;
}
+
+ protected static JsonObjectBuilder addNonNullJsonValue(JsonObjectBuilder builder, String key, JsonValue value) {
+ if (value == null || value.getValueType() == JsonValue.ValueType.NULL) {
+ return builder;
+ }
+
+ builder.add(key, value);
+ return builder;
+ }
}
diff --git a/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/utils/UiAssetMapper.java b/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/utils/UiAssetMapper.java
index 8895a6fb2..b48acacb8 100644
--- a/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/utils/UiAssetMapper.java
+++ b/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/utils/UiAssetMapper.java
@@ -26,6 +26,7 @@
import jakarta.json.JsonValue;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
+import lombok.val;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@@ -98,6 +99,8 @@ public UiAsset buildUiAsset(JsonObject assetJsonLd, String connectorEndpoint, St
var publisher = JsonLdUtils.object(properties, Prop.Dcterms.PUBLISHER);
uiAsset.setPublisherHomepage(JsonLdUtils.string(publisher, Prop.Foaf.HOMEPAGE));
+ uiAsset.setCustomJsonAsString(JsonLdUtils.string(properties, Prop.SovityDcatExt.CUSTOM_JSON));
+
uiAsset.setCreatorOrganizationName(creatorOrganizationName);
// Additional / Remaining Properties
@@ -140,19 +143,40 @@ public UiAsset buildUiAsset(JsonObject assetJsonLd, String connectorEndpoint, St
HttpDatasourceHints.BODY,
HttpDatasourceHints.METHOD,
HttpDatasourceHints.PATH,
- HttpDatasourceHints.QUERY_PARAMS
+ HttpDatasourceHints.QUERY_PARAMS,
+
+ Prop.SovityDcatExt.CUSTOM_JSON
));
- uiAsset.setAdditionalProperties(getStringProperties(remaining));
- uiAsset.setAdditionalJsonProperties(getJsonProperties(remaining));
+
+ // custom properties
+ val serializedJsonLd = packAsJsonLdProperties(remaining);
+ uiAsset.setCustomJsonLdAsString(serializedJsonLd);
// Private Properties
- var privateProperties = JsonLdUtils.tryCompact(getPrivateProperties(assetJsonLd));
- uiAsset.setPrivateProperties(getStringProperties(privateProperties));
- uiAsset.setPrivateJsonProperties(getJsonProperties(privateProperties));
+ val privateProperties = getPrivateProperties(assetJsonLd);
+ if (privateProperties != null) {
+ val privateCustomJson = JsonLdUtils.string(privateProperties, Prop.SovityDcatExt.PRIVATE_CUSTOM_JSON);
+ uiAsset.setPrivateCustomJsonAsString(privateCustomJson);
+
+ val privateRemaining = removeHandledProperties(
+ privateProperties,
+ List.of(Prop.SovityDcatExt.PRIVATE_CUSTOM_JSON));
+ val privateSerializedJsonLd = packAsJsonLdProperties(privateRemaining);
+ uiAsset.setPrivateCustomJsonLdAsString(privateSerializedJsonLd);
+ }
return uiAsset;
}
+ private static String packAsJsonLdProperties(JsonObject remaining) {
+ val customJsonLd = Json.createObjectBuilder();
+ for (val e : remaining.entrySet()) {
+ customJsonLd.add(e.getKey(), e.getValue());
+ }
+ JsonObject compacted = JsonLdUtils.tryCompact(customJsonLd.build());
+ return JsonUtils.toJson(compacted);
+ }
+
@SneakyThrows
@Nullable
public JsonObject buildAssetJsonLd(
@@ -213,37 +237,49 @@ private JsonObjectBuilder getAssetProperties(
.add(Prop.Foaf.NAME, organizationName));
var dataAddress = uiAssetCreateRequest.getDataAddressProperties();
- if (dataAddress.get(Prop.Edc.TYPE).equals("HttpData")) {
+ if (dataAddress != null && dataAddress.get(Prop.Edc.TYPE).equals("HttpData")) {
addNonNull(properties, HttpDatasourceHints.BODY, trueIfTrue(dataAddress, Prop.Edc.PROXY_BODY));
addNonNull(properties, HttpDatasourceHints.PATH, trueIfTrue(dataAddress, Prop.Edc.PROXY_PATH));
addNonNull(properties, HttpDatasourceHints.QUERY_PARAMS, trueIfTrue(dataAddress, Prop.Edc.PROXY_QUERY_PARAMS));
addNonNull(properties, HttpDatasourceHints.METHOD, trueIfTrue(dataAddress, Prop.Edc.PROXY_METHOD));
}
- var additionalProperties = uiAssetCreateRequest.getAdditionalProperties();
- if (additionalProperties != null) {
- additionalProperties.forEach((k, v) -> addNonNull(properties, k, v));
- }
+ addNonNull(properties, Prop.SovityDcatExt.CUSTOM_JSON, uiAssetCreateRequest.getCustomJsonAsString());
- var additionalJsonProperties = uiAssetCreateRequest.getAdditionalJsonProperties();
- if (additionalJsonProperties != null) {
- additionalJsonProperties.forEach((k, v) -> addNonNullJsonValue(properties, k, v));
+ val jsonLdStr = uiAssetCreateRequest.getCustomJsonLdAsString();
+ if (jsonLdStr != null) {
+ val jsonLd = JsonUtils.parseJsonObj(jsonLdStr);
+ for (val e : jsonLd.entrySet()) {
+ addNonNullJsonValue(properties, e.getKey(), e.getValue());
+ }
}
return properties;
}
+ private void add(JsonObject overrides, JsonObjectBuilder properties, String key, String value) {
+ val override = JsonLdUtils.string(overrides, key);
+ if (override != null) {
+ properties.add(key, override);
+ }
+ properties.add(key, value);
+ }
+
private JsonObjectBuilder getAssetPrivateProperties(UiAssetCreateRequest uiAssetCreateRequest) {
var privateProperties = Json.createObjectBuilder();
- var stringProperties = uiAssetCreateRequest.getPrivateProperties();
- if (stringProperties != null) {
- stringProperties.forEach((k, v) -> addNonNull(privateProperties, k, v));
+ val privateJsonStr = uiAssetCreateRequest.getPrivateCustomJsonAsString();
+ if (privateJsonStr != null) {
+ addNonNull(
+ privateProperties,
+ Prop.SovityDcatExt.PRIVATE_CUSTOM_JSON,
+ privateJsonStr);
}
- var jsonProperties = uiAssetCreateRequest.getPrivateJsonProperties();
- if (jsonProperties != null) {
- jsonProperties.forEach((k, v) -> addNonNullJsonValue(privateProperties, k, v));
+ val privateJsonLdStr = uiAssetCreateRequest.getPrivateCustomJsonLdAsString();
+ if (privateJsonLdStr != null) {
+ val privateJsonLd = JsonUtils.parseJsonObj(privateJsonLdStr);
+ privateJsonLd.forEach((k, v) -> addNonNullJsonValue(privateProperties, k, v));
}
return privateProperties;
@@ -260,22 +296,6 @@ private JsonObjectBuilder getDataAddress(UiAssetCreateRequest uiAssetCreateReque
.add(Prop.Edc.PROPERTIES, Json.createObjectBuilder(props));
}
- private Map getStringProperties(JsonObject jsonObject) {
- return getPropertyMap(
- jsonObject,
- it -> it.getValueType() == JsonValue.ValueType.STRING,
- it -> ((JsonString) it).getString()
- );
- }
-
- private Map getJsonProperties(JsonObject jsonObject) {
- return getPropertyMap(
- jsonObject,
- it -> it.getValueType() != JsonValue.ValueType.STRING,
- JsonUtils::toJson
- );
- }
-
private JsonObject removeHandledProperties(JsonObject properties, List handledProperties) {
var remaining = Json.createObjectBuilder(JsonLdUtils.tryCompact(properties));
handledProperties.forEach(remaining::remove);
diff --git a/extensions/wrapper/wrapper-common-mappers/src/test/java/de/sovity/edc/ext/wrapper/api/common/mappers/AssetMapperTest.java b/extensions/wrapper/wrapper-common-mappers/src/test/java/de/sovity/edc/ext/wrapper/api/common/mappers/AssetMapperTest.java
index 7ccde1954..03ede23ac 100644
--- a/extensions/wrapper/wrapper-common-mappers/src/test/java/de/sovity/edc/ext/wrapper/api/common/mappers/AssetMapperTest.java
+++ b/extensions/wrapper/wrapper-common-mappers/src/test/java/de/sovity/edc/ext/wrapper/api/common/mappers/AssetMapperTest.java
@@ -8,20 +8,19 @@
import de.sovity.edc.utils.JsonUtils;
import de.sovity.edc.utils.jsonld.vocab.Prop;
import lombok.SneakyThrows;
+import net.javacrumbs.jsonunit.assertj.JsonAssertions;
import org.eclipse.edc.jsonld.TitaniumJsonLd;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.transform.spi.TypeTransformerRegistry;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import java.nio.file.Files;
-import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
-import java.util.Map;
import static jakarta.json.Json.createArrayBuilder;
import static jakarta.json.Json.createObjectBuilder;
+import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -43,7 +42,7 @@ void setup() {
@SneakyThrows
void test_buildAssetDto() {
// Arrange
- String assetJsonLd = new String(Files.readAllBytes(Paths.get(getClass().getResource("/example-asset.jsonld").toURI())));
+ String assetJsonLd = TestUtils.loadResourceAsString("/example-asset.jsonld");
// Act
var uiAsset = assetMapper.buildUiAsset(JsonUtils.parseJsonObj(assetJsonLd), endpoint, participantId);
@@ -54,8 +53,10 @@ void test_buildAssetDto() {
assertThat(uiAsset.getParticipantId()).isEqualTo(participantId);
assertThat(uiAsset.getTitle()).isEqualTo("My Asset");
assertThat(uiAsset.getLanguage()).isEqualTo("https://w3id.org/idsa/code/EN");
- assertThat(uiAsset.getDescription()).isEqualTo("# Lorem Ipsum...\n## h2 title\n[Link text Here](example.com) 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789");
- assertThat(uiAsset.getDescriptionShortText()).isEqualTo("Lorem Ipsum... h2 title Link text Here 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890");
+ assertThat(uiAsset.getDescription()).isEqualTo(
+ "# Lorem Ipsum...\n## h2 title\n[Link text Here](example.com) 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789");
+ assertThat(uiAsset.getDescriptionShortText()).isEqualTo(
+ "Lorem Ipsum... h2 title Link text Here 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890");
assertThat(uiAsset.getIsOwnConnector()).isEqualTo(true);
assertThat(uiAsset.getCreatorOrganizationName()).isEqualTo("My Organization Name");
assertThat(uiAsset.getPublisherHomepage()).isEqualTo("https://data-source.my-org/about");
@@ -85,14 +86,48 @@ void test_buildAssetDto() {
assertThat(uiAsset.getTemporalCoverageToInclusive()).isEqualTo("2024-01-22");
assertThat(uiAsset.getAssetJsonLd()).contains("\"%s\"".formatted(Prop.Edc.ID));
- assertThat(uiAsset.getAdditionalProperties()).containsExactlyEntriesOf(Map.of(
- "http://unknown/some-custom-string", "some-string-value"));
- assertThat(uiAsset.getAdditionalJsonProperties()).containsExactlyEntriesOf(Map.of(
- "http://unknown/some-custom-obj", "{\"http://unknown/a\":\"b\"}"));
- assertThat(uiAsset.getPrivateProperties()).containsExactlyEntriesOf(Map.of(
- "http://unknown/some-custom-private-string", "some-private-value"));
- assertThat(uiAsset.getPrivateJsonProperties()).containsExactlyEntriesOf(Map.of(
- "http://unknown/some-custom-private-obj", "{\"http://unknown/a-private\":\"b-private\"}"));
+
+ JsonAssertions.assertThatJson(uiAsset.getCustomJsonAsString())
+ .isEqualTo("""
+ {
+ "array": [3, 1, 4, 1, 5],
+ "boolean": false,
+ "null": null,
+ "number": 116,
+ "object": {
+ "key": "value"
+ },
+ "string": "value"
+ }
+ """);
+ JsonAssertions.assertThatJson(uiAsset.getCustomJsonLdAsString())
+ .isObject()
+ .containsEntry("http://unknown/some-custom-string", "some-string-value")
+ .containsEntry("http://unknown/some-custom-obj", json("""
+ { "http://unknown/a": "b" }
+ """));
+
+ JsonAssertions.assertThatJson(uiAsset.getPrivateCustomJsonAsString())
+ .isEqualTo("""
+ {
+ "priv_array": [3, 1, 4, 1, 5],
+ "priv_boolean": false,
+ "priv_null": null,
+ "priv_number": 116,
+ "priv_object": {
+ "key": "value"
+ },
+ "priv_string": "value"
+ }
+ """);
+ JsonAssertions.assertThatJson(uiAsset.getPrivateCustomJsonLdAsString())
+ .isObject()
+ .containsEntry("http://unknown/some-custom-private-string", "some-private-value")
+ .containsEntry("http://unknown/some-custom-private-obj", json("""
+ {
+ "http://unknown/a-private": "b-private"
+ }
+ """));
}
@Test
@@ -102,6 +137,7 @@ void test_empty() {
var assetJsonLd = createObjectBuilder()
.add(Prop.ID, "my-asset-1")
.build();
+
// Act
var uiAsset = assetMapper.buildUiAsset(assetJsonLd, endpoint, participantId);
diff --git a/extensions/wrapper/wrapper-common-mappers/src/test/java/de/sovity/edc/ext/wrapper/api/common/mappers/TestUtils.java b/extensions/wrapper/wrapper-common-mappers/src/test/java/de/sovity/edc/ext/wrapper/api/common/mappers/TestUtils.java
new file mode 100644
index 000000000..113f847f1
--- /dev/null
+++ b/extensions/wrapper/wrapper-common-mappers/src/test/java/de/sovity/edc/ext/wrapper/api/common/mappers/TestUtils.java
@@ -0,0 +1,15 @@
+package de.sovity.edc.ext.wrapper.api.common.mappers;
+
+import lombok.SneakyThrows;
+import org.jetbrains.annotations.NotNull;
+
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+public class TestUtils {
+ @NotNull
+ @SneakyThrows
+ public static String loadResourceAsString(String name) {
+ return new String(Files.readAllBytes(Paths.get(TestUtils.class.getResource(name).toURI())));
+ }
+}
diff --git a/extensions/wrapper/wrapper-common-mappers/src/test/resources/example-asset.jsonld b/extensions/wrapper/wrapper-common-mappers/src/test/resources/example-asset.jsonld
index 9bd1b1b95..a30868cbc 100644
--- a/extensions/wrapper/wrapper-common-mappers/src/test/resources/example-asset.jsonld
+++ b/extensions/wrapper/wrapper-common-mappers/src/test/resources/example-asset.jsonld
@@ -34,18 +34,29 @@
"http://w3id.org/mds#transportMode": "my-geo-reference-method",
"https://semantic.sovity.io/mds-dcat-ext#sovereign": "my-sovereign",
"https://semantic.sovity.io/mds-dcat-ext#geolocation": "my-geolocation",
- "https://semantic.sovity.io/mds-dcat-ext#nuts-location": ["my-nuts-location1", "my-nuts-location2"],
- "https://semantic.sovity.io/mds-dcat-ext#data-sample-urls": ["my-data-sample-urls1", "my-data-sample-urls2"],
- "https://semantic.sovity.io/mds-dcat-ext#reference-files": ["my-reference-files1", "my-reference-files2"],
+ "https://semantic.sovity.io/mds-dcat-ext#nuts-location": [
+ "my-nuts-location1",
+ "my-nuts-location2"
+ ],
+ "https://semantic.sovity.io/mds-dcat-ext#data-sample-urls": [
+ "my-data-sample-urls1",
+ "my-data-sample-urls2"
+ ],
+ "https://semantic.sovity.io/mds-dcat-ext#reference-files": [
+ "my-reference-files1",
+ "my-reference-files2"
+ ],
"https://semantic.sovity.io/mds-dcat-ext#additional-description": "my-additional-description",
"https://semantic.sovity.io/mds-dcat-ext#conditions-for-use": "my-conditions-for-use",
"https://semantic.sovity.io/mds-dcat-ext#data-update-frequency": "my-data-update-frequency",
"https://semantic.sovity.io/mds-dcat-ext#temporal-coverage-from": "2007-12-03",
"https://semantic.sovity.io/mds-dcat-ext#temporal-coverage-to": "2024-01-22",
+ "https://semantic.sovity.io/dcat-ext#customJson": "{\"array\":[3,1,4,1,5],\"boolean\":false,\"null\":null,\"number\":116,\"object\":{\"key\":\"value\"},\"string\":\"value\"}",
"http://unknown/some-custom-string": "some-string-value",
"http://unknown/some-custom-obj": {"http://unknown/a": "b"}
},
- "privateProperties": {
+ "https://w3id.org/edc/v0.0.1/ns/privateProperties": {
+ "https://semantic.sovity.io/dcat-ext#privateCustomJson":"{\"priv_array\":[3,1,4,1,5],\"priv_boolean\":false,\"priv_null\":null,\"priv_number\":116,\"priv_object\":{\"key\":\"value\"},\"priv_string\":\"value\"}",
"http://unknown/some-custom-private-string": "some-private-value",
"http://unknown/some-custom-private-obj": {"http://unknown/a-private": "b-private"}
},
diff --git a/extensions/wrapper/wrapper/build.gradle.kts b/extensions/wrapper/wrapper/build.gradle.kts
index 200b23aee..31f75b73c 100644
--- a/extensions/wrapper/wrapper/build.gradle.kts
+++ b/extensions/wrapper/wrapper/build.gradle.kts
@@ -1,11 +1,12 @@
+val assertj: String by project
val edcVersion: String by project
val edcGroup: String by project
-val restAssured: String by project
-val assertj: String by project
-val mockitoVersion: String by project
-val lombokVersion: String by project
-val jettyVersion: String by project
val jettyGroup: String by project
+val jettyVersion: String by project
+val jsonUnit: String by project
+val lombokVersion: String by project
+val mockitoVersion: String by project
+val restAssured: String by project
plugins {
`java-library`
@@ -61,6 +62,7 @@ dependencies {
testImplementation("${edcGroup}:dsp-api-configuration:${edcVersion}")
testImplementation("${edcGroup}:data-plane-selector-core:${edcVersion}")
+ testImplementation("net.javacrumbs.json-unit:json-unit-assertj:${jsonUnit}")
testImplementation("io.rest-assured:rest-assured:${restAssured}")
testImplementation("org.mockito:mockito-core:${mockitoVersion}")
testImplementation("org.assertj:assertj-core:${assertj}")
diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/asset/AssetApiService.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/asset/AssetApiService.java
index bae70eeb8..3e3206015 100644
--- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/asset/AssetApiService.java
+++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/asset/AssetApiService.java
@@ -22,6 +22,7 @@
import de.sovity.edc.ext.wrapper.api.ui.model.IdResponseDto;
import de.sovity.edc.ext.wrapper.api.ui.pages.dashboard.services.SelfDescriptionService;
import lombok.RequiredArgsConstructor;
+import lombok.val;
import org.eclipse.edc.connector.spi.asset.AssetService;
import org.eclipse.edc.spi.query.QuerySpec;
import org.eclipse.edc.spi.types.domain.asset.Asset;
@@ -49,17 +50,17 @@ public List getAssets() {
@NotNull
public IdResponseDto createAsset(UiAssetCreateRequest request) {
- var asset = assetBuilder.fromCreateRequest(request);
- asset = assetService.create(asset).orElseThrow(ServiceException::new);
- return new IdResponseDto(asset.getId());
+ val asset = assetBuilder.fromCreateRequest(request);
+ val createdAsset = assetService.create(asset).orElseThrow(ServiceException::new);
+ return new IdResponseDto(createdAsset.getId());
}
public IdResponseDto editAsset(String assetId, UiAssetEditMetadataRequest request) {
- var asset = assetService.findById(assetId);
- Objects.requireNonNull(asset, "Asset with ID %s not found".formatted(assetId));
- asset = assetBuilder.fromEditMetadataRequest(asset, request);
- asset = assetService.update(asset).orElseThrow(ServiceException::new);
- return new IdResponseDto(asset.getId());
+ val foundAsset = assetService.findById(assetId);
+ Objects.requireNonNull(foundAsset, "Asset with ID %s not found".formatted(assetId));
+ val editedAsset = assetBuilder.fromEditMetadataRequest(foundAsset, request);
+ val updatedAsset = assetService.update(editedAsset).orElseThrow(ServiceException::new);
+ return new IdResponseDto(updatedAsset.getId());
}
@NotNull
diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/asset/AssetBuilder.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/asset/AssetBuilder.java
index 4591a554a..372a052ef 100644
--- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/asset/AssetBuilder.java
+++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/asset/AssetBuilder.java
@@ -20,6 +20,7 @@
import de.sovity.edc.ext.wrapper.api.common.model.UiAssetEditMetadataRequest;
import de.sovity.edc.ext.wrapper.api.ui.pages.dashboard.services.SelfDescriptionService;
import lombok.RequiredArgsConstructor;
+import lombok.val;
import org.eclipse.edc.spi.types.domain.asset.Asset;
@RequiredArgsConstructor
@@ -52,9 +53,14 @@ public Asset fromEditMetadataRequest(Asset asset, UiAssetEditMetadataRequest req
var createRequest = buildCreateRequest(asset, request);
var tmpAsset = fromCreateRequest(createRequest);
- return asset.toBuilder()
+ // DEBT: On each EDC update, check that no field was added in
+ // org.eclipse.edc.spi.types.domain.asset.Asset.toBuilder
+ return Asset.Builder.newInstance()
+ .id(asset.getId())
.properties(tmpAsset.getProperties())
.privateProperties(tmpAsset.getPrivateProperties())
+ .dataAddress(asset.getDataAddress())
+ .createdAt(asset.getCreatedAt())
.build();
}
@@ -64,35 +70,35 @@ private UiAssetCreateRequest buildCreateRequest(Asset asset, UiAssetEditMetadata
var createRequest = new UiAssetCreateRequest();
createRequest.setId(asset.getId());
+ createRequest.setConditionsForUse(editRequest.getConditionsForUse());
+ createRequest.setCustomJsonAsString(editRequest.getCustomJsonAsString());
+ createRequest.setCustomJsonLdAsString(editRequest.getCustomJsonLdAsString());
createRequest.setDataAddressProperties(dataAddress);
- createRequest.setAdditionalJsonProperties(editRequest.getAdditionalJsonProperties());
- createRequest.setAdditionalProperties(editRequest.getAdditionalProperties());
createRequest.setDataCategory(editRequest.getDataCategory());
createRequest.setDataModel(editRequest.getDataModel());
+ createRequest.setDataSampleUrls(editRequest.getDataSampleUrls());
createRequest.setDataSubcategory(editRequest.getDataSubcategory());
+ createRequest.setDataUpdateFrequency(editRequest.getDataUpdateFrequency());
createRequest.setDescription(editRequest.getDescription());
+ createRequest.setGeoLocation(editRequest.getGeoLocation());
createRequest.setGeoReferenceMethod(editRequest.getGeoReferenceMethod());
createRequest.setKeywords(editRequest.getKeywords());
createRequest.setLandingPageUrl(editRequest.getLandingPageUrl());
createRequest.setLanguage(editRequest.getLanguage());
createRequest.setLicenseUrl(editRequest.getLicenseUrl());
createRequest.setMediaType(editRequest.getMediaType());
- createRequest.setPrivateJsonProperties(editRequest.getPrivateJsonProperties());
- createRequest.setPrivateProperties(editRequest.getPrivateProperties());
- createRequest.setPublisherHomepage(editRequest.getPublisherHomepage());
- createRequest.setTitle(editRequest.getTitle());
- createRequest.setTransportMode(editRequest.getTransportMode());
- createRequest.setVersion(editRequest.getVersion());
- createRequest.setSovereignLegalName(editRequest.getSovereignLegalName());
- createRequest.setGeoLocation(editRequest.getGeoLocation());
createRequest.setNutsLocation(editRequest.getNutsLocation());
- createRequest.setDataSampleUrls(editRequest.getDataSampleUrls());
+ createRequest.setPrivateCustomJsonAsString(editRequest.getPrivateCustomJsonAsString());
+ createRequest.setPrivateCustomJsonLdAsString(editRequest.getPrivateCustomJsonLdAsString());
+ createRequest.setPublisherHomepage(editRequest.getPublisherHomepage());
createRequest.setReferenceFileUrls(editRequest.getReferenceFileUrls());
createRequest.setReferenceFilesDescription(editRequest.getReferenceFilesDescription());
- createRequest.setConditionsForUse(editRequest.getConditionsForUse());
- createRequest.setDataUpdateFrequency(editRequest.getDataUpdateFrequency());
+ createRequest.setSovereignLegalName(editRequest.getSovereignLegalName());
createRequest.setTemporalCoverageFrom(editRequest.getTemporalCoverageFrom());
createRequest.setTemporalCoverageToInclusive(editRequest.getTemporalCoverageToInclusive());
+ createRequest.setTitle(editRequest.getTitle());
+ createRequest.setTransportMode(editRequest.getTransportMode());
+ createRequest.setVersion(editRequest.getVersion());
return createRequest;
}
diff --git a/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/api/ui/pages/asset/AssetApiServiceTest.java b/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/api/ui/pages/asset/AssetApiServiceTest.java
index b54ed364c..de6a5d54e 100644
--- a/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/api/ui/pages/asset/AssetApiServiceTest.java
+++ b/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/api/ui/pages/asset/AssetApiServiceTest.java
@@ -39,6 +39,7 @@
import java.util.List;
import java.util.Map;
+import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
import static org.assertj.core.api.Assertions.assertThat;
@ApiTest
@@ -132,6 +133,28 @@ void testAssetCreation(AssetService assetService) {
.keywords(List.of("keyword1", "keyword2"))
.publisherHomepage("publisherHomepage")
.dataAddressProperties(dataAddressProperties)
+ .customJsonAsString("{\"test\":\"value\"}")
+ .customJsonLdAsString("""
+ {
+ "https://string": "value",
+ "https://number": 3.14,
+ "https://array": [1,2,3],
+ "https://object": { "https://key": "value" },
+ "https://booleans/are/not/supported/by/Eclipse/EDC": true,
+ "https://null/will/be/eliminated": null
+ }
+ """)
+ .privateCustomJsonAsString("{\"private test\":\"private value\"}")
+ .privateCustomJsonLdAsString("""
+ {
+ "https://private/string": "value",
+ "https://private/number": 3.14,
+ "https://private/array": [1,2,3],
+ "https://private/object": { "https://key": "value" },
+ "https://private/booleans/are/not/supported/by/Eclipse/EDC": true,
+ "https://private/null/will/be/eliminated": null
+ }
+ """)
.build();
// act
@@ -172,6 +195,28 @@ void testAssetCreation(AssetService assetService) {
assertThat(asset.getHttpDatasourceHintsProxyPath()).isTrue();
assertThat(asset.getHttpDatasourceHintsProxyQueryParams()).isTrue();
assertThat(asset.getHttpDatasourceHintsProxyBody()).isTrue();
+ assertThatJson(asset.getCustomJsonAsString()).isEqualTo("""
+ { "test": "value" }
+ """);
+ assertThatJson(asset.getCustomJsonLdAsString()).isEqualTo("""
+ {
+ "https://string": "value",
+ "https://number": 3.14,
+ "https://array": [1.0, 2.0, 3.0],
+ "https://object": { "https://key": "value" }
+ }
+ """);
+ assertThatJson(asset.getPrivateCustomJsonAsString()).isEqualTo("""
+ { "private test": "private value" }
+ """);
+ assertThatJson(asset.getPrivateCustomJsonLdAsString()).isEqualTo("""
+ {
+ "https://private/string": "value",
+ "https://private/number": 3.14,
+ "https://private/array": [1.0, 2.0, 3.0],
+ "https://private/object": { "https://key": "value" }
+ }
+ """);
var assetWithDataAddress = assetService.query(QuerySpec.max()).orElseThrow(FailedMappingException::ofFailure).toList().get(0);
assertThat(assetWithDataAddress.getDataAddress().getProperties()).isEqualTo(dataAddressProperties);
@@ -215,8 +260,19 @@ void testEditAssetMetadata(AssetService assetService) {
.keywords(List.of("keyword1", "keyword2"))
.publisherHomepage("publisherHomepage")
.dataAddressProperties(dataAddress)
+ .customJsonAsString("""
+ { "test": "value" }
+ """)
+ .customJsonLdAsString("""
+ {
+ "https://to-change": "value1",
+ "https://for-deletion": "value2"
+ }
+ """)
.build();
+
client.uiApi().createAsset(createRequest);
+
var editRequest = UiAssetEditMetadataRequest.builder()
.title("AssetTitle 2")
.description("AssetDescription 2")
@@ -241,6 +297,15 @@ void testEditAssetMetadata(AssetService assetService) {
.transportMode("transportMode2")
.keywords(List.of("keyword3"))
.publisherHomepage("publisherHomepage2")
+ .customJsonAsString("""
+ { "edited": "new value" }
+ """)
+ .customJsonLdAsString("""
+ {
+ "https://to-change": "new value LD",
+ "https://for-deletion": null
+ }
+ """)
.build();
// act
@@ -281,6 +346,12 @@ void testEditAssetMetadata(AssetService assetService) {
assertThat(asset.getHttpDatasourceHintsProxyPath()).isTrue();
assertThat(asset.getHttpDatasourceHintsProxyQueryParams()).isTrue();
assertThat(asset.getHttpDatasourceHintsProxyBody()).isTrue();
+ assertThat(asset.getCustomJsonAsString()).isEqualTo("""
+ { "edited": "new value" }
+ """);
+ assertThatJson(asset.getCustomJsonLdAsString()).isEqualTo("""
+ { "https://to-change": "new value LD" }
+ """);
var assetWithDataAddress = assetService.query(QuerySpec.max()).orElseThrow(FailedMappingException::ofFailure).toList().get(0);
assertThat(assetWithDataAddress.getDataAddress().getProperties()).isEqualTo(dataAddress);
@@ -376,4 +447,3 @@ private static long dateFormatterToLong(String date) {
return formatter.parse(date).getTime();
}
}
-
diff --git a/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_agreement/ContractAgreementPageTest.java b/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_agreement/ContractAgreementPageTest.java
index 4a28b4b12..b8defda15 100644
--- a/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_agreement/ContractAgreementPageTest.java
+++ b/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_agreement/ContractAgreementPageTest.java
@@ -82,7 +82,7 @@ void testContractAgreementPage(
// arrange
assetIndex.create(asset(ASSET_ID)).orElseThrow(storeFailure -> new RuntimeException("Failed to create asset"));
contractNegotiationStore.save(contractDefinition(CONTRACT_DEFINITION_ID));
- transferProcessStore.updateOrCreate(transferProcess(1, 1, TransferProcessStates.COMPLETED.code()));
+ transferProcessStore.save(transferProcess(1, 1, TransferProcessStates.COMPLETED.code()));
// act
var actual = client.uiApi().getContractAgreementPage().getContractAgreements();
diff --git a/gradle.properties b/gradle.properties
index 85ff7ebf8..2888950c5 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -6,6 +6,7 @@ edcVersion=0.2.1
tractusGroup=org.eclipse.tractusx.edc
tractusVersion=0.5.3
assertj=3.23.1
+jsonUnit=3.2.7
jupiterVersion=5.8.2
mockitoVersion=4.8.0
okHttpVersion=4.10.0
diff --git a/tests/build.gradle.kts b/tests/build.gradle.kts
index 5e01c61dc..3ff952878 100644
--- a/tests/build.gradle.kts
+++ b/tests/build.gradle.kts
@@ -3,18 +3,23 @@ plugins {
id("org.gradle.test-retry") version "1.5.7"
}
+val assertj: String by project
val edcVersion: String by project
val edcGroup: String by project
+val jsonUnit: String by project
+val lombokVersion: String by project
val mockitoVersion: String by project
-val assertj: String by project
dependencies {
api(project(":launchers:common:base"))
api(project(":launchers:common:auth-mock"))
+ testAnnotationProcessor("org.projectlombok:lombok:${lombokVersion}")
+ testCompileOnly("org.projectlombok:lombok:${lombokVersion}")
testImplementation(project(":extensions:test-backend-controller"))
testImplementation(project(":utils:test-connector-remote"))
testImplementation(project(":extensions:wrapper:clients:java-client"))
+ testImplementation("net.javacrumbs.json-unit:json-unit-assertj:${jsonUnit}")
testImplementation("org.mockito:mockito-core:${mockitoVersion}")
testImplementation("org.assertj:assertj-core:${assertj}")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
diff --git a/tests/src/test/java/de/sovity/edc/e2e/Ms8ConnectorMigrationTest.java b/tests/src/test/java/de/sovity/edc/e2e/Ms8ConnectorMigrationTest.java
index 761782160..9efd13e81 100644
--- a/tests/src/test/java/de/sovity/edc/e2e/Ms8ConnectorMigrationTest.java
+++ b/tests/src/test/java/de/sovity/edc/e2e/Ms8ConnectorMigrationTest.java
@@ -32,7 +32,6 @@
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
-import java.util.Map;
import java.util.function.Predicate;
import static de.sovity.edc.extension.e2e.connector.DataTransferTestUtil.validateDataTransferred;
@@ -124,15 +123,6 @@ void testMs8DataOffer_Properties() {
softly.assertThat(asset.getPublisherHomepage()).isEqualTo("https://publisher");
softly.assertThat(asset.getTransportMode()).isEqualTo("Rail");
softly.assertThat(asset.getVersion()).isEqualTo("1.0");
- softly.assertThat(asset.getAdditionalProperties()).isEqualTo(Map.of(
- "http://unknown/usecase", "my-use-case",
- "http://unknown/custom-prop-1", "1",
- "http://custom-prop-2", "2",
- "https://custom-prop-3", "3"
- ));
- softly.assertThat(asset.getAdditionalJsonProperties()).isNullOrEmpty();
- softly.assertThat(asset.getPrivateJsonProperties()).isNullOrEmpty();
- softly.assertThat(asset.getPrivateProperties()).isNullOrEmpty();
});
}
diff --git a/tests/src/test/java/de/sovity/edc/e2e/UiApiWrapperTest.java b/tests/src/test/java/de/sovity/edc/e2e/UiApiWrapperTest.java
index f618bdce6..d5e9b5e3f 100644
--- a/tests/src/test/java/de/sovity/edc/e2e/UiApiWrapperTest.java
+++ b/tests/src/test/java/de/sovity/edc/e2e/UiApiWrapperTest.java
@@ -43,6 +43,7 @@
import de.sovity.edc.utils.jsonld.vocab.Prop;
import jakarta.json.Json;
import jakarta.json.JsonObject;
+import lombok.val;
import org.awaitility.Awaitility;
import org.eclipse.edc.junit.extensions.EdcExtension;
import org.eclipse.edc.protocol.dsp.spi.types.HttpMessageProtocol;
@@ -62,6 +63,7 @@
import static de.sovity.edc.extension.e2e.connector.DataTransferTestUtil.validateDataTransferred;
import static de.sovity.edc.extension.e2e.connector.config.ConnectorConfigFactory.forTestDatabase;
import static de.sovity.edc.extension.e2e.connector.config.ConnectorRemoteConfigFactory.fromConnectorConfig;
+import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
@@ -163,10 +165,18 @@ void provide_consume_assetMapping_policyMapping_agreements() {
Prop.Edc.METHOD, "GET",
Prop.Edc.BASE_URL, dataAddress.getDataSourceUrl(data)
))
- .additionalProperties(Map.of("http://unknown/a", "x"))
- .additionalJsonProperties(Map.of("http://unknown/b", "{\"http://unknown/c\":\"y\"}"))
- .privateProperties(Map.of("http://unknown/a-private", "x-private"))
- .privateJsonProperties(Map.of("http://unknown/b-private", "{\"http://unknown/c-private\":\"y-private\"}"))
+ .customJsonAsString("""
+ {"test": "value"}
+ """)
+ .customJsonLdAsString("""
+ {"https://public/some#key": "public LD value"}
+ """)
+ .privateCustomJsonAsString("""
+ {"private_test": "private value"}
+ """)
+ .privateCustomJsonLdAsString("""
+ {"https://private/some#key": "private LD value"}
+ """)
.build()).getId();
assertThat(assetId).isEqualTo("asset-1");
@@ -235,26 +245,33 @@ void provide_consume_assetMapping_policyMapping_agreements() {
assertThat(dataOffer.getAsset().getHttpDatasourceHintsProxyPath()).isFalse();
assertThat(dataOffer.getAsset().getHttpDatasourceHintsProxyQueryParams()).isFalse();
assertThat(dataOffer.getAsset().getHttpDatasourceHintsProxyBody()).isFalse();
- assertThat(dataOffer.getAsset().getAdditionalProperties())
- .containsExactlyEntriesOf(Map.of("http://unknown/a", "x"));
- assertThat(dataOffer.getAsset().getAdditionalJsonProperties())
- .containsExactlyEntriesOf(Map.of("http://unknown/b", "{\"http://unknown/c\":\"y\"}"));
- assertThat(dataOffer.getAsset().getPrivateProperties()).isNullOrEmpty();
- assertThat(dataOffer.getAsset().getPrivateJsonProperties()).isNullOrEmpty();
+ assertThatJson(dataOffer.getAsset().getCustomJsonAsString()).isEqualTo("""
+ {"test": "value"}
+ """);
+ assertThatJson(dataOffer.getAsset().getCustomJsonLdAsString()).isEqualTo("""
+ {"https://public/some#key":"public LD value"}
+ """);
+ assertThat(dataOffer.getAsset().getPrivateCustomJsonAsString()).isNullOrEmpty();
+ assertThatJson(dataOffer.getAsset().getPrivateCustomJsonLdAsString()).isObject().isEmpty();
// while the data offer on the consumer side won't contain private properties, the asset page on the provider side should
assertThat(asset.getAssetId()).isEqualTo(assetId);
assertThat(asset.getTitle()).isEqualTo("AssetName");
assertThat(asset.getConnectorEndpoint()).isEqualTo(getProtocolEndpoint(providerConnector));
assertThat(asset.getParticipantId()).isEqualTo(providerConnector.getParticipantId());
- assertThat(asset.getAdditionalProperties())
- .containsExactlyEntriesOf(Map.of("http://unknown/a", "x"));
- assertThat(asset.getAdditionalJsonProperties())
- .containsExactlyEntriesOf(Map.of("http://unknown/b", "{\"http://unknown/c\":\"y\"}"));
- assertThat(asset.getPrivateProperties())
- .containsExactlyEntriesOf(Map.of("http://unknown/a-private", "x-private"));
- assertThat(asset.getPrivateJsonProperties())
- .containsExactlyEntriesOf(Map.of("http://unknown/b-private", "{\"http://unknown/c-private\":\"y-private\"}"));
+
+ assertThatJson(asset.getCustomJsonAsString()).isEqualTo("""
+ { "test": "value" }
+ """);
+ assertThatJson(asset.getCustomJsonLdAsString()).isEqualTo("""
+ { "https://public/some#key": "public LD value" }
+ """);
+ assertThatJson(asset.getPrivateCustomJsonAsString()).isEqualTo("""
+ { "private_test": "private value" }
+ """);
+ assertThatJson(asset.getPrivateCustomJsonLdAsString()).isEqualTo("""
+ { "https://private/some#key": "private LD value" }
+ """);
// Contract Agreement
assertThat(providerAgreements).hasSize(1);
@@ -306,6 +323,50 @@ void provide_consume_assetMapping_policyMapping_agreements() {
validateTransferProcessesOk();
}
+ @Test
+ void canOverrideTheWellKnowPropertiesUsingTheCustomProperties() {
+ // arrange
+ var assetId = providerClient.uiApi().createAsset(UiAssetCreateRequest.builder()
+ .id("asset-1")
+ .title("will be overridden")
+ .dataAddressProperties(Map.of(
+ Prop.Edc.TYPE, "HttpData",
+ Prop.Edc.METHOD, "GET",
+ Prop.Edc.BASE_URL, "http://example.com/base"
+ ))
+ .customJsonLdAsString("""
+ {
+ "http://purl.org/dc/terms/title": "The real title",
+ "https://semantic.sovity.io/mds-dcat-ext#nuts-location": ["a", "b", "c"],
+ "http://example.com/an-actual-custom-property": "custom value"
+ }
+ """)
+ .build()).getId();
+ assertThat(assetId).isEqualTo("asset-1");
+
+ // act
+ val assets = providerClient.uiApi().getAssetPage().getAssets();
+ assertThat(assets).hasSize(1);
+ val asset = assets.get(0);
+
+ // assert
+
+ // while the data offer on the consumer side won't contain private properties, the asset page on the provider side should
+ assertThat(asset.getAssetId()).isEqualTo(assetId);
+ // overridden property
+ assertThat(asset.getTitle()).isEqualTo("The real title");
+ // added property
+ assertThat(asset.getNutsLocation()).isEqualTo(List.of("a", "b", "c"));
+ // remaining custom property
+ assertThatJson(asset.getCustomJsonLdAsString()).isEqualTo("""
+ {
+ "http://example.com/an-actual-custom-property": "custom value"
+ }
+ """);
+ }
+
+ // TODO throw an error if the id is overridden
+
@Test
void customTransferRequest() {
// arrange
@@ -377,6 +438,30 @@ void editAssetMetadataOnLiveContract() {
Prop.Edc.METHOD, "GET",
Prop.Edc.BASE_URL, dataAddress.getDataSourceUrl(data)
))
+ .customJsonAsString("""
+ {
+ "test": "value"
+ }
+ """)
+ .customJsonLdAsString("""
+ {
+ "test": "not a valid key, will be deleted",
+ "http://example.com/key-to-delete": "with a valida key",
+ "http://example.com/key-to-edit": "with a valida key"
+ }
+ """)
+ .privateCustomJsonAsString("""
+ {
+ "private-test": "value"
+ }
+ """)
+ .privateCustomJsonLdAsString("""
+ {
+ "private-test": "not a valid key, will be deleted",
+ "http://example.com/private-key-to-delete": "private with a valid key",
+ "http://example.com/private-key-to-edit": "private with a valid key"
+ }
+ """)
.build()).getId();
providerClient.uiApi().createContractDefinition(ContractDefinitionRequest.builder()
@@ -403,12 +488,61 @@ void editAssetMetadataOnLiveContract() {
// act
providerClient.uiApi().editAssetMetadata(assetId, UiAssetEditMetadataRequest.builder()
.title("Good Asset Title")
+ .customJsonAsString("""
+ {
+ "edited": "new value"
+ }
+ """)
+ .customJsonLdAsString("""
+ {
+ "edited": "not a valid key, will be deleted",
+ "http://example.com/key-to-delete": null,
+ "http://example.com/key-to-edit": "with a valid key",
+ "http://example.com/extra": "value to add"
+ }
+ """)
+ .privateCustomJsonAsString("""
+ {
+ "private-edited": "new value"
+ }
+ """)
+ .privateCustomJsonLdAsString("""
+ {
+ "private-edited": "not a valid key, will be deleted",
+ "http://example.com/private-key-to-delete": null,
+ "http://example.com/private-key-to-edit": "private with a valid key",
+ "http://example.com/private-extra": "private value to add"
+ }
+ """)
.build());
initiateTransfer(negotiation);
// assert
assertThat(consumerClient.uiApi().getCatalogPageDataOffers(getProtocolEndpoint(providerConnector)).get(0).getAsset().getTitle()).isEqualTo("Good Asset Title");
- assertThat(providerClient.uiApi().getContractAgreementPage().getContractAgreements().get(0).getAsset().getTitle()).isEqualTo("Good Asset Title");
+ val firstAsset = providerClient.uiApi().getContractAgreementPage().getContractAgreements().get(0).getAsset();
+ assertThat(firstAsset.getTitle()).isEqualTo("Good Asset Title");
+ assertThat(firstAsset.getCustomJsonAsString()).isEqualTo("""
+ {
+ "edited": "new value"
+ }
+ """);
+ assertThatJson(firstAsset.getCustomJsonLdAsString()).isEqualTo("""
+ {
+ "http://example.com/key-to-edit": "with a valid key",
+ "http://example.com/extra": "value to add"
+ }
+ """);
+ assertThat(firstAsset.getPrivateCustomJsonAsString()).isEqualTo("""
+ {
+ "private-edited": "new value"
+ }
+ """);
+ assertThatJson(firstAsset.getPrivateCustomJsonLdAsString()).isEqualTo("""
+ {
+ "http://example.com/private-key-to-edit": "private with a valid key",
+ "http://example.com/private-extra": "private value to add"
+ }
+ """);
validateDataTransferred(dataAddress.getDataSinkSpyUrl(), data);
validateTransferProcessesOk();
assertThat(providerClient.uiApi().getTransferHistoryPage().getTransferEntries().get(0).getAssetName()).isEqualTo("Good Asset Title");
diff --git a/tests/src/test/resources/db/additional-test-data/provider/V1_9__ms8-test-contract-provider.sql b/tests/src/test/resources/db/additional-test-data/provider/V1_9__ms8-test-contract-provider.sql
index a83a92507..0d40dfbbd 100644
--- a/tests/src/test/resources/db/additional-test-data/provider/V1_9__ms8-test-contract-provider.sql
+++ b/tests/src/test/resources/db/additional-test-data/provider/V1_9__ms8-test-contract-provider.sql
@@ -41,9 +41,6 @@ INSERT INTO public.edc_asset_property VALUES ('urn:artifact:first-asset:1.0', 'h
INSERT INTO public.edc_asset_property VALUES ('urn:artifact:first-asset:1.0', 'asset:prop:originator', 'http://localhost:21003/api/v1/ids/data', 'java.lang.String');
INSERT INTO public.edc_asset_property VALUES ('urn:artifact:first-asset:1.0', 'asset:prop:standardLicense', 'https://standard-license', 'java.lang.String');
INSERT INTO public.edc_asset_property VALUES ('urn:artifact:first-asset:1.0', 'asset:prop:usecase', 'my-use-case', 'java.lang.String');
-INSERT INTO public.edc_asset_property VALUES ('urn:artifact:first-asset:1.0', 'custom-prop-1', '1', 'java.lang.String');
-INSERT INTO public.edc_asset_property VALUES ('urn:artifact:first-asset:1.0', 'http://custom-prop-2', '2', 'java.lang.String');
-INSERT INTO public.edc_asset_property VALUES ('urn:artifact:first-asset:1.0', 'https://custom-prop-3', '3', 'java.lang.String');
INSERT INTO public.edc_asset_property VALUES ('urn:artifact:second-asset', 'asset:prop:id', 'urn:artifact:second-asset', 'java.lang.String');
diff --git a/utils/catalog-parser/src/main/java/de/sovity/edc/utils/catalog/mapper/DspContractOfferUtils.java b/utils/catalog-parser/src/main/java/de/sovity/edc/utils/catalog/mapper/DspContractOfferUtils.java
index e7cbc18a3..53ad296ff 100644
--- a/utils/catalog-parser/src/main/java/de/sovity/edc/utils/catalog/mapper/DspContractOfferUtils.java
+++ b/utils/catalog-parser/src/main/java/de/sovity/edc/utils/catalog/mapper/DspContractOfferUtils.java
@@ -29,7 +29,7 @@ public class DspContractOfferUtils {
* @return A base64 string that can be used as an id for the {@code contract}
*/
public static String buildStableId(JsonObject contract) {
- // FIXME: This doesn't enforce any property order and may cause trouble if the returned policy schema is not consistent.
+ // NOTE: This doesn't enforce any property order and may cause trouble if the returned policy schema is not consistent.
// Use canonical form if needed later.
val noId = Json.createObjectBuilder(contract).remove(Prop.ID).build();
val policyId = hash(noId);
diff --git a/utils/json-and-jsonld-utils/src/main/java/de/sovity/edc/utils/jsonld/vocab/Prop.java b/utils/json-and-jsonld-utils/src/main/java/de/sovity/edc/utils/jsonld/vocab/Prop.java
index 23ec8b90b..b3364a1ec 100644
--- a/utils/json-and-jsonld-utils/src/main/java/de/sovity/edc/utils/jsonld/vocab/Prop.java
+++ b/utils/json-and-jsonld-utils/src/main/java/de/sovity/edc/utils/jsonld/vocab/Prop.java
@@ -106,6 +106,8 @@ public class Dcterms {
@UtilityClass
public class SovityDcatExt {
public final String CTX = "https://semantic.sovity.io/dcat-ext#";
+ public final String CUSTOM_JSON = CTX + "customJson";
+ public final String PRIVATE_CUSTOM_JSON = CTX + "privateCustomJson";
@UtilityClass
public class HttpDatasourceHints {
From cc8a8679706d6d296bf28e47e7217af4f7e40e25 Mon Sep 17 00:00:00 2001
From: Tim Berthold <75306992+tmberthold@users.noreply.github.com>
Date: Tue, 5 Mar 2024 16:08:29 +0100
Subject: [PATCH 15/24] chore: update data-transfer-methods documentation
(#824)
* Update data-transfer-methods.md
---------
Co-authored-by: efiege <105237007+efiege@users.noreply.github.com>
---
.../documentation/data-transfer-methods.md | 2 --
.../images/data-transfer-methods.png | Bin 1109458 -> 1265584 bytes
2 files changed, 2 deletions(-)
diff --git a/docs/getting-started/documentation/data-transfer-methods.md b/docs/getting-started/documentation/data-transfer-methods.md
index c30fabe9e..b4fc551c4 100644
--- a/docs/getting-started/documentation/data-transfer-methods.md
+++ b/docs/getting-started/documentation/data-transfer-methods.md
@@ -5,8 +5,6 @@ The connector supports three different data transfer modes:
1. HTTPData: The provider EDC fetches the data from its own backend and pushes it to the consumer's desired data sink.
2. HTTPProxy: The provider EDC fetches the data and passes it on consumer's data transfer request synchronously back to the consumer.
-3. OutOfBand: The data source transfers the data directly to the data sink. The data is not routed through the connector.
The following diagram illustrates the different transmission modes:
-
![data-transfer-methods.png](images%2Fdata-transfer-methods.png)
diff --git a/docs/getting-started/documentation/images/data-transfer-methods.png b/docs/getting-started/documentation/images/data-transfer-methods.png
index a0caec16432f83bcaa2dadeb91b8fd924b8b7ed9..9117f5a8c5396b0b990c57df0b9dd43208a1c65b 100644
GIT binary patch
literal 1265584
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 uuG#YVO`4NhX=y9-6rtTgqIGBo4V%+vG-#HQM5f
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%0D^?q!3Rt5=hh6lO3UC${z3N7P=B7#HRl
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