diff --git a/include/fastdds_monitor/Controller.h b/include/fastdds_monitor/Controller.h index 5d3d680e..a6817cc1 100644 --- a/include/fastdds_monitor/Controller.h +++ b/include/fastdds_monitor/Controller.h @@ -66,6 +66,9 @@ class Controller : public QObject { std::map errors; std::map warnings; + // Represents the subset of errors that a entity shares with other entities, and are deleted when the entity becomes inactive + // Fist key is the entity id, second key is the remote entity guid and value is the number of errors shared + std::map> shared_errors; int32_t total_errors = 0; int32_t total_warnings = 0; } diff --git a/include/fastdds_monitor/backend/SyncBackendConnection.h b/include/fastdds_monitor/backend/SyncBackendConnection.h index a7536e67..fceb3dc0 100644 --- a/include/fastdds_monitor/backend/SyncBackendConnection.h +++ b/include/fastdds_monitor/backend/SyncBackendConnection.h @@ -132,6 +132,10 @@ class SyncBackendConnection std::string get_data_type_name( backend::EntityId id); + //! Get the guid associated to a given entity from the Backend by calling \c get_info + std::string get_guid( + backend::EntityId id); + //! Get the status level of an entity from the Backend by calling \c get_status StatusLevel get_status( backend::EntityId id); @@ -199,6 +203,14 @@ class SyncBackendConnection EntityId source_entity_id, SampleLostSample& sample); + bool get_status_data( + EntityId source_entity_id, + ExtendedIncompatibleQosSample& sample); + + //! Convert a given entity guid to string format + std::string get_deserialized_guid( + const backend::GUID_s& data); + //! Get info from an entity from the Backend std::vector get_entities( EntityKind entity_type, diff --git a/include/fastdds_monitor/backend/backend_types.h b/include/fastdds_monitor/backend/backend_types.h index 6f689b70..13a256d7 100644 --- a/include/fastdds_monitor/backend/backend_types.h +++ b/include/fastdds_monitor/backend/backend_types.h @@ -28,6 +28,7 @@ #include #include #include +#include namespace backend { @@ -40,6 +41,7 @@ using StatusLevel = eprosima::statistics_backend::StatusLevel; using StatisticKind = eprosima::statistics_backend::StatisticKind; using EntityInfo = eprosima::statistics_backend::Info; using Timestamp = eprosima::statistics_backend::Timestamp; +using GUID_s = eprosima::fastdds::statistics::detail::GUID_s; // Status types from backend using ConnectionListSample = eprosima::statistics_backend::ConnectionListSample; @@ -50,6 +52,8 @@ using LivelinessChangedSample = eprosima::statistics_backend::LivelinessChangedS using LivelinessLostSample = eprosima::statistics_backend::LivelinessLostSample; using ProxySample = eprosima::statistics_backend::ProxySample; using SampleLostSample = eprosima::statistics_backend::SampleLostSample; +using ExtendedIncompatibleQoSStatusSeq = eprosima::fastdds::statistics::ExtendedIncompatibleQoSStatusSeq_s; +using ExtendedIncompatibleQoSStatus = eprosima::fastdds::statistics::ExtendedIncompatibleQoSStatus_s; //using StatusesSizeSample = eprosima::statistics_backend::StatusesSizeSample; //! Reference the ID_ALL in the project diff --git a/include/fastdds_monitor/backend/backend_utils.h b/include/fastdds_monitor/backend/backend_utils.h index 5418208d..f736c2fe 100644 --- a/include/fastdds_monitor/backend/backend_utils.h +++ b/include/fastdds_monitor/backend/backend_utils.h @@ -124,6 +124,9 @@ std::string entity_status_description( std::string policy_documentation_description( const uint32_t& id); +std::string guid_s_to_string( + const backend::GUID_s& guid); + } //namespace backend #endif // _EPROSIMA_FASTDDS_MONITOR_BACKEND_BACKENDUTILS_H diff --git a/include/fastdds_monitor/model/tree/StatusTreeItem.h b/include/fastdds_monitor/model/tree/StatusTreeItem.h index ab795e43..7fd8467c 100644 --- a/include/fastdds_monitor/model/tree/StatusTreeItem.h +++ b/include/fastdds_monitor/model/tree/StatusTreeItem.h @@ -43,6 +43,7 @@ #define _EPROSIMA_FASTDDS_MONITOR_MODEL_TREE_StatusTreeItem_H #include +#include #include namespace models { @@ -54,8 +55,10 @@ namespace models { * Parenting and deletion are dealt from the StatusTreeModel. Deleting a StatusTreeItem * will call the delete for each child node. */ -class StatusTreeItem +class StatusTreeItem : public QObject { + Q_OBJECT + friend class StatusTreeModel; public: @@ -72,7 +75,8 @@ class StatusTreeItem const backend::EntityId& id, const std::string& name, const backend::StatusLevel& status_level, - const std::string& description); + const std::string& description, + const std::string& guid); //! Create an item with the status parameters explicit StatusTreeItem( @@ -83,6 +87,17 @@ class StatusTreeItem const std::string& value, const std::string& description); + //! Create an item with the status parameters and guid and configures whether the item should be deleted if it has no children + explicit StatusTreeItem( + const backend::EntityId& id, + const backend::StatusKind& kind, + const std::string& name, + const backend::StatusLevel& status_level, + const std::string& value, + const std::string& description, + const std::string& guid, + bool delete_if_no_children); + //! Destroy the item. It will destroy every child. ~StatusTreeItem(); @@ -107,6 +122,12 @@ class StatusTreeItem //! Return the number of children nodes. int childCount() const; + //! Return the number of descendant nodes + int descendantCount() const; + + //! Return the number of leaf nodes of the subtree rooted at this node + int leafCount() const; + int row() const; //! Return true if the node is a leaf node (no children). @@ -129,10 +150,25 @@ class StatusTreeItem std::string name_str(); + std::string guid_str(); + std::string value_str(); std::string description_str(); + //! Getter for delete_if_no_children_ attribute + bool get_delete_if_no_children(); + + //! Setter for delete_if_no_children_ attribute + void set_delete_if_no_children( + bool delete_if_no_children); + + //! Check if it has children, and if not, delete it if configured to do so + void delete_if_no_children(); + + //! Remove item from tree and delete it + void remove(); + //! Increases the issues counter of a top level entity item int recalculate_entity_counter(); @@ -156,10 +192,12 @@ class StatusTreeItem backend::EntityId id_; backend::StatusKind kind_; std::string name_; + std::string guid_; backend::StatusLevel status_level_; std::string value_; std::string description_; bool is_active_; + bool delete_if_no_children_; QVariant id_variant_; QVariant kind_variant_; QVariant name_variant_; @@ -167,6 +205,24 @@ class StatusTreeItem QVariant value_variant_; QVariant description_variant_; QVariant is_active_variant_; + + /////////////////////// + // Signals and slots // + /////////////////////// + +public slots: + + void onItemRemoved( + std::string guid); + +signals: + + // Notify when the node is removed from the tree + // NOTE: Currently, the signal is only used to communicate item changes between top-level items and leaf items to update the Tree View when an endpoint becomes inactive. + // Signal-slot connections between different types of nodes could lead to unexpected behaviors. + void itemRemoved( + std::string guid); + }; } // namespace models diff --git a/include/fastdds_monitor/model/tree/StatusTreeModel.h b/include/fastdds_monitor/model/tree/StatusTreeModel.h index 64a00ffd..2e47e9a8 100644 --- a/include/fastdds_monitor/model/tree/StatusTreeModel.h +++ b/include/fastdds_monitor/model/tree/StatusTreeModel.h @@ -175,11 +175,15 @@ class StatusTreeModel : public QAbstractItemModel const backend::EntityId& id, const std::string& data, const backend::StatusLevel& status, - const std::string& description); + const std::string& description, + const std::string& guid); void set_source_model( StatusTreeModel* source_model); + //! Disconnect every item connected to a StatusTreeModel signal + void disconnect_all_item_signals(); + /*! * Filters the model if it is defined as proxy */ @@ -205,6 +209,23 @@ class StatusTreeModel : public QAbstractItemModel bool is_empty_; backend::EntityId current_filter_; + + /////////////////////// + // Signals and Slots // + /////////////////////// + +public slots: + + void onItemRemoved( + std::string guid); + +signals: + + // Notify when the node is removed from the tree + // NOTE: Currently, the signal is only used to communicate item changes between top-level items and leaf items to update the Status Tree View when an endpoint becomes inactive. + // Signal-slot connections between different types of nodes could lead to unexpected behaviors. + void itemRemoved( + std::string guid); }; } // namespace models diff --git a/src/Engine.cpp b/src/Engine.cpp index 6434f6f8..6a9f0b37 100644 --- a/src/Engine.cpp +++ b/src/Engine.cpp @@ -18,6 +18,8 @@ #include #include +#include + #include #include #include @@ -1009,13 +1011,15 @@ bool Engine::update_entity_status( if (id == backend::ID_ALL) { auto empty_item = new models::StatusTreeItem(backend::ID_ALL, - std::string("No issues found"), backend::StatusLevel::OK_STATUS, std::string("")); + std::string("No issues found"), backend::StatusLevel::OK_STATUS, std::string(""), std::string( + "")); entity_status_model_->addTopLevelItem(empty_item); } else { backend::StatusLevel new_status = backend::StatusLevel::OK_STATUS; std::string description = backend::entity_status_description(kind); + std::string entity_guid = backend_connection_.get_guid(id); switch (kind) { @@ -1028,7 +1032,7 @@ bool Engine::update_entity_status( { backend::StatusLevel entity_status = backend_connection_.get_status(id); auto entity_item = entity_status_model_->getTopLevelItem( - id, backend_connection_.get_name(id), entity_status, description); + id, backend_connection_.get_name(id), entity_status, description, entity_guid); new_status = sample.status; std::string handle_string; auto deadline_missed_item = new models::StatusTreeItem(id, kind, std::string("Deadline missed"), @@ -1051,45 +1055,6 @@ bool Engine::update_entity_status( } break; } - case backend::StatusKind::INCOMPATIBLE_QOS: - { - backend::IncompatibleQosSample sample; - if (backend_connection_.get_status_data(id, sample)) - { - if (sample.status != backend::StatusLevel::OK_STATUS) - { - std::string fastdds_version = "v3.1.0"; - backend::StatusLevel entity_status = backend_connection_.get_status(id); - auto entity_item = entity_status_model_->getTopLevelItem( - id, backend_connection_.get_name(id), entity_status, description); - new_status = sample.status; - auto incompatible_qos_item = new models::StatusTreeItem(id, kind, std::string( - "Incompatible QoS"), - sample.status, std::string(""), description); - for (eprosima::fastdds::statistics::QosPolicyCount_s policy : - sample.incompatible_qos_status.policies()) - { - if (policy.count() > 0) - { - auto policy_item = new models::StatusTreeItem(id, kind, - std::string(backend::policy_id_to_string(policy.policy_id()) + ":"), - sample.status, std::to_string(policy.count()), - std::string( - "Check for compatible rules ") + - std::string( - "here")); - entity_status_model_->addItem(incompatible_qos_item, policy_item); - } - } - entity_status_model_->addItem(entity_item, incompatible_qos_item); - counter = entity_item->recalculate_entity_counter(); - } - } - break; - } case backend::StatusKind::INCONSISTENT_TOPIC: { backend::InconsistentTopicSample sample; @@ -1099,7 +1064,7 @@ bool Engine::update_entity_status( { backend::StatusLevel entity_status = backend_connection_.get_status(id); auto entity_item = entity_status_model_->getTopLevelItem( - id, backend_connection_.get_name(id), entity_status, description); + id, backend_connection_.get_name(id), entity_status, description, entity_guid); new_status = sample.status; auto inconsistent_topic_item = new models::StatusTreeItem(id, kind, std::string("Inconsistent topics:"), @@ -1120,7 +1085,7 @@ bool Engine::update_entity_status( { backend::StatusLevel entity_status = backend_connection_.get_status(id); auto entity_item = entity_status_model_->getTopLevelItem( - id, backend_connection_.get_name(id), entity_status, description); + id, backend_connection_.get_name(id), entity_status, description, entity_guid); new_status = sample.status; auto liveliness_changed_item = new models::StatusTreeItem(id, kind, std::string("Liveliness changed"), @@ -1159,7 +1124,7 @@ bool Engine::update_entity_status( { backend::StatusLevel entity_status = backend_connection_.get_status(id); auto entity_item = entity_status_model_->getTopLevelItem( - id, backend_connection_.get_name(id), entity_status, description); + id, backend_connection_.get_name(id), entity_status, description, entity_guid); new_status = sample.status; auto liveliness_lost_item = new models::StatusTreeItem(id, kind, std::string( "Liveliness lost:"), @@ -1180,7 +1145,7 @@ bool Engine::update_entity_status( { backend::StatusLevel entity_status = backend_connection_.get_status(id); auto entity_item = entity_status_model_->getTopLevelItem( - id, backend_connection_.get_name(id), entity_status, description); + id, backend_connection_.get_name(id), entity_status, description, entity_guid); new_status = sample.status; auto samples_lost_item = new models::StatusTreeItem(id, kind, std::string("Samples lost:"), sample.status, std::to_string( @@ -1191,9 +1156,65 @@ bool Engine::update_entity_status( } break; } + case backend::StatusKind::EXTENDED_INCOMPATIBLE_QOS: + { + backend::ExtendedIncompatibleQosSample sample; + if (backend_connection_.get_status_data(id, sample)) + { + if (sample.status != backend::StatusLevel::OK_STATUS) + { + std::string fastdds_version = "v3.1.0"; + backend::StatusLevel entity_status = backend_connection_.get_status(id); + auto entity_item = entity_status_model_->getTopLevelItem( + id, backend_connection_.get_name(id), entity_status, description, entity_guid); + new_status = sample.status; + auto incompatible_qos_item = new models::StatusTreeItem(id, kind, std::string( + "Extended Incompatible QoS"), + sample.status, std::string(""), description, "", true); + + backend::ExtendedIncompatibleQoSStatusSeq status_seq = sample.extended_incompatible_qos_status; + + for (auto const& status : status_seq) + { + std::string remote_entity_guid = backend_connection_.get_deserialized_guid( + status.remote_guid()); + controller_->status_counters.shared_errors[id][remote_entity_guid] = 0; + for (const uint32_t policy_id : status.current_incompatible_policies()) + { + auto policy_item = new models::StatusTreeItem(id, kind, + std::string(backend::policy_id_to_string(policy_id) + ":"), + sample.status, "", + std::string( + "Check for compatible rules ") + + std::string( + "here"), + "", true); + auto remote_entity_item = new models::StatusTreeItem(id, kind, + std::string("Remote entity: " + remote_entity_guid), + sample.status, std::string(""), std::string( + ""), remote_entity_guid, false); + entity_status_model_->addItem(incompatible_qos_item, policy_item); + entity_status_model_->addItem(policy_item, remote_entity_item); + controller_->status_counters.shared_errors[id][remote_entity_guid] += 1; + } + } + + entity_status_model_->addItem(entity_item, incompatible_qos_item); + counter = entity_item->recalculate_entity_counter(); + } + } + break; + } case backend::StatusKind::CONNECTION_LIST: case backend::StatusKind::PROXY: + // Errors caused by incompatible QoS are counted twice (once for incompatible QoS and once for extended incompatible QoS). + // Incompatible QoS error counts must be ignored here to refresh counters correctly in case an entity causing an error is deleted, + // because the count attribute in IncompatibleQosSample will not be updated in that case. + case backend::StatusKind::INCOMPATIBLE_QOS: //case backend::StatusKind::STATUSES_SIZE: default: { @@ -1203,6 +1224,7 @@ bool Engine::update_entity_status( } if (new_status != backend::StatusLevel::OK_STATUS) { + // Update entity errors and warnings counters if (new_status == backend::StatusLevel::ERROR_STATUS) { std::map::iterator it = controller_->status_counters.errors.find(id); @@ -1260,14 +1282,14 @@ bool Engine::remove_inactive_entities_from_status_model( { // remove item from tree entity_status_model_->removeItem(entity_status_model_->getTopLevelItem(id, "", - backend::StatusLevel::OK_STATUS, "")); + backend::StatusLevel::OK_STATUS, "", entity_info["guid"])); // add empty item if removed last item if (entity_status_model_->rowCount(entity_status_model_->rootIndex()) == 0) { entity_status_model_->addTopLevelItem(new models::StatusTreeItem( backend::ID_ALL, std::string("No issues found"), backend::StatusLevel::OK_STATUS, - std::string(""))); + std::string(""), std::string(""))); } // update error counter @@ -1276,13 +1298,27 @@ bool Engine::remove_inactive_entities_from_status_model( { //element found; controller_->status_counters.total_errors -= err_it->second; - if (controller_->status_counters.total_errors < 0) + } + + controller_->status_counters.errors.erase(id); + + // Check if entity has associated errors in other entities + for (auto& sh_error_map : controller_->status_counters.shared_errors) + { + if (sh_error_map.second.find(entity_info["guid"]) != sh_error_map.second.end()) { - controller_->status_counters.total_errors = 0; + controller_->status_counters.total_errors -= sh_error_map.second[entity_info["guid"]]; + controller_->status_counters.errors[sh_error_map.first] -= sh_error_map.second[entity_info["guid"]]; + sh_error_map.second.erase(entity_info["guid"]); } } - controller_->status_counters.errors.erase(id); + controller_->status_counters.shared_errors.erase(id); + + if (controller_->status_counters.total_errors < 0) + { + controller_->status_counters.total_errors = 0; + } // update warning counter std::map::iterator warn_it = controller_->status_counters.warnings.find(id); if (warn_it != controller_->status_counters.warnings.end()) diff --git a/src/backend/SyncBackendConnection.cpp b/src/backend/SyncBackendConnection.cpp index 01fd373e..1d5b32d1 100644 --- a/src/backend/SyncBackendConnection.cpp +++ b/src/backend/SyncBackendConnection.cpp @@ -643,6 +643,12 @@ std::string SyncBackendConnection::get_data_type_name( return backend::get_info_value(get_info(id), "data_type"); } +std::string SyncBackendConnection::get_guid( + backend::EntityId id) +{ + return backend::get_info_value(get_info(id), "guid"); +} + StatusLevel SyncBackendConnection:: get_status( EntityId id) { @@ -908,6 +914,32 @@ bool SyncBackendConnection::get_status_data( return false; } +bool SyncBackendConnection::get_status_data( + EntityId id, + ExtendedIncompatibleQosSample& sample) +{ + try + { + StatisticsBackend::get_status_data(id, sample); + return true; + } + catch (const Error& e) + { + qWarning() << "Error retrieving sample: " << e.what(); + } + catch (const BadParameter& e) + { + qWarning() << "Bad Parameter retrieving sample " << e.what(); + } + return false; +} + +std::string SyncBackendConnection::get_deserialized_guid( + const backend::GUID_s& data) +{ + return StatisticsBackend::deserialize_guid(data); +} + bool SyncBackendConnection::build_source_target_entities_vectors( DataKind data_kind, EntityId source_entity_id, @@ -1453,7 +1485,9 @@ bool SyncBackendConnection::update_one_entity_in_model_( // If it didnt exist yet something has gone wrong in backend if (!new_entity) { + #ifdef QT_DEBUG qWarning() << "Trying to update an entity that did not exist"; + #endif // ifdef QT_DEBUG } // Get alive status and metatraffic attribute of entity. diff --git a/src/backend/backend_utils.cpp b/src/backend/backend_utils.cpp index 9aef3b8c..63e88d50 100644 --- a/src/backend/backend_utils.cpp +++ b/src/backend/backend_utils.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include @@ -147,6 +148,8 @@ std::string status_kind_to_string( return "SAMPLE_LOST"; case StatusKind::STATUSES_SIZE: return "STATUSES_SIZE"; + case StatusKind::EXTENDED_INCOMPATIBLE_QOS: + return "EXTENDED_INCOMPATIBLE_QOS"; case StatusKind::INVALID: default: return "INVALID"; @@ -517,6 +520,8 @@ std::string entity_status_description( return "Collection of Parameters describing the Proxy Data of the entity"; case backend::StatusKind::SAMPLE_LOST: return "Tracks the number of times that the entity lost samples"; + case backend::StatusKind::EXTENDED_INCOMPATIBLE_QOS: + return "Tracks the current incompatible QoS policies of the entity with another remote entity"; //case backend::StatusKind::STATUSES_SIZE: // return ""; default: diff --git a/src/model/tree/StatusTreeItem.cpp b/src/model/tree/StatusTreeItem.cpp index d398c593..fe611744 100644 --- a/src/model/tree/StatusTreeItem.cpp +++ b/src/model/tree/StatusTreeItem.cpp @@ -53,10 +53,12 @@ StatusTreeItem::StatusTreeItem() , id_(backend::ID_ALL) , kind_(backend::StatusKind::INVALID) , name_() + , guid_() , status_level_(backend::StatusLevel::OK_STATUS) , value_() , description_() , is_active_(true) + , delete_if_no_children_(false) , id_variant_(QVariant(backend::backend_id_to_models_id(id_))) , kind_variant_(QVariant(QString::fromStdString(backend::status_kind_to_string(kind_)))) , name_variant_(QVariant()) @@ -73,10 +75,12 @@ StatusTreeItem::StatusTreeItem( , id_(backend::ID_ALL) , kind_(backend::StatusKind::INVALID) , name_(data.toString().toStdString()) + , guid_() , status_level_(backend::StatusLevel::OK_STATUS) , value_() , description_() , is_active_(true) + , delete_if_no_children_(false) , id_variant_(QVariant(backend::backend_id_to_models_id(id_))) , kind_variant_(QVariant(QString::fromStdString(backend::status_kind_to_string(kind_)))) , name_variant_(QVariant(QString::fromStdString(name_))) @@ -91,15 +95,18 @@ StatusTreeItem::StatusTreeItem( const backend::EntityId& id, const std::string& name, const backend::StatusLevel& status_level, - const std::string& description) + const std::string& description, + const std::string& guid) : parent_item_(nullptr) , id_(id) , kind_(backend::StatusKind::INVALID) , name_(name) + , guid_(guid) , status_level_(status_level) , value_() , description_(description) , is_active_(true) + , delete_if_no_children_(true) , id_variant_(QVariant(backend::backend_id_to_models_id(id_))) , kind_variant_(QVariant(QString::fromStdString(backend::status_kind_to_string(kind_)))) , name_variant_(QVariant(QString::fromStdString(name))) @@ -121,10 +128,41 @@ StatusTreeItem::StatusTreeItem( , id_(id) , kind_(kind) , name_(name) + , guid_() + , status_level_(status_level) + , value_(value) + , description_(description) + , is_active_(true) + , delete_if_no_children_(false) + , id_variant_(QVariant(backend::backend_id_to_models_id(id_))) + , kind_variant_(QVariant(QString::fromStdString(backend::status_kind_to_string(kind_)))) + , name_variant_(QVariant(QString::fromStdString(name_))) + , status_level_variant_(QVariant(QString::fromStdString(backend::status_level_to_string(status_level_)))) + , value_variant_(QVariant(QString::fromStdString(value))) + , description_variant_(QVariant(QString::fromStdString(description))) + , is_active_variant_(QVariant(true)) +{ +} + +StatusTreeItem::StatusTreeItem( + const backend::EntityId& id, + const backend::StatusKind& kind, + const std::string& name, + const backend::StatusLevel& status_level, + const std::string& value, + const std::string& description, + const std::string& guid, + bool delete_if_no_children) + : parent_item_(nullptr) + , id_(id) + , kind_(kind) + , name_(name) + , guid_(guid) , status_level_(status_level) , value_(value) , description_(description) , is_active_(true) + , delete_if_no_children_(delete_if_no_children) , id_variant_(QVariant(backend::backend_id_to_models_id(id_))) , kind_variant_(QVariant(QString::fromStdString(backend::status_kind_to_string(kind_)))) , name_variant_(QVariant(QString::fromStdString(name_))) @@ -138,6 +176,7 @@ StatusTreeItem::StatusTreeItem( StatusTreeItem::~StatusTreeItem() { qDeleteAll(child_items_); + emit itemRemoved(guid_); } StatusTreeItem* StatusTreeItem::parentItem() @@ -168,6 +207,8 @@ void StatusTreeItem::removeChild( child_items_.removeAll(item); delete item; } + + this->delete_if_no_children(); } StatusTreeItem* StatusTreeItem::child( @@ -181,6 +222,26 @@ int StatusTreeItem::childCount() const return child_items_.count(); } +int StatusTreeItem::descendantCount() const +{ + int count = 0; + for (int i = 0; i < child_items_.count(); i++) + { + count += child_items_.value(i)->descendantCount(); + } + return count + child_items_.count(); +} + +int StatusTreeItem::leafCount() const +{ + int count = 0; + for (int i = 0; i < child_items_.count(); i++) + { + count += child_items_.value(i)->leafCount(); + } + return count + int(child_items_.isEmpty()); +} + const QVariant& StatusTreeItem::entity_id() const { return id_variant_; @@ -277,6 +338,11 @@ std::string StatusTreeItem::name_str() return name_; } +std::string StatusTreeItem::guid_str() +{ + return guid_; +} + std::string StatusTreeItem::value_str() { return value_; @@ -287,33 +353,61 @@ std::string StatusTreeItem::description_str() return description_; } +bool StatusTreeItem::get_delete_if_no_children() +{ + return delete_if_no_children_; +} + +void StatusTreeItem::set_delete_if_no_children( + bool delete_if_no_children) +{ + delete_if_no_children_ = delete_if_no_children; +} + +void StatusTreeItem::delete_if_no_children() +{ + if (delete_if_no_children_ && child_items_.isEmpty()) + { + remove(); + } +} + +void StatusTreeItem::remove() +{ + if (parent_item_) + { + parent_item_->removeChild(this); + } + + else + { + delete this; + } +} + int StatusTreeItem::recalculate_entity_counter() { int count = 0; // check if top level item / entity item if (id_ != backend::ID_ALL && kind_ == backend::StatusKind::INVALID) { - for (int i = 0; i < child_items_.count(); i++) - { - try - { - if (child_items_.value(i)->childCount() > 0) - { - for (int j = 0; j < child_items_.value(i)->childCount(); j++) - { - count += child_items_.value(i)->child(j)->value().toInt(); - } - } - count += child_items_.value(i)->value().toInt(); - } - catch (...) - { - } - } + count = leafCount(); value_ = std::to_string(count); value_variant_ = QVariant(QString::fromStdString(value_)); + return count; } return count; } +// Slots + +void StatusTreeItem::onItemRemoved( + std::string guid) +{ + if (guid_ == guid) + { + remove(); + } +} + } // namespace models diff --git a/src/model/tree/StatusTreeModel.cpp b/src/model/tree/StatusTreeModel.cpp index f86f253e..1955c1f3 100644 --- a/src/model/tree/StatusTreeModel.cpp +++ b/src/model/tree/StatusTreeModel.cpp @@ -61,6 +61,7 @@ StatusTreeModel::StatusTreeModel( StatusTreeModel::~StatusTreeModel() { + disconnect_all_item_signals(); delete root_item_; } @@ -71,6 +72,11 @@ void StatusTreeModel::set_source_model( filter(current_filter_); } +void StatusTreeModel::disconnect_all_item_signals() +{ + QObject::disconnect(this, SIGNAL(itemRemoved(std::string)), nullptr, nullptr); +} + void StatusTreeModel::filter_proxy( const QVariant& entity_id) { @@ -110,7 +116,9 @@ StatusTreeItem* StatusTreeModel::filtered_copy( source->name_str(), source->status_level(), source->value_str(), - source->description_str()); + source->description_str(), + source->guid_str(), + source->get_delete_if_no_children()); for (int i = 0; i < source->childCount(); i++) { addItem(destination, filtered_copy(source->child(i), entity_id)); @@ -325,6 +333,13 @@ void StatusTreeModel::addTopLevelItem( { is_empty_ = true; } + + if (child->status_level() == backend::StatusLevel::ERROR_STATUS) + { + // Notify entity item destruction + QObject::connect(child, &StatusTreeItem::itemRemoved, this, &StatusTreeModel::onItemRemoved); + } + } } @@ -348,13 +363,20 @@ void StatusTreeModel::addItem( if (parent->child(i)->id() == child->id() && parent->child(i)->kind() == child->kind()) { emit layoutAboutToBeChanged(); + // Prevent removing entity item if it is the only child + parent->set_delete_if_no_children(false); beginRemoveRows(QModelIndex(), qMax(parent->childCount() - 1, 0), qMax(parent->childCount(), 0)); parent->removeChild(parent->child(i)); endRemoveRows(); + parent->set_delete_if_no_children(true); emit layoutChanged(); } } } + else if (child->isLeaf() && child->kind() == backend::StatusKind::EXTENDED_INCOMPATIBLE_QOS) + { + QObject::connect(this, &StatusTreeModel::itemRemoved, child, &StatusTreeItem::onItemRemoved); + } emit layoutAboutToBeChanged(); @@ -428,6 +450,10 @@ void StatusTreeModel::clear() { emit layoutAboutToBeChanged(); beginResetModel(); + // NOTE: When the root item is deleted, all its children are also deleted. This causes every leaf node to receive a signal notifying the destruction + // of every entity item, which is unnecessary. Disconnecting all signal-slot communications between the TreeModel and leaf nodes (i.e., disconnecting + // all slots connected to the TreeModel's itemRemoved signal) before deleting the root item prevents this behavior. + disconnect_all_item_signals(); delete root_item_; root_item_ = new StatusTreeItem(); endResetModel(); @@ -504,7 +530,8 @@ StatusTreeItem* StatusTreeModel::getTopLevelItem( const backend::EntityId& id, const std::string& data, const backend::StatusLevel& status, - const std::string& description) + const std::string& description, + const std::string& guid) { // For each entity item in the three (root) for (int i = 0; i < root_item_->childCount(); i++) @@ -517,9 +544,16 @@ StatusTreeItem* StatusTreeModel::getTopLevelItem( } // if not existing, create new topLevelItem - StatusTreeItem* new_entity_item = new StatusTreeItem(id, data, status, description); + StatusTreeItem* new_entity_item = new StatusTreeItem(id, data, status, description, guid); addTopLevelItem(new_entity_item); return new_entity_item; } +// Slots +void StatusTreeModel::onItemRemoved( + std::string guid) +{ + emit itemRemoved(guid); +} + } // namespace models