瀏覽代碼

Impact analysis diagram is now considered as beta !

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@3588 a333f486-631f-4898-b8df-5754b55c2be0
dflaven 10 年之前
父節點
當前提交
a886de7dbb

+ 39 - 27
core/displayablegraph.class.inc.php

@@ -235,8 +235,6 @@ class DisplayableNode extends GraphNode
 	 */
 	public function GroupSimilarNeighbours(DisplayableGraph $oGraph, $iThresholdCount, $bDirectionUp = false, $bDirectionDown = true)
 	{
-//echo "<p>".$this->GetProperty('label').":</p>";
-		
 		if ($this->GetProperty('grouped') === true) return;
 		$this->SetProperty('grouped', true);
 			
@@ -270,9 +268,7 @@ class DisplayableNode extends GraphNode
 					{
 						$aNodesPerClass[$sClass][$sKey]['nodes'][$oNode->GetId()] = $oNode;
 						$aNodesPerClass[$sClass][$sKey]['count'] += (int)$oNode->GetProperty('count', 1);
-//echo "<p>New count: ".$aNodesPerClass[$sClass][$sKey]['count']."</p>";
 					}
-						
 				}
 				else
 				{
@@ -284,16 +280,14 @@ class DisplayableNode extends GraphNode
 			{
 				foreach($aDefs as $sStatus => $aGroupProps)
 				{
-//echo "<p>$sClass/$sStatus: {$aGroupProps['count']} object(s), actually: ".count($aGroupProps['nodes'])."</p>";
 					if (count($aGroupProps['nodes']) >= $iThresholdCount)
 					{
-						$oNewNode = new DisplayableGroupNode($oGraph, $this->GetId().'::'.$sClass);
+						$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']);
-						//$oNewNode->SetProperty('grouped', true);
 						
 						$oIncomingEdge = new DisplayableEdge($oGraph, $this->GetId().'-'.$oNewNode->GetId(), $this, $oNewNode);
 										
@@ -349,7 +343,6 @@ class DisplayableNode extends GraphNode
 		{
 			foreach($aContextRootCauses as $key => $aObjects)
 			{
-				//$sHtml .= print_r($aContextDefs, true);
 				$aContext = $aContextDefs[$key];
 				$aRootCauses = array();
 				foreach($aObjects as $oRootCause)
@@ -458,7 +451,6 @@ class DisplayableRedundancyNode extends DisplayableNode
 			{
 				foreach($aDefs as $sStatus => $aNodes)
 				{
-//echo "<p>".$this->GetId().' has '.count($aNodes)." neighbours of class $sClass in status $sStatus\n";
 					if (count($aNodes) >= $iThresholdCount)
 					{
 						$oNewNode = new DisplayableGroupNode($oGraph, '-'.$this->GetId().'::'.$sClass.'/'.$sStatus);
@@ -482,7 +474,6 @@ class DisplayableRedundancyNode extends DisplayableNode
 									$oNewEdge = new DisplayableEdge($oGraph, '-'.$oEdge->GetId().'::'.$sClass.'/'.$sStatus, $oNewNode, $oEdge->GetSinkNode());
 								}
 							}
-//echo "<p>Replacing ".$oNode->GetId().' by '.$oNewNode->GetId()."\n";
 							$oGraph->_RemoveNode($oNode);
 							$oNewNode->AddObject($oNode->GetProperty('object'));
 						}
@@ -503,10 +494,10 @@ class DisplayableRedundancyNode extends DisplayableNode
 	public function GetTooltip($aContextDefs)
 	{
 		$sHtml = '';
-		$sHtml .= "Redundancy<hr>";
+		$sHtml .= Dict::S('UI:RelationTooltip:Redundancy')."<hr>";
 		$sHtml .= '<table><tbody>';
-		$sHtml .= "<tr><td># Items Impacted:&nbsp;</td><td>".$this->GetProperty('is_reached_count')."&nbsp;/&nbsp;".($this->GetProperty('min_up') + $this->GetProperty('threshold'))."</td></tr>";
-		$sHtml .= "<tr><td>Critical Threshold:&nbsp;</td><td>".$this->GetProperty('threshold')."&nbsp;/&nbsp;".($this->GetProperty('min_up') + $this->GetProperty('threshold'))."</td></tr>";
+		$sHtml .= "<tr><td>".Dict::Format('UI:RelationTooltip:ImpactedItems_N_of_M' , $this->GetProperty('is_reached_count'), $this->GetProperty('min_up') + $this->GetProperty('threshold'))."</td></tr>";
+		$sHtml .= "<tr><td>".Dict::Format('UI:RelationTooltip:CriticalThreshold_N_of_M' , $this->GetProperty('threshold'), $this->GetProperty('min_up') + $this->GetProperty('threshold'))."</td></tr>";
 		$sHtml .= '</tbody></table>';
 		return $sHtml;		
 	}
@@ -839,7 +830,6 @@ class DisplayableGraph extends SimpleGraph
 		$aChunks = explode(";", $sDot);
 		foreach($aChunks as $sChunk)
 		{
-			//echo "<p>$sChunk</p>";
 			if(preg_match('/"([^"]+)".+pos="([0-9\\.]+),([0-9\\.]+)"/ms', $sChunk, $aMatches))
 			{
 				$sId = $aMatches[1];
@@ -849,12 +839,6 @@ class DisplayableGraph extends SimpleGraph
 				$oNode = $this->GetNode($sId);
 				$oNode->x = (float)$xPos;
 				$oNode->y = (float)$yPos;
-				
-				//echo "<p>$sId at $xPos,$yPos</p>";
-			}
-			else
-			{
-				//echo "<p>No match</p>";
 			}
 		}
 	}
@@ -985,7 +969,7 @@ class DisplayableGraph extends SimpleGraph
 		
 		$fBreakMargin = $oPdf->getBreakMargin();
 		$oPdf->SetAutoPageBreak(false);
-		$aRemainingArea = $this->RenderKey($oPdf, $sComments, $xMin, $yMin, $xMax, $yMax);
+		$aRemainingArea = $this->RenderKey($oPdf, $sComments, $xMin, $yMin, $xMax, $yMax, $aContextDefs);
 		$xMin = $aRemainingArea['xmin'];
 		$xMax = $aRemainingArea['xmax'];
 		$yMin = $aRemainingArea['ymin'];
@@ -1033,9 +1017,10 @@ class DisplayableGraph extends SimpleGraph
 	 * @param float $yMin
 	 * @param float $xMax
 	 * @param float $yMax
+	 * @param hash $aContextDefs
 	 * @return hash An array ('xmin' => , 'xmax' => ,'ymin' => , 'ymax' => ) of the remaining available area to paint the graph
 	 */
-	protected function RenderKey(TCPDF $oPdf, $sComments, $xMin, $yMin, $xMax, $yMax)
+	protected function RenderKey(TCPDF $oPdf, $sComments, $xMin, $yMin, $xMax, $yMax, $aContextDefs)
 	{
 		$fFontSize = 7; // in mm
 		$fIconSize = 6; // in mm
@@ -1044,6 +1029,8 @@ class DisplayableGraph extends SimpleGraph
 		$fMaxWidth = max($oPdf->GetStringWidth(Dict::S('UI:Relation:Key')) - $fIconSize, $oPdf->GetStringWidth(Dict::S('UI:Relation:Comments')) - $fIconSize);
 		$aClasses = array();
 		$aIcons = array();
+		$aContexts = array();
+		$aContextIcons = array();
 		$oPdf->SetFont('dejavusans', '', $fFontSize, '', true);
 		foreach($oIterator as $sId => $oNode)
 		{
@@ -1060,6 +1047,15 @@ class DisplayableGraph extends SimpleGraph
 					$aIcons[$sClass] = $sIconPath;
 				}
 			}
+			$aContextRootCauses = $oNode->GetProperty('context_root_causes');
+			if (!is_null($aContextRootCauses))
+			{
+				foreach($aContextRootCauses as $key => $aObjects)
+				{
+					$aContexts[$key] = Dict::S($aContextDefs[$key]['dict']);
+					$aContextIcons[$key] = APPROOT.'env-'.utils::GetCurrentEnvironment().'/'.$aContextDefs[$key]['icon'];
+				}
+			}
 		}
 		$oPdf->SetXY($xMin + $fPadding, $yMin + $fPadding);
 		$yPos = $yMin + $fPadding;
@@ -1073,6 +1069,13 @@ class DisplayableGraph extends SimpleGraph
 			$oPdf->Image($aIcons[$sClass], $xMin+1, $yPos, $fIconSize, $fIconSize);
 			$yPos += $fIconSize + 2*$fPadding; 
 		}
+		foreach($aContexts as $key => $sLabel)
+		{
+			$oPdf->SetX($xMin + $fIconSize + $fPadding);
+			$oPdf->Cell(0, $fIconSize + 2*$fPadding, $sLabel, 0 /* border */, 1 /* ln */);
+			$oPdf->Image($aContextIcons[$key], $xMin+1+$fIconSize*0.125, $yPos+$fIconSize*0.125, $fIconSize*0.75, $fIconSize*0.75);
+			$yPos += $fIconSize + 2*$fPadding;
+		}
 		$oPdf->Rect($xMin, $yMin, $fMaxWidth + $fIconSize + 3*$fPadding, $yMax - $yMin, 'D');
 		
 		if ($sComments != '')
@@ -1093,20 +1096,29 @@ class DisplayableGraph extends SimpleGraph
 		return array('xmin' => $fMaxWidth + $fIconSize + 4*$fPadding, 'xmax' => $xMax, 'ymin' => $yMin, 'ymax' => $yMax);
 	}
 	
-	//itop-tickets/relation_context/UserRequest/impacts/down
 	/**
-	 * 
+	 * Get the context definitions from the parameters / configuration. The format of the "key" string is:
+	 * <module>/relation_context/<class>/<relation>/<direction>
+	 * The values will be retrieved for the given class and all its parents and merged together as a single array.
+	 * Entries with an invalid query are removed from the list.
 	 * @param string $sContextKey The key to fetch the queries in the configuration. Example: itop-tickets/relation_context/UserRequest/impacts/down
+	 * @param bool $bDevelopParams Whether or not to substitute the parameters inside the queries with the supplied "context params"
+	 * @param array $aContextParams Arguments for the queries (via ToArgs()) if $bDevelopParams == true
+	 * @return multitype:multitype:string
 	 */
 	public function GetContextDefinitions($sContextKey, $bDevelopParams = true, $aContextParams = array())
 	{
 		$aLevels = explode('/', $sContextKey);
+		$sLeafClass = $aLevels[2];
+		
 		$aRelationContext = MetaModel::GetConfig()->GetModuleSetting($aLevels[0], $aLevels[1], array());
 		$aContextDefs = array();
-		if (isset($aRelationContext[$aLevels[2]][$aLevels[3]][$aLevels[4]]['items']))
+		foreach(MetaModel::EnumParentClasses($sLeafClass, ENUM_PARENT_CLASSES_ALL) as $sClass)
 		{
-			$aContextDefs = $aRelationContext[$aLevels[2]][$aLevels[3]][$aLevels[4]]['items'];
-
+			if (isset($aRelationContext[$sClass][$aLevels[3]][$aLevels[4]]['items']))
+			{
+				$aContextDefs = array_merge($aContextDefs, $aRelationContext[$sClass][$aLevels[3]][$aLevels[4]]['items']);
+			}
 		}
 		
 		// Check if the queries are valid

+ 90 - 54
datamodels/2.x/itop-change-mgmt-itil/datamodel.itop-change-mgmt-itil.xml

@@ -4518,62 +4518,98 @@
   </menus>
   <module_parameters>
     <parameters id="itop-tickets">
-    <relation_context>
-      <UserRequest>
-        <impacts>
-        <down>
-          <items type="array">
-             <item id="open_changes" _delta="define">
-              <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed', 'rejected')) AND (L.impact_code != 'not_impacted') AND (C.id != :this->id)]]></oql>
-              <dict>Tickets:Related:OpenChanges</dict>
-              <icon>itop-change-mgmt/images/change-ongoing.png</icon>
-            </item>
-             <item id="recent_changes" _delta="define">
-              <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed')) AND (L.impact_code != 'not_impacted') AND (C.id != :this->id) AND (DATE_ADD(C.end_date, INTERVAL 3 DAY) < NOW())]]></oql>
-              <dict>Tickets:Related:RecentChanges</dict>
-              <icon>itop-change-mgmt/images/change-done.png</icon>
-            </item>
-          </items>
-        </down>
-      </impacts>
-      </UserRequest>
-    </relation_context>
+      <relation_context>
+        <UserRequest>
+          <impacts>
+            <down>
+              <items type="array">
+                <item id="open_changes" _delta="define">
+                  <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed', 'rejected')) AND (L.impact_code != 'not_impacted') AND (C.id != :this->id)]]></oql>
+                  <dict>Tickets:Related:OpenChanges</dict>
+                  <icon>itop-change-mgmt/images/change-ongoing.png</icon>
+                </item>
+                <item id="recent_changes" _delta="define">
+                  <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed')) AND (L.impact_code != 'not_impacted') AND (C.id != :this->id) AND (DATE_ADD(C.end_date, INTERVAL 3 DAY) < NOW())]]></oql>
+                  <dict>Tickets:Related:RecentChanges</dict>
+                  <icon>itop-change-mgmt/images/change-done.png</icon>
+                </item>
+              </items>
+            </down>
+          </impacts>
+        </UserRequest>
+        <Incident>
+          <impacts>
+            <down>
+              <items type="array">
+                <item id="open_changes" _delta="define">
+                  <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed', 'rejected')) AND (L.impact_code != 'not_impacted') AND (C.id != :this->id)]]></oql>
+                  <dict>Tickets:Related:OpenChanges</dict>
+                  <icon>itop-change-mgmt/images/change-ongoing.png</icon>
+                </item>
+                <item id="recent_changes" _delta="define">
+                  <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed')) AND (L.impact_code != 'not_impacted') AND (C.id != :this->id) AND (DATE_ADD(C.end_date, INTERVAL 3 DAY) < NOW())]]></oql>
+                  <dict>Tickets:Related:RecentChanges</dict>
+                  <icon>itop-change-mgmt/images/change-done.png</icon>
+                </item>
+              </items>
+            </down>
+          </impacts>
+        </Incident>
+        <Change>
+          <impacts>
+            <down>
+              <items type="array">
+                <item id="open_changes" _delta="define">
+                  <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed', 'rejected')) AND (L.impact_code != 'not_impacted') AND (C.id != :this->id)]]></oql>
+                  <dict>Tickets:Related:OpenChanges</dict>
+                  <icon>itop-change-mgmt/images/change-ongoing.png</icon>
+                </item>
+                <item id="recent_changes" _delta="define">
+                  <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed')) AND (L.impact_code != 'not_impacted') AND (C.id != :this->id) AND (DATE_ADD(C.end_date, INTERVAL 3 DAY) < NOW())]]></oql>
+                  <dict>Tickets:Related:RecentChanges</dict>
+                  <icon>itop-change-mgmt/images/change-done.png</icon>
+                </item>
+              </items>
+            </down>
+          </impacts>
+        </Change>
+      </relation_context>
     </parameters>
     <parameters id="itop-config-mgmt">
-    <relation_context>
-      <FunctionalCI>
-        <impacts>
-          <down>
-            <items type="array">
-               <item id="open_changes" _delta="define">
-                <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed', 'rejected')) AND (L.impact_code != 'not_impacted')]]></oql>
-                <dict>Tickets:Related:OpenChanges</dict>
-                <icon>itop-change-mgmt/images/change-ongoing.png</icon>
-              </item>
-               <item id="recent_changes" _delta="define">
-                <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed')) AND (L.impact_code != 'not_impacted') AND (DATE_ADD(C.end_date, INTERVAL 3 DAY) < NOW())]]></oql>
-                <dict>Tickets:Related:RecentChanges</dict>
-                <icon>itop-change-mgmt/images/change-done.png</icon>
-              </item>
-            </items>
-          </down>
-          <up>
-            <items type="array">
-               <item id="open_changes" _delta="define">
-                <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed', 'rejected')) AND (L.impact_code != 'not_impacted') AND (C.id != :this->id)]]></oql>
-                <dict>Tickets:Related:OpenChanges</dict>
-                <icon>itop-change-mgmt/images/change-ongoing.png</icon>
-              </item>
-               <item id="recent_changes" _delta="define">
-                <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed')) AND (L.impact_code != 'not_impacted') AND (C.id != :this->id) AND (DATE_ADD(C.end_date, INTERVAL 3 DAY) < NOW())]]></oql>
-                <dict>Tickets:Related:RecentChanges</dict>
-                <icon>itop-change-mgmt/images/change-done.png</icon>
-              </item>
-            </items>
-          </up>
-        </impacts>
-      </FunctionalCI>
-    </relation_context>
+      <relation_context>
+        <FunctionalCI>
+          <impacts>
+            <down>
+              <items type="array">
+                <item id="open_changes" _delta="define">
+                  <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed', 'rejected')) AND (L.impact_code != 'not_impacted')]]></oql>
+                  <dict>Tickets:Related:OpenChanges</dict>
+                  <icon>itop-change-mgmt/images/change-ongoing.png</icon>
+                </item>
+                <item id="recent_changes" _delta="define">
+                  <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status IN ('closed')) AND (L.impact_code != 'not_impacted') AND (DATE_ADD(C.end_date, INTERVAL 3 DAY) < NOW())]]></oql>
+                  <dict>Tickets:Related:RecentChanges</dict>
+                  <icon>itop-change-mgmt/images/change-done.png</icon>
+                </item>
+              </items>
+            </down>
+            <up>
+              <items type="array">
+                <item id="open_changes" _delta="define">
+                  <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed', 'rejected')) AND (L.impact_code != 'not_impacted')]]></oql>
+                  <dict>Tickets:Related:OpenChanges</dict>
+                  <icon>itop-change-mgmt/images/change-ongoing.png</icon>
+                </item>
+                <item id="recent_changes" _delta="define">
+                  <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status IN ('closed')) AND (L.impact_code != 'not_impacted') AND (DATE_ADD(C.end_date, INTERVAL 3 DAY) < NOW())]]></oql>
+                  <dict>Tickets:Related:RecentChanges</dict>
+                  <icon>itop-change-mgmt/images/change-done.png</icon>
+                </item>
+              </items>
+            </up>
+          </impacts>
+        </FunctionalCI>
+      </relation_context>
     </parameters>
   </module_parameters>
 </itop_design>

