web interface for table_definition improved
This commit is contained in:
196
web/src/pages/admin/table_definition/loader.rs
Normal file
196
web/src/pages/admin/table_definition/loader.rs
Normal file
@@ -0,0 +1,196 @@
|
||||
//! Reads everything the workspace shows.
|
||||
//!
|
||||
//! Three calls, in this order: the profile tree names the profiles and their
|
||||
//! tables, the profile details describe the selected table's columns and
|
||||
//! scripts, and the rename history explains how those columns got their names.
|
||||
//! Nothing here trusts the posted selection — a profile or table that is gone
|
||||
//! is dropped from the selection rather than reported as an error, because the
|
||||
//! commonest way to get here with a stale one is having just deleted it.
|
||||
|
||||
use axum::http::HeaderMap;
|
||||
|
||||
use crate::{
|
||||
AppState,
|
||||
auth::GetAuthorizationRequest,
|
||||
definitions::{
|
||||
common::Empty,
|
||||
table_definition::{
|
||||
GetColumnAliasRenameHistoryRequest, GetProfileDetailsRequest, MoneyRounding,
|
||||
},
|
||||
},
|
||||
services::authenticated_request,
|
||||
};
|
||||
|
||||
use super::state::{
|
||||
DetailColumn, LoadError, PageInputs, RenameEntry, ScriptView, TableDefinitionPageState,
|
||||
TableDetailView, TableSummary,
|
||||
};
|
||||
|
||||
pub(crate) async fn load_page(
|
||||
state: AppState,
|
||||
headers: &HeaderMap,
|
||||
mut inputs: PageInputs,
|
||||
) -> Result<TableDefinitionPageState, LoadError> {
|
||||
let authorization_request = authenticated_request(headers, GetAuthorizationRequest {})
|
||||
.map_err(|_| LoadError::Unauthenticated)?;
|
||||
let mut auth = state.auth;
|
||||
let authorization = auth
|
||||
.get_authorization(authorization_request)
|
||||
.await
|
||||
.map_err(|error| match error.code() {
|
||||
tonic::Code::Unauthenticated => LoadError::Unauthenticated,
|
||||
_ => LoadError::Backend(error.message().to_string()),
|
||||
})?
|
||||
.into_inner();
|
||||
if authorization.role != "admin" {
|
||||
return Err(LoadError::Forbidden);
|
||||
}
|
||||
|
||||
let mut definitions = state.definitions;
|
||||
let tree = definitions
|
||||
.get_profile_tree(
|
||||
authenticated_request(headers, Empty {}).map_err(|_| LoadError::Unauthenticated)?,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| LoadError::Backend(error.message().to_string()))?
|
||||
.into_inner();
|
||||
|
||||
let profiles = tree
|
||||
.profiles
|
||||
.iter()
|
||||
.map(|profile| profile.name.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// A profile that no longer exists takes the table selection with it.
|
||||
if !profiles.contains(&inputs.selection.profile) {
|
||||
inputs.selection.profile.clear();
|
||||
inputs.selection.table.clear();
|
||||
}
|
||||
|
||||
let tables = tree
|
||||
.profiles
|
||||
.iter()
|
||||
.find(|profile| profile.name == inputs.selection.profile)
|
||||
.map(|profile| {
|
||||
profile
|
||||
.tables
|
||||
.iter()
|
||||
.map(|table| TableSummary {
|
||||
name: table.name.clone(),
|
||||
table_kind: table.table_kind.clone(),
|
||||
depends_on: table.depends_on.clone(),
|
||||
row_display_columns: table.row_display_columns.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
if !tables
|
||||
.iter()
|
||||
.any(|table| table.name == inputs.selection.table)
|
||||
{
|
||||
inputs.selection.table.clear();
|
||||
}
|
||||
|
||||
let detail = match inputs.selection.has_table() {
|
||||
true => {
|
||||
let request = GetProfileDetailsRequest {
|
||||
profile_name: inputs.selection.profile.clone(),
|
||||
};
|
||||
let details = definitions
|
||||
.get_profile_details(
|
||||
authenticated_request(headers, request)
|
||||
.map_err(|_| LoadError::Unauthenticated)?,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| LoadError::Backend(error.message().to_string()))?
|
||||
.into_inner();
|
||||
|
||||
details
|
||||
.tables
|
||||
.into_iter()
|
||||
.find(|table| table.name == inputs.selection.table)
|
||||
.map(|table| TableDetailView {
|
||||
id: table.id,
|
||||
columns: table
|
||||
.columns
|
||||
.iter()
|
||||
.map(|column| {
|
||||
let behavior = table.column_behaviors.get(&column.name);
|
||||
DetailColumn {
|
||||
name: column.name.clone(),
|
||||
field_type: column.field_type.clone(),
|
||||
currency: column.currency.clone(),
|
||||
quantity_ledger: column.quantity_ledger,
|
||||
rounded: column.rounding == i32::from(MoneyRounding::HalfUp),
|
||||
generated: behavior.is_some_and(|behavior| behavior.generated),
|
||||
read_only: behavior.is_some_and(|behavior| behavior.read_only),
|
||||
generated_from: behavior
|
||||
.map(|behavior| behavior.generated_from.clone())
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
scripts: table
|
||||
.scripts
|
||||
.into_iter()
|
||||
.map(|script| ScriptView {
|
||||
target_column: script.target_column,
|
||||
target_column_type: script.target_column_type,
|
||||
description: script.description,
|
||||
script: script.script,
|
||||
})
|
||||
.collect(),
|
||||
row_display_columns: table.row_display_columns,
|
||||
table_kind: table.table_kind,
|
||||
name: table.name,
|
||||
})
|
||||
}
|
||||
false => None,
|
||||
};
|
||||
|
||||
// The history is per profile; a selected table narrows it to that table.
|
||||
let history = match inputs.selection.has_profile() {
|
||||
true => {
|
||||
let request = GetColumnAliasRenameHistoryRequest {
|
||||
profile_name: inputs.selection.profile.clone(),
|
||||
table_definition_id: detail.as_ref().map(|detail| detail.id),
|
||||
};
|
||||
definitions
|
||||
.get_column_alias_rename_history(
|
||||
authenticated_request(headers, request)
|
||||
.map_err(|_| LoadError::Unauthenticated)?,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| LoadError::Backend(error.message().to_string()))?
|
||||
.into_inner()
|
||||
.entries
|
||||
.into_iter()
|
||||
.map(|entry| RenameEntry {
|
||||
table_name: entry.table_name,
|
||||
old_column_name: entry.old_column_name,
|
||||
new_column_name: entry.new_column_name,
|
||||
created_at: entry.created_at,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
false => Vec::new(),
|
||||
};
|
||||
|
||||
Ok(TableDefinitionPageState {
|
||||
nav: crate::ui::Nav::new(headers, "admin").with_role(authorization.role),
|
||||
profiles,
|
||||
tables,
|
||||
detail,
|
||||
history,
|
||||
selection: inputs.selection,
|
||||
columns: inputs.columns,
|
||||
rename: inputs.rename,
|
||||
copy: inputs.copy,
|
||||
invoice: inputs.invoice,
|
||||
status: inputs.status,
|
||||
error: inputs.error,
|
||||
sql: inputs.sql,
|
||||
generated: inputs.generated,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user