fixing formatting bug for accounts to the client
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
mod query_builder;
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
@@ -32,6 +32,118 @@ const DEFAULT_LIST_LIMIT: usize = 5;
|
||||
const SEARCH_SCORE_RELATIVE_FLOOR: f32 = 0.25;
|
||||
const SEARCH_SCORE_GROUP_WIDTH: f32 = 0.25;
|
||||
|
||||
pub struct AccountPathProjection {
|
||||
paths: HashMap<i64, String>,
|
||||
account_column: Option<String>,
|
||||
accounts_table: bool,
|
||||
}
|
||||
|
||||
impl AccountPathProjection {
|
||||
pub async fn load(
|
||||
pool: &PgPool,
|
||||
profile_name: &str,
|
||||
table_name: &str,
|
||||
physical_to_display: &HashMap<String, String>,
|
||||
) -> Result<Self, Status> {
|
||||
let account_column = physical_to_display
|
||||
.get(common::system_column::ACCOUNT_REFERENCE_COLUMN)
|
||||
.cloned();
|
||||
let accounts_table = table_name == "accounts";
|
||||
if account_column.is_none() && !accounts_table {
|
||||
return Ok(Self {
|
||||
paths: HashMap::new(),
|
||||
account_column,
|
||||
accounts_table,
|
||||
});
|
||||
}
|
||||
|
||||
let qualified_accounts = format!(
|
||||
"\"{}\".\"accounts\"",
|
||||
profile_name.replace('"', "\"\""),
|
||||
);
|
||||
let rows = sqlx::query(AssertSqlSafe(format!(
|
||||
"SELECT id, segment, parent_account_id FROM {qualified_accounts} WHERE deleted = FALSE"
|
||||
)))
|
||||
.fetch_all(pool)
|
||||
.await
|
||||
.map_err(|error| Status::internal(format!("Account path lookup failed: {error}")))?;
|
||||
let mut nodes = BTreeMap::new();
|
||||
for row in rows {
|
||||
let id: i64 = row.try_get("id").map_err(|error| {
|
||||
Status::internal(format!("Account id read failed: {error}"))
|
||||
})?;
|
||||
let segment: String = row.try_get("segment").map_err(|error| {
|
||||
Status::internal(format!("Account segment read failed: {error}"))
|
||||
})?;
|
||||
let parent: Option<i64> = row.try_get("parent_account_id").map_err(|error| {
|
||||
Status::internal(format!("Account parent read failed: {error}"))
|
||||
})?;
|
||||
nodes.insert(id, (segment, parent));
|
||||
}
|
||||
let mut paths = HashMap::new();
|
||||
for &id in nodes.keys() {
|
||||
let mut segments = Vec::new();
|
||||
let mut visited = BTreeSet::new();
|
||||
let mut current = Some(id);
|
||||
while let Some(node_id) = current {
|
||||
if !visited.insert(node_id) {
|
||||
return Err(Status::internal("Stored account hierarchy contains a cycle"));
|
||||
}
|
||||
let (segment, parent) = nodes.get(&node_id).ok_or_else(|| {
|
||||
Status::internal(format!("Account {node_id} was not found"))
|
||||
})?;
|
||||
segments.push(segment.clone());
|
||||
current = *parent;
|
||||
}
|
||||
segments.reverse();
|
||||
paths.insert(id, segments.join("/"));
|
||||
}
|
||||
Ok(Self {
|
||||
paths,
|
||||
account_column,
|
||||
accounts_table,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn apply(&self, row_id: i64, value: &mut serde_json::Value) -> Result<(), Status> {
|
||||
let Some(object) = value.as_object_mut() else {
|
||||
return Ok(());
|
||||
};
|
||||
if let Some(column) = &self.account_column {
|
||||
if let Some(stored) = object.get(column).filter(|value| !value.is_null()) {
|
||||
let account_id = match stored {
|
||||
serde_json::Value::Number(value) => value.as_i64(),
|
||||
serde_json::Value::String(value) => value.parse().ok(),
|
||||
_ => None,
|
||||
}
|
||||
.ok_or_else(|| Status::internal("Stored account reference is invalid"))?;
|
||||
let path = self.paths.get(&account_id).ok_or_else(|| {
|
||||
Status::internal(format!("Account {account_id} was not found"))
|
||||
})?;
|
||||
object.insert(column.clone(), serde_json::Value::String(path.clone()));
|
||||
}
|
||||
}
|
||||
if self.accounts_table {
|
||||
let path = self.paths.get(&row_id).ok_or_else(|| {
|
||||
Status::internal(format!("Account {row_id} was not found"))
|
||||
})?;
|
||||
object.insert(
|
||||
common::system_column::ACCOUNT_API_COLUMN.to_string(),
|
||||
serde_json::Value::String(path.clone()),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn row_display_columns(&self, columns: &[String]) -> Vec<String> {
|
||||
if self.accounts_table {
|
||||
vec![common::system_column::ACCOUNT_API_COLUMN.to_string()]
|
||||
} else {
|
||||
columns.to_vec()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SearcherService {
|
||||
pub pool: PgPool,
|
||||
profiles: Mutex<HashMap<String, Arc<ProfileIndex>>>,
|
||||
@@ -1243,7 +1355,16 @@ async fn fetch_ordered_rows(
|
||||
) -> Result<Vec<Hit>, Status> {
|
||||
let physical_to_display = table_physical_to_display_map(pool, profile_name, table_name).await?;
|
||||
let internal_columns = table_internal_column_names(pool, profile_name, table_name).await?;
|
||||
let display_columns = table_row_display_columns(pool, profile_name, table_name).await?;
|
||||
let account_projection = AccountPathProjection::load(
|
||||
pool,
|
||||
profile_name,
|
||||
table_name,
|
||||
&physical_to_display,
|
||||
)
|
||||
.await?;
|
||||
let display_columns = account_projection.row_display_columns(
|
||||
&table_row_display_columns(pool, profile_name, table_name).await?,
|
||||
);
|
||||
let (resolved_order, direction) = match order {
|
||||
Some(order) => (
|
||||
resolve_order_column(pool, profile_name, table_name, &order.column).await?,
|
||||
@@ -1286,11 +1407,12 @@ async fn fetch_ordered_rows(
|
||||
.get("version")
|
||||
.and_then(|value| value.as_i64())
|
||||
.unwrap_or(0);
|
||||
let json_data = remap_json_to_display_names(
|
||||
let mut json_data = remap_json_to_display_names(
|
||||
json_data,
|
||||
&physical_to_display,
|
||||
&internal_columns,
|
||||
)?;
|
||||
account_projection.apply(id, &mut json_data)?;
|
||||
let row_display_values = row_display_values(&json_data, &display_columns);
|
||||
Ok(Hit {
|
||||
id,
|
||||
@@ -1531,7 +1653,16 @@ async fn run_search(
|
||||
table_physical_to_display_map(pool, profile_name, &table_name).await?;
|
||||
let internal_columns =
|
||||
table_internal_column_names(pool, profile_name, &table_name).await?;
|
||||
let display_columns = table_row_display_columns(pool, profile_name, &table_name).await?;
|
||||
let account_projection = AccountPathProjection::load(
|
||||
pool,
|
||||
profile_name,
|
||||
&table_name,
|
||||
&physical_to_display,
|
||||
)
|
||||
.await?;
|
||||
let display_columns = account_projection.row_display_columns(
|
||||
&table_row_display_columns(pool, profile_name, &table_name).await?,
|
||||
);
|
||||
let qualified_table = qualified_visible_table(pool, profile_name, &table_name).await?;
|
||||
let pg_ids = candidate_rows.iter().map(|item| item.0).collect::<Vec<_>>();
|
||||
let revisions_by_id = candidate_rows.into_iter().collect::<HashMap<_, _>>();
|
||||
@@ -1555,11 +1686,12 @@ async fn run_search(
|
||||
continue;
|
||||
}
|
||||
let version = json_data.get("version").and_then(|value| value.as_i64()).unwrap_or(0);
|
||||
let json_data = remap_json_to_display_names(
|
||||
let mut json_data = remap_json_to_display_names(
|
||||
json_data,
|
||||
&physical_to_display,
|
||||
&internal_columns,
|
||||
)?;
|
||||
account_projection.apply(id, &mut json_data)?;
|
||||
let display_values = row_display_values(&json_data, &display_columns);
|
||||
content_map.insert(search_row_key(&table_name, id), (
|
||||
json_data.to_string(),
|
||||
@@ -1587,7 +1719,16 @@ async fn run_search(
|
||||
table_physical_to_display_map(pool, profile_name, &table_name).await?;
|
||||
let internal_columns =
|
||||
table_internal_column_names(pool, profile_name, &table_name).await?;
|
||||
let display_columns = table_row_display_columns(pool, profile_name, &table_name).await?;
|
||||
let account_projection = AccountPathProjection::load(
|
||||
pool,
|
||||
profile_name,
|
||||
&table_name,
|
||||
&physical_to_display,
|
||||
)
|
||||
.await?;
|
||||
let display_columns = account_projection.row_display_columns(
|
||||
&table_row_display_columns(pool, profile_name, &table_name).await?,
|
||||
);
|
||||
let table_definition_ids = archives.iter().map(|item| item.0).collect::<Vec<_>>();
|
||||
let row_ids = archives.iter().map(|item| item.1).collect::<Vec<_>>();
|
||||
let versions = archives.iter().map(|item| item.2).collect::<Vec<_>>();
|
||||
@@ -1635,11 +1776,12 @@ async fn run_search(
|
||||
row_id,
|
||||
version,
|
||||
);
|
||||
let json_data = remap_json_to_display_names(
|
||||
let mut json_data = remap_json_to_display_names(
|
||||
snapshot,
|
||||
&physical_to_display,
|
||||
&internal_columns,
|
||||
)?;
|
||||
account_projection.apply(row_id, &mut json_data)?;
|
||||
let display_values = row_display_values(&json_data, &display_columns);
|
||||
content_map.insert(row_key, (
|
||||
json_data.to_string(),
|
||||
@@ -1683,7 +1825,16 @@ async fn fetch_ordered_candidate_rows(
|
||||
) -> Result<Vec<Hit>, Status> {
|
||||
let physical_to_display = table_physical_to_display_map(pool, profile_name, table_name).await?;
|
||||
let internal_columns = table_internal_column_names(pool, profile_name, table_name).await?;
|
||||
let display_columns = table_row_display_columns(pool, profile_name, table_name).await?;
|
||||
let account_projection = AccountPathProjection::load(
|
||||
pool,
|
||||
profile_name,
|
||||
table_name,
|
||||
&physical_to_display,
|
||||
)
|
||||
.await?;
|
||||
let display_columns = account_projection.row_display_columns(
|
||||
&table_row_display_columns(pool, profile_name, table_name).await?,
|
||||
);
|
||||
let resolved_order =
|
||||
resolve_order_column(pool, profile_name, table_name, &order.column).await?;
|
||||
let qualified_table = qualified_visible_table(pool, profile_name, table_name).await?;
|
||||
@@ -1754,11 +1905,12 @@ async fn fetch_ordered_candidate_rows(
|
||||
.get("version")
|
||||
.and_then(|value| value.as_i64())
|
||||
.unwrap_or(0);
|
||||
let json_data = remap_json_to_display_names(
|
||||
let mut json_data = remap_json_to_display_names(
|
||||
json_data,
|
||||
&physical_to_display,
|
||||
&internal_columns,
|
||||
)?;
|
||||
account_projection.apply(id, &mut json_data)?;
|
||||
let display_values = row_display_values(&json_data, &display_columns);
|
||||
Ok(Hit {
|
||||
id,
|
||||
@@ -1813,6 +1965,35 @@ mod tests {
|
||||
assert_eq!(mapped, serde_json::json!({"customer": "Acme", "id": 4}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn account_projection_replaces_internal_ids_with_public_paths() {
|
||||
let projection = AccountPathProjection {
|
||||
paths: HashMap::from([(3, "600/10".to_string())]),
|
||||
account_column: Some("account".to_string()),
|
||||
accounts_table: false,
|
||||
};
|
||||
let mut value = serde_json::json!({"account": 3, "name": "posting"});
|
||||
|
||||
projection.apply(9, &mut value).unwrap();
|
||||
|
||||
assert_eq!(value, serde_json::json!({"account": "600/10", "name": "posting"}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accounts_search_exposes_and_displays_the_complete_path() {
|
||||
let projection = AccountPathProjection {
|
||||
paths: HashMap::from([(3, "600/10".to_string())]),
|
||||
account_column: None,
|
||||
accounts_table: true,
|
||||
};
|
||||
let mut value = serde_json::json!({"segment": "10", "parent_account_id": 2});
|
||||
|
||||
projection.apply(3, &mut value).unwrap();
|
||||
|
||||
assert_eq!(value["account"], "600/10");
|
||||
assert_eq!(projection.row_display_columns(&["segment".to_string()]), ["account"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn count_collector_returns_zero_documents_for_an_empty_index() {
|
||||
let schema = create_search_schema();
|
||||
|
||||
2
server
2
server
Submodule server updated: d0504ad2a9...f83ebc802a
Reference in New Issue
Block a user