-
Notifications
You must be signed in to change notification settings - Fork 291
N°9639 - Uninstallation Analysis result doesn't list all the object #950
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
41d4f04
N°9639 - Uninstallation Analysis better count method
eespie fc9df64
N°9639 - Fix tests
eespie bcb42fd
N°9639 - Refactor deletion plan handling with new entity classes for …
eespie 63879a6
N°9639 - Refactor deletion plan methods to use class names instead of…
eespie 4d2da15
N°9639 - Add messages to tests
eespie dc3f6a9
N°9639 - Refactor data feature removal logic and update database quer…
eespie 51d5692
N°9639 - Refactor SQL queries to use backticks for table and field names
eespie 87dcbf7
N°9639 - Enhance deletion logic and add circular reference handling i…
eespie e5e91e8
N°9639 - Add filtering methods to DeletionPlan classes for improved d…
eespie 357ae22
N°9639 - Add filtering methods to DeletionPlan classes for improved d…
eespie b73960a
N°9639 - Refactor DeletionPlanItem to remove unused queries and simpl…
eespie a1dcd03
N°9639 - Refactor variable name for clarity in hierarchical key updat…
eespie fe32ebc
Update tests/php-unit-tests/unitary-tests/datamodels/2.x/combodo-data…
eespie 3da34da
N°9639 - Clarify comment on deletion plan to indicate prevention of i…
eespie 7ddc090
N°9639 - Remove unused deletion plan from DataFeatureRemovalController
eespie File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
37 changes: 37 additions & 0 deletions
37
datamodels/2.x/combodo-data-feature-removal/src/Entity/DeletionPlanEntity.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| <?php | ||
|
|
||
| /* | ||
| * @copyright Copyright (C) 2010-2026 Combodo SAS | ||
| * @license http://opensource.org/licenses/AGPL-3.0 | ||
| */ | ||
|
|
||
| namespace Combodo\iTop\DataFeatureRemoval\Entity; | ||
|
|
||
| class DeletionPlanEntity | ||
| { | ||
| public readonly DeletionPlanItem $oDelete; | ||
| public readonly DeletionPlanItem $oUpdate; | ||
| public readonly DeletionPlanItem $oIssue; | ||
|
|
||
| /** | ||
| * @param \Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanItem $oDelete | ||
| * @param \Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanItem $oUpdate | ||
| * @param \Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanItem $oIssue | ||
| */ | ||
| public function __construct() | ||
| { | ||
| $this->oDelete = $oDelete ?? new DeletionPlanItem(); | ||
| $this->oUpdate = $oUpdate ?? new DeletionPlanItem(); | ||
| $this->oIssue = $oIssue ?? new DeletionPlanItem(); | ||
| } | ||
|
|
||
| public function TotalCount(): int | ||
| { | ||
| return $this->oDelete->Count() + $this->oUpdate->Count() + $this->oIssue->Count(); | ||
| } | ||
|
|
||
| public function FilterUpdatesByDeletes() | ||
| { | ||
| $this->oUpdate->FilterBy($this->oDelete); | ||
| } | ||
| } |
36 changes: 36 additions & 0 deletions
36
datamodels/2.x/combodo-data-feature-removal/src/Entity/DeletionPlanItem.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| <?php | ||
|
|
||
| /* | ||
| * @copyright Copyright (C) 2010-2026 Combodo SAS | ||
| * @license http://opensource.org/licenses/AGPL-3.0 | ||
| */ | ||
|
|
||
| namespace Combodo\iTop\DataFeatureRemoval\Entity; | ||
|
|
||
| class DeletionPlanItem | ||
| { | ||
| public array $aIds = []; | ||
|
|
||
| /** | ||
| * @param array $aIds | ||
| */ | ||
| public function __construct(array $aIds = []) | ||
| { | ||
| $this->aIds = $aIds; | ||
| } | ||
|
|
||
| public function Merge(DeletionPlanItem $oItem): void | ||
| { | ||
| $this->aIds = array_unique(array_merge($this->aIds, $oItem->aIds)); | ||
| } | ||
|
|
||
| public function Count(): int | ||
| { | ||
| return count($this->aIds); | ||
| } | ||
|
|
||
| public function FilterBy(DeletionPlanItem $oItem): void | ||
| { | ||
| $this->aIds = array_diff($this->aIds, $oItem->aIds); | ||
| } | ||
| } |
219 changes: 219 additions & 0 deletions
219
datamodels/2.x/combodo-data-feature-removal/src/Service/StaticDeletionPlan.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,219 @@ | ||
| <?php | ||
|
|
||
| /* | ||
| * @copyright Copyright (C) 2010-2026 Combodo SAS | ||
| * @license http://opensource.org/licenses/AGPL-3.0 | ||
| */ | ||
|
|
||
| namespace Combodo\iTop\DataFeatureRemoval\Service; | ||
|
|
||
| use CMDBSource; | ||
| use Combodo\iTop\DataFeatureRemoval\Entity\DataCleanupSummaryEntity; | ||
| use Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanEntity; | ||
| use Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanItem; | ||
| use MetaModel; | ||
|
|
||
| class StaticDeletionPlan | ||
| { | ||
| /** @var array<DeletionPlanEntity> */ | ||
| private array $aDeletionPlan = []; | ||
|
|
||
| /** | ||
| * Get a summary of the deletion plan computed for the classes. | ||
| * The result is used for display | ||
| * | ||
| * @param array|null $aClasses | ||
| * | ||
| * @return array<\Combodo\iTop\DataFeatureRemoval\Entity\DataCleanupSummaryEntity> | ||
| * @throws \CoreException | ||
| */ | ||
| public function GetCleanupSummary(?array $aClasses): array | ||
| { | ||
| $aSummary = []; | ||
| $aDeletionPlan = $this->GetStaticDeletionPlan($aClasses ?? []); | ||
|
|
||
| foreach ($aDeletionPlan as $sClass => $oDeletionPlanEntity) { | ||
| if ($oDeletionPlanEntity->TotalCount() === 0) { | ||
| continue; | ||
| } | ||
| $oDeletionPlanEntity->FilterUpdatesByDeletes(); | ||
| $oDataCleanupSummary = new DataCleanupSummaryEntity($sClass); | ||
| $oDataCleanupSummary->iUpdateCount = $oDeletionPlanEntity->oUpdate->Count(); | ||
| $oDataCleanupSummary->iDeleteCount = $oDeletionPlanEntity->oDelete->Count(); | ||
| $oDataCleanupSummary->iIssueCount = $oDeletionPlanEntity->oIssue->Count(); | ||
|
|
||
| $aSummary[$sClass] = $oDataCleanupSummary; | ||
| } | ||
|
|
||
| return $aSummary; | ||
| } | ||
|
|
||
| /** | ||
| * @param array $aClasses Classes to clean entirely | ||
| * | ||
| * @return array ['class' => DeletionPlanEntity]; | ||
| * | ||
| * @throws \CoreException | ||
| */ | ||
| public function GetStaticDeletionPlan(array $aClasses): array | ||
| { | ||
| foreach ($aClasses as $sClass) { | ||
| $oDeletionPlanItem = $this->GetInitialClassDeletionPlan($sClass); | ||
| $oDeletionPlanEntity = new DeletionPlanEntity(); | ||
| $oDeletionPlanEntity->oDelete->Merge($oDeletionPlanItem); | ||
| $this->aDeletionPlan[$sClass] = $oDeletionPlanEntity; | ||
|
|
||
| $this->DeletionPlanForReferencingClasses($sClass); | ||
| } | ||
|
|
||
| return $this->aDeletionPlan; | ||
| } | ||
|
|
||
| private function DeletionPlanForReferencingClasses(string $sClass): void | ||
| { | ||
| $sIdsToRemove = implode(', ', $this->aDeletionPlan[$sClass]->oDelete->aIds); | ||
| $aReferencingMe = MetaModel::EnumReferencingClasses($sClass); | ||
| foreach ($aReferencingMe as $sRemoteClass => $aExtKeys) { | ||
| if (!isset($this->aDeletionPlan[$sRemoteClass])) { | ||
| $this->aDeletionPlan[$sRemoteClass] = new DeletionPlanEntity(); | ||
| } | ||
| $oDeletionPlanEntity = $this->aDeletionPlan[$sRemoteClass]; | ||
| /** @var \AttributeExternalKey $oExtKeyAttDef */ | ||
| foreach ($aExtKeys as $sExtKeyAttCode => $oExtKeyAttDef) { | ||
| // skip if this external key is behind an external field | ||
| if (!$oExtKeyAttDef->IsExternalKey(EXTKEY_ABSOLUTE)) { | ||
| continue; | ||
| } | ||
|
|
||
| if ($oExtKeyAttDef->IsNullAllowed()) { | ||
| // update | ||
| $oUpdateItem = $this->UpdateExtKeyNullable($sRemoteClass, $sExtKeyAttCode, $sIdsToRemove); | ||
| $oDeletionPlanEntity->oUpdate->Merge($oUpdateItem); | ||
| } else { | ||
| // delete | ||
| $aRemoteIdsToRemove = $this->GetRemoteIdsForExtKey($sRemoteClass, $sExtKeyAttCode, $sIdsToRemove); | ||
|
|
||
| $iDeletePropagationOption = $oExtKeyAttDef->GetDeletionPropagationOption(); | ||
| if ($iDeletePropagationOption == DEL_MANUAL) { | ||
| // Issue, do not recurse | ||
| $oDeletionPlanItem = new DeletionPlanItem(aIds: $aRemoteIdsToRemove); | ||
| $oDeletionPlanEntity->oIssue->Merge($oDeletionPlanItem); | ||
| continue; | ||
| } | ||
|
|
||
| if (($iDeletePropagationOption == DEL_MOVEUP) && ($oExtKeyAttDef->IsHierarchicalKey())) { | ||
| // update hierarchical keys due to row cleanup in the same table | ||
| $sTargetIdsToRemove = implode(',', $this->aDeletionPlan[$sRemoteClass]->oDelete->aIds); | ||
| $oUpdateItem = $this->UpdateHierarchicalExtKey($sRemoteClass, $sExtKeyAttCode, $sTargetIdsToRemove); | ||
| $oDeletionPlanEntity->oUpdate->Merge($oUpdateItem); | ||
| } | ||
|
|
||
| // Delete entries in Remote Class | ||
| if (count($aRemoteIdsToRemove) !== 0) { | ||
| $oDeletionPlanEntity->oDelete->Merge(new DeletionPlanItem($aRemoteIdsToRemove)); | ||
| // Infinite loops do not occurs due to the datamodel structure | ||
| $this->DeletionPlanForReferencingClasses($sRemoteClass); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @param string $sRemoteClass | ||
| * @param string $sExtKeyAttCode | ||
| * @param string $sIdsToRemoveInTargetClass | ||
| * | ||
| * @return \Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanItem | ||
| * @throws \CoreException | ||
| */ | ||
| public function UpdateExtKeyNullable(string $sRemoteClass, string $sExtKeyAttCode, string $sIdsToRemoveInTargetClass): DeletionPlanItem | ||
| { | ||
| $aIds = $this->GetRemoteIdsForExtKey($sRemoteClass, $sExtKeyAttCode, $sIdsToRemoveInTargetClass); | ||
|
|
||
| return new DeletionPlanItem($aIds); | ||
| } | ||
|
|
||
| /** | ||
| * @param string $sRemoteClass | ||
| * @param string $sExtKeyAttCode | ||
| * @param string $sIdsToRemoveInTargetClass | ||
| * | ||
| * @return \Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanItem | ||
| * @throws \CoreException | ||
| * @throws \MySQLException | ||
| */ | ||
| public function UpdateHierarchicalExtKey(string $sRemoteClass, string $sExtKeyAttCode, string $sIdsToRemoveInTargetClass): DeletionPlanItem | ||
| { | ||
| [$sDBTable, $sDBField, $sDBKey] = $this->GetDBInfoForAttcode($sRemoteClass, $sExtKeyAttCode); | ||
|
|
||
| $sSQL = <<<SQL | ||
| SELECT `$sDBKey` | ||
| FROM `$sDBTable` AS `updated` | ||
| INNER JOIN `$sDBTable` AS `removed` ON `updated`.`$sDBField` = `removed`.`$sDBKey` | ||
| WHERE `removed`.`$sDBKey` IN ($sIdsToRemoveInTargetClass) | ||
| SQL; | ||
| $aIds = CMDBSource::QueryToCol($sSQL, $sDBKey); | ||
|
|
||
| return new DeletionPlanItem($aIds); | ||
| } | ||
|
|
||
| /** | ||
| * @param string $sRemoteClass | ||
| * @param string $sExtKeyAttCode | ||
| * @param string $sIdsToRemoveInTargetClass | ||
| * | ||
| * @return array | ||
| * @throws \CoreException | ||
| * @throws \MySQLException | ||
| */ | ||
| public function GetRemoteIdsForExtKey(string $sRemoteClass, string $sExtKeyAttCode, string $sIdsToRemoveInTargetClass): array | ||
| { | ||
| if (\utils::IsNullOrEmptyString($sIdsToRemoveInTargetClass)) { | ||
| return []; | ||
| } | ||
| [$sDBTable, $sDBField, $sDBKey] = $this->GetDBInfoForAttcode($sRemoteClass, $sExtKeyAttCode); | ||
| $sSQL = "SELECT `$sDBKey` FROM `$sDBTable` WHERE `$sDBField` IN ($sIdsToRemoveInTargetClass)"; | ||
|
|
||
| return CMDBSource::QueryToCol($sSQL, $sDBKey); | ||
| } | ||
|
|
||
| /** | ||
| * @param string $sClass | ||
| * | ||
| * @return \Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanItem | ||
| * @throws \CoreException | ||
| * @throws \MySQLException | ||
| */ | ||
| public function GetInitialClassDeletionPlan(string $sClass): DeletionPlanItem | ||
| { | ||
| $sTable = MetaModel::DBGetTable($sClass); | ||
| $sDBKey = MetaModel::DBGetKey($sClass); | ||
| $sSQL = "SELECT `$sDBKey` FROM `$sTable`"; | ||
| $aIds = CMDBSource::QueryToCol($sSQL, $sDBKey); | ||
|
|
||
| return new DeletionPlanItem($aIds); | ||
| } | ||
|
|
||
| /** | ||
| * Get database table for an attcode | ||
| * | ||
| * @param string $sClass | ||
| * @param string $sExtKeyAttCode | ||
| * | ||
| * @return array | ||
| * @throws \CoreException | ||
| * @throws \Exception | ||
| */ | ||
| public function GetDBInfoForAttcode(string $sClass, string $sExtKeyAttCode): array | ||
| { | ||
| $sOriginClass = MetaModel::GetAttributeOrigin($sClass, $sExtKeyAttCode); | ||
| $sDBTable = MetaModel::DBGetTable($sOriginClass); | ||
| $oAttDef = MetaModel::GetAttributeDef($sOriginClass, $sExtKeyAttCode); | ||
| // External key is on a single DB column | ||
| $sDBField = array_keys($oAttDef->GetSQLColumns())[0]; | ||
| $sDBKey = MetaModel::DBGetKey($sClass); | ||
| return [$sDBTable, $sDBField, $sDBKey]; | ||
| } | ||
|
|
||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.