organization of incoming data

This commit is contained in:
Priec
2026-08-12 17:46:58 +02:00
parent 08af99e334
commit 719bae0cd2
10 changed files with 184 additions and 24 deletions

View File

@@ -27,6 +27,13 @@ pub(crate) struct PreviewRow {
pub source: String,
}
#[derive(Clone, Debug)]
pub(crate) struct RelationTableOption {
pub name: String,
pub global: bool,
pub system: bool,
}
/// The whole Add-table page state, minus presentation.
#[derive(Clone, Debug, Default)]
pub(crate) struct TableDraft {
@@ -46,6 +53,7 @@ pub(crate) struct TableDraft {
/// Tables in the target profile, offered as `link(...)` targets by the
/// column picker.
pub relation_tables: Vec<String>,
pub relation_table_options: Vec<RelationTableOption>,
/// Columns identifying a row to users, in the order they are shown.
/// Empty means rows are identified by their id alone.
pub row_display_columns: Vec<String>,
@@ -125,11 +133,57 @@ impl TableDraft {
/// Records the tables the target profile offers as link targets. A table
/// cannot link to itself, so its own name is never among them.
#[cfg(test)]
pub(crate) fn set_available_relation_tables(&mut self, table_names: Vec<String>) {
self.relation_tables = table_names
self.set_available_relation_table_options(
table_names
.into_iter()
.map(|name| RelationTableOption {
name,
global: false,
system: false,
})
.collect(),
);
}
pub(crate) fn set_available_relation_table_options(
&mut self,
options: Vec<RelationTableOption>,
) {
self.relation_table_options = options
.into_iter()
.filter(|table_name| table_name != &self.table_name)
.filter(|option| option.name != self.table_name)
.collect();
self.relation_tables = self
.relation_table_options
.iter()
.map(|option| option.name.clone())
.collect();
}
pub(crate) fn global_relation_tables(&self) -> Vec<&str> {
self.relation_table_options
.iter()
.filter(|option| option.global)
.map(|option| option.name.as_str())
.collect()
}
pub(crate) fn user_relation_tables(&self) -> Vec<&str> {
self.relation_table_options
.iter()
.filter(|option| !option.global && !option.system)
.map(|option| option.name.as_str())
.collect()
}
pub(crate) fn system_relation_tables(&self) -> Vec<&str> {
self.relation_table_options
.iter()
.filter(|option| !option.global && option.system)
.map(|option| option.name.as_str())
.collect()
}
// ---- derived state ---------------------------------------------------

View File

@@ -5,7 +5,7 @@ use crate::{
services::authenticated_request,
};
use super::{draft::TableDraft, state::AddTablePageState};
use super::{draft::{RelationTableOption, TableDraft}, state::AddTablePageState};
/// Loads everything the builder needs around the draft: the column-type
/// vocabulary, the profiles that can be picked, and — for whichever profile the
@@ -66,9 +66,14 @@ pub(crate) async fn load_page(
.iter()
.flat_map(|profile| profile.tables.iter())
.filter(|table| table.global)
.map(|table| table.name.clone())
.collect::<std::collections::BTreeSet<_>>()
.map(|table| (table.name.clone(), table.table_kind.clone()))
.collect::<std::collections::BTreeMap<_, _>>()
.into_iter()
.map(|(name, table_kind)| RelationTableOption {
name,
global: true,
system: table_kind == "system",
})
.collect::<Vec<_>>();
draft.existing_profile_tables = tree
.profiles
@@ -78,7 +83,7 @@ pub(crate) async fn load_page(
.collect::<std::collections::BTreeSet<_>>()
.into_iter()
.collect();
draft.set_available_relation_tables(global_tables);
draft.set_available_relation_table_options(global_tables);
} else { match tree
.profiles
.iter()
@@ -87,19 +92,27 @@ pub(crate) async fn load_page(
// An existing profile: its tables are the link targets, and their
// names are reserved against duplicate table creation.
Some(profile) => {
let table_names = profile
let table_options = profile
.tables
.iter()
.filter(|table| table.name != "accounts")
.map(|table| table.name.clone())
.map(|table| RelationTableOption {
name: table.name.clone(),
global: table.global,
system: table.table_kind == "system",
})
.collect::<Vec<_>>();
draft.existing_profile_tables = table_names.clone();
draft.set_available_relation_tables(table_names);
draft.existing_profile_tables = table_options
.iter()
.map(|table| table.name.clone())
.collect();
draft.set_available_relation_table_options(table_options);
}
// A brand-new (or not-yet-named) profile has nothing to link to.
None => {
draft.existing_profile_tables.clear();
draft.relation_tables.clear();
draft.relation_table_options.clear();
}
}}

View File

@@ -143,6 +143,7 @@ impl BuilderForm {
table_name: self.table_name.clone(),
columns,
relation_tables: self.relation_tables.clone(),
relation_table_options: Vec::new(),
row_display_columns,
// Filled in by the loader from the live profile tree, never by the
// client: it is what duplicate table names are checked against.

View File

@@ -63,7 +63,7 @@ pub(crate) fn render_submission_error(message: &str) -> String {
mod tests {
use super::*;
use crate::{
pages::add_table::draft::TableDraft,
pages::add_table::draft::{RelationTableOption, TableDraft},
schema::{ColumnDefinition, MoneyMode},
};
@@ -80,7 +80,23 @@ mod tests {
money_mode: MoneyMode::Exact,
currency: String::new(),
});
draft.set_available_relation_tables(vec!["customer".to_string()]);
draft.set_available_relation_table_options(vec![
RelationTableOption {
name: "customer".to_string(),
global: false,
system: false,
},
RelationTableOption {
name: "currencies".to_string(),
global: true,
system: false,
},
RelationTableOption {
name: "audit_log".to_string(),
global: false,
system: true,
},
]);
draft.toggle_row_display_candidate(1);
AddTablePageState {
@@ -172,9 +188,13 @@ mod tests {
state.draft.columns.type_input = "link".to_string();
let html = render_builder(&state);
assert!(html.contains(r#">FKlink</option>"#));
assert!(html.contains("Link alias"));
assert!(html.contains(r#"name="link_table_input""#));
assert!(html.contains(r#"<option value="customer""#));
assert!(html.contains(r#"<optgroup label="Global">"#));
assert!(html.contains(r#"<optgroup label="User-created">"#));
assert!(html.contains(r#"<optgroup label="System-created">"#));
}
#[test]