app: Allow custom attributes in group creation

This commit is contained in:
Valentin Tolmer
2024-10-28 15:36:03 +01:00
committed by nitnelave
parent a190fe7ddf
commit 4ebfd0525b
7 changed files with 145 additions and 29 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
mutation CreateGroup($name: String!) { mutation CreateGroup($group: CreateGroupInput!) {
createGroup(name: $name) { createGroupWithDetails(request: $group) {
id id
displayName displayName
} }
@@ -7,6 +7,7 @@ query GetGroupAttributesSchema {
isList isList
isVisible isVisible
isHardcoded isHardcoded
isReadonly
} }
} }
} }
+118 -12
View File
@@ -1,11 +1,23 @@
use crate::{ use crate::{
components::{ components::{
form::{field::Field, submit::Submit}, form::{
attribute_input::{ListAttributeInput, SingleAttributeInput},
field::Field,
submit::Submit,
},
router::AppRoute, router::AppRoute,
}, },
infra::common_component::{CommonComponent, CommonComponentParts}, convert_attribute_type,
infra::{
common_component::{CommonComponent, CommonComponentParts},
form_utils::{
read_all_form_attributes, AttributeValue, EmailIsRequired, GraphQlAttributeSchema,
IsAdmin,
},
schema::AttributeType,
},
}; };
use anyhow::{bail, Result}; use anyhow::{ensure, Result};
use gloo_console::log; use gloo_console::log;
use graphql_client::GraphQLQuery; use graphql_client::GraphQLQuery;
use validator_derive::Validate; use validator_derive::Validate;
@@ -13,6 +25,33 @@ use yew::prelude::*;
use yew_form_derive::Model; use yew_form_derive::Model;
use yew_router::{prelude::History, scope_ext::RouterScopeExt}; use yew_router::{prelude::History, scope_ext::RouterScopeExt};
#[derive(GraphQLQuery)]
#[graphql(
schema_path = "../schema.graphql",
query_path = "queries/get_group_attributes_schema.graphql",
response_derives = "Debug,Clone,PartialEq,Eq",
custom_scalars_module = "crate::infra::graphql"
)]
pub struct GetGroupAttributesSchema;
use get_group_attributes_schema::ResponseData;
pub type Attribute =
get_group_attributes_schema::GetGroupAttributesSchemaSchemaGroupSchemaAttributes;
convert_attribute_type!(get_group_attributes_schema::AttributeType);
impl From<&Attribute> for GraphQlAttributeSchema {
fn from(attr: &Attribute) -> Self {
Self {
name: attr.name.clone(),
is_list: attr.is_list,
is_readonly: attr.is_readonly,
is_editable: false, // Need to be admin to edit it.
}
}
}
#[derive(GraphQLQuery)] #[derive(GraphQLQuery)]
#[graphql( #[graphql(
schema_path = "../schema.graphql", schema_path = "../schema.graphql",
@@ -25,6 +64,8 @@ pub struct CreateGroup;
pub struct CreateGroupForm { pub struct CreateGroupForm {
common: CommonComponentParts<Self>, common: CommonComponentParts<Self>,
form: yew_form::Form<CreateGroupModel>, form: yew_form::Form<CreateGroupModel>,
attributes_schema: Option<Vec<Attribute>>,
form_ref: NodeRef,
} }
#[derive(Model, Validate, PartialEq, Eq, Clone, Default)] #[derive(Model, Validate, PartialEq, Eq, Clone, Default)]
@@ -35,6 +76,7 @@ pub struct CreateGroupModel {
pub enum Msg { pub enum Msg {
Update, Update,
ListAttributesResponse(Result<ResponseData>),
SubmitForm, SubmitForm,
CreateGroupResponse(Result<create_group::ResponseData>), CreateGroupResponse(Result<create_group::ResponseData>),
} }
@@ -48,12 +90,33 @@ impl CommonComponent<CreateGroupForm> for CreateGroupForm {
match msg { match msg {
Msg::Update => Ok(true), Msg::Update => Ok(true),
Msg::SubmitForm => { Msg::SubmitForm => {
if !self.form.validate() { ensure!(self.form.validate(), "Check the form for errors");
bail!("Check the form for errors");
} let all_values = read_all_form_attributes(
self.attributes_schema.iter().flatten(),
&self.form_ref,
IsAdmin(true),
EmailIsRequired(false),
)?;
let attributes = Some(
all_values
.into_iter()
.filter(|a| !a.values.is_empty())
.map(
|AttributeValue { name, values }| create_group::AttributeValueInput {
name,
value: values,
},
)
.collect(),
);
let model = self.form.model(); let model = self.form.model();
let req = create_group::Variables { let req = create_group::Variables {
name: model.groupname, group: create_group::CreateGroupInput {
displayName: model.groupname,
attributes,
},
}; };
self.common.call_graphql::<CreateGroup, _>( self.common.call_graphql::<CreateGroup, _>(
ctx, ctx,
@@ -66,11 +129,16 @@ impl CommonComponent<CreateGroupForm> for CreateGroupForm {
Msg::CreateGroupResponse(response) => { Msg::CreateGroupResponse(response) => {
log!(&format!( log!(&format!(
"Created group '{}'", "Created group '{}'",
&response?.create_group.display_name &response?.create_group_with_details.display_name
)); ));
ctx.link().history().unwrap().push(AppRoute::ListGroups); ctx.link().history().unwrap().push(AppRoute::ListGroups);
Ok(true) Ok(true)
} }
Msg::ListAttributesResponse(schema) => {
self.attributes_schema =
Some(schema?.schema.group_schema.attributes.into_iter().collect());
Ok(true)
}
} }
} }
@@ -83,11 +151,22 @@ impl Component for CreateGroupForm {
type Message = Msg; type Message = Msg;
type Properties = (); type Properties = ();
fn create(_: &Context<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
Self { let mut component = Self {
common: CommonComponentParts::<Self>::create(), common: CommonComponentParts::<Self>::create(),
form: yew_form::Form::<CreateGroupModel>::new(CreateGroupModel::default()), form: yew_form::Form::<CreateGroupModel>::new(CreateGroupModel::default()),
} attributes_schema: None,
form_ref: NodeRef::default(),
};
component
.common
.call_graphql::<GetGroupAttributesSchema, _>(
ctx,
get_group_attributes_schema::Variables {},
Msg::ListAttributesResponse,
"Error trying to fetch group schema",
);
component
} }
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool { fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
@@ -98,7 +177,8 @@ impl Component for CreateGroupForm {
let link = ctx.link(); let link = ctx.link();
html! { html! {
<div class="row justify-content-center"> <div class="row justify-content-center">
<form class="form py-3" style="max-width: 636px"> <form class="form py-3" style="max-width: 636px"
ref={self.form_ref.clone()}>
<div class="row mb-3"> <div class="row mb-3">
<h5 class="fw-bold">{"Create a group"}</h5> <h5 class="fw-bold">{"Create a group"}</h5>
</div> </div>
@@ -108,6 +188,14 @@ impl Component for CreateGroupForm {
label="Group name" label="Group name"
field_name="groupname" field_name="groupname"
oninput={link.callback(|_| Msg::Update)} /> oninput={link.callback(|_| Msg::Update)} />
{
self.attributes_schema
.iter()
.flatten()
.filter(|a| !a.is_readonly && a.name != "display_name")
.map(get_custom_attribute_input)
.collect::<Vec<_>>()
}
<Submit <Submit
disabled={self.common.is_task_running()} disabled={self.common.is_task_running()}
onclick={link.callback(|e: MouseEvent| {e.prevent_default(); Msg::SubmitForm})} /> onclick={link.callback(|e: MouseEvent| {e.prevent_default(); Msg::SubmitForm})} />
@@ -124,3 +212,21 @@ impl Component for CreateGroupForm {
} }
} }
} }
fn get_custom_attribute_input(attribute_schema: &Attribute) -> Html {
if attribute_schema.is_list {
html! {
<ListAttributeInput
name={attribute_schema.name.clone()}
attribute_type={Into::<AttributeType>::into(attribute_schema.attribute_type.clone())}
/>
}
} else {
html! {
<SingleAttributeInput
name={attribute_schema.name.clone()}
attribute_type={Into::<AttributeType>::into(attribute_schema.attribute_type.clone())}
/>
}
}
}
+7 -4
View File
@@ -11,7 +11,10 @@ use crate::{
infra::{ infra::{
api::HostService, api::HostService,
common_component::{CommonComponent, CommonComponentParts}, common_component::{CommonComponent, CommonComponentParts},
form_utils::{read_all_form_attributes, AttributeValue, GraphQlAttributeSchema}, form_utils::{
read_all_form_attributes, AttributeValue, EmailIsRequired, GraphQlAttributeSchema,
IsAdmin,
},
schema::AttributeType, schema::AttributeType,
}, },
}; };
@@ -121,8 +124,8 @@ impl CommonComponent<CreateUserForm> for CreateUserForm {
let all_values = read_all_form_attributes( let all_values = read_all_form_attributes(
self.attributes_schema.iter().flatten(), self.attributes_schema.iter().flatten(),
&self.form_ref, &self.form_ref,
true, IsAdmin(true),
true, EmailIsRequired(true),
)?; )?;
let attributes = Some( let attributes = Some(
all_values all_values
@@ -302,7 +305,7 @@ impl Component for CreateUserForm {
} }
} }
pub fn get_custom_attribute_input(attribute_schema: &Attribute) -> Html { fn get_custom_attribute_input(attribute_schema: &Attribute) -> Html {
if attribute_schema.is_list { if attribute_schema.is_list {
html! { html! {
<ListAttributeInput <ListAttributeInput
+3 -3
View File
@@ -9,7 +9,7 @@ use crate::{
}, },
infra::{ infra::{
common_component::{CommonComponent, CommonComponentParts}, common_component::{CommonComponent, CommonComponentParts},
form_utils::{read_all_form_attributes, AttributeValue}, form_utils::{read_all_form_attributes, AttributeValue, EmailIsRequired, IsAdmin},
schema::AttributeType, schema::AttributeType,
}, },
}; };
@@ -210,8 +210,8 @@ impl GroupDetailsForm {
let mut all_values = read_all_form_attributes( let mut all_values = read_all_form_attributes(
ctx.props().group_attributes_schema.iter(), ctx.props().group_attributes_schema.iter(),
&self.form_ref, &self.form_ref,
ctx.props().is_admin, IsAdmin(ctx.props().is_admin),
false, EmailIsRequired(false),
)?; )?;
let base_attributes = &self.group.attributes; let base_attributes = &self.group.attributes;
all_values.retain(|a| { all_values.retain(|a| {
+3 -3
View File
@@ -9,7 +9,7 @@ use crate::{
}, },
infra::{ infra::{
common_component::{CommonComponent, CommonComponentParts}, common_component::{CommonComponent, CommonComponentParts},
form_utils::{read_all_form_attributes, AttributeValue}, form_utils::{read_all_form_attributes, AttributeValue, EmailIsRequired, IsAdmin},
schema::AttributeType, schema::AttributeType,
}, },
}; };
@@ -212,8 +212,8 @@ impl UserDetailsForm {
let mut all_values = read_all_form_attributes( let mut all_values = read_all_form_attributes(
ctx.props().user_attributes_schema.iter(), ctx.props().user_attributes_schema.iter(),
&self.form_ref, &self.form_ref,
ctx.props().is_admin, IsAdmin(ctx.props().is_admin),
!ctx.props().is_edited_user_admin, EmailIsRequired(!ctx.props().is_edited_user_admin),
)?; )?;
let base_attributes = &self.user.attributes; let base_attributes = &self.user.attributes;
all_values.retain(|a| { all_values.retain(|a| {
+11 -5
View File
@@ -16,9 +16,12 @@ pub struct GraphQlAttributeSchema {
pub is_editable: bool, pub is_editable: bool,
} }
fn validate_attributes(all_values: &[AttributeValue], email_is_required: bool) -> Result<()> { fn validate_attributes(
all_values: &[AttributeValue],
email_is_required: EmailIsRequired,
) -> Result<()> {
let maybe_email_values = all_values.iter().find(|a| a.name == "mail"); let maybe_email_values = all_values.iter().find(|a| a.name == "mail");
if email_is_required || maybe_email_values.is_some() { if email_is_required.0 || maybe_email_values.is_some() {
let email_values = &maybe_email_values let email_values = &maybe_email_values
.ok_or_else(|| anyhow!("Email is required"))? .ok_or_else(|| anyhow!("Email is required"))?
.values; .values;
@@ -28,11 +31,14 @@ fn validate_attributes(all_values: &[AttributeValue], email_is_required: bool) -
Ok(()) Ok(())
} }
pub struct IsAdmin(pub bool);
pub struct EmailIsRequired(pub bool);
pub fn read_all_form_attributes( pub fn read_all_form_attributes(
schema: impl IntoIterator<Item = impl Into<GraphQlAttributeSchema>>, schema: impl IntoIterator<Item = impl Into<GraphQlAttributeSchema>>,
form_ref: &NodeRef, form_ref: &NodeRef,
is_admin: bool, is_admin: IsAdmin,
email_is_required: bool, email_is_required: EmailIsRequired,
) -> Result<Vec<AttributeValue>> { ) -> Result<Vec<AttributeValue>> {
let form = form_ref.cast::<HtmlFormElement>().unwrap(); let form = form_ref.cast::<HtmlFormElement>().unwrap();
let form_data = FormData::new_with_form(&form) let form_data = FormData::new_with_form(&form)
@@ -40,7 +46,7 @@ pub fn read_all_form_attributes(
let all_values = schema let all_values = schema
.into_iter() .into_iter()
.map(Into::<GraphQlAttributeSchema>::into) .map(Into::<GraphQlAttributeSchema>::into)
.filter(|attr| !attr.is_readonly && (is_admin || attr.is_editable)) .filter(|attr| !attr.is_readonly && (is_admin.0 || attr.is_editable))
.map(|attr| -> Result<AttributeValue> { .map(|attr| -> Result<AttributeValue> {
let val = form_data let val = form_data
.get_all(attr.name.as_str()) .get_all(attr.name.as_str())