+ 90 - 54
datamodels/2.x/itop-change-mgmt/datamodel.itop-change-mgmt.xml

@@ -819,62 +819,98 @@
   </menus>
   <module_parameters>
     <parameters id="itop-tickets">
-    <relation_context>
-      <UserRequest>
-        <impacts>
-          <down>
-            <items type="array">
-               <item id="open_changes" _delta="define">
-                <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed', 'rejected')) AND (L.impact_code != 'not_impacted') AND (C.id != :this->id)]]></oql>
-                <dict>Tickets:Related:OpenChanges</dict>
-                <icon>itop-change-mgmt/images/change-ongoing.png</icon>
-              </item>
-               <item id="recent_changes" _delta="define">
-                <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed')) AND (L.impact_code != 'not_impacted') AND (C.id != :this->id) AND (DATE_ADD(C.end_date, INTERVAL 3 DAY) < NOW())]]></oql>
-                <dict>Tickets:Related:RecentChanges</dict>
-                <icon>itop-change-mgmt/images/change-done.png</icon>
-              </item>
-            </items>
-          </down>
-        </impacts>
-      </UserRequest>
-    </relation_context>
+      <relation_context>
+        <UserRequest>
+          <impacts>
+            <down>
+              <items type="array">
+                <item id="open_changes" _delta="define">
+                  <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed', 'rejected')) AND (L.impact_code != 'not_impacted') AND (C.id != :this->id)]]></oql>
+                  <dict>Tickets:Related:OpenChanges</dict>
+                  <icon>itop-change-mgmt/images/change-ongoing.png</icon>
+                </item>
+                <item id="recent_changes" _delta="define">
+                  <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed')) AND (L.impact_code != 'not_impacted') AND (C.id != :this->id) AND (DATE_ADD(C.end_date, INTERVAL 3 DAY) < NOW())]]></oql>
+                  <dict>Tickets:Related:RecentChanges</dict>
+                  <icon>itop-change-mgmt/images/change-done.png</icon>
+                </item>
+              </items>
+            </down>
+          </impacts>
+        </UserRequest>
+        <Incident>
+          <impacts>
+            <down>
+              <items type="array">
+                <item id="open_changes" _delta="define">
+                  <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed', 'rejected')) AND (L.impact_code != 'not_impacted') AND (C.id != :this->id)]]></oql>
+                  <dict>Tickets:Related:OpenChanges</dict>
+                  <icon>itop-change-mgmt/images/change-ongoing.png</icon>
+                </item>
+                <item id="recent_changes" _delta="define">
+                  <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed')) AND (L.impact_code != 'not_impacted') AND (C.id != :this->id) AND (DATE_ADD(C.end_date, INTERVAL 3 DAY) < NOW())]]></oql>
+                  <dict>Tickets:Related:RecentChanges</dict>
+                  <icon>itop-change-mgmt/images/change-done.png</icon>
+                </item>
+              </items>
+            </down>
+          </impacts>
+        </Incident>
+        <Change>
+          <impacts>
+            <down>
+              <items type="array">
+                <item id="open_changes" _delta="define">
+                  <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed', 'rejected')) AND (L.impact_code != 'not_impacted') AND (C.id != :this->id)]]></oql>
+                  <dict>Tickets:Related:OpenChanges</dict>
+                  <icon>itop-change-mgmt/images/change-ongoing.png</icon>
+                </item>
+                <item id="recent_changes" _delta="define">
+                  <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed')) AND (L.impact_code != 'not_impacted') AND (C.id != :this->id) AND (DATE_ADD(C.end_date, INTERVAL 3 DAY) < NOW())]]></oql>
+                  <dict>Tickets:Related:RecentChanges</dict>
+                  <icon>itop-change-mgmt/images/change-done.png</icon>
+                </item>
+              </items>
+            </down>
+          </impacts>
+        </Change>
+      </relation_context>
     </parameters>
     <parameters id="itop-config-mgmt">
