From 7341898947f02b55529dd7202b7112decc2ccc80 Mon Sep 17 00:00:00 2001 From: kodinkat Date: Tue, 14 May 2024 14:47:42 +0100 Subject: [PATCH 1/3] Initial shape --- dt-metrics/records/genmap.js | 18 +++++++++-- dt-metrics/records/genmap.php | 59 ++++++++++++++++++++++++++--------- 2 files changed, 60 insertions(+), 17 deletions(-) diff --git a/dt-metrics/records/genmap.js b/dt-metrics/records/genmap.js index 9a1ea09994..c8bdc9b933 100644 --- a/dt-metrics/records/genmap.js +++ b/dt-metrics/records/genmap.js @@ -16,18 +16,27 @@ jQuery(document).ready(function ($) { chart.empty().html(`
-
+
+ + ${window.lodash.escape(translations.show_archived)} +
+ + +
+
-
+

${window.lodash.escape(translations.title)}

@@ -70,6 +79,7 @@ jQuery(document).ready(function ($) { .data('p2p_direction'), post_type: selected_post_type, gen_depth_limit: 100, + show_archived: jQuery('#archivedToggle').prop('checked'), }; // Dynamically update URL parameters. @@ -206,6 +216,10 @@ jQuery(document).ready(function ($) { jQuery(document).on('click', '#gen_tree_add_child_but', function (e) { handle_add_child(); }); + + jQuery(document).on('click', '#archivedToggle', function (e) { + window.load_genmap(); + }); } function identify_infinite_loops(data, loops) { diff --git a/dt-metrics/records/genmap.php b/dt-metrics/records/genmap.php index 451df082b3..14b8644054 100644 --- a/dt-metrics/records/genmap.php +++ b/dt-metrics/records/genmap.php @@ -56,9 +56,11 @@ public function tree( WP_REST_Request $request ) { if ( ! isset( $params['p2p_type'], $params['p2p_direction'], $params['post_type'] ) ) { return new WP_Error( __METHOD__, 'Missing parameters! [Required: p2p_type, p2p_direction, post_type ]', [ 'status' => 400 ] ); } - - $query = $this->get_query( $params['post_type'], $params['p2p_type'], $params['p2p_direction'] ); - + dt_write_log( $params ); + $query = $this->get_query( $params['post_type'], $params['p2p_type'], $params['p2p_direction'], [ + 'show_archived' => $params['show_archived'] ?? false + ] ); + dt_write_log( $query ); return $this->get_genmap( $query, $params['gen_depth_limit'] ?? 100, $params['focus_id'] ?? 0 ); } @@ -84,6 +86,7 @@ public function scripts() { 'data' => [], 'translations' => [ 'title' => __( 'Generation Map', 'disciple_tools' ), + 'show_archived' => __( 'Show Archived', 'disciple_tools' ), 'highlight_active' => __( 'Highlight Active', 'disciple_tools' ), 'highlight_churches' => __( 'Highlight Churches', 'disciple_tools' ), 'members' => __( 'Members', 'disciple_tools' ), @@ -131,7 +134,7 @@ public function scripts() { wp_enqueue_style( 'orgchart_css', $css_uri, [], filemtime( $css_dir ) ); } - public function get_query( $post_type, $p2p_type, $p2p_direction ) { + public function get_query( $post_type, $p2p_type, $p2p_direction, $filters = [] ) { global $wpdb; // p2p direction will govern overall query sql shape. @@ -147,33 +150,59 @@ public function get_query( $post_type, $p2p_type, $p2p_direction ) { $select_parent_id = 'p2p_from'; } - $query = $wpdb->get_results( $wpdb->prepare( " + // Determine archived meta values. + $post_settings = DT_Posts::get_post_settings( $post_type ); + dt_write_log( $post_settings['status_field'] ); + $primary_join_sql = ''; + $union_join_sql = ''; + if ( isset( $filters['show_archived'], $post_settings['status_field']['status_key'], $post_settings['status_field']['archived_key'] ) && $filters['show_archived'] ) { + $status_key = $post_settings['status_field']['status_key']; + $archived_key = $post_settings['status_field']['archived_key']; + + dt_write_log( $status_key ); + dt_write_log( $archived_key ); + + //$primary_join_sql = "INNER JOIN $wpdb->postmeta p_status ON( p_status.post_id = a.ID AND p_status.meta_key = '$status_key' AND p_status.meta_value = '$archived_key' )"; + $union_join_sql = "INNER JOIN $wpdb->postmeta u_status ON( u_status.post_id = p.p2p_from AND u_status.meta_key = '$status_key' AND u_status.meta_value = '$archived_key' )"; + } + + $sql = " SELECT a.ID as id, 0 as parent_id, a.post_title as name FROM $wpdb->posts as a - WHERE a.post_type = %s - AND a.ID %1s IN ( + + $primary_join_sql + + WHERE a.post_type = '$post_type' + AND a.ID $not_from IN ( SELECT DISTINCT (p2p_from) FROM $wpdb->p2p - WHERE p2p_type = %s + WHERE p2p_type = '$p2p_type' GROUP BY p2p_from ) - AND a.ID %1s IN ( + AND a.ID $not_to IN ( SELECT DISTINCT (p2p_to) FROM $wpdb->p2p - WHERE p2p_type = %s + WHERE p2p_type = '$p2p_type' GROUP BY p2p_to ) UNION SELECT - p.%1s as id, - p.%1s as parent_id, - (SELECT sub.post_title FROM $wpdb->posts as sub WHERE sub.ID = p.%1s ) as name + p.$select_id as id, + p.$select_parent_id as parent_id, + (SELECT sub.post_title FROM $wpdb->posts as sub WHERE sub.ID = p.$select_id ) as name FROM $wpdb->p2p as p - WHERE p.p2p_type = %s; - ", $post_type, $not_from, $p2p_type, $not_to, $p2p_type, $select_id, $select_parent_id, $select_id, $p2p_type ), ARRAY_A ); + + $union_join_sql + + WHERE p.p2p_type = '$p2p_type'; + "; + + // @phpcs:ignore + $query = $wpdb->get_results( $sql, ARRAY_A ); + dt_write_log( $wpdb->last_query ); return $query; } From 3986385f707ab86ffea4f7c31b21b06c80cfa9e3 Mon Sep 17 00:00:00 2001 From: kodinkat Date: Thu, 16 May 2024 12:03:28 +0100 Subject: [PATCH 2/3] Filter archived records accordingly + Update archived item node color --- dt-metrics/records/genmap.js | 41 +++++++++++ dt-metrics/records/genmap.php | 133 ++++++++++++++++++++-------------- 2 files changed, 119 insertions(+), 55 deletions(-) diff --git a/dt-metrics/records/genmap.js b/dt-metrics/records/genmap.js index c8bdc9b933..ad0b9289ef 100644 --- a/dt-metrics/records/genmap.js +++ b/dt-metrics/records/genmap.js @@ -130,6 +130,29 @@ jQuery(document).ready(function ($) { nodeContent: 'content', direction: 'l2r', nodeTemplate: nodeTemplate, + initCompleted: function (chart) { + // Identify archived items, in order to update corresponding node color. + const archived_items = identify_items_by_field_value( + response, + 'status', + 'closed', + {}, + ); + + // Tweak node colouring of identified items; which have been archived. + if (archived_items) { + for (const [id, item] of Object.entries(archived_items)) { + const node = $(chart).find(`#${id}.node`); + if (node) { + const color = '#808080'; + $(node).css('background-color', color); + $(node).find('.title').css('background-color', color); + $(node).find('.content').css('background-color', color); + $(node).find('.content').css('border', '0px'); + } + } + } + }, }); let container_height = window.innerHeight - 200; // because it is rotated @@ -222,6 +245,24 @@ jQuery(document).ready(function ($) { }); } + function identify_items_by_field_value(data, field, value, items) { + if (data?.[field] === value) { + items[data['id']] = { + id: data['id'], + name: data['name'], + status: data['status'], + }; + } + + if (data?.['children']) { + data['children'].forEach(function (item) { + items = identify_items_by_field_value(item, field, value, items); + }); + } + + return items; + } + function identify_infinite_loops(data, loops) { if (data?.['children']) { data['children'].forEach(function (item) { diff --git a/dt-metrics/records/genmap.php b/dt-metrics/records/genmap.php index 14b8644054..21e8c89c61 100644 --- a/dt-metrics/records/genmap.php +++ b/dt-metrics/records/genmap.php @@ -56,12 +56,19 @@ public function tree( WP_REST_Request $request ) { if ( ! isset( $params['p2p_type'], $params['p2p_direction'], $params['post_type'] ) ) { return new WP_Error( __METHOD__, 'Missing parameters! [Required: p2p_type, p2p_direction, post_type ]', [ 'status' => 400 ] ); } - dt_write_log( $params ); - $query = $this->get_query( $params['post_type'], $params['p2p_type'], $params['p2p_direction'], [ - 'show_archived' => $params['show_archived'] ?? false - ] ); - dt_write_log( $query ); - return $this->get_genmap( $query, $params['gen_depth_limit'] ?? 100, $params['focus_id'] ?? 0 ); + + $post_type = $params['post_type']; + $post_settings = DT_Posts::get_post_settings( $post_type ); + + $filters = [ + 'post_type' => $post_type, + 'show_archived' => $params['show_archived'] ?? false, + 'status_key' => $post_settings['status_field']['status_key'] ?? '', + 'archived_key' => $post_settings['status_field']['archived_key'] ?? '' + ]; + $query = $this->get_query( $post_type, $params['p2p_type'], $params['p2p_direction'], $filters ); + + return $this->get_genmap( $query, $params['gen_depth_limit'] ?? 100, $params['focus_id'] ?? 0, $filters ); } public function scripts() { @@ -151,63 +158,41 @@ public function get_query( $post_type, $p2p_type, $p2p_direction, $filters = [] } // Determine archived meta values. - $post_settings = DT_Posts::get_post_settings( $post_type ); - dt_write_log( $post_settings['status_field'] ); - $primary_join_sql = ''; - $union_join_sql = ''; - if ( isset( $filters['show_archived'], $post_settings['status_field']['status_key'], $post_settings['status_field']['archived_key'] ) && $filters['show_archived'] ) { - $status_key = $post_settings['status_field']['status_key']; - $archived_key = $post_settings['status_field']['archived_key']; - - dt_write_log( $status_key ); - dt_write_log( $archived_key ); - - //$primary_join_sql = "INNER JOIN $wpdb->postmeta p_status ON( p_status.post_id = a.ID AND p_status.meta_key = '$status_key' AND p_status.meta_value = '$archived_key' )"; - $union_join_sql = "INNER JOIN $wpdb->postmeta u_status ON( u_status.post_id = p.p2p_from AND u_status.meta_key = '$status_key' AND u_status.meta_value = '$archived_key' )"; - } - - $sql = " + $status_key = $filters['status_key'] ?? ''; + $query = $wpdb->get_results( $wpdb->prepare( " SELECT a.ID as id, 0 as parent_id, - a.post_title as name + a.post_title as name, + ( SELECT p_status.meta_value FROM $wpdb->postmeta as p_status WHERE ( p_status.post_id = a.ID ) AND ( p_status.meta_key = %s ) ) as status FROM $wpdb->posts as a - - $primary_join_sql - - WHERE a.post_type = '$post_type' - AND a.ID $not_from IN ( + WHERE a.post_type = %s + AND a.ID %1s IN ( SELECT DISTINCT (p2p_from) FROM $wpdb->p2p - WHERE p2p_type = '$p2p_type' + WHERE p2p_type = %s GROUP BY p2p_from ) - AND a.ID $not_to IN ( + AND a.ID %1s IN ( SELECT DISTINCT (p2p_to) FROM $wpdb->p2p - WHERE p2p_type = '$p2p_type' + WHERE p2p_type = %s GROUP BY p2p_to ) UNION SELECT - p.$select_id as id, - p.$select_parent_id as parent_id, - (SELECT sub.post_title FROM $wpdb->posts as sub WHERE sub.ID = p.$select_id ) as name + p.%1s as id, + p.%1s as parent_id, + (SELECT sub.post_title FROM $wpdb->posts as sub WHERE sub.ID = p.%1s ) as name, + ( SELECT u_status.meta_value FROM $wpdb->postmeta as u_status WHERE ( u_status.post_id = p.%1s ) AND ( u_status.meta_key = %s ) ) as status FROM $wpdb->p2p as p - - $union_join_sql - - WHERE p.p2p_type = '$p2p_type'; - "; - - // @phpcs:ignore - $query = $wpdb->get_results( $sql, ARRAY_A ); - dt_write_log( $wpdb->last_query ); + WHERE p.p2p_type = %s; + ", $status_key, $post_type, $not_from, $p2p_type, $not_to, $p2p_type, $select_id, $select_parent_id, $select_id, $select_id, $status_key, $p2p_type ), ARRAY_A ); return $query; } - public function get_genmap( $query, $depth_limit, $focus_id ) { + public function get_genmap( $query, $depth_limit, $focus_id, $filters = [] ) { if ( is_wp_error( $query ) ){ return $this->_circular_structure_error( $query ); @@ -217,7 +202,7 @@ public function get_genmap( $query, $depth_limit, $focus_id ) { } $menu_data = $this->prepare_menu_array( $query ); - return $this->build_array( $focus_id ?? 0, $menu_data, 0, $depth_limit ); + return $this->build_array( $focus_id ?? 0, $menu_data, 0, $depth_limit, $filters ); } public function prepare_menu_array( $query ) { @@ -235,7 +220,7 @@ public function prepare_menu_array( $query ) { return $menu_data; } - public function build_array( $parent_id, $menu_data, $gen, $depth_limit ) { + public function build_array( $parent_id, $menu_data, $gen, $depth_limit, $filters = [] ) { $children = []; if ( isset( $menu_data['parents'][$parent_id] ) && ( $gen < $depth_limit ) ) { @@ -243,34 +228,72 @@ public function build_array( $parent_id, $menu_data, $gen, $depth_limit ) { foreach ( $menu_data['parents'][$parent_id] as $item_id ) { - $children[] = $this->build_array( $item_id, $menu_data, $next_gen, $depth_limit ); + $children[] = $this->build_array( $item_id, $menu_data, $next_gen, $depth_limit, $filters ); } } + $array = [ 'id' => $parent_id, 'name' => $menu_data['items'][ $parent_id ]['name'] ?? 'SYSTEM', - 'content' => 'Gen ' . $gen, - 'children' => $children, - 'has_infinite_loop' => $this->has_infinite_loop( $parent_id, $children ) + 'status' => $menu_data['items'][ $parent_id ]['status'] ?? '', + 'content' => 'Gen ' . $gen ]; + // Determine if archived records are to be excluded. + if ( !$filters['show_archived'] ) { + + // Recursively exclude associated children. + $children = $this->exclude_archived_children( $children, $filters['archived_key'] ); + + // Only capture node, if active children are still detected; otherwise return empty array. + if ( !empty( $children ) ) { + $array['children'] = $children; + $array['has_infinite_loop'] = $this->has_infinite_loop( $parent_id, $children ); + } else { + $array['children'] = []; + $array['has_infinite_loop'] = false; + } + } else { + $array['children'] = $children; + $array['has_infinite_loop'] = $this->has_infinite_loop( $parent_id, $children ); + } + return $array; } public function has_infinite_loop( $parent_id, $children ): bool { foreach ( $children ?? [] as $child ) { - if ( $parent_id === $child['id'] ) { - return true; - } - if ( !empty( $child['children'] ) ) { - if ( $this->has_infinite_loop( $parent_id, $child['children'] ) ) { + if ( isset( $child['id'], $child['children'] ) ) { + if ( $parent_id === $child['id'] ){ return true; } + if ( !empty( $child['children'] ) ){ + if ( $this->has_infinite_loop( $parent_id, $child['children'] ) ){ + return true; + } + } } } return false; } + + public function exclude_archived_children( $children, $archived_key ): array { + $updated_children = []; + foreach ( $children ?? [] as $child ) { + if ( isset( $child['status'] ) && $child['status'] == $archived_key ) { + $child['children'] = $this->exclude_archived_children( $child['children'], $archived_key ); + + if ( !empty( $child['children'] ) ) { + $updated_children[] = $child; + } + } else { + $updated_children[] = $child; + } + } + + return $updated_children; + } } new DT_Metrics_Groups_Genmap(); From bc96c0453c0dfa92dea12494055d090622461e00 Mon Sep 17 00:00:00 2001 From: kodinkat Date: Fri, 17 May 2024 14:13:05 +0100 Subject: [PATCH 3/3] Support archived records across all record types --- dt-metrics/records/genmap.js | 44 ++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/dt-metrics/records/genmap.js b/dt-metrics/records/genmap.js index ad0b9289ef..b455c0f989 100644 --- a/dt-metrics/records/genmap.js +++ b/dt-metrics/records/genmap.js @@ -131,24 +131,34 @@ jQuery(document).ready(function ($) { direction: 'l2r', nodeTemplate: nodeTemplate, initCompleted: function (chart) { + const post_types = window.dtMetricsProject.post_types; + // Identify archived items, in order to update corresponding node color. - const archived_items = identify_items_by_field_value( - response, - 'status', - 'closed', - {}, - ); - - // Tweak node colouring of identified items; which have been archived. - if (archived_items) { - for (const [id, item] of Object.entries(archived_items)) { - const node = $(chart).find(`#${id}.node`); - if (node) { - const color = '#808080'; - $(node).css('background-color', color); - $(node).find('.title').css('background-color', color); - $(node).find('.content').css('background-color', color); - $(node).find('.content').css('border', '0px'); + if ( + post_types && + post_types[selected_post_type] && + post_types[selected_post_type]?.status_field?.archived_key + ) { + const archived_items = identify_items_by_field_value( + response, + 'status', + post_types[selected_post_type]['status_field'][ + 'archived_key' + ], + {}, + ); + + // Tweak node colouring of identified items; which have been archived. + if (archived_items) { + for (const [id, item] of Object.entries(archived_items)) { + const node = $(chart).find(`#${id}.node`); + if (node) { + const color = '#808080'; + $(node).css('background-color', color); + $(node).find('.title').css('background-color', color); + $(node).find('.content').css('background-color', color); + $(node).find('.content').css('border', '0px'); + } } } }