Browse Source

A little bit of polishing on the impact analysis feature...

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@3630 a333f486-631f-4898-b8df-5754b55c2be0
dflaven 10 years ago
parent
commit
5571c138ae

+ 34 - 12
core/displayablegraph.class.inc.php

@@ -282,22 +282,45 @@ class DisplayableNode extends GraphNode
 				{
 					if (count($aGroupProps['nodes']) >= $iThresholdCount)
 					{
-						$oNewNode = new DisplayableGroupNode($oGraph, $this->GetId().'::'.(($sStatus == 'reached') ? '_reached': ''));
-						$oNewNode->SetProperty('label', 'x'.$aGroupProps['count']);
-						$oNewNode->SetProperty('icon_url', $aGroupProps['icon_url']);
-						$oNewNode->SetProperty('class', $sClass);
-						$oNewNode->SetProperty('is_reached', ($sStatus == 'reached'));
-						$oNewNode->SetProperty('count', $aGroupProps['count']);
+						$sNewId = $this->GetId().'::'.(($sStatus == 'reached') ? '_reached': '');
+						$oNewNode = $oGraph->GetNode($sNewId);
+						if ($oNewNode == null)
+						{
+							$oNewNode = new DisplayableGroupNode($oGraph, $sNewId);
+							$oNewNode->SetProperty('label', 'x'.$aGroupProps['count']);
+							$oNewNode->SetProperty('icon_url', $aGroupProps['icon_url']);
+							$oNewNode->SetProperty('class', $sClass);
+							$oNewNode->SetProperty('is_reached', ($sStatus == 'reached'));
+							$oNewNode->SetProperty('count', $aGroupProps['count']);
+						}
+						else
+						{
+							$oNewNode->SetProperty('count', $oNewNode->GetProperty('count')+$aGroupProps['count']);
+						}
+						
+						try
+						{
+							$oIncomingEdge = new DisplayableEdge($oGraph, $this->GetId().'-'.$oNewNode->GetId(), $this, $oNewNode);
+						}
+						catch(Exception $e)
+						{
+							// Ignore this redundant egde
+						}				
 						
-						$oIncomingEdge = new DisplayableEdge($oGraph, $this->GetId().'-'.$oNewNode->GetId(), $this, $oNewNode);
-										
 						foreach($aGroupProps['nodes'] as $oNode)
 						{
 							foreach($oNode->GetIncomingEdges() as $oEdge)
 							{
 								if ($oEdge->GetSourceNode()->GetId() !== $this->GetId())
 								{
-									$oNewEdge = new DisplayableEdge($oGraph, $oEdge->GetId().'::'.$sClass, $oEdge->GetSourceNode(), $oNewNode);
+									try
+									{
+										$oNewEdge = new DisplayableEdge($oGraph, $oEdge->GetId().'::'.$sClass, $oEdge->GetSourceNode(), $oNewNode);
+									}
+									catch(Exception $e)
+									{
+										// ignore this edge
+									}
 								}
 							}
 							foreach($oNode->GetOutgoingEdges() as $oEdge)
@@ -825,7 +848,6 @@ class DisplayableGraph extends SimpleGraph
 		{
 			throw new Exception($sDot);
 		}
-		$sDot = preg_replace('/.*label=.*,/', '', $sDot); // Get rid of label lines since they may contain weird characters than can break the split and pattern matching below
 		
 		$aChunks = explode(";", $sDot);
 		foreach($aChunks as $sChunk)
@@ -975,7 +997,7 @@ class DisplayableGraph extends SimpleGraph
 		$yMin = $aRemainingArea['ymin'];
 		$yMax = $aRemainingArea['ymax'];
 		
-		//$oPdf->Rect($xMin, $yMin, $xMax - $xMin, $yMax - $yMin, 'D', array(), array(225, 225, 225));
+		//$oPdf->Rect($xMin, $yMin, $xMax - $xMin, $yMax - $yMin, 'D', array(), array(225, 50, 50));
 		
 		$fPageW = $xMax - $xMin;
 		$fPageH = $yMax - $yMin;
@@ -1093,7 +1115,7 @@ class DisplayableGraph extends SimpleGraph
 			$yMax = $yPos - $fPadding;
 		}
 		
-		return array('xmin' => $fMaxWidth + $fIconSize + 4*$fPadding, 'xmax' => $xMax, 'ymin' => $yMin, 'ymax' => $yMax);
+		return array('xmin' => $xMin + $fMaxWidth + $fIconSize + 4*$fPadding, 'xmax' => $xMax, 'ymin' => $yMin, 'ymax' => $yMax);
 	}
 	
 	/**

+ 5 - 4
core/metamodel.class.php

@@ -1403,10 +1403,11 @@ abstract class MetaModel
 								$aQueries[$sRemoteClass]['down'][$sLocalClass]['sQueryUp'] = $aNeighbourData['sQueryUp'];
 								$aQueries[$sRemoteClass]['down'][$sLocalClass]['sDirection'] = 'both';
 							}
-							else
-							{
-								throw new Exception("Legacy definition of the relation '$sRelCode/$sRevertCode', defined on $sLocalClass (relation: $sRevertCode, inherited to $sClass), missing the counterpart query on class $sRemoteClass ($sRelCode)");
-							}
+							// Be silent in order to transparently support legacy data models where the counterpart query does not always exist
+							//else
+							//{
+							//	throw new Exception("Legacy definition of the relation '$sRelCode/$sRevertCode', defined on $sLocalClass (relation: $sRevertCode, inherited to $sClass), missing the counterpart query on class $sRemoteClass ($sRelCode)");
+							//}
 						}
 					}
 				}

+ 2 - 2
core/relationgraph.class.inc.php

@@ -48,7 +48,7 @@ class RelationObjectNode extends GraphNode
 	/**
 	 * Formatting for GraphViz
 	 */	 	
-	public function GetDotAttributes()
+	public function GetDotAttributes($bNoLabel = false)
 	{
 		$sDot = parent::GetDotAttributes();
 		if ($this->GetProperty('developped', false))
@@ -114,7 +114,7 @@ class RelationRedundancyNode extends GraphNode
 	/**
 	 * Formatting for GraphViz
 	 */	 	
-	public function GetDotAttributes()
+	public function GetDotAttributes($bNoLabel = false)
 	{
 		$sDisplayThreshold = sprintf('%.1f', $this->GetProperty('threshold'));
 		$sDot = 'shape=doublecircle,fillcolor=indianred,fontcolor=papayawhip,label="'.$sDisplayThreshold.'"';

+ 22 - 12
core/simplegraph.class.inc.php

@@ -128,10 +128,14 @@ class GraphNode extends GraphElement
 		$oGraph->_AddNode($this);
 	}
 	
-	public function GetDotAttributes()
+	public function GetDotAttributes($bNoLabel = false)
 	{
-		$sLabel = addslashes($this->GetProperty('label', $this->GetId()));
-		$sDot = 'label="'.$sLabel.'"';
+		$sDot = '';
+		if (!$bNoLabel)
+		{
+			$sLabel = addslashes($this->GetProperty('label', $this->GetId()));
+			$sDot = 'label="'.$sLabel.'"';
+		}
 		return $sDot;
 	}
 
@@ -264,10 +268,14 @@ class GraphEdge extends GraphElement
 		return $this->oSinkNode;
 	}
 
-	public function GetDotAttributes()
+	public function GetDotAttributes($bNoLabel = false)
 	{
-		$sLabel = addslashes($this->GetProperty('label', ''));
-		$sDot = 'label="'.$sLabel.'"';
+		$sDot = '';
+		if (!$bNoLabel)
+		{
+			$sLabel = addslashes($this->GetProperty('label', ''));
+			$sDot = 'label="'.$sLabel.'"';
+		}
 		return $sDot;
 	}
 }
