Parcourir la source

Internal: BulkChange improved with synchro mode

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@1001 a333f486-631f-4898-b8df-5754b55c2be0
romainq il y a 14 ans
Parent
commit
a498ca5e42
4 fichiers modifiés avec 256 ajouts et 24 suppressions
  1. 142 2
      core/bulkchange.class.inc.php
  2. BIN
      images/delete.png
  3. 45 20
      pages/csvimport.php
  4. 69 2
      test/testlist.inc.php

+ 142 - 2
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 "</pre>\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;
 	}
 }

BIN
images/delete.png


+ 45 - 20
pages/csvimport.php

@@ -327,13 +327,18 @@ try
 			$oMyChange->Set("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('<input type="hidden" name="csvdata_truncated" id="csvdata_truncated" value="'.htmlentities($sCSVDataTruncated, ENT_QUOTES, 'UTF-8').'"/>');
@@ -352,16 +357,15 @@ try
 		}
 		$sHtml .= '<th>Message</th>';
 		$sHtml .= '</tr>';
-		$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 = '<img src="../images/unchanged.png" title="Unchanged">';
 				$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 = '<img src="../images/modified.png" title="Modified">';
 				$sCSSRowClass = 'row_modified';
 				break;
 						
+				case 'RowStatus_Disappeared':
+				$iModified++;
+				$sFinalClass = $aResRow['finalclass'];
+				$oObj = MetaModel::GetObject($sFinalClass, $aResRow['id']->GetValue());
+				$sUrl = $oObj->GetHyperlink();
+				$sStatus = '<img src="../images/delete.png" title="Missing">';
+				$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 = '<img src="../images/added.png" title="Created">';
 				$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 = '<img src="../images/error.png" title="Error">';
 				$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 .= '<tr class="'.$sCSSRowClass.'">';
@@ -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 .= '<td class="cell_error" style="border-right:1px #eee solid;">ERROR: '.htmlentities($aData[$iLine][$iNumber-1], ENT_QUOTES, 'UTF-8').$sCellMessage.'</td>';
+						$sHtml .= '<td class="cell_error" style="border-right:1px #eee solid;">ERROR: '.$sHtmlValue.$sCellMessage.'</td>';
 						break;
 						
 						case 'CellStatus_SearchIssue':
 						$sCellMessage .= $oPage->GetP($oCellStatus->GetDescription());
-						$sHtml .= '<td class="cell_error">ERROR: '.htmlentities($aData[$iLine][$iNumber-1], ENT_QUOTES, 'UTF-8').$sCellMessage.'</td>';
+						$sHtml .= '<td class="cell_error">ERROR: '.$sHtmlValue.$sCellMessage.'</td>';
 						break;
 						
 						case 'CellStatus_Ambiguous':
 						$sCellMessage .= $oPage->GetP($oCellStatus->GetDescription());
-						$sHtml .= '<td class="cell_error" style="border-right:1px #eee solid;">AMBIGUOUS: '.htmlentities($aData[$iLine][$iNumber-1], ENT_QUOTES, 'UTF-8').$sCellMessage.'</td>';
+						$sHtml .= '<td class="cell_error" style="border-right:1px #eee solid;">AMBIGUOUS: '.$sHtmlValue.$sCellMessage.'</td>';
 						break;
 						
 						case 'CellStatus_Modify':
-						$sHtml .= '<td class="cell_modified" style="border-right:1px #eee solid;"><b>'.htmlentities($aData[$iLine][$iNumber-1], ENT_QUOTES, 'UTF-8').'</b></td>';
+						$sHtml .= '<td class="cell_modified" style="border-right:1px #eee solid;"><b>'.$sHtmlValue.'</b></td>';
 						break;
 						
 						default:
-						$sHtml .= '<td class="cell_ok" style="border-right:1px #eee solid;">'.htmlentities($aData[$iLine][$iNumber-1], ENT_QUOTES, 'UTF-8').$sCellMessage.'</td>';
+						$sHtml .= '<td class="cell_ok" style="border-right:1px #eee solid;">'.$sHtmlValue.$sCellMessage.'</td>';
 					}
 				}
 			}
 			$sHtml .= "<td class=\"$sCSSMessageClass\" style=\"background-color:#f1f1f1;\">$sMessage</td>";
-			$iLine++;
 			$sHtml .= '</tr>';
 		}
 		$sHtml .= '</table>';

+ 69 - 2
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(
@@ -1194,6 +1194,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</br>";
+		}
+
+		echo "<h3>Planned for loading...</h3>";
+		$aRes = $oBulk->Process();
+		self::DumpVariable($aRes);
+		if (false)
+		{
+		echo "<h3>Go for loading...</h3>";
+		$aRes = $oBulk->Process($oMyChange);
+		self::DumpVariable($aRes);
+		}
+
+		return;
+	}
+}
+
+
+///////////////////////////////////////////////////////////////////////////
 // Test data load
 ///////////////////////////////////////////////////////////////////////////