From 018344578e2cbc3d72d329b0d0b5cf01a0dea858 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 30 Nov 2010 15:32:30 +0000 Subject: [PATCH] Internal: BulkChange improved with synchro mode SVN:trunk[1001] --- core/bulkchange.class.inc.php | 144 +++++++++++++++++++++++++++++++++- images/delete.png | Bin 0 -> 842 bytes pages/csvimport.php | 65 ++++++++++----- test/testlist.inc.php | 71 ++++++++++++++++- 4 files changed, 256 insertions(+), 24 deletions(-) create mode 100644 images/delete.png diff --git a/core/bulkchange.class.inc.php b/core/bulkchange.class.inc.php index 813ac532a..23159a259 100644 --- a/core/bulkchange.class.inc.php +++ b/core/bulkchange.class.inc.php @@ -219,6 +219,14 @@ class RowStatus_Modify extends RowStatus } } +class RowStatus_Disappeared extends RowStatus_Modify +{ + public function GetDescription() + { + return "disappeared, changed ".$this->m_iChanged." cols"; + } +} + class RowStatus_Issue extends RowStatus { protected $m_sReason; @@ -247,15 +255,19 @@ class BulkChange // #@# todo: rename the variables to sColIndex protected $m_aAttList; // attcode => iCol protected $m_aExtKeys; // aExtKeys[sExtKeyAttCode][sExtReconcKeyAttCode] = iCol; - protected $m_aReconcilKeys;// attcode (attcode = 'id' for the pkey) + protected $m_aReconcilKeys; // attcode (attcode = 'id' for the pkey) + protected $m_sSynchroScope; // OQL - if specified, then the missing items will be reported + protected $m_aOnDisappear; // array of attcode => value, values to be set when an object gets out of scope (ignored if no scope has been defined) - public function __construct($sClass, $aData, $aAttList, $aExtKeys, $aReconcilKeys) + public function __construct($sClass, $aData, $aAttList, $aExtKeys, $aReconcilKeys, $sSynchroScope = null, $aOnDisappear = null) { $this->m_sClass = $sClass; $this->m_aData = $aData; $this->m_aAttList = $aAttList; $this->m_aReconcilKeys = $aReconcilKeys; $this->m_aExtKeys = $aExtKeys; + $this->m_sSynchroScope = $sSynchroScope; + $this->m_aOnDisappear = $aOnDisappear; } protected function ResolveExternalKey($aRowData, $sAttCode, &$aResults) @@ -430,6 +442,70 @@ class BulkChange return $aResults; } + protected function PrepareMissingObject(&$oTargetObj, &$aErrors) + { + $aResults = array(); + $aErrors = array(); + + // External keys + // + foreach($this->m_aExtKeys as $sAttCode => $aKeyConfig) + { + //$oExtKey = MetaModel::GetAttributeDef(get_class($oTargetObj), $sAttCode); + $aResults[$sAttCode]= new CellStatus_Void($oTargetObj->Get($sAttCode)); + + foreach ($aKeyConfig as $sForeignAttCode => $iCol) + { + $aResults[$iCol] = new CellStatus_Void('?'); + } + } + + // Update attributes + // + foreach($this->m_aOnDisappear as $sAttCode => $value) + { + if (!MetaModel::IsValidAttCode(get_class($oTargetObj), $sAttCode)) + { + throw new BulkChangeException('Invalid attribute code', array('class' => get_class($oTargetObj), 'attcode' => $sAttCode)); + } + $oTargetObj->Set($sAttCode, $value); + if (!array_key_exists($sAttCode, $this->m_aAttList)) + { + // #@# will be out of the reporting... (counted anyway) + } + } + + // Reporting on fields + // + $aChangedFields = $oTargetObj->ListChanges(); + foreach ($this->m_aAttList as $sAttCode => $iCol) + { + if ($sAttCode == 'id') + { + $aResults[$iCol]= new CellStatus_Void($oTargetObj->GetKey()); + } + if (array_key_exists($sAttCode, $aChangedFields)) + { + $aResults[$iCol]= new CellStatus_Modify($oTargetObj->Get($sAttCode), $oTargetObj->GetOriginal($sAttCode)); + } + else + { + // By default... nothing happens + $aResults[$iCol]= new CellStatus_Void($oTargetObj->Get($sAttCode)); + } + } + + // Checks + // + $res = $oTargetObj->CheckConsistency(); + if ($res !== true) + { + // $res contains the error description + $aErrors["GLOBAL"] = "Attributes not consistent with each others: $res"; + } + return $aResults; + } + protected function CreateObject(&$aResult, $iRow, $aRowData, CMDBChange $oChange = null) { @@ -512,6 +588,40 @@ class BulkChange $aResult[$iRow]["__STATUS__"] = new RowStatus_NoChange(); } } + + protected function UpdateMissingObject(&$aResult, $iRow, $oTargetObj, CMDBChange $oChange = null) + { + $aResult[$iRow] = $this->PrepareMissingObject($oTargetObj, $aErrors); + + // Reporting + // + $aResult[$iRow]["finalclass"] = get_class($oTargetObj); + $aResult[$iRow]["id"] = new CellStatus_Void($oTargetObj->GetKey()); + + if (count($aErrors) > 0) + { + $sErrors = implode(', ', $aErrors); + $aResult[$iRow]["__STATUS__"] = new RowStatus_Issue("Unexpected attribute value(s)"); + return; + } + + $aChangedFields = $oTargetObj->ListChanges(); + if (count($aChangedFields) > 0) + { + $aResult[$iRow]["__STATUS__"] = new RowStatus_Disappeared(count($aChangedFields)); + + // Optionaly record the results + // + if ($oChange) + { + $oTargetObj->DBUpdateTracked($oChange); + } + } + else + { + $aResult[$iRow]["__STATUS__"] = new RowStatus_Disappeared(0); + } + } public function Process(CMDBChange $oChange = null) { @@ -528,14 +638,23 @@ class BulkChange print_r($this->m_aExtKeys); echo "Reconciliation:\n"; print_r($this->m_aReconcilKeys); + echo "Synchro scope:\n"; + print_r($this->m_sSynchroScope); + echo "Synchro changes:\n"; + print_r($this->m_aOnDisappear); //echo "Data:\n"; //print_r($this->m_aData); echo "\n"; exit; } + // Compute the results // + if (!is_null($this->m_sSynchroScope)) + { + $aVisited = array(); + } $aResult = array(); foreach($this->m_aData as $iRow => $aRowData) { @@ -612,6 +731,10 @@ class BulkChange $oTargetObj = $oReconciliationSet->Fetch(); $this->UpdateObject($aResult, $iRow, $oTargetObj, $aRowData, $oChange); // $aResult[$iRow]["__STATUS__"]=> set in UpdateObject + if (!is_null($this->m_sSynchroScope)) + { + $aVisited[] = $oTargetObj->GetKey(); + } break; default: // Found several matches, ambiguous @@ -645,6 +768,23 @@ class BulkChange } } } + + if (!is_null($this->m_sSynchroScope)) + { + // Compute the delta between the scope and visited objects + $oScopeSearch = DBObjectSearch::FromOQL($this->m_sSynchroScope); + $oScopeSet = new DBObjectSet($oScopeSearch); + while ($oObj = $oScopeSet->Fetch()) + { + $iObj = $oObj->GetKey(); + if (!in_array($iObj, $aVisited)) + { + $iRow++; + $this->UpdateMissingObject($aResult, $iRow, $oObj, $oChange); + } + } + } + return $aResult; } } diff --git a/images/delete.png b/images/delete.png new file mode 100644 index 0000000000000000000000000000000000000000..e9920bf47d78cdf4c3ab8f7642cbf3bd981ced21 GIT binary patch literal 842 zcmV-Q1GW5#P))_`%oDz7N*vvT4qUVYa}$i$!RF&xK8@m>Xi~}+|C=4xYM~~k8C>R{xUVr}QXmr9(By2m8 zu&*9C@Drf?AN*%Le%GFxv+Z-|?9W1>5kL@LhMuU;_d1#e*)09>_`q~;@7Q*eFtkh! z4P9yI?+>xI22Lk2ulGaX1yO)D-_p{tmM2e;o0_0lB-Gj2wl9-;g$@C?*y+`#N`6NpGTPtzu&sW=Eeq|x;m6yyFd_d$?|hCw>y^B^?SEVr3GnZd}W&7-!aX5t?TO@u22Xi7zD#W6a?phstzt`+M*ON&4}%E z;!ss+Y$Th@u`)Bmr-mBEtV~Qecv$I?*m8MW3{3Xoiy1VT; zw_D#0Cw6#7M<0yb^mvSot}gpDTmynno2|os|5#WM-rNCxrah7rf7jSet("userinfo", $sUserString); $iChangeId = $oMyChange->DBInsert(); } + + $sSynchroScope = null; // e.g. "SELECT Server"; + $aSynchroUpdate = null; // e.g. array('status' => 'obsolete') $oBulk = new BulkChange( $sClassName, $aData, $aAttributes, $aExtKeys, - array_keys($aSearchKeys) + array_keys($aSearchKeys), + $sSynchroScope, + $aSynchroUpdate ); $oPage->add(''); @@ -352,16 +357,15 @@ try } $sHtml .= 'Message'; $sHtml .= ''; - $iLine = 0; $iErrors = 0; $iCreated = 0; $iModified = 0; $iUnchanged = 0; - foreach($aData as $aRow) + foreach($aRes as $iLine => $aResRow) { - $oStatus = $aRes[$iLine]['__STATUS__']; + $oStatus = $aResRow['__STATUS__']; $sUrl = ''; $sMessage = ''; $sCSSRowClass = ''; @@ -370,8 +374,8 @@ try { case 'RowStatus_NoChange': $iUnchanged++; - $sFinalClass = $aRes[$iLine]['finalclass']; - $oObj = MetaModel::GetObject($sFinalClass, $aRes[$iLine]['id']->GetValue()); + $sFinalClass = $aResRow['finalclass']; + $oObj = MetaModel::GetObject($sFinalClass, $aResRow['id']->GetValue()); $sUrl = $oObj->GetHyperlink(); $sStatus = ''; $sCSSRowClass = 'row_unchanged'; @@ -379,16 +383,33 @@ try case 'RowStatus_Modify': $iModified++; - $sFinalClass = $aRes[$iLine]['finalclass']; - $oObj = MetaModel::GetObject($sFinalClass, $aRes[$iLine]['id']->GetValue()); + $sFinalClass = $aResRow['finalclass']; + $oObj = MetaModel::GetObject($sFinalClass, $aResRow['id']->GetValue()); $sUrl = $oObj->GetHyperlink(); $sStatus = ''; $sCSSRowClass = 'row_modified'; break; + case 'RowStatus_Disappeared': + $iModified++; + $sFinalClass = $aResRow['finalclass']; + $oObj = MetaModel::GetObject($sFinalClass, $aResRow['id']->GetValue()); + $sUrl = $oObj->GetHyperlink(); + $sStatus = ''; + $sCSSRowClass = 'row_modified'; + if ($bSimulate) + { + $sMessage = 'Missing object: will be updated'; + } + else + { + $sMessage = 'Missing object: updated'; + } + break; + case 'RowStatus_NewObj': $iCreated++; - $sFinalClass = $aRes[$iLine]['finalclass']; + $sFinalClass = $aResRow['finalclass']; $sStatus = ''; $sCSSRowClass = 'row_added'; if ($bSimulate) @@ -397,8 +418,8 @@ try } else { - $sFinalClass = $aRes[$iLine]['finalclass']; - $oObj = MetaModel::GetObject($sFinalClass, $aRes[$iLine]['id']->GetValue()); + $sFinalClass = $aResRow['finalclass']; + $oObj = MetaModel::GetObject($sFinalClass, $aResRow['id']->GetValue()); $sUrl = $oObj->GetHyperlink(); $sMessage = 'Object created'; } @@ -410,7 +431,11 @@ try $sStatus = ''; $sCSSMessageClass = 'cell_error'; $sCSSRowClass = 'row_error'; - $aResult[] = $sTextQualifier.implode($sTextQualifier.$sSeparator.$sTextQualifier,$aRow).$sTextQualifier; // Remove the first line and store it in case of error + if (array_key_exists($iLine, $aData)) + { + $aRow = $aData[$iLine]; + $aResult[] = $sTextQualifier.implode($sTextQualifier.$sSeparator.$sTextQualifier,$aRow).$sTextQualifier; // Remove the first line and store it in case of error + } break; } $sHtml .= ''; @@ -421,12 +446,12 @@ try { if (!empty($sAttCode) && ($sAttCode != ':none:') && ($sAttCode != 'finalclass')) { - $oCellStatus = $aRes[$iLine][$iNumber -1]; + $oCellStatus = $aResRow[$iNumber -1]; $sCellMessage = ''; if (isset($aExternalKeysByColumn[$iNumber -1])) { $sExtKeyName = $aExternalKeysByColumn[$iNumber -1]; - $oExtKeyCellStatus = $aRes[$iLine][$sExtKeyName]; + $oExtKeyCellStatus = $aResRow[$sExtKeyName]; switch(get_class($oExtKeyCellStatus)) { case 'CellStatus_Issue': @@ -443,34 +468,34 @@ try // Do nothing } } + $sHtmlValue = htmlentities($oCellStatus->GetValue(), ENT_QUOTES, 'UTF-8'); switch(get_class($oCellStatus)) { case 'CellStatus_Issue': $sCellMessage .= $oPage->GetP($oCellStatus->GetDescription()); - $sHtml .= 'ERROR: '.htmlentities($aData[$iLine][$iNumber-1], ENT_QUOTES, 'UTF-8').$sCellMessage.''; + $sHtml .= 'ERROR: '.$sHtmlValue.$sCellMessage.''; break; case 'CellStatus_SearchIssue': $sCellMessage .= $oPage->GetP($oCellStatus->GetDescription()); - $sHtml .= 'ERROR: '.htmlentities($aData[$iLine][$iNumber-1], ENT_QUOTES, 'UTF-8').$sCellMessage.''; + $sHtml .= 'ERROR: '.$sHtmlValue.$sCellMessage.''; break; case 'CellStatus_Ambiguous': $sCellMessage .= $oPage->GetP($oCellStatus->GetDescription()); - $sHtml .= 'AMBIGUOUS: '.htmlentities($aData[$iLine][$iNumber-1], ENT_QUOTES, 'UTF-8').$sCellMessage.''; + $sHtml .= 'AMBIGUOUS: '.$sHtmlValue.$sCellMessage.''; break; case 'CellStatus_Modify': - $sHtml .= ''.htmlentities($aData[$iLine][$iNumber-1], ENT_QUOTES, 'UTF-8').''; + $sHtml .= ''.$sHtmlValue.''; break; default: - $sHtml .= ''.htmlentities($aData[$iLine][$iNumber-1], ENT_QUOTES, 'UTF-8').$sCellMessage.''; + $sHtml .= ''.$sHtmlValue.$sCellMessage.''; } } } $sHtml .= "$sMessage"; - $iLine++; $sHtml .= ''; } $sHtml .= ''; diff --git a/test/testlist.inc.php b/test/testlist.inc.php index 6a5a08f09..9dbb4d7ec 100644 --- a/test/testlist.inc.php +++ b/test/testlist.inc.php @@ -987,8 +987,8 @@ class TestBulkChangeOnFarm extends TestBizModel $oParser = new CSVParser("denomination,hauteur,age suzy,123,2009-01-01 chita,456, - "); - $aData = $oParser->ToArray(array('_name', '_height', '_birth'), ','); + ", ',', '"'); + $aData = $oParser->ToArray(1, array('_name', '_height', '_birth')); self::DumpVariable($aData); $oBulk = new BulkChange( @@ -1193,6 +1193,73 @@ class TestItopEfficiency extends TestBizModel } } +/////////////////////////////////////////////////////////////////////////// +// Test bulk load API +/////////////////////////////////////////////////////////////////////////// + +class TestItopBulkLoad extends TestBizModel +{ + static public function GetName() + { + return 'Itop - test BulkChange class'; + } + + static public function GetDescription() + { + return 'Execute a bulk change at the Core API level'; + } + + static public function GetConfigFile() {return '/config-itop.php';} + + + protected function DoExecute() + { + $oParser = new CSVParser("name,org_id->name,brand,model + Server1,Demo,, + server4,Demo,, + ", ',', '"'); + $aData = $oParser->ToArray(1, array('_name', '_org_name', '_brand', '_model')); + self::DumpVariable($aData); + + $oBulk = new BulkChange( + 'Server', + $aData, + // attributes + array('name' => '_name', 'brand' => '_brand', 'model' => '_model'), + // ext keys + array('org_id' => array('name' => '_org_name')), + // reconciliation + array('name'), + // Synchro - scope + "SELECT Server", + // Synchro - set attribute on missing objects + array ('brand' => 'you let package', 'model' => 'tpe', 'cpu' => 'it is pay you') + ); + + if (false) + { + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Testor"); + $iChangeId = $oMyChange->DBInsert(); +// echo "Created new change: $iChangeId
"; + } + + echo "

Planned for loading...

"; + $aRes = $oBulk->Process(); + self::DumpVariable($aRes); + if (false) + { + echo "

Go for loading...

"; + $aRes = $oBulk->Process($oMyChange); + self::DumpVariable($aRes); + } + + return; + } +} + + /////////////////////////////////////////////////////////////////////////// // Test data load ///////////////////////////////////////////////////////////////////////////