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

View file

@ -1,5 +1,5 @@
mutation CreateGroup($name: String!) {
createGroup(name: $name) {
mutation CreateGroup($group: CreateGroupInput!) {
createGroupWithDetails(request: $group) {
id
displayName
}

View file

@ -7,6 +7,7 @@ query GetGroupAttributesSchema {
isList
isVisible
isHardcoded
isReadonly
}
}
}

View file

@ -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<Self>,
form: yew_form::Form<CreateGroupModel>,
attributes_schema: Option<Vec<Attribute>>,
form_ref: NodeRef,
}
#[derive(Model, Validate, PartialEq, Eq, Clone, Default)]
@ -35,6 +76,7 @@ pub struct CreateGroupModel {
pub enum Msg {
Update,
ListAttributesResponse(Result<ResponseData>),
SubmitForm,
CreateGroupResponse(Result<create_group::ResponseData>),
}
@ -48,12 +90,33 @@ impl CommonComponent<CreateGroupForm> 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::<CreateGroup, _>(
ctx,
@ -66,11 +129,16 @@ impl CommonComponent<CreateGroupForm> 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 {
Self {
fn create(ctx: &Context<Self>) -> Self {
let mut component = Self {
common: CommonComponentParts::<Self>::create(),
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 {
@ -98,7 +177,8 @@ impl Component for CreateGroupForm {
let link = ctx.link();
html! {
<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">
<h5 class="fw-bold">{"Create a group"}</h5>
</div>
@ -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::<Vec<_>>()
}
<Submit
disabled={self.common.is_task_running()}
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())}
/>
}
}
}

View file

@ -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<CreateUserForm> 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! {
<ListAttributeInput

View file

@ -9,7 +9,7 @@ use crate::{
},
infra::{
common_component::{CommonComponent, CommonComponentParts},
form_utils::{read_all_form_attributes, AttributeValue},
form_utils::{read_all_form_attributes, AttributeValue, EmailIsRequired, IsAdmin},
schema::AttributeType,
},
};
@ -210,8 +210,8 @@ impl GroupDetailsForm {
let mut all_values = read_all_form_attributes(
ctx.props().group_attributes_schema.iter(),
&self.form_ref,
ctx.props().is_admin,
false,
IsAdmin(ctx.props().is_admin),
EmailIsRequired(false),
)?;
let base_attributes = &self.group.attributes;
all_values.retain(|a| {

View file

@ -9,7 +9,7 @@ use crate::{
},
infra::{
common_component::{CommonComponent, CommonComponentParts},
form_utils::{read_all_form_attributes, AttributeValue},
form_utils::{read_all_form_attributes, AttributeValue, EmailIsRequired, IsAdmin},
schema::AttributeType,
},
};
@ -212,8 +212,8 @@ impl UserDetailsForm {
let mut all_values = read_all_form_attributes(
ctx.props().user_attributes_schema.iter(),
&self.form_ref,
ctx.props().is_admin,
!ctx.props().is_edited_user_admin,
IsAdmin(ctx.props().is_admin),
EmailIsRequired(!ctx.props().is_edited_user_admin),
)?;
let base_attributes = &self.user.attributes;
all_values.retain(|a| {

View file

@ -16,9 +16,12 @@ pub struct GraphQlAttributeSchema {
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");
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<Item = impl Into<GraphQlAttributeSchema>>,
form_ref: &NodeRef,
is_admin: bool,
email_is_required: bool,
is_admin: IsAdmin,
email_is_required: EmailIsRequired,
) -> Result<Vec<AttributeValue>> {
let form = form_ref.cast::<HtmlFormElement>().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::<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> {
let val = form_data
.get_all(attr.name.as_str())