organization of incoming data
This commit is contained in:
@@ -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 ---------------------------------------------------
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -117,6 +117,7 @@ pub(crate) async fn load_page(
|
||||
.map(|table| TableSummary {
|
||||
name: table.name.clone(),
|
||||
table_kind: table.table_kind.clone(),
|
||||
global: table.global,
|
||||
// One entry per link, named by the column carrying it, so a
|
||||
// table pointing at one target twice reads as two links.
|
||||
depends_on: table
|
||||
|
||||
@@ -45,6 +45,7 @@ impl Selection {
|
||||
pub(crate) struct TableSummary {
|
||||
pub name: String,
|
||||
pub table_kind: String,
|
||||
pub global: bool,
|
||||
pub depends_on: Vec<String>,
|
||||
pub row_display_columns: Vec<String>,
|
||||
}
|
||||
@@ -277,10 +278,34 @@ pub(crate) struct TablePermissionAction {
|
||||
}
|
||||
|
||||
impl TableDefinitionPageState {
|
||||
pub(crate) fn link_target_tables(&self) -> Vec<&str> {
|
||||
fn eligible_link_target(&self, table: &TableSummary) -> bool {
|
||||
table.name != self.selection.table && table.name != "accounts"
|
||||
}
|
||||
|
||||
pub(crate) fn global_link_target_tables(&self) -> Vec<&str> {
|
||||
self.tables
|
||||
.iter()
|
||||
.filter(|table| table.name != self.selection.table && table.name != "accounts")
|
||||
.filter(|table| self.eligible_link_target(table) && table.global)
|
||||
.map(|table| table.name.as_str())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn user_link_target_tables(&self) -> Vec<&str> {
|
||||
self.tables
|
||||
.iter()
|
||||
.filter(|table| {
|
||||
self.eligible_link_target(table) && !table.global && !table.is_system()
|
||||
})
|
||||
.map(|table| table.name.as_str())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn system_link_target_tables(&self) -> Vec<&str> {
|
||||
self.tables
|
||||
.iter()
|
||||
.filter(|table| {
|
||||
self.eligible_link_target(table) && !table.global && table.is_system()
|
||||
})
|
||||
.map(|table| table.name.as_str())
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -108,6 +108,7 @@ mod tests {
|
||||
TableSummary {
|
||||
name: name.to_string(),
|
||||
table_kind: kind.to_string(),
|
||||
global: false,
|
||||
depends_on: Vec::new(),
|
||||
row_display_columns: vec!["number".to_string()],
|
||||
}
|
||||
@@ -234,12 +235,21 @@ mod tests {
|
||||
let mut state = page();
|
||||
state.columns.type_input = "link".to_string();
|
||||
state.tables.push(table("customer", "dynamic"));
|
||||
state.tables.push(TableSummary {
|
||||
global: true,
|
||||
..table("currencies", "dynamic")
|
||||
});
|
||||
state.tables.push(table("audit_log", "system"));
|
||||
|
||||
let html = render_column_panel(&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">"#));
|
||||
assert!(!html.contains(r#"<option value="invoice""#));
|
||||
}
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
hx-vals='{"action": "refresh"}'>
|
||||
<option value="">Choose a type</option>
|
||||
{% for column_type in column_types %}
|
||||
<option value="{{ column_type }}" {% if page.draft.columns.type_input == *column_type %}selected{% endif %}>{{ column_type }}</option>
|
||||
<option value="{{ column_type }}" {% if page.draft.columns.type_input == *column_type %}selected{% endif %}>{% if *column_type == "link" %}FKlink{% else %}{{ column_type }}{% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
@@ -152,9 +152,27 @@
|
||||
<label>Referenced table
|
||||
<select name="link_table_input">
|
||||
<option value="">Choose a table</option>
|
||||
{% for table in page.draft.relation_tables %}
|
||||
<option value="{{ table }}" {% if page.draft.columns.link_table_input == *table %}selected{% endif %}>{{ table }}</option>
|
||||
{% endfor %}
|
||||
{% if !page.draft.global_relation_tables().is_empty() %}
|
||||
<optgroup label="Global">
|
||||
{% for table in page.draft.global_relation_tables() %}
|
||||
<option value="{{ table }}" {% if page.draft.columns.link_table_input == *table %}selected{% endif %}>{{ table }}</option>
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% endif %}
|
||||
{% if !page.draft.user_relation_tables().is_empty() %}
|
||||
<optgroup label="User-created">
|
||||
{% for table in page.draft.user_relation_tables() %}
|
||||
<option value="{{ table }}" {% if page.draft.columns.link_table_input == *table %}selected{% endif %}>{{ table }}</option>
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% endif %}
|
||||
{% if !page.draft.system_relation_tables().is_empty() %}
|
||||
<optgroup label="System-created">
|
||||
{% for table in page.draft.system_relation_tables() %}
|
||||
<option value="{{ table }}" {% if page.draft.columns.link_table_input == *table %}selected{% endif %}>{{ table }}</option>
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% endif %}
|
||||
</select>
|
||||
{% if page.draft.relation_tables.is_empty() %}<small>No eligible tables exist in this scope.</small>{% endif %}
|
||||
</label>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
hx-vals='{"action": "refresh"}'>
|
||||
<option value="">Choose a type</option>
|
||||
{% for column_type in column_types %}
|
||||
<option value="{{ column_type }}" {% if page.columns.type_input == *column_type %}selected{% endif %}>{{ column_type }}</option>
|
||||
<option value="{{ column_type }}" {% if page.columns.type_input == *column_type %}selected{% endif %}>{% if *column_type == "link" %}FKlink{% else %}{{ column_type }}{% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
@@ -79,11 +79,29 @@
|
||||
<label>Referenced table
|
||||
<select name="link_table_input">
|
||||
<option value="">Choose a table</option>
|
||||
{% for table in page.link_target_tables() %}
|
||||
<option value="{{ table }}" {% if page.columns.link_table_input == *table %}selected{% endif %}>{{ table }}</option>
|
||||
{% endfor %}
|
||||
{% if !page.global_link_target_tables().is_empty() %}
|
||||
<optgroup label="Global">
|
||||
{% for table in page.global_link_target_tables() %}
|
||||
<option value="{{ table }}" {% if page.columns.link_table_input == *table %}selected{% endif %}>{{ table }}</option>
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% endif %}
|
||||
{% if !page.user_link_target_tables().is_empty() %}
|
||||
<optgroup label="User-created">
|
||||
{% for table in page.user_link_target_tables() %}
|
||||
<option value="{{ table }}" {% if page.columns.link_table_input == *table %}selected{% endif %}>{{ table }}</option>
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% endif %}
|
||||
{% if !page.system_link_target_tables().is_empty() %}
|
||||
<optgroup label="System-created">
|
||||
{% for table in page.system_link_target_tables() %}
|
||||
<option value="{{ table }}" {% if page.columns.link_table_input == *table %}selected{% endif %}>{{ table }}</option>
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% endif %}
|
||||
</select>
|
||||
{% if page.link_target_tables().is_empty() %}<small>No eligible tables exist in this scope.</small>{% endif %}
|
||||
{% if page.global_link_target_tables().is_empty() && page.user_link_target_tables().is_empty() && page.system_link_target_tables().is_empty() %}<small>No eligible tables exist in this scope.</small>{% endif %}
|
||||
</label>
|
||||
{% endif %}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user