mirror of
https://github.com/Combodo/iTop.git
synced 2026-03-16 22:44:13 +01:00
Compare commits
8 Commits
feature/un
...
feature/91
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e52a0fa4d | ||
|
|
c5ad3553de | ||
|
|
787587454e | ||
|
|
e157427d7a | ||
|
|
4853a4992a | ||
|
|
b7b00b1685 | ||
|
|
266b917551 | ||
|
|
55d606f110 |
@@ -691,6 +691,28 @@ abstract class LogAPI
|
||||
static::$m_oMockMetaModelConfig = $oMetaModelConfig;
|
||||
}
|
||||
|
||||
public static function Exception(string $sMessage, throwable $oException, string $sChannel = null, array $aContext = []): void
|
||||
{
|
||||
$aErrorLogs = [];
|
||||
$aErrorLogs[] = static::PrepareErrorLog($sMessage, $oException, $aContext);
|
||||
$oException = $oException->getPrevious();
|
||||
while ($oException !== null) {
|
||||
$aErrorLogs[] = static::PrepareErrorLog($oException->getMessage(), $oException, $aContext, true);
|
||||
$oException = $oException->getPrevious();
|
||||
}
|
||||
$aErrorLogs = array_reverse($aErrorLogs);
|
||||
foreach ($aErrorLogs as $aErrorLog) {
|
||||
static::Error($aErrorLog['message'], $sChannel, $aErrorLog['context']);
|
||||
}
|
||||
}
|
||||
|
||||
private static function PrepareErrorLog(string $sMessage, throwable $oException, array $aContext, bool $isPrevious = false): array
|
||||
{
|
||||
$aContext['Error Message'] = $oException->getMessage();
|
||||
$aContext['Stack Trace'] = $oException->getTraceAsString();
|
||||
return ['message' => ($isPrevious ? "Previous " : '')."Exception: $sMessage", 'context' => $aContext];
|
||||
}
|
||||
|
||||
public static function Error($sMessage, $sChannel = null, $aContext = [])
|
||||
{
|
||||
static::Log(self::LEVEL_ERROR, $sMessage, $sChannel, $aContext);
|
||||
|
||||
@@ -1,5 +1,121 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.3">
|
||||
<classes>
|
||||
<class id="DataFeatureRemovalBackgroundOperation" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>datafeatureremovalbackgroundoperation</db_table>
|
||||
<style>
|
||||
<icon>
|
||||
<fileref ref="icons8-electricity_643cf7fd7a024968679dc0c35a710a03"/>
|
||||
</icon>
|
||||
<main_color>#0000ff</main_color>
|
||||
</style>
|
||||
<naming>
|
||||
<attributes/>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes/>
|
||||
</reconciliation>
|
||||
<uniqueness_rules>
|
||||
<rule id="list_of_classes">
|
||||
<attributes>
|
||||
<attribute id="classes"/>
|
||||
</attributes>
|
||||
<filter><![CDATA[]]></filter>
|
||||
<disabled>false</disabled>
|
||||
<is_blocking>true</is_blocking>
|
||||
</rule>
|
||||
</uniqueness_rules>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="creation_date" xsi:type="AttributeDateTime">
|
||||
<sql>creation_date</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<dependencies/>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
<field id="features_code" xsi:type="AttributeText">
|
||||
<sql>features_code</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<validation_pattern/>
|
||||
<width/>
|
||||
<height/>
|
||||
<dependencies/>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
<field id="classes" xsi:type="AttributeText">
|
||||
<sql>classes</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<validation_pattern/>
|
||||
<width/>
|
||||
<height/>
|
||||
<dependencies/>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation>
|
||||
<list>
|
||||
<items>
|
||||
<item id="creation_date">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="features_code">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
<item id="classes">
|
||||
<rank>30</rank>
|
||||
</item>
|
||||
</items>
|
||||
</list>
|
||||
<search>
|
||||
<items/>
|
||||
</search>
|
||||
<details>
|
||||
<items>
|
||||
<item id="creation_date">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="features_code">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
<item id="classes">
|
||||
<rank>30</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
</class>
|
||||
</classes>
|
||||
<dictionaries>
|
||||
<dictionary id="EN US">
|
||||
<entries>
|
||||
<entry id="Class:DataFeatureRemovalBackgroundOperation/Name" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DataFeatureRemovalBackgroundOperation/ComplementaryName" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DataFeatureRemovalBackgroundOperation" _delta="define"><![CDATA[DataFeatureRemovalBackgroundOperation]]></entry>
|
||||
<entry id="Class:DataFeatureRemovalBackgroundOperation+" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DataFeatureRemovalBackgroundOperation/Attribute:creation_date" _delta="define"><![CDATA[Creation date]]></entry>
|
||||
<entry id="Class:DataFeatureRemovalBackgroundOperation/Attribute:creation_date+" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DataFeatureRemovalBackgroundOperation/Attribute:features_code" _delta="define"><![CDATA[Features code]]></entry>
|
||||
<entry id="Class:DataFeatureRemovalBackgroundOperation/Attribute:features_code+" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DataFeatureRemovalBackgroundOperation/Attribute:classes" _delta="define"><![CDATA[Classes]]></entry>
|
||||
<entry id="Class:DataFeatureRemovalBackgroundOperation/Attribute:classes+" _delta="define"><![CDATA[]]></entry>
|
||||
</entries>
|
||||
</dictionary>
|
||||
</dictionaries>
|
||||
<files>
|
||||
<file id="icons8-electricity_643cf7fd7a024968679dc0c35a710a03" xsi:type="File" _delta="define_if_not_exists">
|
||||
<name>images/icons/icons8-electricity.svg</name>
|
||||
<mime_type>image/svg+xml</mime_type>
|
||||
<data>PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciICB2aWV3Qm94PSIwIDAgNDggNDgiIHdpZHRoPSIyNDBweCIgaGVpZ2h0PSIyNDBweCI+PHBhdGggZmlsbD0iIzM3YzZkMCIgZD0iTTI2LjQ1MTIsMzdsNy43NzY0Ni0xNS41NTI5MWExLDEsMCwwLDAtLjg5NDM1LTEuNDQ3MjJMMjIuOTUxMiwxOS45OTkyNWw2LjMzNi0xMy41NzYzNUExLDEsMCwwLDAsMjguMzgxLDVIMjIuNTk2MzlhMSwxLDAsMCwwLS45MTExMS41ODc4M2wtOC41OTUyNCwxOUExLDEsMCwwLDAsMTQuMDAxMTUsMjZoMTAuNDVsLTUuNSwxMVoiLz48cGF0aCBmaWxsPSIjMzdjNmQwIiBkPSJNMTYuMjIyODYsMzdIMjkuODY0NzFhLjU1NzM1LjU1NzM1LDAsMCwxLC4zNTgxNSwxTDE5Ljk2LDQ0LjM2N2ExLjAzMzY0LDEuMDMzNjQsMCwwLDEtMS40OTEyMS0uNDI1ODZMMTUuNzIyODYsMzcuNzVBLjUwNDUuNTA0NSwwLDAsMSwxNi4yMjI4NiwzN1oiLz48L3N2Zz4=</data>
|
||||
</file>
|
||||
</files>
|
||||
<menus>
|
||||
<menu id="DataFeatureRemovalMenu" xsi:type="WebPageMenuNode" _delta="define">
|
||||
<rank>30</rank>
|
||||
|
||||
@@ -23,6 +23,7 @@ Dict::Add('EN US', 'English', 'English', [
|
||||
'DataFeatureRemoval:Features:Title' => 'Features',
|
||||
'DataFeatureRemoval:Analysis:Title' => 'Analysis result',
|
||||
'DataFeatureRemoval:Analysis:SubTitle' => '%1$s element(s) to clean before continuing',
|
||||
'DataFeatureRemoval:Analysis:Ok' => "No data to cleanup",
|
||||
|
||||
'DataFeatureRemoval:DeletionPlan:Title' => 'Deletion plan',
|
||||
'DataFeatureRemoval:DeletionPlan:SubTitle' => 'Database tables to clean before continuing',
|
||||
@@ -39,6 +40,7 @@ Dict::Add('EN US', 'English', 'English', [
|
||||
'UI:Button:AnalyzeAndSetup' => 'Analyze and go to setup',
|
||||
'UI:Button:PlanDeletion' => 'Prepare deletion plan',
|
||||
'UI:Button:DoDeletion' => 'Delete data',
|
||||
'UI:Button:DoAsyncDeletion' => 'Do asynchronous deletion',
|
||||
'UI:Button:BackToMain' => 'Back to Feature Removal',
|
||||
'UI:Button:Setup' => 'Back to setup',
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ Dict::Add('FR FR', 'French', 'Français', [
|
||||
'DataFeatureRemoval:Features:Title' => 'Features',
|
||||
'DataFeatureRemoval:Analysis:Title' => 'Analysis result',
|
||||
'DataFeatureRemoval:Analysis:SubTitle' => '%1$s element(s) to clean before continuing',
|
||||
'DataFeatureRemoval:Analysis:Ok' => "No data to cleanup",
|
||||
|
||||
'DataFeatureRemoval:DeletionPlan:Title' => 'Deletion plan',
|
||||
'DataFeatureRemoval:DeletionPlan:SubTitle' => 'Database tables to clean before continuing',
|
||||
@@ -38,6 +39,7 @@ Dict::Add('FR FR', 'French', 'Français', [
|
||||
'UI:Button:ModifyChoices' => 'Modify Choices',
|
||||
'UI:Button:AnalyzeAndSetup' => 'Analyze and go to setup',
|
||||
'UI:Button:PlanDeletion' => 'Prepare deletion plan',
|
||||
'UI:Button:DoAsyncDeletion' => 'Do asynchronous deletion',
|
||||
'UI:Button:DoDeletion' => 'Delete data',
|
||||
'UI:Button:BackToMain' => 'Back to Feature Removal',
|
||||
'UI:Button:Setup' => 'Back to setup',
|
||||
|
||||
@@ -31,6 +31,7 @@ SetupWebPage::AddModule(
|
||||
'datamodel' => [
|
||||
'vendor/autoload.php',
|
||||
'model.combodo-data-feature-removal.php', // Contains the PHP code generated by the "compilation" of datamodel.combodo-data-feature-removal.xml
|
||||
'src/Hook/DataFeatureRemovalBackgroundTask.php',
|
||||
],
|
||||
'webservice' => [],
|
||||
'data.struct' => [
|
||||
|
||||
@@ -13,6 +13,7 @@ require_once APPROOT.'setup/feature_removal/DryRemovalRuntimeEnvironment.php';
|
||||
use Combodo\iTop\Application\TwigBase\Controller\Controller;
|
||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException;
|
||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalHelper;
|
||||
use Combodo\iTop\DataFeatureRemoval\Service\BackgroundOperationService;
|
||||
use Combodo\iTop\DataFeatureRemoval\Service\DataFeatureRemoverExtensionService;
|
||||
use Combodo\iTop\DataFeatureRemoval\Service\DeletionPlanService;
|
||||
use Combodo\iTop\Setup\FeatureRemoval\DryRemovalRuntimeEnvironment;
|
||||
@@ -38,15 +39,19 @@ class DataFeatureRemovalController extends Controller
|
||||
$this->AddAnalyzeParams();
|
||||
$aParams['sTransactionId'] = utils::GetNewTransactionId();
|
||||
$aParams['aExtensions'] = $this->GetExtensionsTable();
|
||||
$aParams['aExtensionsCode'] = $this->aSelectedExtensionsForCheck;
|
||||
$aParams['aAnalysisDataTable'] = $this->aAnalysisDataTable;
|
||||
$aParams['aClasses'] = array_keys($this->aCountClassesToCleanup);
|
||||
$aParams['DataFeatureRemovalErrorMessage'] = $sErrorMessage;
|
||||
$aParams['bHasData'] = $this->iCount > 0;
|
||||
$aParams['sSetupUrl'] = utils::GetAbsoluteUrlAppRoot().'setup';
|
||||
$aParams['iCount'] = $this->iCount;
|
||||
$aParams['DataFeatureRemovalErrorMessage'] = $sErrorMessage;
|
||||
$aParams['bAnalysisOk'] = (count($this->aCountClassesToCleanup) > 0) && ($this->iCount === 0);
|
||||
|
||||
$this->AddLinkedStylesheet(utils::GetAbsoluteUrlModulesRoot().DataFeatureRemovalHelper::MODULE_NAME.'/assets/css/DataFeatureRemoval.css');
|
||||
$this->AddLinkedScript(utils::GetAbsoluteUrlModulesRoot().DataFeatureRemovalHelper::MODULE_NAME.'/assets/js/DataFeatureRemoval.js');
|
||||
|
||||
$this->m_sOperation = "Main";
|
||||
$this->DisplayPage($aParams);
|
||||
}
|
||||
|
||||
@@ -119,6 +124,7 @@ class DataFeatureRemovalController extends Controller
|
||||
$aParams['sTransactionId'] = utils::GetNewTransactionId();
|
||||
$aParams['aDeletionPlanSummary'] = $this->GetTableData('Extensions', $aColumns, $aRows);
|
||||
$aParams['aClasses'] = $aClasses;
|
||||
$aParams['aExtensionsCode'] = utils::ReadPostedParam('aExtensionsCode', []);
|
||||
|
||||
$this->DisplayPage($aParams);
|
||||
}
|
||||
@@ -129,6 +135,14 @@ class DataFeatureRemovalController extends Controller
|
||||
$this->ValidateTransactionId();
|
||||
|
||||
$aClasses = utils::ReadPostedParam('classes', null, utils::ENUM_SANITIZATION_FILTER_CLASS);
|
||||
$sDeletionButtonValue = utils::ReadPostedParam('btn_deletion', null);
|
||||
$bAsynchronous = ('async_deletion' === $sDeletionButtonValue);
|
||||
|
||||
if ($bAsynchronous) {
|
||||
BackgroundOperationService::GetInstance()->CreateOperation(utils::ReadPostedParam('aExtensionsCode', []), $aClasses);
|
||||
$this->OperationMain();
|
||||
return;
|
||||
}
|
||||
|
||||
$aDeletionExecutionSummary = DeletionPlanService::GetInstance()->ExecuteDeletionPlan($aClasses);
|
||||
$aColumns = ['Class', 'DeletedCount' , 'UpdatedCount'];
|
||||
@@ -159,16 +173,24 @@ class DataFeatureRemovalController extends Controller
|
||||
foreach (DataFeatureRemoverExtensionService::GetInstance()->ReadItopExtensions() as $sCode => $oExtension) {
|
||||
/** @var \iTopExtension $oExtension */
|
||||
|
||||
$bCleanupOngoing = BackgroundOperationService::GetInstance()->IsExtensionBeingCleaned($sCode);
|
||||
$sChecked = '';
|
||||
$sDisabledHtml = '';
|
||||
if ($oExtension->bRemovedFromDisk) {
|
||||
|
||||
$sLabel = $oExtension->sLabel;
|
||||
if ($bCleanupOngoing) {
|
||||
$sDisabledHtml = 'disabled=""';
|
||||
$sLabel .= <<<HTML
|
||||
<span class="ibo-spinner ibo-is-inline ibo-spinner ibo-block" data-role="ibo-spinner"><i class="ibo-spinner--icon fas fa-sync-alt fa-spin" aria-hidden="true"/></span>
|
||||
HTML;
|
||||
;
|
||||
} elseif ($oExtension->bRemovedFromDisk) {
|
||||
$sDisabledHtml = 'disabled=""';
|
||||
$sChecked = 'checked';
|
||||
} elseif (in_array($sCode, $this->aSelectedExtensionsForCheck)) {
|
||||
$sChecked = 'checked';
|
||||
}
|
||||
|
||||
$sLabel = $oExtension->sLabel;
|
||||
$sVersion = $oExtension->sVersion;
|
||||
$sIdEnable = "aExtensions[$sCode][enable]";
|
||||
|
||||
@@ -226,12 +248,12 @@ HTML,
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @return array
|
||||
*/
|
||||
public function ReadRemovedExtensions(): void
|
||||
public function ReadRemovedExtensions(): array
|
||||
{
|
||||
if (count($this->aSelectedExtensionsForCheck) > 0) {
|
||||
return;
|
||||
return $this->aSelectedExtensionsForCheck;
|
||||
}
|
||||
|
||||
$aSelectedExtensionsFromUI = utils::ReadPostedParam('aExtensions', []);
|
||||
@@ -248,5 +270,7 @@ HTML,
|
||||
$this->aSelectedExtensionsForCheck[] = $sCode;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->aSelectedExtensionsForCheck;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\DataFeatureRemoval\Helper;
|
||||
|
||||
use Config;
|
||||
use MetaModel;
|
||||
use utils;
|
||||
|
||||
class DataFeatureRemovalConfig
|
||||
{
|
||||
private static DataFeatureRemovalConfig $oInstance;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
final public static function GetInstance(): DataFeatureRemovalConfig
|
||||
{
|
||||
if (!isset(static::$oInstance)) {
|
||||
static::$oInstance = new DataFeatureRemovalConfig();
|
||||
}
|
||||
|
||||
return static::$oInstance;
|
||||
}
|
||||
|
||||
public function Get(string $sParamName, $default = null)
|
||||
{
|
||||
return MetaModel::GetModuleSetting(DataFeatureRemovalHelper::MODULE_NAME, $sParamName, $default);
|
||||
}
|
||||
|
||||
public function GetBoolean(string $sParamName, $default = null): bool
|
||||
{
|
||||
$res = $this->Get($sParamName, $default);
|
||||
|
||||
return boolval($res);
|
||||
}
|
||||
|
||||
public function IsEnabled(): bool
|
||||
{
|
||||
return $this->GetBoolean('enable', false);
|
||||
}
|
||||
|
||||
public function Set(string $sParamName, $value)
|
||||
{
|
||||
$oConfig = utils::GetConfig();
|
||||
$oConfig->SetModuleSetting(DataFeatureRemovalHelper::MODULE_NAME, $sParamName, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Config|null $oConfig
|
||||
*
|
||||
* @return void
|
||||
* @throws \ConfigException
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function SaveItopConfiguration(Config $oConfig = null)
|
||||
{
|
||||
if (is_null($oConfig)) {
|
||||
$oConfig = utils::GetConfig();
|
||||
}
|
||||
$sConfigFile = APPROOT.'conf/'.utils::GetCurrentEnvironment().'/config-itop.php';
|
||||
@chmod($sConfigFile, 0770); // Allow overwriting the file
|
||||
$oConfig->WriteToFile($sConfigFile);
|
||||
@chmod($sConfigFile, 0444); // Read-only
|
||||
}
|
||||
}
|
||||
@@ -10,4 +10,14 @@ namespace Combodo\iTop\DataFeatureRemoval\Helper;
|
||||
class DataFeatureRemovalHelper
|
||||
{
|
||||
public const MODULE_NAME = 'combodo-data-feature-removal';
|
||||
|
||||
public static function IsTimeLimitExceeded(int $iUnixTimeLimit): bool
|
||||
{
|
||||
if ($iUnixTimeLimit === 0) {
|
||||
//no time limit
|
||||
return false;
|
||||
}
|
||||
|
||||
return (time() > $iUnixTimeLimit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalConfig;
|
||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalHelper;
|
||||
use Combodo\iTop\DataFeatureRemoval\Service\BackgroundOperationService;
|
||||
use Combodo\iTop\DataFeatureRemoval\Service\DeletionPlanService;
|
||||
|
||||
class DataFeatureRemovalBackgroundTask implements iBackgroundProcess
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetPeriodicity()
|
||||
{
|
||||
return DataFeatureRemovalConfig::GetInstance()->Get('cron_periodicity_in_s', 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function Process($iUnixTimeLimit)
|
||||
{
|
||||
while ($oBackgroundOperation = BackgroundOperationService::GetInstance()->GetNext()) {
|
||||
$aClasses = BackgroundOperationService::GetInstance()->GetClasses($oBackgroundOperation);
|
||||
$aRes = DeletionPlanService::GetInstance()->ExecuteDeletionPlan($aClasses, $iUnixTimeLimit);
|
||||
IssueLog::Info(__METHOD__, null, $aRes);
|
||||
|
||||
IssueLog::Info(__METHOD__, null, [
|
||||
'$iUnixTimeLimit' => $iUnixTimeLimit,
|
||||
'time' => time(),
|
||||
'timeout reached' => DataFeatureRemovalHelper::IsTimeLimitExceeded($iUnixTimeLimit),
|
||||
]);
|
||||
|
||||
if (DataFeatureRemovalHelper::IsTimeLimitExceeded($iUnixTimeLimit)) {
|
||||
//timeout reached
|
||||
return;
|
||||
}
|
||||
|
||||
//execution finished before timeout: nothing left to remove
|
||||
$oBackgroundOperation->DBDelete();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\DataFeatureRemoval\Service;
|
||||
|
||||
use DataFeatureRemovalBackgroundOperation;
|
||||
use DBObjectSet;
|
||||
use DBSearch;
|
||||
use MetaModel;
|
||||
|
||||
class BackgroundOperationService
|
||||
{
|
||||
private static BackgroundOperationService $oInstance;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
final public static function GetInstance(): BackgroundOperationService
|
||||
{
|
||||
if (!isset(self::$oInstance)) {
|
||||
self::$oInstance = new BackgroundOperationService();
|
||||
}
|
||||
|
||||
return self::$oInstance;
|
||||
}
|
||||
|
||||
final public static function SetInstance(?BackgroundOperationService $oInstance): void
|
||||
{
|
||||
static::$oInstance = $oInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DataFeatureRemovalBackgroundOperation
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function GetNext(): ?DataFeatureRemovalBackgroundOperation
|
||||
{
|
||||
$sOQL = <<<OQL
|
||||
SELECT DataFeatureRemovalBackgroundOperation
|
||||
OQL;
|
||||
|
||||
$oSet = new DBObjectSet(DBSearch::FromOQL($sOQL), ['creation_date' => true]);
|
||||
/** @var DataFeatureRemovalBackgroundOperation $oDBObject */
|
||||
$oDBObject = $oSet->Fetch();
|
||||
|
||||
return $oDBObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aExtensionsCodes
|
||||
* @param array $aClasses
|
||||
* @return void
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreCannotSaveObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \CoreWarning
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function CreateOperation(array $aExtensionsCodes, array $aClasses): void
|
||||
{
|
||||
sort($aExtensionsCodes);
|
||||
sort($aClasses);
|
||||
|
||||
$aValues = [
|
||||
'creation_date' => time(),
|
||||
'features_code' => '|'.implode('|', $aExtensionsCodes).'|',
|
||||
'classes' => implode(',', $aClasses),
|
||||
];
|
||||
$oObj = MetaModel::NewObject('DataFeatureRemovalBackgroundOperation', $aValues);
|
||||
$oObj->DBWrite();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sExtensionCode
|
||||
* @return bool
|
||||
* @throws \CoreException
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function IsExtensionBeingCleaned(string $sExtensionCode): bool
|
||||
{
|
||||
$sOQL = <<<OQL
|
||||
SELECT DataFeatureRemovalBackgroundOperation WHERE features_code LIKE '%|$sExtensionCode|%'
|
||||
OQL;
|
||||
|
||||
$oSet = new DBObjectSet(DBSearch::FromOQL($sOQL));
|
||||
return $oSet->Count() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DataFeatureRemovalBackgroundOperation $oBackgroundOperation
|
||||
* @return array
|
||||
*/
|
||||
public function GetClasses(DataFeatureRemovalBackgroundOperation $oBackgroundOperation): array
|
||||
{
|
||||
return explode(',', $oBackgroundOperation->Get('classes'));
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ namespace Combodo\iTop\DataFeatureRemoval\Service;
|
||||
use CMDBSource;
|
||||
use Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanSummaryEntity;
|
||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException;
|
||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalHelper;
|
||||
use DBObjectSearch;
|
||||
use DeletionPlan;
|
||||
use MetaModel;
|
||||
@@ -13,6 +14,8 @@ class DeletionPlanService
|
||||
{
|
||||
private static DeletionPlanService $oInstance;
|
||||
|
||||
public int $iExecutionCount = 0;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
@@ -89,7 +92,10 @@ class DeletionPlanService
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.3.0
|
||||
* @param array $aClasses
|
||||
* @param int $iUnixTimeLimit : max execution time in seconds since Epoch before stopping deletion. by default: no limit (ie remove all without stop)
|
||||
* @param int $iMaxExecutionCount : max execution count before stopping deletion. by default: no limit (ie remove all without stop)
|
||||
*
|
||||
* @return array<\Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanSummaryEntity>
|
||||
* @throws \Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException
|
||||
@@ -97,7 +103,7 @@ class DeletionPlanService
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
public function ExecuteDeletionPlan(array $aClasses): array
|
||||
public function ExecuteDeletionPlan(array $aClasses, int $iUnixTimeLimit = 0, int $iMaxExecutionCount = -1): array
|
||||
{
|
||||
$oDeletionPlan = $this->GetDeletionPlan($aClasses);
|
||||
|
||||
@@ -105,11 +111,19 @@ class DeletionPlanService
|
||||
throw new DataFeatureRemovalException("Deletion Plan cannot be executed due to issues");
|
||||
}
|
||||
|
||||
$this->iExecutionCount = 0;
|
||||
|
||||
$aSummary = [];
|
||||
foreach ($oDeletionPlan->ListUpdates() as $sClass => $aToUpdate) {
|
||||
$oDeletionPlanSummaryEntity = $aSummary[$sClass] ?? new DeletionPlanSummaryEntity($sClass);
|
||||
|
||||
foreach ($aToUpdate as $aData) {
|
||||
if ($this->IsTimeLimitExceeded($iUnixTimeLimit, $iMaxExecutionCount)) {
|
||||
$aSummary[$sClass] = $oDeletionPlanSummaryEntity;
|
||||
return $aSummary;
|
||||
}
|
||||
|
||||
$this->iExecutionCount++;
|
||||
$oToUpdate = $aData['to_reset'];
|
||||
/** @var \DBObject $oToUpdate */
|
||||
foreach ($aData['attributes'] as $sRemoteExtKey => $aRemoteAttDef) {
|
||||
@@ -126,20 +140,35 @@ class DeletionPlanService
|
||||
$oDeletionPlanSummaryEntity = $aSummary[$sClass] ?? new DeletionPlanSummaryEntity($sClass);
|
||||
|
||||
foreach ($aDeletes as $sId => $aDelete) {
|
||||
// Delete any existing change tracking about the current object
|
||||
$oFilter = new DBObjectSearch('CMDBChangeOp');
|
||||
$oFilter->AddCondition('objclass', $sClass, '=');
|
||||
$oFilter->AddCondition('objkey', $sId, '=');
|
||||
MetaModel::PurgeData($oFilter);
|
||||
|
||||
// Delete the entry
|
||||
$aClassesToRemove = array_merge(MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL), MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_EXCLUDELEAF, false));
|
||||
foreach ($aClassesToRemove as $sParentClass) {
|
||||
$oFilter = DBObjectSearch::FromOQL_AllData("SELECT $sParentClass WHERE id=:id");
|
||||
$sQuery = $oFilter->MakeDeleteQuery(['id' => $sId]);
|
||||
CMDBSource::DeleteFrom($sQuery);
|
||||
if ($this->IsTimeLimitExceeded($iUnixTimeLimit, $iMaxExecutionCount)) {
|
||||
$aSummary[$sClass] = $oDeletionPlanSummaryEntity;
|
||||
return $aSummary;
|
||||
}
|
||||
|
||||
$this->iExecutionCount++;
|
||||
|
||||
try {
|
||||
CMDBSource::Query('START TRANSACTION');
|
||||
// Delete any existing change tracking about the current object
|
||||
$oFilter = new DBObjectSearch('CMDBChangeOp');
|
||||
$oFilter->AddCondition('objclass', $sClass, '=');
|
||||
$oFilter->AddCondition('objkey', $sId, '=');
|
||||
MetaModel::PurgeData($oFilter);
|
||||
|
||||
// Delete the entry
|
||||
$aClassesToRemove = array_merge(MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL), MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_EXCLUDELEAF, false));
|
||||
foreach ($aClassesToRemove as $sParentClass) {
|
||||
$oFilter = DBObjectSearch::FromOQL_AllData("SELECT $sParentClass WHERE id=:id");
|
||||
$sQuery = $oFilter->MakeDeleteQuery(['id' => $sId]);
|
||||
CMDBSource::DeleteFrom($sQuery);
|
||||
}
|
||||
|
||||
CMDBSource::Query('COMMIT');
|
||||
} catch (\Exception $e) {
|
||||
\IssueLog::Exception(__METHOD__.": Cleanup failed", $e);
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
throw $e;
|
||||
}
|
||||
$oDeletionPlanSummaryEntity->iDeleteCount++;
|
||||
}
|
||||
|
||||
@@ -171,4 +200,13 @@ class DeletionPlanService
|
||||
|
||||
return $oDeletionPlan;
|
||||
}
|
||||
|
||||
public function IsTimeLimitExceeded(int $iUnixTimeLimit, int $iMaxExecutionCount = -1): bool
|
||||
{
|
||||
if (($iMaxExecutionCount !== -1) && ($iMaxExecutionCount <= $this->iExecutionCount)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return DataFeatureRemovalHelper::IsTimeLimitExceeded($iUnixTimeLimit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,14 @@
|
||||
{% for sKey, sClass in aClasses %}
|
||||
{% UIInput ForHidden { sName:"classes[" ~ sKey ~ "]", sValue:sClass } %}
|
||||
{% endfor %}
|
||||
|
||||
{% for sKey, sCode in aExtensionsCode %}
|
||||
{% UIInput ForHidden { sName:"aExtensionsCode[" ~ sKey ~ "]", sValue:sCode } %}
|
||||
{% endfor %}
|
||||
|
||||
{% UIToolbar ForButton {} %}
|
||||
{% UIButton ForPrimaryAction {sLabel:'UI:Button:DoDeletion'|dict_s, sName:'btn_deletion', sId:'btn_deletion', bIsSubmit:true} %}
|
||||
{% UIButton ForPrimaryAction {sLabel:'UI:Button:DoDeletion'|dict_s, sName:'btn_deletion', sId:'btn_deletion', sValue: 'immediate_deletion', bIsSubmit:true} %}
|
||||
{% UIButton ForPrimaryAction {sLabel:'UI:Button:DoAsyncDeletion'|dict_s, sName:'btn_deletion', sId:'btn_async_deletion', sValue: 'async_deletion', bIsSubmit:true} %}
|
||||
{% EndUIToolbar %}
|
||||
{% EndUIForm %}
|
||||
|
||||
|
||||
@@ -12,8 +12,13 @@
|
||||
{% for sKey, sClass in aClasses %}
|
||||
{% UIInput ForHidden { sName:"classes[" ~ sKey ~ "]", sValue:sClass } %}
|
||||
{% endfor %}
|
||||
|
||||
{% for sKey, sCode in aExtensionsCode %}
|
||||
{% UIInput ForHidden { sName:"aExtensionsCode[" ~ sKey ~ "]", sValue:sCode } %}
|
||||
{% endfor %}
|
||||
{% UIToolbar ForButton {} %}
|
||||
{% UIButton ForPrimaryAction {sLabel:'UI:Button:PlanDeletion'|dict_s, sName:'btn_plandeletion', sId:'btn_plandeletion', bIsSubmit:true} %}
|
||||
{% EndUIToolbar %}
|
||||
{% EndUIForm %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -25,7 +25,9 @@
|
||||
{% include 'Features.html.twig' %}
|
||||
{% include 'ExtensionRemovalData.html.twig' %}
|
||||
|
||||
{% if not bHasData %}
|
||||
{% if bAnalysisOk %}
|
||||
{{ "DataFeatureRemoval:Analysis:Ok"|dict_s }}
|
||||
|
||||
{% UIToolbar ForButton {} %}
|
||||
<a href="{{ sSetupUrl }}">
|
||||
{% UIButton ForPrimaryAction {sLabel:'UI:Button:Setup'|dict_s, sName:'btn_setup', sId:'btn_setup', bIsSubmit:false} %}
|
||||
|
||||
@@ -14,7 +14,10 @@ if (PHP_VERSION_ID < 50600) {
|
||||
echo $err;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException($err);
|
||||
trigger_error(
|
||||
$err,
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
@@ -8,9 +8,11 @@ $baseDir = dirname($vendorDir);
|
||||
return array(
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Controller\\DataFeatureRemovalController' => $baseDir . '/src/Controller/DataFeatureRemovalController.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Entity\\DeletionPlanSummaryEntity' => $baseDir . '/src/Entity/DeletionPlanSummaryEntity.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalConfig' => $baseDir . '/src/Helper/DataFeatureRemovalConfig.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalException' => $baseDir . '/src/Helper/DataFeatureRemovalException.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalHelper' => $baseDir . '/src/Helper/DataFeatureRemovalHelper.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalLog' => $baseDir . '/src/Helper/DataFeatureRemovalLog.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\BackgroundOperationService' => $baseDir . '/src/Service/BackgroundOperationService.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\DataFeatureRemoverExtensionService' => $baseDir . '/src/Service/DataFeatureRemoverExtensionService.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\DeletionPlanService' => $baseDir . '/src/Service/DeletionPlanService.php',
|
||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||
|
||||
@@ -27,9 +27,11 @@ class ComposerStaticInit4f96a7199e2c0d90e547333758b26464
|
||||
public static $classMap = array (
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Controller\\DataFeatureRemovalController' => __DIR__ . '/../..' . '/src/Controller/DataFeatureRemovalController.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Entity\\DeletionPlanSummaryEntity' => __DIR__ . '/../..' . '/src/Entity/DeletionPlanSummaryEntity.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalConfig' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalConfig.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalException' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalException.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalHelper' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalHelper.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalLog' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalLog.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\BackgroundOperationService' => __DIR__ . '/../..' . '/src/Service/BackgroundOperationService.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\DataFeatureRemoverExtensionService' => __DIR__ . '/../..' . '/src/Service/DataFeatureRemoverExtensionService.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\DeletionPlanService' => __DIR__ . '/../..' . '/src/Service/DeletionPlanService.php',
|
||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\DataFeatureRemoval\Service\Test;
|
||||
|
||||
use Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanSummaryEntity;
|
||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException;
|
||||
use Combodo\iTop\DataFeatureRemoval\Service\DeletionPlanService;
|
||||
use Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase;
|
||||
|
||||
class DeletionPlanServiceTest extends ItopCustomDatamodelTestCase
|
||||
{
|
||||
public function testExecuteDeletionPlan_DeleteOneObjPerClassWithoutLimit()
|
||||
{
|
||||
$this->GivenDFRTreeInDB(<<<EOF
|
||||
DFRToRemoveLeaf_1 <- DFRToUpdate_1
|
||||
DFRToRemoveLeaf_1 <- DFRRemovedCollateral_1
|
||||
DFRRemovedCollateral_1 <- DFRRemovedCollateralCascade_1
|
||||
EOF);
|
||||
|
||||
$aClasses = [ 'DFRToRemoveLeaf' ];
|
||||
$aRes = DeletionPlanService::GetInstance()->ExecuteDeletionPlan($aClasses);
|
||||
$aExpected = [
|
||||
['DFRToUpdate', 1, 0 ],
|
||||
['DFRToRemoveLeaf', 0, 1 ],
|
||||
['DFRRemovedCollateral', 0, 1 ],
|
||||
['DFRRemovedCollateralCascade', 0, 1 ],
|
||||
];
|
||||
$this->AssertSummaryEquals($aExpected, $aRes);
|
||||
}
|
||||
|
||||
public function testExecuteDeletionPlan_DeleteManyObjPerClassWithoutLimit()
|
||||
{
|
||||
$this->GivenDFRTreeInDB(<<<EOF
|
||||
DFRToRemoveLeaf_1 <- DFRToUpdate_1
|
||||
DFRToRemoveLeaf_1 <- DFRRemovedCollateral_1
|
||||
DFRRemovedCollateral_1 <- DFRRemovedCollateralCascade_1
|
||||
|
||||
DFRToRemoveLeaf_2 <- DFRToUpdate_2
|
||||
DFRToRemoveLeaf_2 <- DFRRemovedCollateral_2
|
||||
DFRRemovedCollateral_2 <- DFRRemovedCollateralCascade_2
|
||||
|
||||
DFRToRemoveLeaf_3 <- DFRToUpdate_3
|
||||
DFRToRemoveLeaf_3 <- DFRRemovedCollateral_3
|
||||
DFRRemovedCollateral_3 <- DFRRemovedCollateralCascade_3
|
||||
EOF);
|
||||
|
||||
$aClasses = [ 'DFRToRemoveLeaf' ];
|
||||
$aRes = DeletionPlanService::GetInstance()->ExecuteDeletionPlan($aClasses);
|
||||
$aExpected = [
|
||||
['DFRToUpdate', 3, 0 ],
|
||||
['DFRToRemoveLeaf', 0, 3 ],
|
||||
['DFRRemovedCollateral', 0, 3 ],
|
||||
['DFRRemovedCollateralCascade', 0, 3 ],
|
||||
];
|
||||
$this->AssertSummaryEquals($aExpected, $aRes);
|
||||
}
|
||||
|
||||
public function testExecuteDeletionPlan_ManualDeleteShouldFail()
|
||||
{
|
||||
$this->GivenDFRTreeInDB(<<<EOF
|
||||
DFRToRemoveLeaf_1 <- DFRManual_1
|
||||
EOF);
|
||||
|
||||
$aClasses = [ 'DFRToRemoveLeaf' ];
|
||||
$this->expectException(DataFeatureRemovalException::class);
|
||||
$this->expectExceptionMessage('Deletion Plan cannot be executed due to issues');
|
||||
DeletionPlanService::GetInstance()->ExecuteDeletionPlan($aClasses);
|
||||
}
|
||||
|
||||
private function AssertSummaryEquals(array $expected, $actual, $sMessage = '')
|
||||
{
|
||||
$aExpected = [];
|
||||
foreach ($expected as $line) {
|
||||
$sClass = $line[0];
|
||||
$iUpdate = $line[1];
|
||||
$iDelete = $line[2];
|
||||
|
||||
$oDeletionPlanSummaryEntity = new DeletionPlanSummaryEntity($sClass);
|
||||
$oDeletionPlanSummaryEntity->iUpdateCount = $iUpdate;
|
||||
$oDeletionPlanSummaryEntity->iDeleteCount = $iDelete;
|
||||
$aExpected[$sClass] = $oDeletionPlanSummaryEntity;
|
||||
}
|
||||
$this->assertEquals($aExpected, $actual, $sMessage);
|
||||
}
|
||||
|
||||
public function testExecuteDeletionPlan_StopInUpdates()
|
||||
{
|
||||
$this->GivenDFRTreeInDB(<<<EOF
|
||||
DFRToRemoveLeaf_1 <- DFRToUpdate_1
|
||||
DFRToRemoveLeaf_1 <- DFRRemovedCollateral_1
|
||||
DFRRemovedCollateral_1 <- DFRRemovedCollateralCascade_1
|
||||
|
||||
DFRToRemoveLeaf_2 <- DFRToUpdate_2
|
||||
DFRToRemoveLeaf_2 <- DFRRemovedCollateral_2
|
||||
DFRRemovedCollateral_2 <- DFRRemovedCollateralCascade_2
|
||||
|
||||
DFRToRemoveLeaf_3 <- DFRToUpdate_3
|
||||
DFRToRemoveLeaf_3 <- DFRRemovedCollateral_3
|
||||
DFRRemovedCollateral_3 <- DFRRemovedCollateralCascade_3
|
||||
EOF);
|
||||
|
||||
$aClasses = [ 'DFRToRemoveLeaf' ];
|
||||
$aRes = DeletionPlanService::GetInstance()->ExecuteDeletionPlan($aClasses, 0, 3);
|
||||
$aExpected = [
|
||||
['DFRToUpdate', 3, 0 ],
|
||||
['DFRToRemoveLeaf', 0, 0 ],
|
||||
];
|
||||
$this->AssertSummaryEquals($aExpected, $aRes);
|
||||
}
|
||||
|
||||
public function testExecuteDeletionPlan_StopInDeletes()
|
||||
{
|
||||
$this->GivenDFRTreeInDB(<<<EOF
|
||||
DFRToRemoveLeaf_1 <- DFRToUpdate_1
|
||||
DFRToRemoveLeaf_1 <- DFRRemovedCollateral_1
|
||||
DFRRemovedCollateral_1 <- DFRRemovedCollateralCascade_1
|
||||
|
||||
DFRToRemoveLeaf_2 <- DFRToUpdate_2
|
||||
DFRToRemoveLeaf_2 <- DFRRemovedCollateral_2
|
||||
DFRRemovedCollateral_2 <- DFRRemovedCollateralCascade_2
|
||||
|
||||
DFRToRemoveLeaf_3 <- DFRToUpdate_3
|
||||
DFRToRemoveLeaf_3 <- DFRRemovedCollateral_3
|
||||
DFRRemovedCollateral_3 <- DFRRemovedCollateralCascade_3
|
||||
EOF);
|
||||
|
||||
$aClasses = [ 'DFRToRemoveLeaf' ];
|
||||
$aRes = DeletionPlanService::GetInstance()->ExecuteDeletionPlan($aClasses, 0, 8);
|
||||
$aExpected = [
|
||||
['DFRToUpdate', 3, 0 ],
|
||||
['DFRToRemoveLeaf', 0, 3 ],
|
||||
['DFRRemovedCollateral', 0, 2 ],
|
||||
];
|
||||
$this->AssertSummaryEquals($aExpected, $aRes);
|
||||
}
|
||||
|
||||
public function testExecuteDeletionPlan_WrongOrderDeletion()
|
||||
{
|
||||
$this->GivenDFRTreeInDB(<<<EOF
|
||||
DFRToRemoveLeaf_1 <- DFRRemovedCollateral_1
|
||||
DFRRemovedCollateral_1 <- DFRRemovedCollateralCascade_1
|
||||
|
||||
DFRToRemoveLeaf_2 <- DFRRemovedCollateral_2
|
||||
DFRRemovedCollateral_2 <- DFRRemovedCollateralCascade_2
|
||||
|
||||
DFRToRemoveLeaf_3 <- DFRRemovedCollateral_3
|
||||
DFRRemovedCollateral_3 <- DFRRemovedCollateralCascade_3
|
||||
EOF);
|
||||
|
||||
$aClasses = [ 'DFRToRemoveLeaf' ];
|
||||
|
||||
$oSet = new \DBObjectSet(\DBObjectSearch::FromOQL("SELECT DFRRemovedCollateral WHERE name='DFRRemovedCollateral_3'"));
|
||||
$oExpectedObj = $oSet->Fetch();
|
||||
self::assertNotNull($oExpectedObj);
|
||||
|
||||
$aRes = DeletionPlanService::GetInstance()->ExecuteDeletionPlan($aClasses, 0, 5);
|
||||
$aExpected = [
|
||||
['DFRToRemoveLeaf', 0, 3 ],
|
||||
['DFRRemovedCollateral', 0, 2 ],
|
||||
];
|
||||
|
||||
$this->AssertSummaryEquals($aExpected, $aRes);
|
||||
|
||||
$oSet = new \DBObjectSet(\DBObjectSearch::FromOQL("SELECT DFRRemovedCollateral WHERE name='DFRRemovedCollateral_3'"));
|
||||
$oActualObj = $oSet->Fetch();
|
||||
self::assertNotNull($oActualObj, "Deletion plan executed in wrong order: DFRRemovedCollateralCascade/DFRRemovedCollateral are not valid anymore");
|
||||
self::assertEquals($oExpectedObj->GetKey(), $oActualObj->GetKey());
|
||||
}
|
||||
|
||||
public function GetDatamodelDeltaAbsPath(): string
|
||||
{
|
||||
return __DIR__.'/deletionplan_delta.xml';
|
||||
}
|
||||
|
||||
private function GivenDFRTreeInDB(string $sTree)
|
||||
{
|
||||
$aTree = explode("\n", $sTree);
|
||||
foreach ($aTree as $sLine) {
|
||||
if (trim($sLine) === "") {
|
||||
continue;
|
||||
}
|
||||
$this->GivenDFRTreeLineInDB($sLine);
|
||||
}
|
||||
}
|
||||
|
||||
private array $aIdByObjectName = [];
|
||||
private function GivenDFRTreeLineInDB(string $sLine)
|
||||
{
|
||||
list($sLeft, $sRight) = explode('<-', $sLine);
|
||||
$sLeft = trim($sLeft);
|
||||
|
||||
$iLeftId = $this->aIdByObjectName[$sLeft] ?? 0;
|
||||
if ($iLeftId === 0) {
|
||||
list($sChildClass, ) = explode('_', $sLeft, 2);
|
||||
$iLeftId = $this->GivenObjectInDB($sChildClass, ['name' => $sLeft]);
|
||||
$this->aIdByObjectName[$sLeft] = $iLeftId;
|
||||
}
|
||||
|
||||
$sRight = trim($sRight);
|
||||
list($sChildClass, ) = explode('_', $sRight, 2);
|
||||
$iRightId = $this->GivenObjectInDB($sChildClass, ['name' => $sRight, 'extkey_id' => $iLeftId]);
|
||||
$this->aIdByObjectName[$sRight] = $iRightId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.2">
|
||||
<classes>
|
||||
<class id="DFRToRemove" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>true</abstract>
|
||||
<db_table>dfrtoremove</db_table>
|
||||
<naming>
|
||||
<attributes/>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes/>
|
||||
</reconciliation>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString">
|
||||
<sql>name</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<validation_pattern/>
|
||||
<dependencies/>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation>
|
||||
<list>
|
||||
<items/>
|
||||
</list>
|
||||
<search>
|
||||
<items/>
|
||||
</search>
|
||||
<details>
|
||||
<items>
|
||||
<item id="name">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
</class>
|
||||
<class id="DFRToUpdate" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>dfrtoupdate</db_table>
|
||||
<naming>
|
||||
<attributes/>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes/>
|
||||
</reconciliation>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString">
|
||||
<sql>name</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<validation_pattern/>
|
||||
<dependencies/>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
<field id="extkey_id" xsi:type="AttributeExternalKey">
|
||||
<sql>extkey_id</sql>
|
||||
<filter/>
|
||||
<dependencies/>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<target_class>DFRToRemove</target_class>
|
||||
<on_target_delete>DEL_AUTO</on_target_delete>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation>
|
||||
<list>
|
||||
<items/>
|
||||
</list>
|
||||
<search>
|
||||
<items/>
|
||||
</search>
|
||||
<details>
|
||||
<items>
|
||||
<item id="name">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="extkey_id">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
</class>
|
||||
<class id="DFRRemovedCollateral" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>dfrremovedcollateral</db_table>
|
||||
<naming>
|
||||
<attributes/>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes/>
|
||||
</reconciliation>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString">
|
||||
<sql>name</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<validation_pattern/>
|
||||
<dependencies/>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
<field id="extkey_id" xsi:type="AttributeExternalKey">
|
||||
<sql>extkey_id</sql>
|
||||
<filter/>
|
||||
<dependencies/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<target_class>DFRToRemove</target_class>
|
||||
<on_target_delete>DEL_AUTO</on_target_delete>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation>
|
||||
<list>
|
||||
<items/>
|
||||
</list>
|
||||
<search>
|
||||
<items/>
|
||||
</search>
|
||||
<details>
|
||||
<items>
|
||||
<item id="name">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="extkey_id">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
</class>
|
||||
<class id="DFRToRemoveLeaf" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>dfrtoremoveleaf</db_table>
|
||||
<naming>
|
||||
<attributes/>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes/>
|
||||
</reconciliation>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="desc" xsi:type="AttributeString">
|
||||
<sql>desc</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<validation_pattern/>
|
||||
<dependencies/>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation>
|
||||
<list>
|
||||
<items/>
|
||||
</list>
|
||||
<search>
|
||||
<items/>
|
||||
</search>
|
||||
<details>
|
||||
<items>
|
||||
<item id="name">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="desc">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
<parent>DFRToRemove</parent>
|
||||
</class>
|
||||
<class id="DFRRemovedCollateralCascade" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>dfrremovedcollateralcascade</db_table>
|
||||
<naming>
|
||||
<attributes/>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes/>
|
||||
</reconciliation>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString">
|
||||
<sql>name</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<validation_pattern/>
|
||||
<dependencies/>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
<field id="extkey_id" xsi:type="AttributeExternalKey">
|
||||
<sql>extkey_id</sql>
|
||||
<filter/>
|
||||
<dependencies/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<target_class>DFRRemovedCollateral</target_class>
|
||||
<on_target_delete>DEL_AUTO</on_target_delete>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation>
|
||||
<list>
|
||||
<items/>
|
||||
</list>
|
||||
<search>
|
||||
<items/>
|
||||
</search>
|
||||
<details>
|
||||
<items>
|
||||
<item id="name">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="extkey_id">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
</class>
|
||||
<class id="DFRManual" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>dfrmanual</db_table>
|
||||
<naming>
|
||||
<attributes/>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes/>
|
||||
</reconciliation>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString">
|
||||
<sql>name</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<validation_pattern/>
|
||||
<dependencies/>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
<field id="extkey_id" xsi:type="AttributeExternalKey">
|
||||
<sql>extkey_id</sql>
|
||||
<filter/>
|
||||
<dependencies/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<target_class>DFRToRemove</target_class>
|
||||
<on_target_delete>DEL_MANUAL</on_target_delete>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation>
|
||||
<list>
|
||||
<items/>
|
||||
</list>
|
||||
<search>
|
||||
<items/>
|
||||
</search>
|
||||
<details>
|
||||
<items>
|
||||
<item id="name">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="extkey_id">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
</class>
|
||||
</classes>
|
||||
<dictionaries>
|
||||
<dictionary id="EN US">
|
||||
<entries>
|
||||
<entry id="Class:DFRToRemove/Name" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRToRemove/ComplementaryName" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRToRemove" _delta="define"><![CDATA[DFRToRemove]]></entry>
|
||||
<entry id="Class:DFRToRemove+" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRToRemove/Attribute:name" _delta="define"><![CDATA[Name]]></entry>
|
||||
<entry id="Class:DFRToRemove/Attribute:name+" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRToUpdate/Name" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRToUpdate/ComplementaryName" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRToUpdate" _delta="define"><![CDATA[DFRToUpdate]]></entry>
|
||||
<entry id="Class:DFRToUpdate+" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRToUpdate/Attribute:name" _delta="define"><![CDATA[Name]]></entry>
|
||||
<entry id="Class:DFRToUpdate/Attribute:name+" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRToUpdate/Attribute:extkey_id" _delta="define"><![CDATA[Dfrtoremove id]]></entry>
|
||||
<entry id="Class:DFRToUpdate/Attribute:extkey_id+" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRRemovedCollateral/Name" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRRemovedCollateral/ComplementaryName" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRRemovedCollateral" _delta="define"><![CDATA[DFRRemovedCollateral]]></entry>
|
||||
<entry id="Class:DFRRemovedCollateral+" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRRemovedCollateral/Attribute:name" _delta="define"><![CDATA[Name]]></entry>
|
||||
<entry id="Class:DFRRemovedCollateral/Attribute:name+" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRRemovedCollateral/Attribute:extkey_id" _delta="define"><![CDATA[Dfrtoremove id]]></entry>
|
||||
<entry id="Class:DFRRemovedCollateral/Attribute:extkey_id+" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRToRemoveLeaf/Name" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRToRemoveLeaf/ComplementaryName" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRToRemoveLeaf" _delta="define"><![CDATA[DFRToRemoveLeaf]]></entry>
|
||||
<entry id="Class:DFRToRemoveLeaf+" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRToRemoveLeaf/Attribute:desc" _delta="define"><![CDATA[Desc]]></entry>
|
||||
<entry id="Class:DFRToRemoveLeaf/Attribute:desc+" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRRemovedCollateralCascade/Name" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRRemovedCollateralCascade/ComplementaryName" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRRemovedCollateralCascade" _delta="define"><![CDATA[DFRRemovedCollateralCascade]]></entry>
|
||||
<entry id="Class:DFRRemovedCollateralCascade+" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRRemovedCollateralCascade/Attribute:name" _delta="define"><![CDATA[Name]]></entry>
|
||||
<entry id="Class:DFRRemovedCollateralCascade/Attribute:name+" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRRemovedCollateralCascade/Attribute:extkey_id" _delta="define"><![CDATA[Dfrremovedcollateral id]]></entry>
|
||||
<entry id="Class:DFRRemovedCollateralCascade/Attribute:extkey_id+" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRManual/Name" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRManual/ComplementaryName" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRManual" _delta="define"><![CDATA[DFRManual]]></entry>
|
||||
<entry id="Class:DFRManual+" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRManual/Attribute:name" _delta="define"><![CDATA[Name]]></entry>
|
||||
<entry id="Class:DFRManual/Attribute:name+" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRManual/Attribute:extkey_id" _delta="define"><![CDATA[Dfrtoremove id]]></entry>
|
||||
<entry id="Class:DFRManual/Attribute:extkey_id+" _delta="define"><![CDATA[]]></entry>
|
||||
</entries>
|
||||
</dictionary>
|
||||
</dictionaries>
|
||||
</itop_design>
|
||||
Reference in New Issue
Block a user