-    <relation_context>
-      <FunctionalCI>
-        <impacts>
-          <down>
-            <items type="array">
-               <item id="open_changes" _delta="define">
-                <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed', 'rejected')) AND (L.impact_code != 'not_impacted')]]></oql>
-                <dict>Tickets:Related:OpenChanges</dict>
-                <icon>itop-change-mgmt/images/change-ongoing.png</icon>
-              </item>
-               <item id="recent_changes" _delta="define">
-                <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed')) AND (L.impact_code != 'not_impacted') AND (DATE_ADD(C.end_date, INTERVAL 3 DAY) < NOW())]]></oql>
-                <dict>Tickets:Related:RecentChanges</dict>
-                <icon>itop-change-mgmt/images/change-done.png</icon>
-              </item>
-            </items>
-          </down>
-          <up>
-            <items type="array">
-               <item id="open_changes" _delta="define">
-                <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed', 'rejected')) AND (L.impact_code != 'not_impacted') AND (C.id != :this->id)]]></oql>
-                <dict>Tickets:Related:OpenChanges</dict>
-                <icon>itop-change-mgmt/images/change-ongoing.png</icon>
-              </item>
-               <item id="recent_changes" _delta="define">
-                <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed')) AND (L.impact_code != 'not_impacted') AND (C.id != :this->id) AND (DATE_ADD(C.end_date, INTERVAL 3 DAY) < NOW())]]></oql>
-                <dict>Tickets:Related:RecentChanges</dict>
-                <icon>itop-change-mgmt/images/change-done.png</icon>
-              </item>
-            </items>
-          </up>
-        </impacts>
-      </FunctionalCI>
-    </relation_context>
+      <relation_context>
+        <FunctionalCI>
+          <impacts>
+            <down>
+              <items type="array">
+                <item id="open_changes" _delta="define">
+                  <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed', 'rejected')) AND (L.impact_code != 'not_impacted')]]></oql>
+                  <dict>Tickets:Related:OpenChanges</dict>
+                  <icon>itop-change-mgmt/images/change-ongoing.png</icon>
+                </item>
+                <item id="recent_changes" _delta="define">
+                  <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status IN ('closed')) AND (L.impact_code != 'not_impacted') AND (DATE_ADD(C.end_date, INTERVAL 3 DAY) < NOW())]]></oql>
+                  <dict>Tickets:Related:RecentChanges</dict>
+                  <icon>itop-change-mgmt/images/change-done.png</icon>
+                </item>
+              </items>
+            </down>
+            <up>
+              <items type="array">
+                <item id="open_changes" _delta="define">
+                  <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status NOT IN ('closed', 'rejected')) AND (L.impact_code != 'not_impacted')]]></oql>
+                  <dict>Tickets:Related:OpenChanges</dict>
+                  <icon>itop-change-mgmt/images/change-ongoing.png</icon>
+                </item>
+                <item id="recent_changes" _delta="define">
+                  <oql><![CDATA[SELECT FCI, C FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Change AS C ON L.ticket_id = C.id WHERE (C.status IN ('closed')) AND (L.impact_code != 'not_impacted') AND (DATE_ADD(C.end_date, INTERVAL 3 DAY) < NOW())]]></oql>
+                  <dict>Tickets:Related:RecentChanges</dict>
+                  <icon>itop-change-mgmt/images/change-done.png</icon>
+                </item>
+              </items>
+            </up>
+          </impacts>
+        </FunctionalCI>
+      </relation_context>
     </parameters>
   </module_parameters>
 </itop_design>

