talbe definition web
This commit is contained in:
@@ -15,7 +15,7 @@ use crate::{
|
||||
definitions::table_definition::{
|
||||
PostTableDefinitionRequest, TableLink as ProtoTableLink,
|
||||
},
|
||||
schema::{ColumnDraft, proto_columns, validate_identifier},
|
||||
schema::{ColumnCatalog, ColumnDraft, proto_columns, validate_identifier},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
@@ -102,11 +102,13 @@ pub(crate) struct TableDraft {
|
||||
}
|
||||
|
||||
impl TableDraft {
|
||||
/// A draft for a brand-new page load, matching the client's defaults.
|
||||
/// A draft for a brand-new page load, matching the client's defaults. The
|
||||
/// column panel's vocabulary is filled in by the loader, which is the only
|
||||
/// thing that knows it.
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
accounting_currency: "EUR".to_string(),
|
||||
columns: ColumnDraft::new(),
|
||||
columns: ColumnDraft::new(ColumnCatalog::default()),
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
@@ -148,6 +150,11 @@ impl TableDraft {
|
||||
self.row_display_columns.clear();
|
||||
return;
|
||||
}
|
||||
// A compound column leaves no column of its own name behind, so it can
|
||||
// never identify a row; `row_display_candidates` does not offer one.
|
||||
if !self.columns.is_indexable(index - 1) {
|
||||
return;
|
||||
}
|
||||
let Some(column) = self
|
||||
.columns
|
||||
.added
|
||||
@@ -354,6 +361,9 @@ mod tests {
|
||||
|
||||
fn draft_with_column(name: &str, data_type: &str) -> TableDraft {
|
||||
let mut draft = TableDraft::new();
|
||||
// The loader fills this in from the backend; a draft under test gets
|
||||
// the same vocabulary directly.
|
||||
draft.columns.catalog = crate::schema::tests::catalog();
|
||||
draft.profile_name = "billing".to_string();
|
||||
draft.table_name = "invoice".to_string();
|
||||
draft.columns.added.push(ColumnDefinition {
|
||||
@@ -471,6 +481,26 @@ mod tests {
|
||||
assert!(draft.row_display_columns.is_empty());
|
||||
}
|
||||
|
||||
/// A compound column expands into schema-managed companions, so there is
|
||||
/// no column of that name for a row to be identified by — the builder does
|
||||
/// not offer it, and a crafted post cannot choose it either.
|
||||
#[test]
|
||||
fn a_compound_column_never_identifies_a_row() {
|
||||
let mut draft = draft_with_column("number", "text");
|
||||
draft.columns.added.push(ColumnDefinition {
|
||||
name: "accounting".to_string(),
|
||||
data_type: "accounting".to_string(),
|
||||
indexed: false,
|
||||
quantity_ledger: false,
|
||||
money_mode: MoneyMode::Exact,
|
||||
currency: "EUR".to_string(),
|
||||
});
|
||||
|
||||
draft.toggle_row_display_candidate(2);
|
||||
|
||||
assert!(draft.row_display_columns.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removing_a_column_drops_it_from_the_display_columns() {
|
||||
let mut draft = draft_with_column("number", "text");
|
||||
|
||||
@@ -7,12 +7,14 @@ use crate::{
|
||||
|
||||
use super::{draft::TableDraft, state::AddTablePageState};
|
||||
|
||||
/// Loads everything the builder needs around the draft: the profiles that can
|
||||
/// be picked, and — for whichever profile the table will belong to — the tables
|
||||
/// that are link targets and the names the new table may not reuse.
|
||||
/// Loads everything the builder needs around the draft: the column-type
|
||||
/// vocabulary, the profiles that can be picked, and — for whichever profile the
|
||||
/// table will belong to — the tables that are link targets and the names the
|
||||
/// new table may not reuse.
|
||||
///
|
||||
/// The link targets and reserved names always come from the live profile tree,
|
||||
/// never from the posted form, so they cannot be spoofed by a crafted request.
|
||||
/// The vocabulary, link targets and reserved names always come from the live
|
||||
/// backend, never from the posted form, so they cannot be spoofed by a crafted
|
||||
/// request.
|
||||
pub(crate) async fn load_page(
|
||||
state: AppState,
|
||||
headers: &HeaderMap,
|
||||
@@ -36,6 +38,19 @@ pub(crate) async fn load_page(
|
||||
}
|
||||
|
||||
let mut definitions = state.definitions;
|
||||
// What a column may be is the backend's to say; the picker and every rule
|
||||
// the draft applies are read from this.
|
||||
draft.columns.catalog = crate::schema::column_catalog(
|
||||
definitions
|
||||
.list_column_types(
|
||||
authenticated_request(headers, Empty {}).map_err(|_| LoadError::Unauthenticated)?,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| LoadError::Backend(error.message().to_string()))?
|
||||
.into_inner()
|
||||
.column_types,
|
||||
);
|
||||
|
||||
let tree = definitions
|
||||
.get_profile_tree(
|
||||
authenticated_request(headers, Empty {}).map_err(|_| LoadError::Unauthenticated)?,
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
//! by [`crate::schema::columns_from_rows`], so the two screens that describe
|
||||
//! columns cannot drift apart.
|
||||
|
||||
use crate::schema::{ColumnDraft, columns_from_rows};
|
||||
use crate::schema::{ColumnCatalog, ColumnDraft, columns_from_rows};
|
||||
|
||||
use super::draft::{LinkDefinition, LinkMode, TableDraft};
|
||||
|
||||
@@ -110,8 +110,12 @@ impl BuilderForm {
|
||||
&self.column_rounding,
|
||||
&self.column_currencies,
|
||||
),
|
||||
// The table is being created here, so ACCOUNTING is on the table.
|
||||
accounting_allowed: true,
|
||||
// Filled in by the loader from `ListColumnTypes`, never by the
|
||||
// form: it is the vocabulary the draft is validated against.
|
||||
catalog: ColumnCatalog::default(),
|
||||
// The table is being created here, so the creation-only types are
|
||||
// on the table.
|
||||
creating_table: true,
|
||||
};
|
||||
|
||||
let link_count = self.link_tables.len().min(self.link_modes.len());
|
||||
@@ -189,6 +193,9 @@ impl AddTablePageState {
|
||||
.added
|
||||
.iter()
|
||||
.enumerate()
|
||||
// A compound column expands into schema-managed companions, so
|
||||
// there is no column of that name for a row to be shown by.
|
||||
.filter(|(index, _)| self.draft.columns.is_indexable(*index))
|
||||
.map(|(index, column)| RowDisplayCandidate {
|
||||
index: index + 1,
|
||||
name: column.name.clone(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use askama::Template;
|
||||
|
||||
use crate::{
|
||||
schema::{CURRENCY_CODES, GTIN_TYPES, TEMPORAL_TYPES},
|
||||
schema::CURRENCY_CODES,
|
||||
ui::{Alert, Nav, render},
|
||||
};
|
||||
|
||||
@@ -13,9 +13,9 @@ use super::state::AddTablePageState;
|
||||
struct AddTablePage<'a> {
|
||||
nav: Nav,
|
||||
page: &'a AddTablePageState,
|
||||
column_types: &'static [&'static str],
|
||||
temporal_types: &'static [&'static str],
|
||||
gtin_types: &'static [&'static str],
|
||||
column_types: Vec<String>,
|
||||
temporal_types: Vec<String>,
|
||||
gtin_types: Vec<String>,
|
||||
currency_codes: &'static [&'static str],
|
||||
}
|
||||
|
||||
@@ -25,18 +25,20 @@ struct AddTablePage<'a> {
|
||||
#[template(path = "pages/add_table/builder.html")]
|
||||
struct BuilderFragment<'a> {
|
||||
page: &'a AddTablePageState,
|
||||
column_types: &'static [&'static str],
|
||||
temporal_types: &'static [&'static str],
|
||||
gtin_types: &'static [&'static str],
|
||||
column_types: Vec<String>,
|
||||
temporal_types: Vec<String>,
|
||||
gtin_types: Vec<String>,
|
||||
}
|
||||
|
||||
pub(crate) fn render_page(page: &AddTablePageState) -> String {
|
||||
render(&AddTablePage {
|
||||
nav: page.nav.clone(),
|
||||
page,
|
||||
// Every picker is the backend's own vocabulary, asked through the
|
||||
// draft so it can only offer what the draft would accept.
|
||||
column_types: page.draft.columns.offered_types(),
|
||||
temporal_types: TEMPORAL_TYPES,
|
||||
gtin_types: GTIN_TYPES,
|
||||
temporal_types: page.draft.columns.temporal_types(),
|
||||
gtin_types: page.draft.columns.gtin_types(),
|
||||
currency_codes: CURRENCY_CODES,
|
||||
})
|
||||
}
|
||||
@@ -45,8 +47,8 @@ pub(crate) fn render_builder(page: &AddTablePageState) -> String {
|
||||
render(&BuilderFragment {
|
||||
page,
|
||||
column_types: page.draft.columns.offered_types(),
|
||||
temporal_types: TEMPORAL_TYPES,
|
||||
gtin_types: GTIN_TYPES,
|
||||
temporal_types: page.draft.columns.temporal_types(),
|
||||
gtin_types: page.draft.columns.gtin_types(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -67,6 +69,7 @@ mod tests {
|
||||
|
||||
fn page() -> AddTablePageState {
|
||||
let mut draft = TableDraft::new();
|
||||
draft.columns.catalog = crate::schema::tests::catalog();
|
||||
draft.profile_name = "billing".to_string();
|
||||
draft.table_name = "invoice".to_string();
|
||||
draft.columns.added.push(ColumnDefinition {
|
||||
|
||||
Reference in New Issue
Block a user