From 0769b2c481fe85326e5998e280d48cf542ad1f69 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 24 May 2015 20:47:11 +0000 Subject: [PATCH] Relations & Impact analysis enhancements: - Detailled tooltips in the graph - Context queries ("knowing that") SVN:trunk[3583] --- core/displayablegraph.class.inc.php | 223 +++++++++++++++--- core/metamodel.class.php | 12 +- core/relationgraph.class.inc.php | 99 +++++++- .../datamodel.itop-change-mgmt-itil.xml | 60 +++++ .../images/change-done.png | Bin 0 -> 1659 bytes .../images/change-ongoing.png | Bin 0 -> 1609 bytes .../datamodel.itop-change-mgmt.xml | 60 +++++ .../de.dict.itop-change-mgmt.php | 2 + .../en.dict.itop-change-mgmt.php | 2 + .../fr.dict.itop-change-mgmt.php | 5 +- .../itop-change-mgmt/images/change-done.png | Bin 0 -> 1659 bytes .../images/change-ongoing.png | Bin 0 -> 1609 bytes .../datamodel.itop-incident-mgmt-itil.xml | 45 ++++ .../images/incident-red.png | Bin 0 -> 1913 bytes .../datamodel.itop-request-mgmt.xml | 45 ++++ .../de.dict.itop-request-mgmt.php | 1 + .../en.dict.itop-request-mgmt.php | 1 + .../fr.dict.itop-request-mgmt.php | 1 + .../itop-request-mgmt/images/incident-red.png | Bin 0 -> 1913 bytes dictionaries/de.dictionary.itop.ui.php | 2 + dictionaries/dictionary.itop.ui.php | 2 + dictionaries/fr.dictionary.itop.ui.php | 2 + js/simple_graph.js | 136 ++++++++++- pages/UI.php | 23 +- pages/ajax.render.php | 26 +- setup/modelfactory.class.inc.php | 12 +- 26 files changed, 688 insertions(+), 71 deletions(-) create mode 100755 datamodels/2.x/itop-change-mgmt-itil/images/change-done.png create mode 100755 datamodels/2.x/itop-change-mgmt-itil/images/change-ongoing.png create mode 100755 datamodels/2.x/itop-change-mgmt/images/change-done.png create mode 100755 datamodels/2.x/itop-change-mgmt/images/change-ongoing.png create mode 100755 datamodels/2.x/itop-incident-mgmt-itil/images/incident-red.png create mode 100755 datamodels/2.x/itop-request-mgmt/images/incident-red.png diff --git a/core/displayablegraph.class.inc.php b/core/displayablegraph.class.inc.php index 19d96b5da..4bdf7ae99 100644 --- a/core/displayablegraph.class.inc.php +++ b/core/displayablegraph.class.inc.php @@ -81,7 +81,7 @@ class DisplayableNode extends GraphNode return sqrt($this->Distance2($oNode)); } - public function GetForRaphael() + public function GetForRaphael($aContextDefs) { $aNode = array(); $aNode['shape'] = 'icon'; @@ -97,18 +97,28 @@ class DisplayableNode extends GraphNode $aNode['id'] = $this->GetId(); $fOpacity = ($this->GetProperty('is_reached') ? 1 : 0.4); $aNode['icon_attr'] = array('opacity' => $fOpacity); - $aNode['text_attr'] = array('opacity' => $fOpacity); + $aNode['text_attr'] = array('opacity' => $fOpacity); + $aNode['tooltip'] = $this->GetTooltip($aContextDefs); + $aNode['context_icons'] = array(); + $aContextRootCauses = $this->GetProperty('context_root_causes'); + if (!is_null($aContextRootCauses)) + { + foreach($aContextRootCauses as $key => $aObjects) + { + $aNode['context_icons'][] = utils::GetAbsoluteUrlModulesRoot().$aContextDefs[$key]['icon']; + } + } return $aNode; } - public function RenderAsPDF(TCPDF $oPdf, DisplayableGraph $oGraph, $fScale) + public function RenderAsPDF(TCPDF $oPdf, DisplayableGraph $oGraph, $fScale, $aContextDefs) { $Alpha = 1.0; $oPdf->SetFillColor(200, 200, 200); $oPdf->setAlpha(1); $sIconUrl = $this->GetProperty('icon_url'); - $sIconPath = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-production/', $sIconUrl); + $sIconPath = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-'.utils::GetCurrentEnvironment().'/', $sIconUrl); if ($this->GetProperty('source')) { @@ -134,6 +144,24 @@ class DisplayableNode extends GraphNode $oPdf->Image($sIconPath, ($this->x - 16)*$fScale, ($this->y - 16)*$fScale, 32*$fScale, 32*$fScale); + $aContextRootCauses = $this->GetProperty('context_root_causes'); + if (!is_null($aContextRootCauses)) + { + $idx = 0; + foreach($aContextRootCauses as $key => $aObjects) + { + $sgn = 2*($idx %2) -1; + $coef = floor((1+$idx)/2) * $sgn; + $alpha = $coef*pi()/4 - pi()/2; + $x = $this->x * $fScale + cos($alpha) * 16*1.25 * $fScale; + $y = $this->y * $fScale + sin($alpha) * 16*1.25 * $fScale; + $l = 32 * $fScale / 3; + $sIconPath = APPROOT.'env-'.utils::GetCurrentEnvironment().'/'.$aContextDefs[$key]['icon']; + $oPdf->Image($sIconPath, $x - $l/2, $y - $l/2, $l, $l); + $idx++; + } + } + $oPdf->SetFont('dejavusans', '', 24 * $fScale, '', true); $width = $oPdf->GetStringWidth($this->GetProperty('label')); $height = $oPdf->GetStringHeight(1000, $this->GetProperty('label')); @@ -309,6 +337,38 @@ class DisplayableNode extends GraphNode } } } + + public function GetTooltip($aContextDefs) + { + $sHtml = ''; + $oCurrObj = $this->GetProperty('object'); + $sSubClass = get_class($oCurrObj); + $sHtml .= $oCurrObj->GetHyperlink()."
"; + $aContextRootCauses = $this->GetProperty('context_root_causes'); + if (!is_null($aContextRootCauses)) + { + foreach($aContextRootCauses as $key => $aObjects) + { + //$sHtml .= print_r($aContextDefs, true); + $aContext = $aContextDefs[$key]; + $aRootCauses = array(); + foreach($aObjects as $oRootCause) + { + $aRootCauses[] = $oRootCause->GetHyperlink(); + } + $sHtml .= '

 '.implode(', ', $aRootCauses).'

'; + } + $sHtml .= '
'; + } + $sHtml .= ''; + foreach(MetaModel::GetZListItems($sSubClass, 'list') as $sAttCode) + { + $oAttDef = MetaModel::GetAttributeDef($sSubClass, $sAttCode); + $sHtml .= ''; + } + $sHtml .= '
'.$oAttDef->GetLabel().': '.$oCurrObj->GetAsHtml($sAttCode).'
'; + return $sHtml; + } } class DisplayableRedundancyNode extends DisplayableNode @@ -318,7 +378,7 @@ class DisplayableRedundancyNode extends DisplayableNode return 24; } - public function GetForRaphael() + public function GetForRaphael($aContextDefs) { $aNode = array(); $aNode['shape'] = 'disc'; @@ -330,16 +390,25 @@ class DisplayableRedundancyNode extends DisplayableNode $aNode['label'] = $this->GetLabel(); $aNode['id'] = $this->GetId(); $fDiscOpacity = ($this->GetProperty('is_reached') ? 1 : 0.2); - $aNode['disc_attr'] = array('stroke-width' => 3, 'stroke' => '#000', 'fill' => '#c33', 'opacity' => $fDiscOpacity); + $sColor = ($this->GetProperty('is_reached_count') > $this->GetProperty('threshold')) ? '#c33' : '#999'; + $aNode['disc_attr'] = array('stroke-width' => 3, 'stroke' => '#000', 'fill' => $sColor, 'opacity' => $fDiscOpacity); $fTextOpacity = ($this->GetProperty('is_reached') ? 1 : 0.4); - $aNode['text_attr'] = array('fill' => '#fff', 'opacity' => $fTextOpacity); + $aNode['text_attr'] = array('fill' => '#fff', 'opacity' => $fTextOpacity); + $aNode['tooltip'] = $this->GetTooltip($aContextDefs); return $aNode; } - public function RenderAsPDF(TCPDF $oPdf, DisplayableGraph $oGraph, $fScale) + public function RenderAsPDF(TCPDF $oPdf, DisplayableGraph $oGraph, $fScale, $aContextDefs) { $oPdf->SetAlpha(1); - $oPdf->SetFillColor(200, 0, 0); + if($this->GetProperty('is_reached_count') > $this->GetProperty('threshold')) + { + $oPdf->SetFillColor(200, 0, 0); + } + else + { + $oPdf->SetFillColor(144, 144, 144); + } $oPdf->SetDrawColor(0, 0, 0); $oPdf->Circle($this->x*$fScale, $this->y*$fScale, 16*$fScale, 0, 360, 'DF'); @@ -430,11 +499,22 @@ class DisplayableRedundancyNode extends DisplayableNode } } } + + public function GetTooltip($aContextDefs) + { + $sHtml = ''; + $sHtml .= "Redundancy
"; + $sHtml .= ''; + $sHtml .= ""; + $sHtml .= ""; + $sHtml .= '
# Items Impacted: ".$this->GetProperty('is_reached_count')." / ".($this->GetProperty('min_up') + $this->GetProperty('threshold'))."
Critical Threshold: ".$this->GetProperty('threshold')." / ".($this->GetProperty('min_up') + $this->GetProperty('threshold'))."
'; + return $sHtml; + } } class DisplayableEdge extends GraphEdge { - public function RenderAsPDF(TCPDF $oPdf, DisplayableGraph $oGraph, $fScale) + public function RenderAsPDF(TCPDF $oPdf, DisplayableGraph $oGraph, $fScale, $aContextDefs) { $xStart = $this->GetSourceNode()->x * $fScale; $yStart = $this->GetSourceNode()->y * $fScale; @@ -498,7 +578,7 @@ class DisplayableGroupNode extends DisplayableNode return 50; } - public function GetForRaphael() + public function GetForRaphael($aContextDefs) { $aNode = array(); $aNode['shape'] = 'group'; @@ -515,10 +595,11 @@ class DisplayableGroupNode extends DisplayableNode $aNode['icon_attr'] = array('opacity' => $fTextOpacity); $aNode['disc_attr'] = array('stroke-width' => 3, 'stroke' => '#000', 'fill' => '#fff', 'opacity' => $fDiscOpacity); $aNode['text_attr'] = array('fill' => '#000', 'opacity' => $fTextOpacity); + $aNode['tooltip'] = $this->GetTooltip($aContextDefs); return $aNode; } - public function RenderAsPDF(TCPDF $oPdf, DisplayableGraph $oGraph, $fScale) + public function RenderAsPDF(TCPDF $oPdf, DisplayableGraph $oGraph, $fScale, $aContextDefs) { $bReached = $this->GetProperty('is_reached'); $oPdf->SetFillColor(255, 255, 255); @@ -533,7 +614,7 @@ class DisplayableGroupNode extends DisplayableNode $oPdf->SetLineStyle(array('width' => 2*$fScale, 'cap' => 'round', 'join' => 'miter', 'dash' => 0, 'color' => $aBorderColor)); $sIconUrl = $this->GetProperty('icon_url'); - $sIconPath = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-production/', $sIconUrl); + $sIconPath = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-'.utils::GetCurrentEnvironment().'/', $sIconUrl); $oPdf->SetAlpha(1); $oPdf->Circle($this->x*$fScale, $this->y*$fScale, $this->GetWidth() / 2 * $fScale, 0, 360, 'DF'); @@ -553,6 +634,14 @@ class DisplayableGroupNode extends DisplayableNode $oPdf->SetTextColor(0, 0, 0); $oPdf->Text($this->x*$fScale - $width/2, ($this->y + 25)*$fScale, $this->GetProperty('label')); } + + public function GetTooltip($aContextDefs) + { + $sHtml = ''; + $iGroupIdx = $this->GetProperty('group_index'); + $sHtml .= Dict::Format('UI:RelationGroupNumber_N', (1+$iGroupIdx)); + return $sHtml; + } } /** @@ -633,12 +722,17 @@ class DisplayableGraph extends SimpleGraph $oNewNode->SetProperty('icon_url', $oObj->GetIcon(false)); $oNewNode->SetProperty('label', $oObj->GetRawName()); $oNewNode->SetProperty('is_reached', $bDirectionDown ? $oNode->GetProperty('is_reached') : true); // When going "up" is_reached does not matter - $oNewNode->SetProperty('developped', $oNode->GetProperty('developped')); + $oNewNode->SetProperty('is_reached_allowed', $oNode->GetProperty('is_reached_allowed')); + $oNewNode->SetProperty('context_root_causes', $oNode->GetProperty('context_root_causes')); break; default: $oNewNode = new DisplayableRedundancyNode($oNewGraph, $oNode->GetId(), 0, 0); - $oNewNode->SetProperty('label', $oNode->GetProperty('min_up')); + $iNbReached = (is_null($oNode->GetProperty('is_reached_count'))) ? 0 : $oNode->GetProperty('is_reached_count'); + $oNewNode->SetProperty('label', $iNbReached."/".($oNode->GetProperty('min_up') + $oNode->GetProperty('threshold'))); + $oNewNode->SetProperty('min_up', $oNode->GetProperty('min_up')); + $oNewNode->SetProperty('threshold', $oNode->GetProperty('threshold')); + $oNewNode->SetProperty('is_reached_count', $iNbReached); $oNewNode->SetProperty('is_reached', true); } } @@ -677,16 +771,24 @@ class DisplayableGraph extends SimpleGraph } } - $iNbGrouping = 1; - //for($iter=0; $iter<$iNbGrouping; $iter++) + $oNodesIter = new RelationTypeIterator($oNewGraph, 'Node'); + foreach($oNodesIter as $oNode) { - $oNodesIter = new RelationTypeIterator($oNewGraph, 'Node'); - foreach($oNodesIter as $oNode) + if ($oNode->GetProperty('source')) { - if ($oNode->GetProperty('source')) - { - $oNode->GroupSimilarNeighbours($oNewGraph, $iGroupingThreshold, true, true); - } + $oNode->GroupSimilarNeighbours($oNewGraph, $iGroupingThreshold, true, true); + } + } + // Groups numbering + $oIterator = new RelationTypeIterator($oNewGraph, 'Node'); + $iGroupIdx = 0; + foreach($oIterator as $oNode) + { + if ($oNode instanceof DisplayableGroupNode) + { + $aGroups[] = $oNode->GetObjects(); + $oNode->SetProperty('group_index', $iGroupIdx); + $iGroupIdx++; } } @@ -811,8 +913,10 @@ class DisplayableGraph extends SimpleGraph /** * Renders as JSON string suitable for loading into the simple_graph widget */ - function GetAsJSON() + function GetAsJSON($sContextKey) { + $aContextDefs = $this->GetContextDefinitions($sContextKey, false); + $aData = array('nodes' => array(), 'edges' => array()); $iGroupIdx = 0; $oIterator = new RelationTypeIterator($this, 'Node'); @@ -824,7 +928,7 @@ class DisplayableGraph extends SimpleGraph $oNode->SetProperty('group_index', $iGroupIdx); $iGroupIdx++; } - $aData['nodes'][] = $oNode->GetForRaphael(); + $aData['nodes'][] = $oNode->GetForRaphael($aContextDefs); } $oIterator = new RelationTypeIterator($this, 'Edge'); @@ -846,13 +950,15 @@ class DisplayableGraph extends SimpleGraph * Renders the graph in a PDF document: centered in the current page * @param PDFPage $oPage The PDFPage representing the PDF document to draw into * @param string $sComments An optional comment to display next to the graph (HTML entities will be escaped, \n replaced by
) + * @param string $sContextKey The key to fetch the queries in the configuration. Example: itop-tickets/relation_context/UserRequest/impacts/down * @param float $xMin Left coordinate of the bounding box to display the graph * @param float $xMax Right coordinate of the bounding box to display the graph * @param float $yMin Top coordinate of the bounding box to display the graph * @param float $yMax Bottom coordinate of the bounding box to display the graph */ - function RenderAsPDF(PDFPage $oPage, $sComments = '', $xMin = -1, $xMax = -1, $yMin = -1, $yMax = -1) + function RenderAsPDF(PDFPage $oPage, $sComments = '', $sContextKey, $xMin = -1, $xMax = -1, $yMin = -1, $yMax = -1) { + $aContextDefs = $this->GetContextDefinitions($sContextKey, false); // No need to develop the parameters $oPdf = $oPage->get_tcpdf(); $aBB = $this->GetBoundingBox(); @@ -904,14 +1010,14 @@ class DisplayableGraph extends SimpleGraph foreach($oIterator as $sId => $oEdge) { set_time_limit($iLoopTimeLimit); - $oEdge->RenderAsPDF($oPdf, $this, $fScale); + $oEdge->RenderAsPDF($oPdf, $this, $fScale, $aContextDefs); } $oIterator = new RelationTypeIterator($this, 'Node'); foreach($oIterator as $sId => $oNode) { set_time_limit($iLoopTimeLimit); - $oNode->RenderAsPDF($oPdf, $this, $fScale); + $oNode->RenderAsPDF($oPdf, $this, $fScale, $aContextDefs); } $oIterator = new RelationTypeIterator($this, 'Node'); $oPdf->SetAutoPageBreak(true, $fBreakMargin); @@ -950,7 +1056,7 @@ class DisplayableGraph extends SimpleGraph $fMaxWidth = max($width, $fMaxWidth); $aClasses[$sClass] = $sClassLabel; $sIconUrl = $oNode->GetProperty('icon_url'); - $sIconPath = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-production/', $sIconUrl); + $sIconPath = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-'.utils::GetCurrentEnvironment().'/', $sIconUrl); $aIcons[$sClass] = $sIconPath; } } @@ -987,6 +1093,40 @@ class DisplayableGraph extends SimpleGraph return array('xmin' => $fMaxWidth + $fIconSize + 4*$fPadding, 'xmax' => $xMax, 'ymin' => $yMin, 'ymax' => $yMax); } + //itop-tickets/relation_context/UserRequest/impacts/down + /** + * + * @param string $sContextKey The key to fetch the queries in the configuration. Example: itop-tickets/relation_context/UserRequest/impacts/down + */ + public function GetContextDefinitions($sContextKey, $bDevelopParams = true, $aContextParams = array()) + { + $aLevels = explode('/', $sContextKey); + $aRelationContext = MetaModel::GetConfig()->GetModuleSetting($aLevels[0], $aLevels[1], array()); + $aContextDefs = array(); + if (isset($aRelationContext[$aLevels[2]][$aLevels[3]][$aLevels[4]]['items'])) + { + $aContextDefs = $aRelationContext[$aLevels[2]][$aLevels[3]][$aLevels[4]]['items']; + + } + + // Check if the queries are valid + foreach($aContextDefs as $sKey => $sDefs) + { + $sOQL = $aContextDefs[$sKey]['oql']; + try + { + // Expand the parameters. If anything goes wrong, then the query is considered as invalid and removed from the list + $oSearch = DBObjectSearch::FromOQL($sOQL); + $aContextDefs[$sKey]['oql'] = $oSearch->ToOQL($bDevelopParams, $aContextParams); + } + catch(Exception $e) + { + unset($aContextDefs[$sKey]); + } + } + return $aContextDefs; + } + /** * Display the graph inside the given page, with the "filter" drawer above it * @param WebPage $oP @@ -995,8 +1135,9 @@ class DisplayableGraph extends SimpleGraph * @param ApplicationContext $oAppContext * @param array $aExcludedObjects */ - function Display(WebPage $oP, $aResults, $sRelation, ApplicationContext $oAppContext, $aExcludedObjects = array(), $sObjClass = null, $iObjKey = null) + function Display(WebPage $oP, $aResults, $sRelation, ApplicationContext $oAppContext, $aExcludedObjects = array(), $sObjClass = null, $iObjKey = null, $sContextKey, $aContextParams = array()) { + $aContextDefs = $this->GetContextDefinitions($sContextKey, true, $aContextParams); $aExcludedByClass = array(); foreach($aExcludedObjects as $oObj) { @@ -1026,7 +1167,7 @@ EOF $aSortedElements[$sSubClass] = MetaModel::GetName($sSubClass); } } - + asort($aSortedElements); $idx = 0; foreach($aSortedElements as $sSubClass => $sClassName) @@ -1038,7 +1179,13 @@ EOF $oP->add("\n"); $oP->add("
\n"); $oP->add("
".Dict::S('UI:ElementsDisplayed')."
\n"); - + + $aAdditionalContexts = array(); + foreach($aContextDefs as $sKey => $aDefinition) + { + $aAdditionalContexts[] = array('key' => $sKey, 'label' => Dict::S($aDefinition['dict']), 'oql' => $aDefinition['oql']); + } + $sDirection = utils::ReadParam('d', 'horizontal'); $iGroupingThreshold = utils::ReadParam('g', 5); @@ -1087,6 +1234,11 @@ EOF 'comments' => Dict::S('UI:RelationOption:Comments'), 'grouping_threshold' => Dict::S('UI:RelationOption:GroupingThreshold'), 'refresh' => Dict::S('UI:Button:Refresh'), + 'check_all' => Dict::S('UI:SearchValue:CheckAll'), + 'uncheck_all' => Dict::S('UI:SearchValue:UncheckAll'), + 'none_selected' => Dict::S('UI:Relation:NoneSelected'), + 'nb_selected' => Dict::S('UI:SearchValue:NbSelected'), + 'additional_context_info' => Dict::S('UI:Relation:AdditionalContextInfo'), ), 'page_format' => array( 'label' => Dict::S('UI:Relation:PDFExportPageFormat'), @@ -1103,16 +1255,17 @@ EOF 'L' => Dict::S('UI:PageOrientation_Landscape'), ), ), + 'additional_contexts' => $aAdditionalContexts, + 'context_key' => $sContextKey, ); - if (!extension_loaded('gd')) + if (!extension_loaded('gd')) { // PDF export requires GD unset($aParams['export_as_pdf']); } if (!extension_loaded('gd') || is_null($sObjClass) || is_null($iObjKey)) { - // PDF export requires GD AND a valid objclass/objkey couple - unset($aParams['export_as_pdf']); + // Export as Attachment requires GD (for building the PDF) AND a valid objclass/objkey couple unset($aParams['export_as_attachment']); } $oP->add_ready_script("$('#$sId').simple_graph(".json_encode($aParams).");"); diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 908b9913f..3b1b1194f 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -1458,13 +1458,17 @@ abstract class MetaModel * * @return RelationGraph The graph of all the related objects */ - static public function GetRelatedObjectsDown($sRelCode, $aSourceObjects, $iMaxDepth = 99, $bEnableRedundancy = true, $aUnreachable = array()) + static public function GetRelatedObjectsDown($sRelCode, $aSourceObjects, $iMaxDepth = 99, $bEnableRedundancy = true, $aUnreachable = array(), $aContexts = array()) { $oGraph = new RelationGraph(); foreach ($aSourceObjects as $oObject) { $oGraph->AddSourceObject($oObject); } + foreach($aContexts as $key => $sOQL) + { + $oGraph->AddContextQuery($key, $sOQL); + } $oGraph->ComputeRelatedObjectsDown($sRelCode, $iMaxDepth, $bEnableRedundancy, $aUnreachable); return $oGraph; } @@ -1479,13 +1483,17 @@ abstract class MetaModel * * @return RelationGraph The graph of all the related objects */ - static public function GetRelatedObjectsUp($sRelCode, $aSourceObjects, $iMaxDepth = 99, $bEnableRedundancy = true) + static public function GetRelatedObjectsUp($sRelCode, $aSourceObjects, $iMaxDepth = 99, $bEnableRedundancy = true, $aContexts = array()) { $oGraph = new RelationGraph(); foreach ($aSourceObjects as $oObject) { $oGraph->AddSinkObject($oObject); } + foreach($aContexts as $key => $sOQL) + { + $oGraph->AddContextQuery($key, $sOQL); + } $oGraph->ComputeRelatedObjectsUp($sRelCode, $iMaxDepth, $bEnableRedundancy); return $oGraph; } diff --git a/core/relationgraph.class.inc.php b/core/relationgraph.class.inc.php index 5ed1e0326..cbcd72727 100644 --- a/core/relationgraph.class.inc.php +++ b/core/relationgraph.class.inc.php @@ -169,6 +169,7 @@ class RelationGraph extends SimpleGraph protected $aSourceNodes; // Index of source nodes (for a quicker access) protected $aSinkNodes; // Index of sink nodes (for a quicker access) protected $aRedundancySettings; // Cache of user settings + protected $aContextSearches; // Context ("knowing that") stored as a hash array 'class' => DBObjectSearch public function __construct() { @@ -176,6 +177,7 @@ class RelationGraph extends SimpleGraph $this->aSourceNodes = array(); $this->aSinkNodes = array(); $this->aRedundancySettings = array(); + $this->aContextSearches = array(); } /** @@ -197,6 +199,74 @@ class RelationGraph extends SimpleGraph $oSinkNode->SetProperty('sink', true); $this->aSinkNodes[$oSinkNode->GetId()] = $oSinkNode; } + + /** + * Add a 'context' OQL query, specifying extra objects to be marked as 'is_reached' + * even though they are not part of the sources. + * @param string $sOQL The OQL query defining the context objects + */ + public function AddContextQuery($key, $sOQL) + { + if ($sOQL === '') return; + + $oSearch = DBObjectSearch::FromOQL($sOQL); + $aAliases = $oSearch->GetSelectedClasses(); + if (count($aAliases) < 2 ) + { + IssueLog::Error("Invalid context query '$sOQL'. A context query must contain at least two columns."); + throw new Exception("Invalid context query '$sOQL'. A context query must contain at least two columns. Columns: ".implode(', ', $aAliases).'. '); + } + $aAliasNames = array_keys($aAliases); + $sClassAlias = $oSearch->GetClassAlias(); + $oCondition = new BinaryExpression(new FieldExpression('id', $aAliasNames[0]), '=', new VariableExpression('id')); + $oSearch->AddConditionExpression($oCondition); + + $sClass = $oSearch->GetClass(); + if (!array_key_exists($sClass, $this->aContextSearches)) + { + $this->aContextSearches[$sClass] = array(); + } + $this->aContextSearches[$sClass][] = array('key' => $key, 'search' => $oSearch); + } + + /** + * Determines if the given DBObject is part of a 'context' + * @param DBObject $oObj + * @return boolean + */ + public function IsPartOfContext(DBObject $oObj, &$aRootCauses) + { + $bRet = false; + $sFinalClass = get_class($oObj); + $aParentClasses = MetaModel::EnumParentClasses($sFinalClass, ENUM_PARENT_CLASSES_ALL); + + foreach($aParentClasses as $sClass) + { + if (array_key_exists($sClass, $this->aContextSearches)) + { + foreach($this->aContextSearches[$sClass] as $aContextQuery) + { + $aAliases = $aContextQuery['search']->GetSelectedClasses(); + $aAliasNames = array_keys($aAliases); + $sRootCauseAlias = $aAliasNames[1]; // 1st column (=0) = object, second column = root cause + $oSet = new DBObjectSet($aContextQuery['search'], array(), array('id' => $oObj->GetKey())); + while($aRow = $oSet->FetchAssoc()) + { + if (!is_null($aRow[$sRootCauseAlias])) + { + if (!array_key_exists($aContextQuery['key'], $aRootCauses)) + { + $aRootCauses[$aContextQuery['key']] = array(); + } + $aRootCauses[$aContextQuery['key']][] = $aRow[$sRootCauseAlias]; + $bRet = true; + } + } + } + } + } + return $bRet; + } /** * Build the graph downstream, and mark the nodes that can be reached from the source node @@ -220,9 +290,6 @@ class RelationGraph extends SimpleGraph { $oNode->SetProperty('is_reached_allowed', false); } - else - { - } } // Determine the reached nodes @@ -231,6 +298,19 @@ class RelationGraph extends SimpleGraph $oSourceNode->ReachDown('is_reached', true); //echo "
After reaching from {$oSourceNode->GetId()}
\n".$this->DumpAsHtmlImage()."
\n"; } + + // Mark also the "context" nodes as reached and record the "root causes" for each node + $oIterator = new RelationTypeIterator($this, 'Node'); + foreach($oIterator as $oNode) + { + $oObj = $oNode->GetProperty('object'); + $aRootCauses = array(); + if (!is_null($oObj) && $this->IsPartOfContext($oObj, $aRootCauses)) + { + $oNode->SetProperty('context_root_causes', $aRootCauses); + $oNode->ReachDown('is_reached', true); + } + } } /** @@ -245,6 +325,19 @@ class RelationGraph extends SimpleGraph $this->AddRelatedObjects($sRelCode, false, $oSinkNode, $iMaxDepth, $bEnableRedundancy); //echo "
After processing of {$oSinkNode->GetId()}
\n".$this->DumpAsHtmlImage()."
\n"; } + + // Mark also the "context" nodes as reached and record the "root causes" for each node + $oIterator = new RelationTypeIterator($this, 'Node'); + foreach($oIterator as $oNode) + { + $oObj = $oNode->GetProperty('object'); + $aRootCauses = array(); + if (!is_null($oObj) && $this->IsPartOfContext($oObj, $aRootCauses)) + { + $oNode->SetProperty('context_root_causes', $aRootCauses); + $oNode->ReachDown('is_reached', true); + } + } } diff --git a/datamodels/2.x/itop-change-mgmt-itil/datamodel.itop-change-mgmt-itil.xml b/datamodels/2.x/itop-change-mgmt-itil/datamodel.itop-change-mgmt-itil.xml index 78e8d0369..ec86df4a9 100755 --- a/datamodels/2.x/itop-change-mgmt-itil/datamodel.itop-change-mgmt-itil.xml +++ b/datamodels/2.x/itop-change-mgmt-itil/datamodel.itop-change-mgmt-itil.xml @@ -4516,4 +4516,64 @@ fast + + + + + + + + + id)]]> + Tickets:Related:OpenChanges + itop-change-mgmt/images/change-ongoing.png + + + id) AND (DATE_ADD(C.end_date, INTERVAL 3 DAY) < NOW())]]> + Tickets:Related:RecentChanges + itop-change-mgmt/images/change-done.png + + + + + + + + + + + + + + + + Tickets:Related:OpenChanges + itop-change-mgmt/images/change-ongoing.png + + + + Tickets:Related:RecentChanges + itop-change-mgmt/images/change-done.png + + + + + + + id)]]> + Tickets:Related:OpenChanges + itop-change-mgmt/images/change-ongoing.png + + + id) AND (DATE_ADD(C.end_date, INTERVAL 3 DAY) < NOW())]]> + Tickets:Related:RecentChanges + itop-change-mgmt/images/change-done.png + + + + + + + + diff --git a/datamodels/2.x/itop-change-mgmt-itil/images/change-done.png b/datamodels/2.x/itop-change-mgmt-itil/images/change-done.png new file mode 100755 index 0000000000000000000000000000000000000000..e27142f662ec072a36d7a57683cd7cdf22d4a1d6 GIT binary patch literal 1659 zcmV->288*EP)Mum>2`ZK{l9@$mI|E!w{oRqkq&86SHW7Fo-(Xg*j#% z-P&&Jx(jQ&ZtTLkZr#?k^*x7y0|!=M@WZp5>`TA%o$q;{_j#Z9d=kIkk4HHKJmMk# zdhYZ+Jt&b#R!9VCHC+b#ectDY$>E2_0KFVOpTE4NxoKTfW5a5%*IUVcduRZpQZd)k z-Su{7ml>9x9&~kf?x5d_9tr@W9aApX(TUAY&02O znwXqKW_lX3Gcz#ha>3Nz{w&>lZCL>bQc*cP=V3Io=llHrvi~B;fB{HFSz2F@WuM(K zi0G&&q^4)U=beX_W=d)rVxyxmI5dnNOZQ%4D(8U$U=hu@-Q@;@AySYC(CTy~J`v1C z*%U<)TCEOJK>)1@bx%zl3{;RpV9|@~x;i_ykByHbDKQb6ghY6Wa*C8s#zK681}VwO zm~cAL*r1Al-N>Sni;Rq7pm46H27C9FV}C^@2Cnq;G=+ypP+4YTW@ZL@ zy?zyC=gs>9K*Ec)TL<1Buv!rr9*&&ce6ZdrS14e!+u@p;f^BdRLk%f(Rv`4}{0bmi$dps2+s$`$fhwSWZ1gX^&?G@D3)?#dI9C5KR*tw+?D_5;1 zO?bE<i$=QD5<#n&b# zpox17>o=@tBIPB?1_P3umX5yuK3HvbbefFY3JQzAnxFSH2h0EymCH4Gn0dThE`v^2 z0LDUN)9={){6>V43o0oMtnQl{FA`YkKiJdKGYYvJ=5CA3>6|!nr?M38;PcZQ!V2mWz5V^L*{l_msugzyzz-UJ z{~n{!pk;oaot=fyuuz^?WiUzdX}P{cDu22`&OhA8+Ka9)-pq) za^o*n2B}eZz7}QW2T*b71GHY&bN6(RUb@ZZy~J?Ak^oo~?&+xm219%3brF%@%N4kB zYL>}lX#KMlvvYGOEGndQBy$G#?0yGiRQR#F8h>1B#^;}YiW8qz!AbgInmJW-4#B}8 zylWbbMiq(sy~ToD1VFl;{J!+sgcAuG4HA=*IrR(7ur7yBs=k0OF9%W4G0cs@u*Jt~ zu<6Ck&=u(L;m0R%@`q~D#VoG?QIuk2Yz(K*)F71v$v}0OOs`O%c;OZRQz;d= zNWvYk+PG-h8FF~oag>5m-!vjxyNzgkoGDtT)8fMU3$W4yQt>)ftmK&aNa*joZ@%R( z=%54X@YN90}0npr$p+D_G+#g46SV1gP|o4!rE?&+AfDAgZHYJWK! zPtByczM(U!>d?n4X@d+kWbTLHM_m zPywZhG=|BkX*~JFn$gs>j5Q*a+adCM4flSp7j^Y@eB(%JC`%0m0a-l4s7wPLtyGpg zW%MqSsUuR8kaSq2;5M>|SSP!2!kL|&UA9lL>mM~fFrV@JeNRz(#jn?vltT@-&6nq002ovPDHLk FV1m&8DNz6b literal 0 HcmV?d00001 diff --git a/datamodels/2.x/itop-change-mgmt-itil/images/change-ongoing.png b/datamodels/2.x/itop-change-mgmt-itil/images/change-ongoing.png new file mode 100755 index 0000000000000000000000000000000000000000..0cc46b8630645434d97633b950d0f736deb46b18 GIT binary patch literal 1609 zcmV-P2DbT$P)q$gGRCwCtS8HrkMHK$--Mi23w%gry zw=bd66ol47BR(P`7!Z|Lq$Cnb3JO|@3RLA03chLqh1i6kKogM~gF-|M2!bSth#?>Z z5sG3u3V7s=vMzSV=!t88M<|zi`T(8Esa& z){m>(ENSv1@ZtA?_kcv!fLzaJr@3pWX|vOS8m#5d?f`(9?yoH~ zLWF6F54uso(`$k|w9%B2t8|6(Pl25YOp9hqrxO4Uopk2%GK$v0S+l{B?z) zx!H@W74^7W90Y zV3upAWtvt)zj;)Cm=H9 zUQdJ+dmvs2Y5HpKI@0^$4EA&nC!SXFBW0)&`;WOHGZ~^Nz-c$b>+9uiDJpCkO+06` z=9%FCjpqq#-v9(oq%7Cxk;$BRi^%oSs^|j0FG+nS24H@≤sRh|tbbpsl?F6UJOXa-tDdiva~|Jveg0flb>|apY(cR_3JQ)!ZsHHu+#Q z=&*H96&fZQgLxv#M3v*}ZiKuc>?SsTsOOsZPj;|B@8W8woVx3G@fsr}jfjE`7rD0r^gZG42q4g@`f=CyHn=^lFq@5> zpk3b^CJNVsKbK>#E&)P3spmUk=q#f0O@|P*C@tmgUs#u8-_o{CkI)Yqu)QN0r6mDq zwJKw@0Au8M7zHfcD3clOCMERE4xj36LZK-~2!u2?BrlO;JhL7JqLfj?Hd^ z{H_-IIUR7Usas0C`bZ#!i)lZNUu$Q?#EJ?85W^la*bCGfu$N;Ra2h_9~K8z0BR0xL#-)gnh`=OGD#qAO;m zy1gG2Zl6slTsoA(hOURgT8XmgPil5Zbi64Xo^n!NVhOktO-b1#aAO5xcn+(<%igmb z9}9lfast + + + + + + + + + id)]]> + Tickets:Related:OpenChanges + itop-change-mgmt/images/change-ongoing.png + + + id) AND (DATE_ADD(C.end_date, INTERVAL 3 DAY) < NOW())]]> + Tickets:Related:RecentChanges + itop-change-mgmt/images/change-done.png + + + + + + + + + + + + + + + + Tickets:Related:OpenChanges + itop-change-mgmt/images/change-ongoing.png + + + + Tickets:Related:RecentChanges + itop-change-mgmt/images/change-done.png + + + + + + + id)]]> + Tickets:Related:OpenChanges + itop-change-mgmt/images/change-ongoing.png + + + id) AND (DATE_ADD(C.end_date, INTERVAL 3 DAY) < NOW())]]> + Tickets:Related:RecentChanges + itop-change-mgmt/images/change-done.png + + + + + + + + diff --git a/datamodels/2.x/itop-change-mgmt/de.dict.itop-change-mgmt.php b/datamodels/2.x/itop-change-mgmt/de.dict.itop-change-mgmt.php index 8a4e2d6a9..0d7da474a 100644 --- a/datamodels/2.x/itop-change-mgmt/de.dict.itop-change-mgmt.php +++ b/datamodels/2.x/itop-change-mgmt/de.dict.itop-change-mgmt.php @@ -102,6 +102,8 @@ Dict::Add('DE DE', 'German', 'Deutsch', array( 'UI-ChangeManagementOverview-Last-7-days' => 'Zahl der Changes in den letzten sieben Tagen', '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~~', 'Class:Change/Attribute:changemanager_email' => 'Change Manager Email', 'Class:Change/Attribute:changemanager_email+' => '', 'Class:Change/Attribute:parent_name' => 'Parent Change ref', diff --git a/datamodels/2.x/itop-change-mgmt/en.dict.itop-change-mgmt.php b/datamodels/2.x/itop-change-mgmt/en.dict.itop-change-mgmt.php index b70ed3803..76e5bb7b0 100755 --- a/datamodels/2.x/itop-change-mgmt/en.dict.itop-change-mgmt.php +++ b/datamodels/2.x/itop-change-mgmt/en.dict.itop-change-mgmt.php @@ -46,6 +46,8 @@ Dict::Add('EN US', 'English', 'English', array( 'UI-ChangeManagementOverview-Last-7-days' => 'Number of changes for the last 7 days', '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', )); // Dictionnay conventions diff --git a/datamodels/2.x/itop-change-mgmt/fr.dict.itop-change-mgmt.php b/datamodels/2.x/itop-change-mgmt/fr.dict.itop-change-mgmt.php index 1f4c75e77..6d5c13d7d 100755 --- a/datamodels/2.x/itop-change-mgmt/fr.dict.itop-change-mgmt.php +++ b/datamodels/2.x/itop-change-mgmt/fr.dict.itop-change-mgmt.php @@ -125,8 +125,7 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI-ChangeManagementOverview-ChangeByDomain-last-7-days' => 'Changements par domaine', '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', )); ?> diff --git a/datamodels/2.x/itop-change-mgmt/images/change-done.png b/datamodels/2.x/itop-change-mgmt/images/change-done.png new file mode 100755 index 0000000000000000000000000000000000000000..e27142f662ec072a36d7a57683cd7cdf22d4a1d6 GIT binary patch literal 1659 zcmV->288*EP)Mum>2`ZK{l9@$mI|E!w{oRqkq&86SHW7Fo-(Xg*j#% z-P&&Jx(jQ&ZtTLkZr#?k^*x7y0|!=M@WZp5>`TA%o$q;{_j#Z9d=kIkk4HHKJmMk# zdhYZ+Jt&b#R!9VCHC+b#ectDY$>E2_0KFVOpTE4NxoKTfW5a5%*IUVcduRZpQZd)k z-Su{7ml>9x9&~kf?x5d_9tr@W9aApX(TUAY&02O znwXqKW_lX3Gcz#ha>3Nz{w&>lZCL>bQc*cP=V3Io=llHrvi~B;fB{HFSz2F@WuM(K zi0G&&q^4)U=beX_W=d)rVxyxmI5dnNOZQ%4D(8U$U=hu@-Q@;@AySYC(CTy~J`v1C z*%U<)TCEOJK>)1@bx%zl3{;RpV9|@~x;i_ykByHbDKQb6ghY6Wa*C8s#zK681}VwO zm~cAL*r1Al-N>Sni;Rq7pm46H27C9FV}C^@2Cnq;G=+ypP+4YTW@ZL@ zy?zyC=gs>9K*Ec)TL<1Buv!rr9*&&ce6ZdrS14e!+u@p;f^BdRLk%f(Rv`4}{0bmi$dps2+s$`$fhwSWZ1gX^&?G@D3)?#dI9C5KR*tw+?D_5;1 zO?bE<i$=QD5<#n&b# zpox17>o=@tBIPB?1_P3umX5yuK3HvbbefFY3JQzAnxFSH2h0EymCH4Gn0dThE`v^2 z0LDUN)9={){6>V43o0oMtnQl{FA`YkKiJdKGYYvJ=5CA3>6|!nr?M38;PcZQ!V2mWz5V^L*{l_msugzyzz-UJ z{~n{!pk;oaot=fyuuz^?WiUzdX}P{cDu22`&OhA8+Ka9)-pq) za^o*n2B}eZz7}QW2T*b71GHY&bN6(RUb@ZZy~J?Ak^oo~?&+xm219%3brF%@%N4kB zYL>}lX#KMlvvYGOEGndQBy$G#?0yGiRQR#F8h>1B#^;}YiW8qz!AbgInmJW-4#B}8 zylWbbMiq(sy~ToD1VFl;{J!+sgcAuG4HA=*IrR(7ur7yBs=k0OF9%W4G0cs@u*Jt~ zu<6Ck&=u(L;m0R%@`q~D#VoG?QIuk2Yz(K*)F71v$v}0OOs`O%c;OZRQz;d= zNWvYk+PG-h8FF~oag>5m-!vjxyNzgkoGDtT)8fMU3$W4yQt>)ftmK&aNa*joZ@%R( z=%54X@YN90}0npr$p+D_G+#g46SV1gP|o4!rE?&+AfDAgZHYJWK! zPtByczM(U!>d?n4X@d+kWbTLHM_m zPywZhG=|BkX*~JFn$gs>j5Q*a+adCM4flSp7j^Y@eB(%JC`%0m0a-l4s7wPLtyGpg zW%MqSsUuR8kaSq2;5M>|SSP!2!kL|&UA9lL>mM~fFrV@JeNRz(#jn?vltT@-&6nq002ovPDHLk FV1m&8DNz6b literal 0 HcmV?d00001 diff --git a/datamodels/2.x/itop-change-mgmt/images/change-ongoing.png b/datamodels/2.x/itop-change-mgmt/images/change-ongoing.png new file mode 100755 index 0000000000000000000000000000000000000000..0cc46b8630645434d97633b950d0f736deb46b18 GIT binary patch literal 1609 zcmV-P2DbT$P)q$gGRCwCtS8HrkMHK$--Mi23w%gry zw=bd66ol47BR(P`7!Z|Lq$Cnb3JO|@3RLA03chLqh1i6kKogM~gF-|M2!bSth#?>Z z5sG3u3V7s=vMzSV=!t88M<|zi`T(8Esa& z){m>(ENSv1@ZtA?_kcv!fLzaJr@3pWX|vOS8m#5d?f`(9?yoH~ zLWF6F54uso(`$k|w9%B2t8|6(Pl25YOp9hqrxO4Uopk2%GK$v0S+l{B?z) zx!H@W74^7W90Y zV3upAWtvt)zj;)Cm=H9 zUQdJ+dmvs2Y5HpKI@0^$4EA&nC!SXFBW0)&`;WOHGZ~^Nz-c$b>+9uiDJpCkO+06` z=9%FCjpqq#-v9(oq%7Cxk;$BRi^%oSs^|j0FG+nS24H@≤sRh|tbbpsl?F6UJOXa-tDdiva~|Jveg0flb>|apY(cR_3JQ)!ZsHHu+#Q z=&*H96&fZQgLxv#M3v*}ZiKuc>?SsTsOOsZPj;|B@8W8woVx3G@fsr}jfjE`7rD0r^gZG42q4g@`f=CyHn=^lFq@5> zpk3b^CJNVsKbK>#E&)P3spmUk=q#f0O@|P*C@tmgUs#u8-_o{CkI)Yqu)QN0r6mDq zwJKw@0Au8M7zHfcD3clOCMERE4xj36LZK-~2!u2?BrlO;JhL7JqLfj?Hd^ z{H_-IIUR7Usas0C`bZ#!i)lZNUu$Q?#EJ?85W^la*bCGfu$N;Ra2h_9~K8z0BR0xL#-)gnh`=OGD#qAO;m zy1gG2Zl6slTsoA(hOURgT8XmgPil5Zbi64Xo^n!NVhOktO-b1#aAO5xcn+(<%igmb z9}9l + + + + + + + + + id)]]> + Tickets:Related:OpenIncidents + itop-request-mgmt/images/incident-red.png + + + + + + + + + + + + + + + id)]]> + Tickets:Related:OpenIncidents + itop-request-mgmt/images/incident-red.png + + + + + + + id)]]> + Tickets:Related:OpenIncidents + itop-request-mgmt/images/incident-red.png + + + + + + + + diff --git a/datamodels/2.x/itop-incident-mgmt-itil/images/incident-red.png b/datamodels/2.x/itop-incident-mgmt-itil/images/incident-red.png new file mode 100755 index 0000000000000000000000000000000000000000..5aac4ab4bb65196f17a3d3bcb0c4522532a536f6 GIT binary patch literal 1913 zcmV-<2Zs2GP)Y1ujk&G*`3L5t#*>hPG;}9-*?aX&bjB3quF(Eqf5w}KnKyt zcJr3hPP4mxvz2YTRky6H{GPeasMoQ|xzn}df{R~>fMo(VzR1532!U@i`_s>N zjSTP24HS!}W`fQ*2+s%LA{eLQTZTQ&j+sDS})I6|j(?D=k6L<*7IRozd>J}IpE|{DHn0&udLE)sJXQ=-{Evp|# zKaH%#qiev25d53s=$2vs?;vzU*AtqMz}!Y}`aD>*{E;D$I!MumCxny;{_z;JTW#R= z=ZEH3=f1@mc0kM%GzM;E?$1x3Ue2ZsMQHb1EXNRTHLyAJ3D_l0F&V8MitX=mFjkU zFgK2Zwej9$EIUsDcRmA1Q8lCDCK`5JlIZCMr|fL2mTIpdi8tyRkg3*t|HuJat^u=c z1QtjdV~8c^HOIqx z?m{?B6&(>02(ZJ+q?jZDy9ELC0CRD2DclHDkq4DMB6?O6W0_4GfCP3^Jw-Q*nyX?~ zUnw*|8|*_hOZV37CISq@qTCG=q%FoEZ@40X0||$Jj??c*9u5 zwG{y=Lg{|STf3i!X&HKzV7<`Ov_N^)&oUPYpj)EBY9NRDU{x2w$v6|O31Ipv0-Ps^ zN6$?QLIPTfwo$%}dF{Ar7ZYra%M*ryD`luywR)Wph%oh%paK6#M*#t%=|HFsl>5Ab zsnso&DJO?Ga1=i->Y`LBX169F!L_;vLNAI&M)8mc;6e%JAtA)3`0q8}_lr6<6pBC` zG*ME+NzmB0Yt00zc96a?57bv5g;wp>_|BK9XvwB#L6+ii&vpUN^RV5OBPx@!*zQx1O?qmAxM*mj0t|)h&6B+a<#wc$7wv*wq3u z_=pzv$$7Z^^C!4(gs#R(G>)y*-|k39t12sOYQY*4zgP?=u( z8eh(;egI7lI!n$zBW0vCs;N~(WjIi8=xUt6pYdSesb`_&vh740kgY6A{5@w^@YDMO#e-_Udjt zD{-RdWc!mDC_rU$;bSy8ooGst&6~c%PBW1tAiO}iB2qAsA!051c&?gJ9s)P+P<%L~ zV?!S}E31EcCFgZ=owj_0c3D4KS*yTSov>^|qNvCfrADx;LA^wKhUy8Is$l=Vgfohk z2b}Esg6ympS0-k6AozI8u2_Zw**&VK^utCzQ*dW0N|bmctH)HO+;0oG+J#1^49Rm! z92g58bmGb}8y1%*W*>`#w>%f%fM09Zst3!2LME|0 zaio*2UT+$@MhC%n+;Ifog=0>v#nYd#I-v!L8Ncj(!91@m-QRVOS!k!6Fz{EsFcfPL zleP-DeYhLUjyAVEHUDAlN_juNzxclor{QYuv-&az(!HIJ;0~7aaohFrV8XFISKF_2 zBc7TxY%plb1e!_7Vzy+@E`R6DRNuj`Pm-HG9Vbpi0E1duf7s}-b{cKgoti;=2-Pxj z&DbYa@xU_gRh$WL#W}`p_C1cD{4dAze*_o+dE?}SyMt8o00000NkvXXu0mjfuZoRP literal 0 HcmV?d00001 diff --git a/datamodels/2.x/itop-request-mgmt/datamodel.itop-request-mgmt.xml b/datamodels/2.x/itop-request-mgmt/datamodel.itop-request-mgmt.xml index 52241a102..2df925ca0 100755 --- a/datamodels/2.x/itop-request-mgmt/datamodel.itop-request-mgmt.xml +++ b/datamodels/2.x/itop-request-mgmt/datamodel.itop-request-mgmt.xml @@ -1844,4 +1844,49 @@ fast + + + + + + + + + id)]]> + Tickets:Related:OpenIncidents + itop-request-mgmt/images/incident-red.png + + + + + + + + + + + + + + + id)]]> + Tickets:Related:OpenIncidents + itop-request-mgmt/images/incident-red.png + + + + + + + id)]]> + Tickets:Related:OpenIncidents + itop-request-mgmt/images/incident-red.png + + + + + + + + diff --git a/datamodels/2.x/itop-request-mgmt/de.dict.itop-request-mgmt.php b/datamodels/2.x/itop-request-mgmt/de.dict.itop-request-mgmt.php index 8708341a1..76115dfa8 100644 --- a/datamodels/2.x/itop-request-mgmt/de.dict.itop-request-mgmt.php +++ b/datamodels/2.x/itop-request-mgmt/de.dict.itop-request-mgmt.php @@ -268,5 +268,6 @@ Dict::Add('DE DE', 'German', 'Deutsch', array( 'Portal:SelectLanguage' => 'Ändern Sie Ihre Spracheinstellung', 'Portal:LanguageChangedTo_Lang' => 'Spracheinstellung geändert auf: ', 'Portal:ChooseYourFavoriteLanguage' => 'WÄhlen Sie Ihre bevorzugte Sprache', + 'Tickets:Related:OpenIncidents' => 'Open incidents~~', )); ?> diff --git a/datamodels/2.x/itop-request-mgmt/en.dict.itop-request-mgmt.php b/datamodels/2.x/itop-request-mgmt/en.dict.itop-request-mgmt.php index 3e6db35f4..e66d35365 100755 --- a/datamodels/2.x/itop-request-mgmt/en.dict.itop-request-mgmt.php +++ b/datamodels/2.x/itop-request-mgmt/en.dict.itop-request-mgmt.php @@ -58,6 +58,7 @@ Dict::Add('EN US', 'English', 'English', array( 'Menu:UserRequest:MyWorkOrders' => 'Work orders assigned to me', 'Menu:UserRequest:MyWorkOrders+' => 'All work orders assigned to me', 'Class:Problem:KnownProblemList' => 'Known problems', + 'Tickets:Related:OpenIncidents' => 'Open incidents', )); // Dictionnay conventions diff --git a/datamodels/2.x/itop-request-mgmt/fr.dict.itop-request-mgmt.php b/datamodels/2.x/itop-request-mgmt/fr.dict.itop-request-mgmt.php index 5e3592f9d..8699783d8 100755 --- a/datamodels/2.x/itop-request-mgmt/fr.dict.itop-request-mgmt.php +++ b/datamodels/2.x/itop-request-mgmt/fr.dict.itop-request-mgmt.php @@ -215,6 +215,7 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Class:UserRequest/Stimulus:ev_wait_for_approval' => 'Attendre une approbation', 'Class:UserRequest/Stimulus:ev_wait_for_approval+' => '', 'Class:UserRequest/Error:CannotAssignParentRequestIdToSelf' => 'La Requête parente ne peut pas être assignée à elle même', + 'Tickets:Related:OpenIncidents' => 'Incidents en cours', )); diff --git a/datamodels/2.x/itop-request-mgmt/images/incident-red.png b/datamodels/2.x/itop-request-mgmt/images/incident-red.png new file mode 100755 index 0000000000000000000000000000000000000000..5aac4ab4bb65196f17a3d3bcb0c4522532a536f6 GIT binary patch literal 1913 zcmV-<2Zs2GP)Y1ujk&G*`3L5t#*>hPG;}9-*?aX&bjB3quF(Eqf5w}KnKyt zcJr3hPP4mxvz2YTRky6H{GPeasMoQ|xzn}df{R~>fMo(VzR1532!U@i`_s>N zjSTP24HS!}W`fQ*2+s%LA{eLQTZTQ&j+sDS})I6|j(?D=k6L<*7IRozd>J}IpE|{DHn0&udLE)sJXQ=-{Evp|# zKaH%#qiev25d53s=$2vs?;vzU*AtqMz}!Y}`aD>*{E;D$I!MumCxny;{_z;JTW#R= z=ZEH3=f1@mc0kM%GzM;E?$1x3Ue2ZsMQHb1EXNRTHLyAJ3D_l0F&V8MitX=mFjkU zFgK2Zwej9$EIUsDcRmA1Q8lCDCK`5JlIZCMr|fL2mTIpdi8tyRkg3*t|HuJat^u=c z1QtjdV~8c^HOIqx z?m{?B6&(>02(ZJ+q?jZDy9ELC0CRD2DclHDkq4DMB6?O6W0_4GfCP3^Jw-Q*nyX?~ zUnw*|8|*_hOZV37CISq@qTCG=q%FoEZ@40X0||$Jj??c*9u5 zwG{y=Lg{|STf3i!X&HKzV7<`Ov_N^)&oUPYpj)EBY9NRDU{x2w$v6|O31Ipv0-Ps^ zN6$?QLIPTfwo$%}dF{Ar7ZYra%M*ryD`luywR)Wph%oh%paK6#M*#t%=|HFsl>5Ab zsnso&DJO?Ga1=i->Y`LBX169F!L_;vLNAI&M)8mc;6e%JAtA)3`0q8}_lr6<6pBC` zG*ME+NzmB0Yt00zc96a?57bv5g;wp>_|BK9XvwB#L6+ii&vpUN^RV5OBPx@!*zQx1O?qmAxM*mj0t|)h&6B+a<#wc$7wv*wq3u z_=pzv$$7Z^^C!4(gs#R(G>)y*-|k39t12sOYQY*4zgP?=u( z8eh(;egI7lI!n$zBW0vCs;N~(WjIi8=xUt6pYdSesb`_&vh740kgY6A{5@w^@YDMO#e-_Udjt zD{-RdWc!mDC_rU$;bSy8ooGst&6~c%PBW1tAiO}iB2qAsA!051c&?gJ9s)P+P<%L~ zV?!S}E31EcCFgZ=owj_0c3D4KS*yTSov>^|qNvCfrADx;LA^wKhUy8Is$l=Vgfohk z2b}Esg6ympS0-k6AozI8u2_Zw**&VK^utCzQ*dW0N|bmctH)HO+;0oG+J#1^49Rm! z92g58bmGb}8y1%*W*>`#w>%f%fM09Zst3!2LME|0 zaio*2UT+$@MhC%n+;Ifog=0>v#nYd#I-v!L8Ncj(!91@m-QRVOS!k!6Fz{EsFcfPL zleP-DeYhLUjyAVEHUDAlN_juNzxclor{QYuv-&az(!HIJ;0~7aaohFrV8XFISKF_2 zBc7TxY%plb1e!_7Vzy+@E`R6DRNuj`Pm-HG9Vbpi0E1duf7s}-b{cKgoti;=2-Pxj z&DbYa@xU_gRh$WL#W}`p_C1cD{4dAze*_o+dE?}SyMt8o00000NkvXXu0mjfuZoRP literal 0 HcmV?d00001 diff --git a/dictionaries/de.dictionary.itop.ui.php b/dictionaries/de.dictionary.itop.ui.php index 6e3e9bdd9..89c19f113 100644 --- a/dictionaries/de.dictionary.itop.ui.php +++ b/dictionaries/de.dictionary.itop.ui.php @@ -778,6 +778,8 @@ Wenn Aktionen mit Trigger verknüpft sind, bekommt jede Aktion eine Auftragsnumm 'UI:RelationGroupNumber_N' => 'Gruppe #%1$d~~', 'UI:Relation:ExportAsPDF' => 'Export as PDF...~~', 'UI:RelationOption:GroupingThreshold' => 'Grouping threshold~~', + 'UI:Relation:AdditionalContextInfo' => 'Additional context info~~', + 'UI:Relation:NoneSelected' => 'Nichts~~', 'UI:Relation:ExportAsDocument' => 'Export as Document...~~', 'UI:Relation:DrillDown' => 'Details...~~', 'UI:Relation:PDFExportOptions' => 'PDF Export Options~~', diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index 1be80e823..93d04ace8 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -971,6 +971,8 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:RelationGroupNumber_N' => 'Group #%1$d', 'UI:Relation:ExportAsPDF' => 'Export as PDF...', 'UI:RelationOption:GroupingThreshold' => 'Grouping threshold', + 'UI:Relation:AdditionalContextInfo' => 'Additional context info', + 'UI:Relation:NoneSelected' => 'None', 'UI:Relation:ExportAsAttachment' => 'Export as Attachment...', 'UI:Relation:DrillDown' => 'Details...', 'UI:Relation:PDFExportOptions' => 'PDF Export Options', diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index 4a7d1ac3e..e9e048748 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -814,6 +814,8 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé 'UI:RelationGroupNumber_N' => 'Groupe n°%1$d', 'UI:Relation:ExportAsPDF' => 'Exporter en PDF...', 'UI:RelationOption:GroupingThreshold' => 'Seuil de groupage', + 'UI:Relation:AdditionalContextInfo' => 'Infos complémentaires de contexte', + 'UI:Relation:NoneSelected' => 'Aucune', 'UI:Relation:ExportAsAttachment' => 'Exporter comme une Pièce Jointe...', 'UI:Relation:DrillDown' => 'Détails...', 'UI:Relation:PDFExportOptions' => 'Options de l\'export en PDF', diff --git a/js/simple_graph.js b/js/simple_graph.js index 3f4589d8c..4602902ef 100644 --- a/js/simple_graph.js +++ b/js/simple_graph.js @@ -28,14 +28,21 @@ $(function() include_list: 'Include the list of objects', comments: 'Comments', grouping_threshold: 'Grouping Threshold', - refresh: 'Refresh' + additional_context_info: 'Additional Context Info', + refresh: 'Refresh', + check_all: 'Check All', + uncheck_all: 'Uncheck All', + none_selected: 'None', + nb_selected: '# selected', }, export_as_document: null, drill_down: null, grouping_threshold: 10, excluded_classes: [], attachment_obj_class: null, - attachment_obj_key: null + attachment_obj_key: null, + additional_contexts: [], + context_key: '' }, // the constructor @@ -107,6 +114,7 @@ $(function() this.aEdges[k].aElements = []; this._draw_edge(this.aEdges[k]); } + this._make_tooltips(); }, _draw_node: function(oNode) { @@ -158,6 +166,19 @@ $(function() oNode.aElements.push(this.oPaper.image(oNode.icon_url, xPos - iWidth * this.fZoom/2, yPos - iHeight * this.fZoom/2, iWidth*this.fZoom, iHeight*this.fZoom).colorShift('#fff', 1)); } oNode.aElements.push(this.oPaper.image(oNode.icon_url, xPos - iWidth * this.fZoom/2, yPos - iHeight * this.fZoom/2, iWidth*this.fZoom, iHeight*this.fZoom).attr(oNode.icon_attr)); + + var idx = 0; + for(var i in oNode.context_icons) + { + var sgn = 2*(idx % 2) -1; // Suite: -1, 1, -1, 1, -1, 1, -1, etc. + var coef = Math.floor((1+idx)/2) * sgn; // Suite: 0, 1, -1, 2, -2, 3, -3, etc. + var alpha = coef*Math.PI/4 - Math.PI/2; + var x = xPos + Math.cos(alpha) * 1.25*iWidth * this.fZoom / 2; + var y = yPos + Math.sin(alpha) * 1.25*iWidth * this.fZoom / 2; + var l = iWidth/3 * this.fZoom; + oNode.aElements.push(this.oPaper.image(oNode.context_icons[i], x - l/2, y - l/2, l , l).attr(oNode.icon_attr)); + idx++; + } var oText = this.oPaper.text( xPos, yPos, oNode.label); oNode.text_attr['font-size'] = iFontSize * this.fZoom; oText.attr(oNode.text_attr); @@ -186,7 +207,18 @@ $(function() { var sNodeId = oNode.id; $(oNode.aElements[k].node).attr({'data-type': oNode.shape, 'data-id': oNode.id} ).attr('class', 'popupMenuTarget'); - oNode.aElements[k].drag(function(dx, dy, x, y, event) { me._move(sNodeId, dx, dy, x, y, event); }, function(x, y, event) { me._drag_start(sNodeId, x, y, event); }, function (event) { me._drag_end(sNodeId, event); }); + oNode.aElements[k].drag( + function(dx, dy, x, y, event) { + clearTimeout($(this.node).data('openTimeoutId')); + me._move(sNodeId, dx, dy, x, y, event); + }, + function(x, y, event) { + me._drag_start(sNodeId, x, y, event); + }, + function (event) { + me._drag_end(sNodeId, event); + } + ); } }, _move: function(sNodeId, dx, dy, x, y, event) @@ -367,7 +399,17 @@ $(function() var sPopupMenuId = 'tk_graph'+this.element.attr('id'); var sHtml = '
'; var sId = this.element.attr('id'); - sHtml += this.options.labels.grouping_threshold+'  '; + sHtml += this.options.labels.grouping_threshold+' '; + if (this.options.additional_contexts.length > 0) + { + sHtml += ' '+this.options.labels.additional_context_info+' ' + } + sHtml += ' '; sHtml += '
    • '; if (this.options.export_as_pdf != null) { @@ -389,6 +431,7 @@ $(function() $('#'+sPopupMenuId+'_pdf').click(function() { me.export_as_pdf(); }); $('#'+sPopupMenuId+'_attachment').click(function() { me.export_as_attachment(); }); $('#'+sId+'_grouping_threshold').spinner({ min: 2}); + $('#'+sId+'_contexts').multiselect({header: true, checkAllText: this.options.labels.check_all, uncheckAllText: this.options.labels.uncheck_all, noneSelectedText: this.options.labels.none_selected, selectedText: this.options.labels.nb_selected, selectedList: 1}); $('#'+sId+'_refresh_btn').button().click(function() { me.reload(); }); }, _build_context_menus: function() @@ -456,6 +499,8 @@ $(function() }, _export_dlg: function(sTitle, sSubmitUrl, sOperation) { + var sId = this.element.attr('id'); + var me = this; var oPositions = {}; for(k in this.aNodes) { @@ -463,6 +508,11 @@ $(function() } var sHtmlForm = '
      '; sHtmlForm += ''; + sHtmlForm += ''; + $('#'+sId+'_contexts').multiselect('getChecked').each(function() { + sHtmlForm += ''; + }); + sHtmlForm += ''; for(k in this.options.excluded_classes) { @@ -561,9 +611,12 @@ $(function() this.options.grouping_threshold = 2; $('#'+sId+'_grouping_threshold').val(this.options.grouping_threshold); } + var aContexts = []; + $('#'+sId+'_contexts').multiselect('getChecked').each(function() { aContexts[$(this).val()] = me.options.additional_contexts[$(this).val()].oql; }); 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}); - $.post(sUrl, {excluded_classes: this.options.excluded_classes, g: this.options.grouping_threshold, sources: this.options.sources, excluded: this.options.excluded }, function(data) { + $.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); }, 'json'); }, @@ -619,6 +672,79 @@ $(function() reload: function() { this.load_from_url(this.options.load_from_url); + }, + _make_tooltips: function() + { + var me = this; + $( ".popupMenuTarget" ).tooltip({ + content: function() { + var sDataId = $(this).attr('data-id'); + var sTooltipContent = '
      ×
      '; + sTooltipContent += me._get_tooltip_content(sDataId); + return sTooltipContent; + }, + items: '.popupMenuTarget', + position: { + my: "center bottom-10", + at: "center top", + using: function( position, feedback ) { + $(this).css( position ); + $( "
      " ) + .addClass( "arrow" ) + .addClass( feedback.vertical ) + .addClass( feedback.horizontal ) + .appendTo( this ); + } + } + }) + .off( "mouseover mouseout" ) + .on( "mouseover", function(event){ + event.stopImmediatePropagation(); + var jMe = $(this); + $(this).data('openTimeoutId', setTimeout(function() { + var sDataId = jMe.attr('data-id'); + if ($('.tooltip-close-button[data-id="'+sDataId+'"]').length == 0) + { + jMe.tooltip('open'); + } + }, 500)); + }) + .on( "mouseout", function(event){ + event.stopImmediatePropagation(); + clearTimeout($(this).data('openTimeoutId')); + }); + /* Happens at every on_drag_end !!! + .on( "click", function(){ + var sDataId = $(this).attr('data-id'); + if ($('.tooltip-close-button[data-id="'+sDataId+'"]').length == 0) + { + $(this).tooltip( 'open' ); + } + else + { + $(this).tooltip( 'close' ); + } + $( this ).unbind( "mouseleave" ); + return false; + }); + */ + $('body').on('click', '.tooltip-close-button', function() { + var sDataId = $(this).attr('data-id'); + $('.popupMenuTarget[data-id="'+sDataId+'"]').tooltip('close'); + }); + }, + _get_tooltip_content: function(sNodeId) + { + var oNode = this._find_node(sNodeId); + if (oNode !== null) + { + return oNode.tooltip; + } + return '

      Node Id:'+sNodeId+'

      '; + }, + _close_all_tooltips: function() + { + this.element.find('.popupMenuTarget').tooltip('close'); } }); }); diff --git a/pages/UI.php b/pages/UI.php index cd1150fc5..18f88120e 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -1439,6 +1439,7 @@ 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') @@ -1476,17 +1477,35 @@ EOF $oP->SetCurrentTabContainer('Navigator'); $sFirstTab = MetaModel::GetConfig()->Get('impact_analysis_first_tab'); + $sContextKey = "itop-config-mgmt/relation_context/$sRootClass/$sRelation/$sDirection"; + + // Check if the current object supports Attachments, similar to AttachmentPlugin::IsTargetObject + $sClassForAttachment = null; + $iIdForAttachment = null; + if (class_exists('Attachment')) + { + $aAllowedClasses = MetaModel::GetModuleSetting('itop-attachments', 'allowed_classes', array('Ticket')); + foreach($aAllowedClasses as $sAllowedClass) + { + if ($oObj instanceof $sAllowedClass) + { + $iIdForAttachment = $id; + $sClassForAttachment = $sClass; + } + } + } + // Display the tabs if ($sFirstTab == 'list') { DisplayNavigatorListTab($oP, $aResults, $sRelation, $oObj); $oP->SetCurrentTab(Dict::S('UI:RelationshipGraph')); - $oDisplayGraph->Display($oP, $aResults, $sRelation, $oAppContext); + $oDisplayGraph->Display($oP, $aResults, $sRelation, $oAppContext, array(), $sClassForAttachment, $iIdForAttachment, $sContextKey, array('this' => $oObj)); DisplayNavigatorGroupTab($oP, $aGroups, $sRelation, $oObj); } else { $oP->SetCurrentTab(Dict::S('UI:RelationshipGraph')); - $oDisplayGraph->Display($oP, $aResults, $sRelation, $oAppContext); + $oDisplayGraph->Display($oP, $aResults, $sRelation, $oAppContext, array(), $sClassForAttachment, $iIdForAttachment, $sContextKey, array('this' => $oObj)); DisplayNavigatorListTab($oP, $aResults, $sRelation, $oObj); DisplayNavigatorGroupTab($oP, $aGroups, $sRelation, $oObj); } diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 163bd6820..aa2f210fc 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -1743,6 +1743,8 @@ EOF $aExcludedClasses = utils::ReadParam('excluded_classes', array(), false, 'raw_data'); $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'); $aPositions = null; if ($sPositions != null) { @@ -1780,11 +1782,11 @@ EOF $iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth', 20); if ($sDirection == 'up') { - $oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth); + $oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aContexts); } else { - $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects); + $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects, $aContexts); } // Remove excluded classes from the graph @@ -1809,20 +1811,18 @@ EOF { $oGraph->UpdatePositions($aPositions); } - $iGroupIdx = 0; + $aGroups = array(); $oIterator = new RelationTypeIterator($oGraph, 'Node'); foreach($oIterator as $oNode) { if ($oNode instanceof DisplayableGroupNode) { - $aGroups[] = $oNode->GetObjects(); - $oNode->SetProperty('group_index', $iGroupIdx); - $iGroupIdx++; + $aGroups[$oNode->GetProperty('group_index')] = $oNode->GetObjects(); } } // First page is the graph - $oGraph->RenderAsPDF($oPage, $sComments); + $oGraph->RenderAsPDF($oPage, $sComments, $sContextKey); if ($bIncludeList) { @@ -1908,6 +1908,8 @@ EOF $iGroupingThreshold = utils::ReadParam('g', 5); $sPositions = utils::ReadParam('positions', null, false, 'raw_data'); $aExcludedClasses = utils::ReadParam('excluded_classes', array(), false, 'raw_data'); + $aContexts = utils::ReadParam('contexts', array(), false, 'raw_data'); + $sContextKey = utils::ReadParam('context_key', array(), false, 'raw_data'); $aPositions = null; if ($sPositions != null) { @@ -1946,11 +1948,11 @@ EOF $iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth', 20); if ($sDirection == 'up') { - $oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth); + $oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aContexts); } else { - $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects); + $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects, $aContexts); } // Remove excluded classes from the graph @@ -1973,7 +1975,7 @@ EOF { $oGraph->UpdatePositions($aPositions); } - $oPage->add($oGraph->GetAsJSON()); + $oPage->add($oGraph->GetAsJSON($sContextKey)); $oPage->SetContentType('application/json'); break; @@ -2024,8 +2026,10 @@ EOF $aResults = $oRelGraph->GetObjectsByClass(); $oGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down')); + + $sContextKey = 'itop-tickets/relation_context/'.$sClass.'/'.$sRelation.'/'.$sDirection; $oAppContext = new ApplicationContext(); - $oGraph->Display($oPage, $aResults, $sRelation, $oAppContext, $aExcludedObjects, $sClass, $iId); + $oGraph->Display($oPage, $aResults, $sRelation, $oAppContext, $aExcludedObjects, $sClass, $iId, $sContextKey, array('this' => $oTicket)); break; default: diff --git a/setup/modelfactory.class.inc.php b/setup/modelfactory.class.inc.php index c3d789c33..c6d350969 100644 --- a/setup/modelfactory.class.inc.php +++ b/setup/modelfactory.class.inc.php @@ -2152,16 +2152,8 @@ class MFParameters throw new Exception("Invalid Parameters: mixed tags ('$sFirstTagName' and '".$oChildElement->nodeName."') inside array '".$oNode->nodeName."'"); } $val = $this->ReadElement($oChildElement); - $idx = (string)$oChildElement->getAttribute('id'); // Don't cast into float, since floats are converted to int (i.e. truncated) when used as hash indexes (cf: http://php.net/manual/en/language.types.array.php) - if ($idx !== '') - { - $value[$idx] = $val; - } - else - { - // No specific Id, just push the value at the end of the array - $value[] = $val; - } + // No specific Id, just push the value at the end of the array + $value[] = $val; } } ksort($value, SORT_NUMERIC);