+ 28 - 2
datamodels/2.x/itop-incident-mgmt-itil/datamodel.itop-incident-mgmt-itil.xml

@@ -1764,6 +1764,19 @@
   <module_parameters>
     <parameters id="itop-tickets">
       <relation_context>
+        <UserRequest>
+          <impacts>
+            <down>
+              <items type="array">
+                <item id="open_incidents" _delta="define">
+                  <oql><![CDATA[SELECT FCI, I FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Incident AS I ON L.ticket_id = I.id WHERE (I.status NOT IN ('closed', 'resolved')) AND (L.impact_code != 'not_impacted') AND (I.id != :this->id)]]></oql>
+                  <dict>Tickets:Related:OpenIncidents</dict>
+                  <icon>itop-request-mgmt/images/incident-red.png</icon>
+                </item>
+              </items>
+            </down>
+          </impacts>
+        </UserRequest>
         <Incident>
           <impacts>
             <down>
@@ -1777,6 +1790,19 @@
             </down>
           </impacts>
         </Incident>
+        <Change>
+          <impacts>
+            <down>
+              <items type="array">
+                <item id="open_incidents" _delta="define">
+                  <oql><![CDATA[SELECT FCI, I FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Incident AS I ON L.ticket_id = I.id WHERE (I.status NOT IN ('closed', 'resolved')) AND (L.impact_code != 'not_impacted') AND (I.id != :this->id)]]></oql>
+                  <dict>Tickets:Related:OpenIncidents</dict>
+                  <icon>itop-request-mgmt/images/incident-red.png</icon>
+                </item>
+              </items>
+            </down>
+          </impacts>
+        </Change>
       </relation_context>
     </parameters>
     <parameters id="itop-config-mgmt">