@@ -443,9 +451,10 @@ class SimpleGraph
 
 	/**
 	 * Get the description of the graph as a text string in the graphviz 'dot' language
+	 * @param $bNoLabel bool Whether or not to include the labels in the dot file
 	 * @return string
 	 */
-	public function GetDotDescription()
+	public function GetDotDescription($bNoLabel = false)
 	{
 		$sDot =
 <<<EOF
@@ -464,12 +473,12 @@ EOF
 		
 		foreach($oIterator as $key => $oNode)
 		{
-			$sDot .= "\t\"".$oNode->GetId()."\" [ ".$oNode->GetDotAttributes()." ];\n";
+			$sDot .= "\t\"".$oNode->GetId()."\" [ ".$oNode->GetDotAttributes($bNoLabel)." ];\n";
 			if (count($oNode->GetOutgoingEdges()) > 0)
 			{
 				foreach($oNode->GetOutgoingEdges() as $oEdge)
 				{
-					$sDot .= "\t\"".$oNode->GetId()."\" -> \"".$oEdge->GetSinkNode()->GetId()."\" [ ".$oEdge->GetDotAttributes()." ];\n";
+					$sDot .= "\t\"".$oNode->GetId()."\" -> \"".$oEdge->GetSinkNode()->GetId()."\" [ ".$oEdge->GetDotAttributes($bNoLabel)." ];\n";
 				}
 			}
 		}
@@ -556,7 +565,7 @@ EOF
 				@mkdir(APPROOT."data/tmp");
 			}
 			$sXdotFilePath = tempnam(APPROOT."data/tmp", 'xdot-');
