From 4ebfd0525b83beb11edd214ee64b48cce27a37f8 Mon Sep 17 00:00:00 2001 From: Valentin Tolmer Date: Mon, 28 Oct 2024 15:36:03 +0100 Subject: [PATCH] app: Allow custom attributes in group creation --- app/queries/create_group.graphql | 4 +- .../get_group_attributes_schema.graphql | 1 + app/src/components/create_group.rs | 130 ++++++++++++++++-- app/src/components/create_user.rs | 11 +- app/src/components/group_details_form.rs | 6 +- app/src/components/user_details_form.rs | 6 +- app/src/infra/form_utils.rs | 16 ++- 7 files changed, 145 insertions(+), 29 deletions(-) diff --git a/app/queries/create_group.graphql b/app/queries/create_group.graphql index 96ea2fa..1272fd8 100644 --- a/app/queries/create_group.graphql +++ b/app/queries/create_group.graphql @@ -1,5 +1,5 @@ -mutation CreateGroup($name: String!) { - createGroup(name: $name) { +mutation CreateGroup($group: CreateGroupInput!) { + createGroupWithDetails(request: $group) { id displayName } diff --git a/app/queries/get_group_attributes_schema.graphql b/app/queries/get_group_attributes_schema.graphql index 979591d..4feab7e 100644 --- a/app/queries/get_group_attributes_schema.graphql +++ b/app/queries/get_group_attributes_schema.graphql @@ -7,6 +7,7 @@ query GetGroupAttributesSchema { isList isVisible isHardcoded + isReadonly } } } diff --git a/app/src/components/create_group.rs b/app/src/components/create_group.rs index 3282007..78e86e3 100644 --- a/app/src/components/create_group.rs +++ b/app/src/components/create_group.rs @@ -1,11 +1,23 @@ use crate::{ components::{ - form::{field::Field, submit::Submit}, + form::{ + attribute_input::{ListAttributeInput, SingleAttributeInput}, + field::Field, + submit::Submit, + }, 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 graphql_client::GraphQLQuery; use validator_derive::Validate; @@ -13,6 +25,33 @@ use yew::prelude::*; use yew_form_derive::Model; 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)] #[graphql( schema_path = "../schema.graphql", @@ -25,6 +64,8 @@ pub struct CreateGroup; pub struct CreateGroupForm { common: CommonComponentParts, form: yew_form::Form, + attributes_schema: Option>, + form_ref: NodeRef, } #[derive(Model, Validate, PartialEq, Eq, Clone, Default)] @@ -35,6 +76,7 @@ pub struct CreateGroupModel { pub enum Msg { Update, + ListAttributesResponse(Result), SubmitForm, CreateGroupResponse(Result), } @@ -48,12 +90,33 @@ impl CommonComponent for CreateGroupForm { match msg { Msg::Update => Ok(true), Msg::SubmitForm => { - if !self.form.validate() { - bail!("Check the form for errors"); - } + ensure!(self.form.validate(), "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 req = create_group::Variables { - name: model.groupname, + group: create_group::CreateGroupInput { + displayName: model.groupname, + attributes, + }, }; self.common.call_graphql::( ctx, @@ -66,11 +129,16 @@ impl CommonComponent for CreateGroupForm { Msg::CreateGroupResponse(response) => { log!(&format!( "Created group '{}'", - &response?.create_group.display_name + &response?.create_group_with_details.display_name )); ctx.link().history().unwrap().push(AppRoute::ListGroups); 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 Properties = (); - fn create(_: &Context) -> Self { - Self { + fn create(ctx: &Context) -> Self { + let mut component = Self { common: CommonComponentParts::::create(), form: yew_form::Form::::new(CreateGroupModel::default()), - } + attributes_schema: None, + form_ref: NodeRef::default(), + }; + component + .common + .call_graphql::( + ctx, + get_group_attributes_schema::Variables {}, + Msg::ListAttributesResponse, + "Error trying to fetch group schema", + ); + component } fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { @@ -98,7 +177,8 @@ impl Component for CreateGroupForm { let link = ctx.link(); html! {
-
+
{"Create a group"}
@@ -108,6 +188,14 @@ impl Component for CreateGroupForm { label="Group name" field_name="groupname" 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::>() + } @@ -124,3 +212,21 @@ impl Component for CreateGroupForm { } } } + +fn get_custom_attribute_input(attribute_schema: &Attribute) -> Html { + if attribute_schema.is_list { + html! { + ::into(attribute_schema.attribute_type.clone())} + /> + } + } else { + html! { + ::into(attribute_schema.attribute_type.clone())} + /> + } + } +} diff --git a/app/src/components/create_user.rs b/app/src/components/create_user.rs index abc8948..ff81da5 100644 --- a/app/src/components/create_user.rs +++ b/app/src/components/create_user.rs @@ -11,7 +11,10 @@ use crate::{ infra::{ api::HostService, common_component::{CommonComponent, CommonComponentParts}, - form_utils::{read_all_form_attributes, AttributeValue, GraphQlAttributeSchema}, + form_utils::{ + read_all_form_attributes, AttributeValue, EmailIsRequired, GraphQlAttributeSchema, + IsAdmin, + }, schema::AttributeType, }, }; @@ -121,8 +124,8 @@ impl CommonComponent for CreateUserForm { let all_values = read_all_form_attributes( self.attributes_schema.iter().flatten(), &self.form_ref, - true, - true, + IsAdmin(true), + EmailIsRequired(true), )?; let attributes = Some( 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 { html! { Result<()> { +fn validate_attributes( + all_values: &[AttributeValue], + email_is_required: EmailIsRequired, +) -> Result<()> { 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 .ok_or_else(|| anyhow!("Email is required"))? .values; @@ -28,11 +31,14 @@ fn validate_attributes(all_values: &[AttributeValue], email_is_required: bool) - Ok(()) } +pub struct IsAdmin(pub bool); +pub struct EmailIsRequired(pub bool); + pub fn read_all_form_attributes( schema: impl IntoIterator>, form_ref: &NodeRef, - is_admin: bool, - email_is_required: bool, + is_admin: IsAdmin, + email_is_required: EmailIsRequired, ) -> Result> { let form = form_ref.cast::().unwrap(); let form_data = FormData::new_with_form(&form) @@ -40,7 +46,7 @@ pub fn read_all_form_attributes( let all_values = schema .into_iter() .map(Into::::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 { let val = form_data .get_all(attr.name.as_str())