@@ -1786,7 +1812,7 @@
             <down>
               <items type="array">
                 <item id="open_incidents" _delta="define">
-                  <oql><![CDATA[SELECT FCI, I FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Incident AS I ON L.ticket_id = I.id WHERE (I.status NOT IN ('closed', 'resolved')) AND (L.impact_code != 'not_impacted') AND (I.id != :this->id)]]></oql>
+                  <oql><![CDATA[SELECT FCI, I FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Incident AS I ON L.ticket_id = I.id WHERE (I.status NOT IN ('closed', 'resolved')) AND (L.impact_code != 'not_impacted')]]></oql>
                   <dict>Tickets:Related:OpenIncidents</dict>
                   <icon>itop-request-mgmt/images/incident-red.png</icon>
                 </item>
@@ -1795,7 +1821,7 @@
             <up>
               <items type="array">
                 <item id="open_incidents" _delta="define">
-                  <oql><![CDATA[SELECT FCI, I FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Incident AS I ON L.ticket_id = I.id WHERE (I.status NOT IN ('closed', 'resolved')) AND (L.impact_code != 'not_impacted') AND (I.id != :this->id)]]></oql>
+                  <oql><![CDATA[SELECT FCI, I FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN Incident AS I ON L.ticket_id = I.id WHERE (I.status NOT IN ('closed', 'resolved')) AND (L.impact_code != 'not_impacted')]]></oql>
                   <dict>Tickets:Related:OpenIncidents</dict>
                   <icon>itop-request-mgmt/images/incident-red.png</icon>
                 </item>

