Skip to content

Commit

Permalink
* Updated react-vmessagebox. Relevant changes:
Browse files Browse the repository at this point in the history
** MS dialog titles don't change the sizing of the dialog. (changed them to use absolute positioning, and cut off overflow)
* Subtree-deletion function in the "subtree operations" dialog is now functional! Closes #319.

Note: This implementation is fairly "naive". For example, it doesn't handle the case where a node is linked to a map only through the tree in question, but the tree includes a link to it twice. In that case, the code needs to unlink it the first time it sees it, and delete it the second time it sees it; but instead, the current implementation will simply error if this case is hit. (one of the "unlink node" commands will fail due to it being the last place the child is linked from)

I don't want to take the time to add handling for those more complex cases right now. For now, the mass-deleter is intended just for "clean" map states, where all the nodes are either "single parent", or multi-parent but with at least one link outside of the current tree being deleted.
  • Loading branch information
Venryx committed May 24, 2024
1 parent 24577f2 commit 76b91c5
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 87 deletions.
6 changes: 6 additions & 0 deletions Docs/MigrationNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@

## Main series

### Pushed on 2024-05-23

* 1\) Added a `deleteSubtree` graphql endpoint (plus `getPreparedDataForDeletingSubtree` endpoint), and a sql function to back it.
* DB response:
* 1\) Execute the sql for the function `descendants_with_ancestry_attributes`, seen in `GraphTraversal.sql`.

### Pushed on 2024-02-20