-			$sDotDescription = $this->GetDotDescription();
+			$sDotDescription = $this->GetDotDescription(true); // true => don't put (localized) labels in the file, since it makes it harder to parse
 			$sDotFilePath = tempnam(APPROOT."data/tmp", 'dot-');
 	
 			$rFile = @fopen($sDotFilePath, "w");
@@ -572,13 +581,14 @@ EOF
 				$sHtml .= "<p><b>Error:</b></p>";
 				$sHtml .= "<p>The command: <pre>$CommandLine</pre> returned $iRetCode</p>";
 				$sHtml .= "<p>The output of the command is:<pre>\n".implode("\n", $aOutput)."</pre></p>";
+				IssueLog::Error($sHtml);
 			}
 			else
 			{
 				$sHtml = '<pre>'.file_get_contents($sXdotFilePath).'</pre>';
-				@unlink($sImageFilePath);
+				@unlink($sXdotFilePath);
 			}
-			@unlink($sXdotFilePath);
+			@unlink($sDotFilePath);
 		}
 		else
 		{

+ 1 - 1
datamodels/2.x/itop-change-mgmt/de.dict.itop-change-mgmt.php

@@ -103,7 +103,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
 	'UI-ChangeManagementOverview-ChangeByDomain-last-7-days' => 'Changes der letzten sieben Tage nach Typ',
 	'UI-ChangeManagementOverview-ChangeByStatus-last-7-days' => 'Changes der letzten sieben Tage nach Status',
 	'Tickets:Related:OpenChanges' => 'Open changes~~',
-	'Tickets:Related:RecentChanges' => 'Recent changes~~',
+	'Tickets:Related:RecentChanges' => 'Recent changes (72h)~~',
 	'Class:Change/Attribute:changemanager_email' => 'Change Manager Email',
 	'Class:Change/Attribute:changemanager_email+' => '',
 	'Class:Change/Attribute:parent_name' => 'Parent Change ref',

+ 1 - 1
datamodels/2.x/itop-change-mgmt/en.dict.itop-change-mgmt.php

@@ -47,7 +47,7 @@ Dict::Add('EN US', 'English', 'English', array(
 	'UI-ChangeManagementOverview-ChangeByDomain-last-7-days' => 'Changes by domain for the last 7 days',
 	'UI-ChangeManagementOverview-ChangeByStatus-last-7-days' => 'Changes by status for the last 7 days',
 	'Tickets:Related:OpenChanges' => 'Open changes',
-	'Tickets:Related:RecentChanges' => 'Recent changes',
+	'Tickets:Related:RecentChanges' => 'Recent changes (72h)',
 ));
 
 // Dictionnay conventions

+ 1 - 1
datamodels/2.x/itop-change-mgmt/fr.dict.itop-change-mgmt.php

@@ -126,6 +126,6 @@ Dict::Add('FR FR', 'French', 'Français', array(
 	'UI-ChangeManagementOverview-ChangeByStatus-last-7-days' => 'Changements par statut',
 	'UI:ChangeMgmtMenuOverview:Title' => 'Tableau de bord des changements pour les 7 derniers jours',
 	'Tickets:Related:OpenChanges' => 'Changements en cours',
-	'Tickets:Related:RecentChanges' => 'Changements récents',
+	'Tickets:Related:RecentChanges' => 'Changements récents (72h)',
 ));
 ?>

+ 25 - 9
js/simple_graph.js

@@ -269,10 +269,10 @@ $(function()
 			this.options.ymax = -9999;
 			for(var k in this.aNodes)
 			{
-				this.options.xmin = Math.min(this.aNodes[k].x + this.aNodes[k].tx, this.options.xmin);
-				this.options.xmax = Math.max(this.aNodes[k].x + this.aNodes[k].tx, this.options.xmax);
-				this.options.ymin = Math.min(this.aNodes[k].y + this.aNodes[k].ty, this.options.ymin);
-				this.options.ymax = Math.max(this.aNodes[k].y + this.aNodes[k].ty, this.options.ymax);
+				this.options.xmin = Math.min(this.aNodes[k].x + this.aNodes[k].tx - this.aNodes[k].width/2, this.options.xmin);
+				this.options.xmax = Math.max(this.aNodes[k].x + this.aNodes[k].tx + this.aNodes[k].width/2, this.options.xmax);
+				this.options.ymin = Math.min(this.aNodes[k].y + this.aNodes[k].ty - this.aNodes[k].width/2, this.options.ymin);
+				this.options.ymax = Math.max(this.aNodes[k].y + this.aNodes[k].ty + this.aNodes[k].width/2, this.options.ymax);
 			}
 		},
 		_get_edge_path: function(oEdge)
@@ -617,8 +617,10 @@ $(function()
 			this.element.closest('.ui-tabs').tabs({ heightStyle: "fill" });
 			this._close_all_tooltips();
 			this.oPaper.rect(0, 0, this.element.width(), this.element.height()).attr({fill: '#000', opacity: 0.4, 'stroke-width': 0});
+			$('#'+sId+'_refresh_btn').button('disable'); 
 			$.post(sUrl, {excluded_classes: this.options.excluded_classes, g: this.options.grouping_threshold, sources: this.options.sources, excluded: this.options.excluded, contexts: aContexts, context_key: this.options.context_key }, function(data) {
 				me.load(data);
+				$('#'+sId+'_refresh_btn').button('enable');
 			}, 'json');
 		},
 		export_as_attachment: function()
@@ -665,13 +667,22 @@ $(function()
 			$.post(sUrl, oParams, function(data) {
 				var sDownloadLink = GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?operation=download_document&class=Attachment&id='+data.att_id+'&field=contents';
 				var sIcon = GetAbsoluteUrlModulesRoot()+'itop-attachments/icons/pdf.png';
-				$('#attachments').append('<div class="attachment" id="display_attachment_'+data.att_id+'"><a data-preview="false" href="'+sDownloadLink+'"><img src="'+sIcon+'"><br/>'+sTitle+'.pdf<input id="attachment_'+data.att_id+'" type="hidden" name="attachments[]" value="'+data.att_id+'"/></a><br/><input type="button" class="btn_hidden" value="{$sDeleteBtn}" onClick="RemoveAttachment('+data.att_id+');"/></div>');
 				if (jTab != null)
 				{
 					var re = /^([^(]+)\(([0-9]+)\)(.*)$/;
 					var aParts = re.exec(sTabText);
-					var iPrevCount = parseInt(aParts[2], 10);
-					jTab.find('span').html(aParts[1]+'('+(1 + iPrevCount)+')'+aParts[3]);
+					if (aParts == null)
+					{
+						// First attachment
+						$('#attachments').html('<div class="attachment" id="display_attachment_'+data.att_id+'"><a data-preview="false" href="'+sDownloadLink+'"><img src="'+sIcon+'"><br/>'+sTitle+'.pdf<input id="attachment_'+data.att_id+'" type="hidden" name="attachments[]" value="'+data.att_id+'"/></a><br/><input type="button" class="btn_hidden" value="{$sDeleteBtn}" onClick="RemoveAttachment('+data.att_id+');"/></div>');
+						jTab.find('span').html(sTabText +' (1)');
+					}
+					else
+					{
+						$('#attachments').append('<div class="attachment" id="display_attachment_'+data.att_id+'"><a data-preview="false" href="'+sDownloadLink+'"><img src="'+sIcon+'"><br/>'+sTitle+'.pdf<input id="attachment_'+data.att_id+'" type="hidden" name="attachments[]" value="'+data.att_id+'"/></a><br/><input type="button" class="btn_hidden" value="{$sDeleteBtn}" onClick="RemoveAttachment('+data.att_id+');"/></div>');
+						var iPrevCount = parseInt(aParts[2], 10);
+						jTab.find('span').html(aParts[1]+'('+(1 + iPrevCount)+')'+aParts[3]);						
+					}
 				}
 			}, 'json');
 			return false;
@@ -691,15 +702,15 @@ $(function()
 					return sTooltipContent;
 				},
 				items: '.popupMenuTarget',
+				tooltipClass: 'tooltip-simple-graph',
 				position: {
 					my: "center bottom-10",
-					at: "center  top",					
+					at: "center  top",	
 					using: function( position, feedback ) { 
 						$(this).css( position );  
 						$( "<div>" )
 						.addClass( "arrow" )
 						.addClass( feedback.vertical )
-						.addClass( feedback.horizontal )
 						.appendTo( this );
 						}
 				}
@@ -739,6 +750,11 @@ $(function()
 				var sDataId = $(this).attr('data-id');
 				$('.popupMenuTarget[data-id="'+sDataId+'"]').tooltip('close');
 			});
+			this.element.on("click", ":not(.tooltip-simple-graph *,.tooltip-simple-graph)", function(){
+				$('.popupMenuTarget').each(function (i) {  
+					$(this).tooltip("close"); 
+				});
+			});
 		},
 		_get_tooltip_content: function(sNodeId)
 		{

+ 1 - 0
pages/ajax.render.php

@@ -1851,6 +1851,7 @@ EOF
 			}
 			
 			$oPage->get_tcpdf()->AddPage();
+			$oPage->get_tcpdf()->SetFont('dejavusans', '', 10, '', true); // Reset the font size to its default
 			$oPage->add('<div class="page_header"><h1>'.Dict::S('UI:RelationshipList').'</h1></div>');
 			$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
 			foreach($aResults as $sListClass => $aObjects)