edc_web_components/pages/
new_contract_negotiation_page.rs1use crate::contexts::use_edc_connector_context;
2use base64::prelude::*;
3use edc_connector_client::EdcConnectorApiVersion;
4use edc_connector_client::types::catalog::CatalogRequest;
5use edc_connector_client::types::contract_negotiation::ContractRequest;
6use edc_connector_client::types::policy::{
7 AtomicConstraint, Constraint, LeftOperand, Operator, Permission, Policy, PolicyKind, Target,
8};
9use edc_connector_client::types::query::Query;
10use edc_connector_client::types::{ExtraTokenFields, Protocol};
11use edc_federated_catalog_client::{FederatedCatalogClient, FederatedCatalogClientVersion};
12use patternfly_yew::prelude::*;
13use serde::{Deserialize, Serialize};
14use std::fmt::Debug;
15use yew::platform::spawn_local;
16use yew::prelude::*;
17use yew::suspense::use_future_with;
18use yew_oauth2::hook::use_latest_access_token;
19
20#[derive(Clone, Debug, PartialEq, Properties)]
21pub struct NewContractNegotiationPageProps {
22 pub originator: String,
23 pub provider_id: String,
24 pub dataset_id: String,
25}
26
27#[component]
28pub fn NewContractNegotiationPage(props: &NewContractNegotiationPageProps) -> Html {
29 html!(
30 <Stack gutter=true>
31 <StackItem>
32 <Split gutter=true>
33 <SplitItem fill=true>
34 <Title level={Level::H3} size={Size::XXLarge}>{ "New Contract Negotiation" }</Title>
35 </SplitItem>
36 </Split>
37 </StackItem>
38 <StackItem>
39 <Card>
40 <CardBody>
41 <Suspense>
42 <NewContractNegotiationPageInner
43 originator={props.originator.clone()}
44 provider_id={props.provider_id.clone()}
45 dataset_id={props.dataset_id.clone()}
46 />
47 </Suspense>
48 </CardBody>
49 </Card>
50 </StackItem>
51 </Stack>
52 )
53}
54
55#[derive(Clone, Debug, PartialEq, Properties)]
56pub struct NewContractNegotiationPageInnerProps {
57 pub originator: String,
58 pub provider_id: String,
59 pub dataset_id: String,
60}
61
62#[component]
63pub fn NewContractNegotiationPageInner(props: &NewContractNegotiationPageInnerProps) -> HtmlResult {
64 let edc_connector_context = use_edc_connector_context();
65 let latest_access_token_context = use_latest_access_token().unwrap();
66
67 let federated_catalog_dataset = use_future_with(
68 (
69 latest_access_token_context,
70 props.originator.clone(),
71 props.provider_id.clone(),
72 props.dataset_id.clone(),
73 edc_connector_context.clone(),
74 ),
75 |parameters| async move {
76 let (latest_access_token_context, originator, provider_id, dataset_id, edc_connector_context) =
77 (*parameters).clone();
78
79 if let Some(edc_client) = edc_connector_context.get_client() {
80 {
84 let originator =
85 "https://controlplane.participant-li1.demo.luminvent.com/dsp/2025-1".to_string();
86 let provider_id =
87 "did:web:participant-li1.demo.luminvent.com:participant-li1".to_string();
88
89 let edc_client = edc_client.clone();
90 spawn_local(async move {
91 let query = Query::builder()
92 .build();
95
96 let catalogue_request = CatalogRequest::builder()
97 .counter_party_address(originator.clone())
98 .counter_party_id(provider_id.clone())
99 .protocol(Protocol::new("dataspace-protocol-http:2025-1"))
100 .query_spec(query)
101 .build();
102
103 #[derive(Debug, Serialize, Deserialize)]
104 struct ExtraDatasetFields {}
105 impl ExtraTokenFields for ExtraDatasetFields {}
106
107 if let Ok(catalog) = edc_client
108 .catalogue(EdcConnectorApiVersion::V4)
109 .request::<ExtraDatasetFields>(&catalogue_request)
110 .await
111 {
112 log::warn!("Catalog: {:?}", catalog);
113 }
114 });
115 }
116
117 let originator =
118 "https://controlplane.participant-li2.demo.luminvent.com/dsp/2025-1".to_string();
119 let provider_id = "did:web:participant-li2.demo.luminvent.com:participant-li2".to_string();
120
121 spawn_local(async move {
122 let query = Query::builder()
123 .build();
126
127 let catalogue_request = CatalogRequest::builder()
128 .counter_party_address(originator.clone())
129 .counter_party_id(provider_id.clone())
130 .protocol(Protocol::new("dataspace-protocol-http:2025-1"))
131 .query_spec(query)
132 .build();
133
134 #[derive(Debug, Serialize, Deserialize)]
135 struct ExtraDatasetFields {}
136 impl ExtraTokenFields for ExtraDatasetFields {}
137
138 if let Ok(catalog) = edc_client
139 .catalogue(EdcConnectorApiVersion::V4)
140 .request::<ExtraDatasetFields>(&catalogue_request)
141 .await
142 {
143 log::warn!("Catalog: {:?}", catalog);
144 }
145 })
146 }
147
148 let server_url = web_sys::window().unwrap().location().origin().unwrap();
149 let federated_catalog_client = FederatedCatalogClient::new(
150 reqwest::Client::new(),
151 format!("{server_url}/federated-catalog"),
152 latest_access_token_context.access_token(),
153 FederatedCatalogClientVersion::V4,
154 );
155
156 let federated_catalog_offers = federated_catalog_client
157 .list_offers()
158 .await
159 .unwrap_or_default();
160
161 federated_catalog_offers
162 .into_iter()
163 .find_map(|federated_catalog_offer| {
164 if federated_catalog_offer.originator != originator
165 && federated_catalog_offer.participant_id.id != provider_id
166 {
167 None
168 } else {
169 federated_catalog_offer
170 .dataset
171 .into_iter()
172 .find(|dataset| dataset.id == dataset_id)
173 }
174 })
175 },
176 )?;
177
178 let federated_catalog_dataset = (*federated_catalog_dataset).clone();
179
180 let selected_offer = use_state_eq(|| None);
181
182 let onchange = use_callback(selected_offer.clone(), |policy: Policy, selected_offer| {
183 selected_offer.set(Some(policy))
184 });
185
186 let onclick = use_callback(
187 (
188 edc_connector_context.clone(),
189 federated_catalog_dataset.clone(),
190 selected_offer.clone(),
191 props.originator.clone(),
192 props.provider_id.clone(),
193 ),
194 |_,
195 (
196 edc_connector_context,
197 federated_catalog_dataset,
198 selected_offer,
199 originator,
200 provider_id,
201 )| {
202 let edc_connector_context = edc_connector_context.clone();
203 if let Some(policy) = (**selected_offer).clone()
204 && let Some(federated_catalog_dataset) = federated_catalog_dataset
205 {
206 let originator = originator.clone();
207 let provider_id = provider_id.clone();
208 let asset_id = federated_catalog_dataset.id.clone();
209
210 spawn_local(async move {
211 if let Some(edc_client) = edc_connector_context.get_client() {
212 let counter_party_address = originator.clone();
214 let counter_party_id = provider_id.clone();
215
216 log::warn!("counter_party_address: {}", counter_party_address);
217 let policy: Policy = policy.clone();
218
219 if let Some(id) = policy.id() {
220 let policy_builder = Policy::builder()
221 .assigner(provider_id)
222 .id(id)
223 .kind(PolicyKind::Offer)
224 .permissions(
225 policy
226 .permissions()
227 .iter()
228 .map(|permission| {
229 Permission::builder()
230 .action(edc_connector_client::types::policy::Action::Simple(
231 "use".to_string(),
232 ))
233 .constraints(
234 permission
235 .constraints()
236 .iter()
237 .map(|constraint| match constraint {
238 Constraint::Atomic(atomic_constraint) => {
239 let left_operand = match &atomic_constraint.left_operand {
240 LeftOperand::Simple(simple) => {
241 LeftOperand::Simple(simple.to_string())
242 }
243 LeftOperand::Id { id } => LeftOperand::Simple(id.to_string()),
244 };
245
246 let operator = match &atomic_constraint.operator {
247 Operator::Simple(simple) => Operator::Simple(simple.to_string()),
248 Operator::Id { id } => Operator::Simple(
249 id.to_string().replace("http://www.w3.org/ns/odrl/2/", ""),
250 ),
251 };
252
253 Constraint::Atomic(AtomicConstraint {
254 left_operand,
255 operator,
256 right_operand: atomic_constraint.right_operand.clone(),
257 })
258 }
259 Constraint::MultiplicityConstraint(multiplicity_constraint) => {
260 Constraint::MultiplicityConstraint(multiplicity_constraint.clone())
261 }
262 })
263 .collect(),
264 )
265 .build()
266 })
267 .collect(),
268 )
269 .prohibitions(policy.prohibitions().to_vec())
270 .obligations(policy.obligations().to_vec())
271 .target(Target::Simple(asset_id));
272
273 let policy = policy_builder.build();
274
275 let new_contract_request = ContractRequest::builder()
276 .protocol(Protocol::new("dataspace-protocol-http:2025-1"))
277 .counter_party_address(&counter_party_address)
278 .counter_party_id(counter_party_id)
279 .policy(policy)
280 .build();
281
282 if let Ok(contract_negotiation_id) = edc_client
283 .contract_negotiations(EdcConnectorApiVersion::V4)
284 .initiate(&new_contract_request)
285 .await
286 {
287 log::warn!(
288 "Contract negotiation initiated: {}",
289 contract_negotiation_id.id()
290 );
291 }
292 };
293 }
294 })
295 }
296 },
297 );
298
299 if let Some(federated_catalog_dataset) = federated_catalog_dataset {
300 let provider_id = props.provider_id.clone();
301 let asset_id = federated_catalog_dataset.id.clone();
302 let asset_name = federated_catalog_dataset.name.clone();
303 let policies = federated_catalog_dataset.has_policy;
304 let selected_offer = selected_offer.clone();
305
306 let offers = policies
307 .iter()
308 .filter_map(|policy| {
309 if let Some(offer_id) = policy.id().and_then(|policy| policy.split(':').next()) {
310 BASE64_STANDARD
311 .decode(offer_id)
312 .ok()
313 .and_then(|offer_id| String::from_utf8(offer_id).ok())
314 .map(|offer_id| (policy, offer_id))
315 } else {
316 None
317 }
318 })
319 .map(|(policy, offer_id)| {
320 let onchange = onchange.clone();
321 let policy = policy.clone();
322
323 html!(
324 <Radio
325 name="offer-id"
326 checked={(*selected_offer).as_ref() == Some(&policy)}
327 onchange={onchange.reform(move |_| policy.clone())}
328 >
329 <span>{ offer_id.to_string() }</span>
330 </Radio>
331 )
332 });
333
334 let disabled = selected_offer.is_none();
335
336 Ok(html!(
337 <>
338 <DescriptionList>
339 <DescriptionGroup term="Provider ID">{ provider_id }</DescriptionGroup>
340 <DescriptionGroup term="Asset ID">{ asset_id }</DescriptionGroup>
341 <DescriptionGroup term="Asset Name">{ asset_name }</DescriptionGroup>
342 <DescriptionGroup term="Offer IDs">{ for offers }</DescriptionGroup>
343 </DescriptionList>
344 <Split gutter=true>
345 <SplitItem fill=true />
346 <SplitItem>
347 <Button variant={ButtonVariant::Primary} icon={Icon::Check} {disabled} {onclick}>
348 { "Sign" }
349 </Button>
350 </SplitItem>
351 </Split>
352 </>
353 ))
354 } else {
355 Ok(html!({ "The offer is not available." }))
356 }
357}