edc_web_components/components/
create_asset.rs1use 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 .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}