Skip to main content

edc_web_components/pages/
offer_page.rs

1use crate::components::{ListAssetsGallery, SelectedFederatedCatalogOffer};
2use crate::contexts::{use_edc_connector_context, use_edc_identity_hub_context};
3use crate::models::{AssetItem, DatasetExtraFields};
4use crate::services::DidResolver;
5use edc_connector_client::EdcConnectorApiVersion;
6use edc_connector_client::types::Protocol;
7use edc_connector_client::types::catalog::CatalogRequest;
8use edc_connector_client::types::query::Query;
9use edc_identity_hub_client::models::{DidWeb, IdentityServiceType};
10use patternfly_yew::prelude::*;
11use yew::prelude::*;
12use yew::suspense::use_future_with;
13
14#[derive(Clone, Debug, PartialEq, Properties)]
15pub struct OfferPageProps {
16  pub onselectedoffer: Callback<SelectedFederatedCatalogOffer>,
17  pub on_new_asset: Callback<()>,
18  pub on_new_policy: Callback<()>,
19  pub on_new_contract: Callback<()>,
20}
21
22#[component]
23pub fn OfferPage(props: &OfferPageProps) -> Html {
24  let refresh = use_state(|| 0usize);
25  let offset = use_state(|| 0usize);
26  let limit = use_state(|| 10usize);
27
28  let on_offset = use_callback(
29    (refresh.clone(), offset.setter()),
30    |offset, (refresh, offset_setter)| {
31      offset_setter.set(offset);
32      refresh.set(**refresh + 1);
33    },
34  );
35
36  let on_limit = use_callback(
37    (refresh.clone(), limit.setter()),
38    |limit, (refresh, limit_setter)| {
39      limit_setter.set(limit);
40      refresh.set(**refresh + 1);
41    },
42  );
43
44  let fallback = html! {
45    <Bullseye>
46      <Spinner size={SpinnerSize::Lg} />
47    </Bullseye>
48  };
49
50  html!(
51    <Stack gutter=true>
52      <StackItem>
53        <Split gutter=true>
54          <SplitItem fill=true>
55            <Title level={Level::H3} size={Size::XXLarge}>{ "List Offers" }</Title>
56          </SplitItem>
57        </Split>
58      </StackItem>
59      <StackItem>
60        <Suspense {fallback}>
61          <OfferPageInner
62            offset={*offset}
63            limit={*limit}
64            {on_offset}
65            {on_limit}
66            force_refresh={*refresh}
67            onselectedoffer={props.onselectedoffer.clone()}
68            on_new_asset={props.on_new_asset.clone()}
69            on_new_policy={props.on_new_policy.clone()}
70            on_new_contract={props.on_new_contract.clone()}
71          />
72        </Suspense>
73      </StackItem>
74    </Stack>
75  )
76}
77
78#[derive(Clone, Debug, PartialEq, Properties)]
79pub struct OfferPageInnerProps {
80  pub offset: usize,
81  pub limit: usize,
82  pub on_offset: Callback<usize>,
83  pub on_limit: Callback<usize>,
84  pub force_refresh: usize,
85  pub onselectedoffer: Callback<SelectedFederatedCatalogOffer>,
86  pub on_new_asset: Callback<()>,
87  pub on_new_policy: Callback<()>,
88  pub on_new_contract: Callback<()>,
89}
90
91#[component]
92pub fn OfferPageInner(props: &OfferPageInnerProps) -> HtmlResult {
93  let edc_connector_context = use_edc_connector_context();
94  let edc_identity_hub_context = use_edc_identity_hub_context();
95
96  let asset_items = use_future_with(
97    (
98      edc_identity_hub_context,
99      edc_connector_context,
100      props.limit,
101      props.offset,
102      props.force_refresh,
103    ),
104    |parameters| async move {
105      let (edc_identity_hub_context, edc_connector_context, limit, offset, _) =
106        (*parameters).clone();
107
108      if let Some(did_web) = DidWeb::new(edc_identity_hub_context.participant_did())
109        && let Ok(did_data) = DidResolver::new(reqwest::Client::new())
110          .resolve(did_web)
111          .await
112        && let Some(identity_service) = did_data
113          .get_identity_services(IdentityServiceType::DataService)
114          .first()
115        && let Some(dataspace_service_client) = identity_service.get_dataspace_service_client()
116        && let Some(dsp_endpoint) = dataspace_service_client.get_first_service_endpoint().await
117        && let Some(client) = edc_connector_context.get_client()
118      {
119        let request = CatalogRequest::builder()
120          .counter_party_address(dsp_endpoint.clone())
121          .counter_party_id(edc_identity_hub_context.participant_did())
122          .protocol(Protocol::default())
123          .query_spec(
124            Query::builder()
125              .limit(limit as u32)
126              .offset(offset as u32)
127              .build(),
128          )
129          .build();
130
131        if let Ok(response) = client
132          .catalogue(EdcConnectorApiVersion::V4)
133          .request::<DatasetExtraFields>(&request)
134          .await
135        {
136          response
137            .datasets()
138            .iter()
139            .map(|dataset| {
140              let dataset_id = dataset.id().to_string();
141              let asset_item = AssetItem::from(dataset);
142
143              let selected_offer = SelectedFederatedCatalogOffer {
144                originator: dsp_endpoint.clone(),
145                provider_id: edc_identity_hub_context.participant_did().to_string(),
146                dataset_id,
147              };
148
149              (asset_item, selected_offer)
150            })
151            .collect::<Vec<_>>()
152            .into_iter()
153            .unzip()
154        } else {
155          (Vec::new(), Vec::new())
156        }
157      } else {
158        (Vec::new(), Vec::new())
159      }
160    },
161  )?;
162
163  let (asset_items, selected_offers): (Vec<AssetItem>, Vec<SelectedFederatedCatalogOffer>) =
164    (*asset_items).clone();
165
166  let onshow = use_callback(
167    (props.onselectedoffer.clone(), selected_offers),
168    |dataset_id, (onselectedoffer, selected_offers)| {
169      if let Some(selected_offer) = selected_offers
170        .iter()
171        .find(|selected_offer| selected_offer.dataset_id == dataset_id)
172      {
173        onselectedoffer.emit(selected_offer.clone());
174      }
175    },
176  );
177
178  if asset_items.is_empty() {
179    let primary = { Action::new("Create my first asset", props.on_new_asset.reform(|_| ())) };
180
181    let secondaries = vec![
182      Action::new("Create my first policy", props.on_new_policy.reform(|_| ())),
183      Action::new(
184        "Create my first contract",
185        props.on_new_contract.reform(|_| ()),
186      ),
187    ];
188
189    Ok(html! {
190      <EmptyState title="Empty state" icon={Icon::Cubes} {primary} {secondaries}>
191        <div>
192          <p>{ "There is no offer yet." }</p>
193          <p>
194            { "The first step consists of creating an asset, then a policy, and finally a contract definition." }
195          </p>
196          <p>{ "This results in an offer." }</p>
197        </div>
198      </EmptyState>
199    })
200  } else {
201    Ok(html!(<ListAssetsGallery {asset_items} {onshow} />))
202  }
203}