Skip to main content

edc_web_components/components/
create_asset.rs

1use crate::components::{DatasetCard, StringListEdit};
2use crate::contexts::use_edc_connector_context;
3use crate::models::{Creator, DataspaceDataset, Thumbnail};
4use edc_connector_client::EdcConnectorApiVersion;
5use edc_connector_client::types::properties::ToValue;
6use edc_connector_client::types::{asset::NewAsset, data_address::DataAddress};
7use patternfly_yew::prelude::*;
8use std::collections::HashMap;
9use yew::platform::spawn_local;
10use yew::prelude::*;
11
12#[derive(Clone, Debug, PartialEq, Properties)]
13pub struct CreateAssetProps {
14  #[prop_or_default]
15  pub company_name: Option<String>,
16  #[prop_or_default]
17  pub company_logo_url: Option<String>,
18  #[prop_or_default]
19  pub on_create: Callback<()>,
20}
21
22#[component]
23pub fn CreateAsset(props: &CreateAssetProps) -> Html {
24  let edc_connector_context = use_edc_connector_context();
25
26  let name = use_state(String::new);
27  let version = use_state(String::new);
28  let description = use_state(String::new);
29  let thumbnail_url = use_state(String::new);
30  let keywords = use_state(Vec::<String>::new);
31  let base_url = use_state(String::new);
32  let company_name = use_state(|| props.company_name.clone().unwrap_or_default());
33  let company_logo_url = use_state(|| props.company_logo_url.clone().unwrap_or_default());
34  let content_type = use_state(|| "application/json".to_string());
35  let proxy_path = use_state(|| false);
36  let proxy_query_params = use_state(|| false);
37  let proxy_method = use_state(|| false);
38  let proxy_body = use_state(|| false);
39  let headers = use_state(HashMap::<String, String>::new);
40
41  let onsubmit = use_callback(
42    (
43      edc_connector_context.clone(),
44      (
45        name.clone(),
46        version.clone(),
47        description.clone(),
48        thumbnail_url.clone(),
49        keywords.clone(),
50      ),
51      base_url.clone(),
52      (company_name.clone(), company_logo_url.clone()),
53      content_type.clone(),
54      proxy_path.clone(),
55      proxy_query_params.clone(),
56      proxy_method.clone(),
57      proxy_body.clone(),
58      headers.clone(),
59      props.on_create.clone(),
60    ),
61    |event: SubmitEvent,
62     (
63      edc_connector_context,
64      (name, version, description, thumbnail_url, keywords),
65      base_url,
66      (company_name, company_logo_url),
67      content_type,
68      proxy_path,
69      proxy_query_params,
70      proxy_method,
71      proxy_body,
72      headers,
73      on_create,
74    )| {
75      event.prevent_default();
76
77      let name = (**name).clone();
78      let version = (**version).clone();
79      let description = (**description).clone();
80      let thumbnail_url = (**thumbnail_url).clone();
81      let keywords = (**keywords).clone();
82      let base_url = (**base_url).clone();
83      let company_name = (**company_name).clone();
84      let company_logo_url = (**company_logo_url).clone();
85      let content_type = (**content_type).clone();
86      let proxy_path = **proxy_path;
87      let proxy_query_params = **proxy_query_params;
88      let proxy_method = **proxy_method;
89      let proxy_body = **proxy_body;
90      let headers = (**headers).clone();
91      let edc_connector_context = edc_connector_context.clone();
92      let on_create = on_create.clone();
93
94      spawn_local(async move {
95        let mut data_address_builder = DataAddress::builder()
96          .kind("HttpData")
97          .property("baseUrl", base_url)
98          .property("proxyPath", if proxy_path { "true" } else { "false" })
99          .property(
100            "proxyQueryParams",
101            if proxy_query_params { "true" } else { "false" },
102          )
103          .property("proxyMethod", if proxy_method { "true" } else { "false" })
104          .property("proxyBody", if proxy_body { "true" } else { "false" });
105
106        struct Creator {
107          name: String,
108          logo_url: String,
109        }
110
111        impl ToValue for Creator {
112          fn into_value(self) -> serde_json::Value {
113            serde_json::json!({
114              "@type": [
115                "http://xmlns.com/foaf/0.1/Organization",
116                "http://www.w3.org/ns/prov#Agent"
117              ],
118              "http://xmlns.com/foaf/0.1/name": self.name,
119              "http://xmlns.com/foaf/0.1/thumbnail": {
120                "rdf:resource": self.logo_url,
121              },
122            })
123          }
124        }
125
126        struct Thumbnail {
127          resource: String,
128        }
129
130        impl ToValue for Thumbnail {
131          fn into_value(self) -> serde_json::Value {
132            serde_json::json!({
133              "rdf:resource": self.resource,
134            })
135          }
136        }
137
138        for (key, value) in &headers {
139          data_address_builder = data_address_builder.property(&format!("header:{key}"), value);
140        }
141
142        let data_address = data_address_builder.build().unwrap();
143
144        let new_asset_builder = NewAsset::builder()
145          // .id(&identifier)
146          .data_address(data_address)
147          .property("name", name.clone())
148          .property("http://www.w3.org/ns/dcat#version", version)
149          .property("contenttype", content_type)
150          .property(
151            "http://purl.org/dc/terms/creator",
152            Creator {
153              name: company_name,
154              logo_url: company_logo_url,
155            },
156          );
157
158        let new_asset_builder = if !name.is_empty() {
159          new_asset_builder.property("http://purl.org/dc/terms/title", name)
160        } else {
161          new_asset_builder
162        };
163
164        let new_asset_builder = if !description.is_empty() {
165          new_asset_builder.property("http://www.w3.org/2000/01/rdf-schema#comment", description)
166        } else {
167          new_asset_builder
168        };
169
170        let new_asset_builder = if !thumbnail_url.is_empty() {
171          new_asset_builder.property(
172            "http://xmlns.com/foaf/0.1/thumbnail",
173            Thumbnail {
174              resource: thumbnail_url,
175            },
176          )
177        } else {
178          new_asset_builder
179        };
180
181        let new_asset_builder = if !keywords.is_empty() {
182          new_asset_builder.property("http://www.w3.org/ns/dcat#keyword", keywords)
183        } else {
184          new_asset_builder
185        };
186
187        let new_asset = new_asset_builder.build();
188
189        if let Some(client) = edc_connector_context.get_client() {
190          if let Err(error) = client
191            .assets(EdcConnectorApiVersion::V4)
192            .create(&new_asset)
193            .await
194          {
195            log::error!("Error creating asset: {}", error);
196          } else {
197            on_create.emit(());
198          }
199        }
200      })
201    },
202  );
203
204  let onchange_name = use_callback(name.setter(), |value, name_setter| {
205    name_setter.set(value);
206  });
207
208  let onchange_version = use_callback(version.setter(), |value, version_setter| {
209    version_setter.set(value);
210  });
211
212  let onchange_description = use_callback(description.setter(), |value, description_setter| {
213    description_setter.set(value);
214  });
215
216  let onchange_thumbnail_url =
217    use_callback(thumbnail_url.setter(), |value, thumbnail_url_setter| {
218      thumbnail_url_setter.set(value);
219    });
220
221  let onchange_keywords = use_callback(keywords.setter(), |keywords, keywords_setter| {
222    keywords_setter.set(keywords);
223  });
224
225  let onchange_company_name = use_callback(company_name.setter(), |value, company_name_setter| {
226    company_name_setter.set(value);
227  });
228
229  let onchange_company_logo_url = use_callback(
230    company_logo_url.setter(),
231    |value, company_logo_url_setter| {
232      company_logo_url_setter.set(value);
233    },
234  );
235
236  let onchange_base_url = use_callback(base_url.setter(), |value, base_url_setter| {
237    base_url_setter.set(value);
238  });
239
240  let onchange_content_type = use_callback(content_type.setter(), |value, content_type_setter| {
241    content_type_setter.set(value);
242  });
243
244  let onchange_proxy_path = use_callback(proxy_path.setter(), |value, proxy_path_setter| {
245    proxy_path_setter.set(value);
246  });
247
248  let onchange_proxy_query_params = use_callback(
249    proxy_query_params.setter(),
250    |value, proxy_query_params_setter| {
251      proxy_query_params_setter.set(value);
252    },
253  );
254
255  let onchange_proxy_method = use_callback(proxy_method.setter(), |value, proxy_method_setter| {
256    proxy_method_setter.set(value);
257  });
258
259  let onchange_proxy_body = use_callback(proxy_body.setter(), |value, proxy_body_setter| {
260    proxy_body_setter.set(value);
261  });
262
263  let disabled = (*name).is_empty() || (*base_url).is_empty();
264
265  let dataset = DataspaceDataset {
266    id: "".to_string(),
267    title: (*name).clone(),
268    version: semver::Version::parse(&version).ok(),
269    comment: if (*description).is_empty() {
270      None
271    } else {
272      Some((*description).clone())
273    },
274    thumbnail: if (*thumbnail_url).is_empty() {
275      None
276    } else {
277      Some(Thumbnail {
278        resource: Some((*thumbnail_url).clone()),
279      })
280    },
281    creator: if (*company_logo_url).is_empty() {
282      None
283    } else {
284      Some(Creator {
285        name: None,
286        thumbnail: Some(Thumbnail {
287          resource: Some((*company_logo_url).clone()),
288        }),
289      })
290    },
291    keywords: (*keywords).clone(),
292    policies: vec![],
293    dcterm_types: vec![],
294  };
295
296  html!(
297    <Form {onsubmit}>
298      <Card>
299        <CardHeader>
300          <Title level={Level::H2} size={Size::XXXLarge}>{ "Provider Information" }</Title>
301        </CardHeader>
302        <CardBody>
303          <FormGroup label="Company Name">
304            <TextInput
305              required=true
306              value={(*company_name).to_string()}
307              onchange={onchange_company_name}
308              readonly={props.company_name.is_some()}
309            />
310          </FormGroup>
311          <FormGroup label="Company Logo URL">
312            <TextInput
313              required=true
314              value={(*company_logo_url).to_string()}
315              onchange={onchange_company_logo_url}
316              readonly={props.company_logo_url.is_some()}
317            />
318          </FormGroup>
319        </CardBody>
320      </Card>
321      <Card>
322        <CardHeader>
323          <Title level={Level::H2} size={Size::XXXLarge}>{ "Dataset Description" }</Title>
324        </CardHeader>
325        <CardBody>
326          <Flex>
327            <FlexItem
328              modifiers={[FlexModifier::Flex1.all(), FlexModifier::Align(Alignment::Start).all()]}
329            >
330              <FormGroup label="Title" required=true>
331                <TextInput required=true value={(*name).to_string()} onchange={onchange_name} />
332              </FormGroup>
333              <FormGroupValidated<TextInput>
334                label="Version"
335                required=true
336                validator={Validator::from(|ctx: ValidationContext<String>| {
337                  if ctx.initial {
338                    ValidationResult::default()
339                  } else if ctx.value.is_empty() {
340                    ValidationResult::error("Must not be empty")
341                  } else {
342                    if semver::Version::parse(&ctx.value).is_ok() {
343                      ValidationResult::ok()
344                    } else {
345                      ValidationResult::error("Not a semantic versioning format (MAJOR.MINOR.PATCH)")
346                    }
347                  }
348               })}
349              >
350                <TextInput
351                  required=true
352                  value={(*version).to_string()}
353                  onchange={onchange_version}
354                />
355              </FormGroupValidated<TextInput>>
356              <FormGroup label="Description">
357                <TextArea
358                  required=true
359                  value={(*description).to_string()}
360                  onchange={onchange_description}
361                />
362              </FormGroup>
363              <FormGroup label="Thumbnail URL">
364                <TextInput
365                  required=true
366                  value={(*thumbnail_url).to_string()}
367                  onchange={onchange_thumbnail_url}
368                />
369              </FormGroup>
370              <FormGroup label="Keywords">
371                <StringListEdit values={(*keywords).clone()} onchange={onchange_keywords} />
372              </FormGroup>
373            </FlexItem>
374            <FlexItem modifiers={[FlexModifier::Align(Alignment::Start).all()]}>
375              <DatasetCard {dataset} />
376            </FlexItem>
377          </Flex>
378        </CardBody>
379      </Card>
380      <Card>
381        <CardHeader>
382          <Title level={Level::H2} size={Size::XXXLarge}>{ "Dataset Source" }</Title>
383        </CardHeader>
384        <CardBody>
385          <FormGroup label="Base URL" required=true>
386            <TextInput
387              required=true
388              value={(*base_url).to_string()}
389              onchange={onchange_base_url}
390              r#type={TextInputType::Url}
391            />
392          </FormGroup>
393          <FormGroup label="Content Type">
394            <TextInput value={(*content_type).to_string()} onchange={onchange_content_type} />
395          </FormGroup>
396          <FormGroup label="Proxy Path">
397            <Switch checked={*proxy_path} onchange={onchange_proxy_path} />
398          </FormGroup>
399          <FormGroup label="Proxy Query Parameters">
400            <Switch checked={*proxy_query_params} onchange={onchange_proxy_query_params} />
401          </FormGroup>
402          <FormGroup label="Proxy Method">
403            <Switch checked={*proxy_method} onchange={onchange_proxy_method} />
404          </FormGroup>
405          <FormGroup label="Proxy Body">
406            <Switch checked={*proxy_body} onchange={onchange_proxy_body} />
407          </FormGroup>
408        </CardBody>
409      </Card>
410      <ActionGroup>
411        <Button
412          variant={ButtonVariant::Primary}
413          label="Submit"
414          r#type={ButtonType::Submit}
415          {disabled}
416        />
417        <Button variant={ButtonVariant::Secondary} label="Reset" r#type={ButtonType::Reset} />
418      </ActionGroup>
419    </Form>
420  )
421}