* 1\) Removed the `Source.claimMinerId` field.
Expand Down
88 changes: 48 additions & 40 deletions Packages/app-server/src/db/general/subtree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use rust_shared::tokio_postgres::Row;
use rust_shared::utils::general_::extensions::VecLenU32;
use rust_shared::utils::type_aliases::JSONValue;
use rust_shared::{serde, to_sub_err, GQLError, SubError};
use tracing::warn;
use std::collections::HashSet;
use std::path::Path;
use std::rc::Rc;
Expand All @@ -36,6 +37,7 @@ use crate::db::node_tags::NodeTag;
use crate::db::nodes_::_node::Node;
use crate::db::terms::Term;
use crate::db::users::User;
use crate::utils::db::agql_ext::gql_utils::IndexMapAGQL;
use crate::utils::db::filter::{QueryFilter, FilterInput};
use crate::utils::db::pg_row_to_json::postgres_row_to_struct;
use crate::utils::db::sql_fragment::SQLFragment;
Expand Down Expand Up @@ -196,8 +198,8 @@ impl QueryShard_General_Subtree {
// subscriptions
// ==========

#[derive(Default)] pub struct SubscriptionShard_DeleteSubtree;
#[Subscription] impl SubscriptionShard_DeleteSubtree {
#[derive(Default)] pub struct SubscriptionShared_General_Subtree;
#[Subscription] impl SubscriptionShared_General_Subtree {
async fn delete_subtree<'a>(&self, gql_ctx: &'a async_graphql::Context<'a>, input: DeleteSubtreeInput) -> impl Stream<Item = Result<DeleteSubtreeResult, SubError>> + 'a {
async_stream::stream! {
let mut anchor = DataAnchorFor1::empty(); // holds pg-client
Expand Down Expand Up @@ -230,18 +232,19 @@ impl QueryShard_General_Subtree {
struct PreparedDataForDeletingSubtree {
// commented; including this in the result structure requires SimpleObject on CommandEntry, *and all downstream input structs*
//subcommands: Vec<CommandEntry>,
subcommand_count: u32,
nodes_to_unlink_ids: Vec<String>,
nodes_to_delete_ids: Vec<String>,
nodes_to_delete_access_policies: Vec<String>,
nodes_to_delete_creator_ids: Vec<String>,
nodes_to_delete_access_policies: IndexMapAGQL<String, u32>,
nodes_to_delete_creator_ids: IndexMapAGQL<String, u32>,
nodes_to_delete_creation_times: Vec<i64>,
}

#[derive(InputObject, Deserialize, Serialize, Clone)]
pub struct DeleteSubtreeInput {
pub map_id: Option<String>,
pub root_node_id: String,
pub max_depth: u32,
pub max_depth: i32,
}

#[derive(SimpleObject, Debug, Serialize)]
Expand All @@ -257,50 +260,53 @@ async fn get_prepared_data_for_deleting_subtree(ctx: &AccessorContext<'_>, _acto
let DeleteSubtreeInput { map_id: _mapID, root_node_id: rootNodeID, max_depth: maxDepth } = input;

let mut subcommands = Vec::<CommandEntry>::new();
let mut nodes_to_unlink_ids = IndexSet::<String>::new();
let mut links_to_unlink = IndexSet::<String>::new();
// collections that'll be inserted into the prepared-data structure
let mut nodes_to_unlink_ids = IndexSet::<String>::new();
let mut nodes_to_delete_ids = IndexSet::<String>::new();
let mut nodes_to_delete_access_policies = IndexSet::<String>::new();
let mut nodes_to_delete_creator_ids = IndexSet::<String>::new();
let mut nodes_to_delete_creation_times = IndexSet::<i64>::new();
let mut nodes_to_delete_access_policies = IndexMap::<String, u32>::new();
let mut nodes_to_delete_creator_ids = IndexMap::<String, u32>::new();
let mut nodes_to_delete_creation_times = Vec::<i64>::new(); // vec used; if someone nodes have identical creation-times, add each entry separately

// version which uses a join
let mut rows: Vec<Row> = ctx.tx.query_raw(r#"
SELECT
d.id, d.link_id, d.distance, n."accessPolicy",
n.creator, n."createdAt",
l.id, l.parent
d.id, d.link_id, d.distance, d.single_parent_ancestry,
n."accessPolicy", n.creator, n."createdAt"
FROM
descendants2($1, $2) d
JOIN nodes n ON (d.id = n.id)
JOIN "nodeLinks" l ON (d.id = l.child)
"#, params(&[&rootNodeID, &maxDepth])).await?.try_collect().await?;
descendants_with_ancestry_attributes($1, $2, $3) d
LEFT JOIN nodes n ON (d.id = n.id)
"#, params(&[&rootNodeID, &maxDepth, &false])).await?.try_collect().await?;
// reverse the results, so we get the deepest nodes first
rows.reverse();

let rows_by_node_id: IndexMap<String, Vec<Row>> = rows.into_iter().group_by(|row| row.get(0)).into_iter().map(|(k, g)| (k, g.collect_vec())).collect();

for (node_id, rows) in rows_by_node_id {
let row_count = rows.len();
// each row represents a node-link for which the node is the child
for row in rows {
//let node_id: String = row.get(0);
//let link_id: Option<String> = row.get(1);
//let distance: i32 = row.get(2);
let access_policy: String = row.get(3);
let creator_id: String = row.get(4);
let creation_time: i64 = row.get(5);
let link_id: String = row.get(6);
//let parent_id: Option<String> = row.get(7);

if row_count > 1 {
//let rows_by_node_id: IndexMap<String, Vec<Row>> = rows.into_iter().group_by(|row| row.get(0)).into_iter().map(|(k, g)| (k, g.collect_vec())).collect();
//for (node_id, rows) in rows_by_node_id {

// each row represents a discovered node-link within the subtree being deleted
for row in rows {
let node_id: String = row.get(0);
let link_id = row.get(1);
//let distance: i32 = row.get(2);
let single_parent_ancestry: bool = row.get(3);
let access_policy: String = row.get(4);
let creator_id: String = row.get(5);
let creation_time: i64 = row.get(6);

// if this link is the only place the child exists, mark the child for deletion
if single_parent_ancestry {
nodes_to_delete_ids.insert(node_id.to_string());
*nodes_to_delete_access_policies.entry(access_policy).or_insert(0) += 1;
*nodes_to_delete_creator_ids.entry(creator_id).or_insert(0) += 1;
nodes_to_delete_creation_times.push(creation_time);
}
// else, mark the child for mere unlinking
else {
if let Some(link_id) = link_id {
nodes_to_unlink_ids.insert(node_id.to_string());
links_to_unlink.insert(link_id);
} else {
nodes_to_delete_ids.insert(node_id.to_string());
nodes_to_delete_access_policies.insert(access_policy);
nodes_to_delete_creator_ids.insert(creator_id);
nodes_to_delete_creation_times.insert(creation_time);
// if link_id is null, then this was the subtree's root-node; the fact that we're here means it had more than one parent/parent-link, which is not ideal so log a warning
warn!("Delete-subtree was called on a tree whose root-node ({}) had more than one parent. Deletion of subtree root was skipped.", node_id);
}
}
}
Expand All @@ -321,14 +327,16 @@ async fn get_prepared_data_for_deleting_subtree(ctx: &AccessorContext<'_>, _acto
subcommands.push(command);
}

let subcommand_count = subcommands.len_u32();
Ok((
subcommands,
PreparedDataForDeletingSubtree {
subcommand_count,
nodes_to_unlink_ids: nodes_to_unlink_ids.into_iter().collect(),
nodes_to_delete_ids: nodes_to_delete_ids.into_iter().collect(),
nodes_to_delete_access_policies: nodes_to_delete_access_policies.into_iter().collect(),
nodes_to_delete_creator_ids: nodes_to_delete_creator_ids.into_iter().collect(),
nodes_to_delete_creation_times: nodes_to_delete_creation_times.into_iter().collect(),
nodes_to_delete_access_policies: IndexMapAGQL(nodes_to_delete_access_policies),
nodes_to_delete_creator_ids: IndexMapAGQL(nodes_to_delete_creator_ids),
nodes_to_delete_creation_times: nodes_to_delete_creation_times,
}
))
}
4 changes: 2 additions & 2 deletions Packages/app-server/src/gql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ use crate::db::commands::update_user_hidden::MutationShard_UpdateUserHidden;
use crate::db::general::backups::QueryShard_General_Backups;
use crate::db::general::search::QueryShard_General_Search;
use crate::db::general::sign_in::SubscriptionShard_SignIn;
use crate::db::general::subtree::{QueryShard_General_Subtree, MutationShard_General_Subtree};
use crate::db::general::subtree::{MutationShard_General_Subtree, QueryShard_General_Subtree, SubscriptionShared_General_Subtree};
use crate::db::general::subtree_old::QueryShard_General_Subtree_Old;
use crate::db::general::trusted_operators::QueryShard_General_TrustedOperators;
use crate::db::timeline_steps::{SubscriptionShard_TimelineStep, QueryShard_TimelineStep};
Expand Down Expand Up @@ -171,7 +171,7 @@ pub struct MutationRoot(

#[derive(MergedSubscription, Default)]
pub struct SubscriptionRoot(
SubscriptionShard_CloneMapSpecial, SubscriptionShard_General, SubscriptionShard_SignIn,
SubscriptionShard_CloneMapSpecial, SubscriptionShard_General, SubscriptionShared_General_Subtree, SubscriptionShard_SignIn,
// table-specific
SubscriptionShard_User, SubscriptionShard_UserHidden,
SubscriptionShard_GlobalData,
Expand Down
2 changes: 1 addition & 1 deletion Packages/app-server/src/utils/db/agql_ext/gql_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub fn get_root_fields_in_doc(doc: ExecutableDocument) -> Vec<String> {
//wrap_slow_macros!{

#[derive(Serialize, Deserialize, Clone)]
pub struct IndexMapAGQL<K: std::hash::Hash + Eq, V>(IndexMap<K, V>);
pub struct IndexMapAGQL<K: std::hash::Hash + Eq, V>(pub IndexMap<K, V>);

// makes-so you can call functions on IndexMapAGQL as though it were an IndexMap (ie. without having to do .0)
impl<K: std::hash::Hash + Eq, V> std::ops::Deref for IndexMapAGQL<K, V> {
Expand Down
Loading

0 comments on commit 76b91c5

Please sign in to comment.