mcai_workflow/components/
edit_notification_hook.rs

1use crate::{ActionButton, Button, Field, Modal, ModalMessage};
2use css_in_rust_next::Style;
3use mcai_models::{NotificationHook, NotificationHookCondition};
4use wasm_bindgen::JsCast;
5use web_sys::{Event, EventTarget, HtmlInputElement, HtmlSelectElement, InputEvent};
6use yew::{html, Callback, Component, Context, Html, Properties};
7use yew_feather::{plus::Plus, trash_2::Trash2};
8
9#[derive(PartialEq, Properties)]
10pub struct EditNotificationHookProperties {
11  pub event: Callback<EditNotificationHookMessage>,
12  pub notification_hook: Option<NotificationHook>,
13}
14
15pub enum EditNotificationHookMessage {
16  Submit(NotificationHook),
17  Cancel,
18  Delete,
19}
20
21pub enum FieldId {
22  Label,
23  Endpoint,
24}
25
26pub enum InternalMessage {
27  Update(FieldId, String),
28  UpdateCredential(Option<String>),
29  UpdateConditions(Vec<NotificationHookCondition>),
30  AddCredential,
31  RemoveCredential,
32  Modal(ModalMessage),
33}
34
35pub struct EditNotificationHook {
36  style: Style,
37  label: String,
38  endpoint: String,
39  credentials: Option<String>,
40  conditions: Vec<NotificationHookCondition>,
41}
42
43impl EditNotificationHook {
44  fn is_valid(&self) -> bool {
45    !self.label.is_empty() && !self.endpoint.is_empty() && !self.conditions.is_empty()
46  }
47}
48
49impl Component for EditNotificationHook {
50  type Message = InternalMessage;
51  type Properties = EditNotificationHookProperties;
52
53  fn create(ctx: &Context<Self>) -> Self {
54    let style = Style::create(
55      "Component",
56      concat!(include_str!("edit_notification_hook.css")),
57    )
58    .unwrap();
59
60    if let Some(notification_hook) = &ctx.props().notification_hook {
61      Self {
62        style,
63        label: notification_hook.label.clone(),
64        endpoint: notification_hook.endpoint.clone(),
65        credentials: notification_hook.credentials.clone(),
66        conditions: notification_hook.conditions.clone(),
67      }
68    } else {
69      Self {
70        style,
71        label: String::new(),
72        endpoint: String::new(),
73        credentials: None,
74        conditions: vec![],
75      }
76    }
77  }
78
79  fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
80    match msg {
81      InternalMessage::Update(field, value) => match field {
82        FieldId::Label => self.label = value,
83        FieldId::Endpoint => self.endpoint = value,
84      },
85      InternalMessage::UpdateCredential(value) => self.credentials = value,
86      InternalMessage::AddCredential => self.credentials = Some(String::new()),
87      InternalMessage::RemoveCredential => self.credentials = None,
88      InternalMessage::UpdateConditions(conditions) => {
89        self.conditions = conditions;
90      }
91      InternalMessage::Modal(message) => match message {
92        ModalMessage::Submit | ModalMessage::Update => {
93          let start_parameter = NotificationHook {
94            label: self.label.clone(),
95            endpoint: self.endpoint.clone(),
96            credentials: self.credentials.clone(),
97            conditions: self.conditions.clone(),
98          };
99
100          ctx
101            .props()
102            .event
103            .emit(EditNotificationHookMessage::Submit(start_parameter));
104        }
105        ModalMessage::Cancel => {
106          ctx.props().event.emit(EditNotificationHookMessage::Cancel);
107        }
108        ModalMessage::Delete => {
109          ctx.props().event.emit(EditNotificationHookMessage::Delete);
110        }
111      },
112    }
113    true
114  }
115
116  fn view(&self, ctx: &Context<Self>) -> Html {
117    let action_buttons = if ctx.props().notification_hook.is_some() {
118      vec![ActionButton::Delete, ActionButton::Update(self.is_valid())]
119    } else {
120      vec![ActionButton::Submit(self.is_valid())]
121    };
122
123    let callback_label = ctx.link().batch_callback(|e: InputEvent| {
124      let target: Option<EventTarget> = e.target();
125      let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
126      input.map(|input| InternalMessage::Update(FieldId::Label, input.value()))
127    });
128
129    let callback_endpoint = ctx.link().batch_callback(|e: InputEvent| {
130      let target: Option<EventTarget> = e.target();
131      let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
132      input.map(|input| InternalMessage::Update(FieldId::Endpoint, input.value()))
133    });
134
135    let callback_credential = ctx.link().batch_callback(|e: InputEvent| {
136      let target: Option<EventTarget> = e.target();
137      let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
138      input.map(|input| InternalMessage::UpdateCredential(Some(input.value())))
139    });
140
141    let credential_inner = self.credentials.as_ref().map(|credential| html!(
142      <>
143        <input type="text" value={credential.clone()} oninput={callback_credential} />
144        <Button label="" icon={html!(<Trash2 />)} onclick={ctx.link().callback(|_| InternalMessage::RemoveCredential)}/>
145      </>
146    )).unwrap_or_else(|| html!(
147      <Button label="Add value" icon={html!(<Plus />)} onclick={ctx.link().callback(|_| InternalMessage::AddCredential)}/>
148    ));
149
150    let callback_conditions = ctx.link().batch_callback(|e: Event| {
151      let target: Option<EventTarget> = e.target();
152      target
153        .and_then(|t| t.dyn_into::<HtmlSelectElement>().ok())
154        .map(|input| {
155          let list = input.selected_options();
156          let mut result = vec![];
157
158          for index in 0..list.length() {
159            if let Some(element) = list.item(index) {
160              match element.get_attribute("value").as_deref() {
161                Some("job_completed") => result.push(NotificationHookCondition::JobCompleted),
162                Some("job_error") => result.push(NotificationHookCondition::JobError),
163                Some("workflow_completed") => {
164                  result.push(NotificationHookCondition::WorkflowCompleted)
165                }
166                Some("workflow_error") => result.push(NotificationHookCondition::WorkflowError),
167                _ => {}
168              }
169            }
170          }
171
172          result
173        })
174        .map(InternalMessage::UpdateConditions)
175    });
176
177    let input_field = html!(
178      <>
179        <Field label="Label"><input type="text" value={self.label.clone()} oninput={callback_label} /></Field>
180        <Field label="Endpoint"><input type="text" value={self.endpoint.clone()} oninput={callback_endpoint} /></Field>
181        <Field label="Credentials">{credential_inner}</Field>
182        <Field label="Condition(s)">
183          <select onchange={callback_conditions} multiple=true>
184            <option value={"job_completed"} selected={self.conditions.contains(&NotificationHookCondition::JobCompleted)}>{"Job completed"}</option>
185            <option value={"job_error"} selected={self.conditions.contains(&NotificationHookCondition::JobError)}>{"Job error"}</option>
186            <option value={"workflow_completed"} selected={self.conditions.contains(&NotificationHookCondition::WorkflowCompleted)}>{"Workflow completed"}</option>
187            <option value={"workflow_error"} selected={self.conditions.contains(&NotificationHookCondition::WorkflowError)}>{"Workflow error"}</option>
188          </select>
189        </Field>
190      </>
191    );
192
193    html!(
194      <Modal
195        event={ctx.link().callback(InternalMessage::Modal)}
196        height="50vh" width="19vw"
197        modal_title={"Edit Notification Hook"}
198        actions={action_buttons}>
199        <div class={self.style.clone()}>
200          <div class="editNotificationHook">
201            {input_field}
202          </div>
203        </div>
204      </Modal>
205    )
206  }
207}