+ 15 - 2
datamodels/2.x/itop-request-mgmt/datamodel.itop-request-mgmt.xml

@@ -1860,6 +1860,19 @@
             </down>
           </impacts>
         </UserRequest>
+        <Change>
+          <impacts>
+            <down>
+              <items type="array">
+                <item id="open_incidents" _delta="define">
+                  <oql><![CDATA[SELECT FCI, R FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN UserRequest AS R ON L.ticket_id = R.id WHERE (R.status NOT IN ('closed', 'resolved')) AND (R.request_type='incident') AND (L.impact_code != 'not_impacted') AND (R.id != :this->id)]]></oql>
+                  <dict>Tickets:Related:OpenIncidents</dict>
+                  <icon>itop-request-mgmt/images/incident-red.png</icon>
+                </item>
+              </items>
+            </down>
+          </impacts>
+        </Change>
       </relation_context>
     </parameters>
     <parameters id="itop-config-mgmt">
@@ -1869,7 +1882,7 @@
             <down>
               <items type="array">
                 <item id="open_incidents" _delta="define">
-                  <oql><![CDATA[SELECT FCI, R FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN UserRequest AS R ON L.ticket_id = R.id WHERE (R.status NOT IN ('closed', 'resolved')) AND (R.request_type='incident') AND (L.impact_code != 'not_impacted') AND (R.id != :this->id)]]></oql>
+                  <oql><![CDATA[SELECT FCI, R FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN UserRequest AS R ON L.ticket_id = R.id WHERE (R.status NOT IN ('closed', 'resolved')) AND (R.request_type='incident') AND (L.impact_code != 'not_impacted')]]></oql>
                   <dict>Tickets:Related:OpenIncidents</dict>
                   <icon>itop-request-mgmt/images/incident-red.png</icon>
                 </item>
