global table content
This commit is contained in:
2
server
2
server
Submodule server updated: d8c8b3521c...2bfe955a8d
@@ -29,6 +29,8 @@ use super::{
|
|||||||
pub(crate) struct NewTableQuery {
|
pub(crate) struct NewTableQuery {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
profile: String,
|
profile: String,
|
||||||
|
#[serde(default)]
|
||||||
|
global: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// GET /admin/tables/new
|
/// GET /admin/tables/new
|
||||||
@@ -39,6 +41,7 @@ pub(crate) async fn new_table_page(
|
|||||||
) -> Response {
|
) -> Response {
|
||||||
let mut draft = TableDraft::new();
|
let mut draft = TableDraft::new();
|
||||||
draft.profile_name = query.profile.trim().to_string();
|
draft.profile_name = query.profile.trim().to_string();
|
||||||
|
draft.global = query.global;
|
||||||
|
|
||||||
match load_page(state, &headers, draft, None, None).await {
|
match load_page(state, &headers, draft, None, None).await {
|
||||||
Ok(page) => Html(ui::render_page(&page)).into_response(),
|
Ok(page) => Html(ui::render_page(&page)).into_response(),
|
||||||
@@ -91,7 +94,11 @@ pub(crate) async fn create_table(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let profile_name = request.profile_name.clone();
|
let profile_name = if request.global {
|
||||||
|
"__global".to_string()
|
||||||
|
} else {
|
||||||
|
request.profile_name.clone()
|
||||||
|
};
|
||||||
let request = match authenticated_request(&headers, request) {
|
let request = match authenticated_request(&headers, request) {
|
||||||
Ok(request) => request,
|
Ok(request) => request,
|
||||||
Err(_) => return Redirect::to("/login").into_response(),
|
Err(_) => return Redirect::to("/login").into_response(),
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use super::state::{
|
use super::state::{
|
||||||
DetailColumn, LoadError, PageInputs, RenameEntry, ScriptView, TableDefinitionPageState,
|
DetailColumn, GLOBAL_SCOPE, LoadError, PageInputs, RenameEntry, ScriptView, TableDefinitionPageState,
|
||||||
TableDetailView, TablePermissionAction, TableRolePermissions, TableSummary,
|
TableDetailView, TablePermissionAction, TableRolePermissions, TableSummary,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -96,19 +96,24 @@ pub(crate) async fn load_page(
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// A profile that no longer exists takes the table selection with it.
|
// A profile that no longer exists takes the table selection with it.
|
||||||
if !profiles.contains(&inputs.selection.profile) {
|
if inputs.selection.profile != GLOBAL_SCOPE && !profiles.contains(&inputs.selection.profile) {
|
||||||
inputs.selection.profile.clear();
|
inputs.selection.profile.clear();
|
||||||
inputs.selection.table.clear();
|
inputs.selection.table.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
let tables = tree
|
let selected_tables = if inputs.selection.is_global() {
|
||||||
.profiles
|
tree.profiles.first().map(|profile| profile.tables.as_slice())
|
||||||
.iter()
|
} else {
|
||||||
.find(|profile| profile.name == inputs.selection.profile)
|
tree.profiles
|
||||||
.map(|profile| {
|
.iter()
|
||||||
profile
|
.find(|profile| profile.name == inputs.selection.profile)
|
||||||
.tables
|
.map(|profile| profile.tables.as_slice())
|
||||||
|
};
|
||||||
|
let tables = selected_tables
|
||||||
|
.map(|tables| {
|
||||||
|
tables
|
||||||
.iter()
|
.iter()
|
||||||
|
.filter(|table| table.global == inputs.selection.is_global())
|
||||||
.map(|table| TableSummary {
|
.map(|table| TableSummary {
|
||||||
name: table.name.clone(),
|
name: table.name.clone(),
|
||||||
table_kind: table.table_kind.clone(),
|
table_kind: table.table_kind.clone(),
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
use crate::schema::{ColumnCatalog, ColumnDraft};
|
use crate::schema::{ColumnCatalog, ColumnDraft};
|
||||||
|
|
||||||
|
pub(crate) const GLOBAL_SCOPE: &str = "__global";
|
||||||
|
|
||||||
/// The profile and table the workspace is pointed at. Arrives as a query
|
/// The profile and table the workspace is pointed at. Arrives as a query
|
||||||
/// string on the selector and on the column-panel endpoints, and as hidden
|
/// string on the selector and on the column-panel endpoints, and as hidden
|
||||||
/// fields on the panels that write.
|
/// fields on the panels that write.
|
||||||
@@ -27,6 +29,10 @@ impl Selection {
|
|||||||
!self.table.is_empty()
|
!self.table.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_global(&self) -> bool {
|
||||||
|
self.profile == GLOBAL_SCOPE
|
||||||
|
}
|
||||||
|
|
||||||
/// The query string the column panel posts back to, so the panel's own
|
/// The query string the column panel posts back to, so the panel's own
|
||||||
/// form does not have to carry the selection among its column fields.
|
/// form does not have to carry the selection among its column fields.
|
||||||
pub(crate) fn query(&self) -> String {
|
pub(crate) fn query(&self) -> String {
|
||||||
|
|||||||
@@ -113,6 +113,20 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn global_tables_have_their_own_scope() {
|
||||||
|
let mut state = page();
|
||||||
|
state.selection.profile = "__global".to_string();
|
||||||
|
|
||||||
|
let html = render_workspace(&state);
|
||||||
|
|
||||||
|
assert!(html.contains(r#"value="__global" selected"#));
|
||||||
|
assert!(html.contains("Global — all profiles"));
|
||||||
|
assert!(html.contains("/admin/tables/new?global=true"));
|
||||||
|
assert!(!html.contains("Copy <code>__global</code>"));
|
||||||
|
assert!(!html.contains("Create tables from an invoice template"));
|
||||||
|
}
|
||||||
|
|
||||||
fn page() -> TableDefinitionPageState {
|
fn page() -> TableDefinitionPageState {
|
||||||
TableDefinitionPageState {
|
TableDefinitionPageState {
|
||||||
nav: Nav::default(),
|
nav: Nav::default(),
|
||||||
@@ -254,7 +268,7 @@ mod tests {
|
|||||||
state.tables.clear();
|
state.tables.clear();
|
||||||
let html = render_workspace(&state);
|
let html = render_workspace(&state);
|
||||||
assert!(!html.contains("/admin/table-definition/copy"));
|
assert!(!html.contains("/admin/table-definition/copy"));
|
||||||
assert!(html.contains("Choose a profile"));
|
assert!(html.contains("Choose a scope"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<p class="eyebrow">Table definition</p>
|
<p class="eyebrow">Table definition</p>
|
||||||
<h1>Table definition</h1>
|
<h1>Table definition</h1>
|
||||||
<p>Pick a profile and a table, then change its definition.</p>
|
<p>Choose Global to manage tables shared by every profile, or choose a profile to manage only its own tables.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<a href="/admin">← Admin panel</a>
|
<a href="/admin">← Admin panel</a>
|
||||||
|
|||||||
@@ -45,10 +45,11 @@
|
|||||||
<section class="panel">
|
<section class="panel">
|
||||||
<h2>Selection</h2>
|
<h2>Selection</h2>
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<label>Profile
|
<label>Scope
|
||||||
<select name="profile" hx-get="/admin/table-definition/workspace"
|
<select name="profile" hx-get="/admin/table-definition/workspace"
|
||||||
hx-target="#table-definition-workspace" hx-swap="innerHTML">
|
hx-target="#table-definition-workspace" hx-swap="innerHTML">
|
||||||
<option value="">Choose a profile</option>
|
<option value="">Choose a scope</option>
|
||||||
|
<option value="__global" {% if page.selection.is_global() %}selected{% endif %}>Global — all profiles</option>
|
||||||
{% for profile in page.profiles %}
|
{% for profile in page.profiles %}
|
||||||
<option value="{{ profile }}" {% if page.selection.profile == *profile %}selected{% endif %}>{{ profile }}</option>
|
<option value="{{ profile }}" {% if page.selection.profile == *profile %}selected{% endif %}>{{ profile }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -73,7 +74,11 @@
|
|||||||
|
|
||||||
{% if page.selection.has_profile() %}
|
{% if page.selection.has_profile() %}
|
||||||
<div class="actions panel-actions">
|
<div class="actions panel-actions">
|
||||||
<a href="/admin/tables/new?profile={{ page.selection.profile }}">+ Create a table in this profile</a>
|
{% if page.selection.is_global() %}
|
||||||
|
<a href="/admin/tables/new?global=true">+ Create a global table</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="/admin/tables/new?profile={{ page.selection.profile }}">+ Create a table in this profile</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if page.tables.is_empty() %}
|
{% if page.tables.is_empty() %}
|
||||||
@@ -253,7 +258,7 @@
|
|||||||
</section>
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if page.selection.has_profile() %}
|
{% if page.selection.has_profile() && !page.selection.is_global() %}
|
||||||
<section class="panel">
|
<section class="panel">
|
||||||
<h2>Copy <code>{{ page.selection.profile }}</code> into a new profile</h2>
|
<h2>Copy <code>{{ page.selection.profile }}</code> into a new profile</h2>
|
||||||
<p class="hint">Copies structure — tables, links and scripts — and no rows. Leave every table unticked to copy the whole profile.</p>
|
<p class="hint">Copies structure — tables, links and scripts — and no rows. Leave every table unticked to copy the whole profile.</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user