@@ -1878,7 +1891,7 @@
             <up>
               <items type="array">
                 <item id="open_incidents" _delta="define">
-                  <oql><![CDATA[SELECT FCI, R FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN UserRequest AS R ON L.ticket_id = R.id WHERE (R.status NOT IN ('closed', 'resolved')) AND (R.request_type='incident') AND (L.impact_code != 'not_impacted') AND (R.id != :this->id)]]></oql>
+                  <oql><![CDATA[SELECT FCI, R FROM FunctionalCI AS FCI JOIN lnkFunctionalCIToTicket AS L ON L.functionalci_id = FCI.id JOIN UserRequest AS R ON L.ticket_id = R.id WHERE (R.status NOT IN ('closed', 'resolved')) AND (R.request_type='incident') AND (L.impact_code != 'not_impacted')]]></oql>
                   <dict>Tickets:Related:OpenIncidents</dict>
                   <icon>itop-request-mgmt/images/incident-red.png</icon>
                 </item>

+ 3 - 0
dictionaries/de.dictionary.itop.ui.php

@@ -798,6 +798,9 @@ Wenn Aktionen mit Trigger verknüpft sind, bekommt jede Aktion eine Auftragsnumm
 	'UI:Relation:PDFExportPageOrientation' => 'Page orientation~~',
 	'UI:PageOrientation_Portrait' => 'Portrait~~',
 	'UI:PageOrientation_Landscape' => 'Landscape~~',
+	'UI:RelationTooltip:Redundancy' => 'Redundancy~~',
+	'UI:RelationTooltip:ImpactedItems_N_of_M' => '# of impacted items: %1$d / %2$d~~',
+	'UI:RelationTooltip:CriticalThreshold_N_of_M' => 'Critical threshold: %1$d / %2$d~~',
 	'UI:OperationCancelled' => 'Operation abgebrochen',
 	'UI:ElementsDisplayed' => 'Filtere',
 	'Portal:Title' => 'iTop-Benutzerportal',

+ 4 - 1
dictionaries/dictionary.itop.ui.php

@@ -990,7 +990,10 @@ When associated with a trigger, each action is given an "order" number, specifyi
 	'UI:PageFormat_Letter' => 'Letter',
 	'UI:Relation:PDFExportPageOrientation' => 'Page orientation',
 	'UI:PageOrientation_Portrait' => 'Portrait',
-	'UI:PageOrientation_Landscape' => 'Landscape',	
+	'UI:PageOrientation_Landscape' => 'Landscape',
+	'UI:RelationTooltip:Redundancy' => 'Redundancy',
+	'UI:RelationTooltip:ImpactedItems_N_of_M' => '# of impacted items: %1$d / %2$d',
+	'UI:RelationTooltip:CriticalThreshold_N_of_M' => 'Critical threshold: %1$d / %2$d',
 	'Portal:Title' => 'iTop user portal',
 	'Portal:NoRequestMgmt' => 'Dear %1$s, you have been redirected to this page because your account is configured with the profile \'Portal user\'. Unfortunately, iTop has not been installed with the feature \'Request Management\'. Please contact your administrator.',
 	'Portal:Refresh' => 'Refresh',

+ 3 - 0
dictionaries/fr.dictionary.itop.ui.php

@@ -834,6 +834,9 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
 	'UI:Relation:PDFExportPageOrientation' => 'Orientation de la page',
 	'UI:PageOrientation_Portrait' => 'Portrait',
 	'UI:PageOrientation_Landscape' => 'Paysage',
+	'UI:RelationTooltip:Redundancy' => 'Redondance',
+	'UI:RelationTooltip:ImpactedItems_N_of_M' => 'Nb éléments impactés: %1$d / %2$d',
+	'UI:RelationTooltip:CriticalThreshold_N_of_M' => 'Seuil critique: %1$d / %2$d',
 	'UI:OperationCancelled' => 'Opération Annulée',
 	'Portal:Title' => 'Portail utilisateur iTop',
 	'Portal:NoRequestMgmt' => 'Chèr(e) %1$s, vous avez été redirigé(e) vers cette page car votre compte utilisateur est configuré avec le profil \'Utilisateur du Portail\'. Malheureusement, iTop n\'a pas été installé avec le module de \'Gestion des Demandes\'. Merci de contacter votre administrateur iTop.',

+ 7 - 0
js/simple_graph.js

@@ -448,6 +448,7 @@ $(function()
 		        	var sType = trigger.attr('data-type');
 		        	var sNodeId = trigger.attr('data-id');
 		        	var oNode = me._find_node(sNodeId);
+		        	clearTimeout(trigger.data('openTimeoutId'));
 		        	
 		        	/*
 		        	var sObjName = trigger.attr('data-class');
@@ -643,6 +644,12 @@ $(function()
 			oParams.p = jForm.find(':input[name="p"]').val();
 			oParams.obj_class = this.options.export_as_attachment.obj_class;
 			oParams.obj_key = this.options.export_as_attachment.obj_key;
+			oParams.contexts = [];
+			var me = this;
+			$('#'+this.element.attr('id')+'_contexts').multiselect('getChecked').each(function() {
+				oParams.contexts[$(this).val()] = me.options.additional_contexts[$(this).val()].oql;				
+			});
+			oParams.context_key = this.options.context_key;
 			var sUrl = jForm.attr('action');
 			var sTitle = oParams.title;
 			var jPanel = $('#attachments').closest('.ui-tabs-panel');

+ 1 - 2
pages/UI.php

@@ -1439,7 +1439,6 @@ EOF
 		$iGroupingThreshold = utils::ReadParam('g', 5);
 		
 		$oObj = MetaModel::GetObject($sClass, $id);
-		$sRootClass = MetaModel::GetRootClass($sClass);
 		$iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth', 20);
 		$aSourceObjects = array($oObj);
 		if ($sRelation == 'depends on')
@@ -1477,7 +1476,7 @@ EOF
 		$oP->SetCurrentTabContainer('Navigator');
 		
 		$sFirstTab = MetaModel::GetConfig()->Get('impact_analysis_first_tab');
-		$sContextKey = "itop-config-mgmt/relation_context/$sRootClass/$sRelation/$sDirection";
+		$sContextKey = "itop-config-mgmt/relation_context/$sClass/$sRelation/$sDirection";
 		
 		// Check if the current object supports Attachments, similar to AttachmentPlugin::IsTargetObject
 		$sClassForAttachment = null;

+ 1 - 1
pages/ajax.render.php

@@ -1744,7 +1744,7 @@ EOF
 		$bIncludeList = (bool)utils::ReadParam('include_list', false);
 		$sComments = utils::ReadParam('comments', '', false, 'raw_data');
 		$aContexts = utils::ReadParam('contexts', array(), false, 'raw_data');
-		$sContextKey = utils::ReadParam('context_key', array(), false, 'raw_data');
+		$sContextKey = utils::ReadParam('context_key', '', false, 'raw_data');
 		$aPositions = null;
 		if ($sPositions != null)
 		{