diff --git a/core/displayablegraph.class.inc.php b/core/displayablegraph.class.inc.php index ed5849c80..529639e7d 100644 --- a/core/displayablegraph.class.inc.php +++ b/core/displayablegraph.class.inc.php @@ -282,22 +282,45 @@ class DisplayableNode extends GraphNode { if (count($aGroupProps['nodes']) >= $iThresholdCount) { - $oNewNode = new DisplayableGroupNode($oGraph, $this->GetId().'::'.(($sStatus == 'reached') ? '_reached': '')); - $oNewNode->SetProperty('label', 'x'.$aGroupProps['count']); - $oNewNode->SetProperty('icon_url', $aGroupProps['icon_url']); - $oNewNode->SetProperty('class', $sClass); - $oNewNode->SetProperty('is_reached', ($sStatus == 'reached')); - $oNewNode->SetProperty('count', $aGroupProps['count']); + $sNewId = $this->GetId().'::'.(($sStatus == 'reached') ? '_reached': ''); + $oNewNode = $oGraph->GetNode($sNewId); + if ($oNewNode == null) + { + $oNewNode = new DisplayableGroupNode($oGraph, $sNewId); + $oNewNode->SetProperty('label', 'x'.$aGroupProps['count']); + $oNewNode->SetProperty('icon_url', $aGroupProps['icon_url']); + $oNewNode->SetProperty('class', $sClass); + $oNewNode->SetProperty('is_reached', ($sStatus == 'reached')); + $oNewNode->SetProperty('count', $aGroupProps['count']); + } + else + { + $oNewNode->SetProperty('count', $oNewNode->GetProperty('count')+$aGroupProps['count']); + } + + try + { + $oIncomingEdge = new DisplayableEdge($oGraph, $this->GetId().'-'.$oNewNode->GetId(), $this, $oNewNode); + } + catch(Exception $e) + { + // Ignore this redundant egde + } - $oIncomingEdge = new DisplayableEdge($oGraph, $this->GetId().'-'.$oNewNode->GetId(), $this, $oNewNode); - foreach($aGroupProps['nodes'] as $oNode) { foreach($oNode->GetIncomingEdges() as $oEdge) { if ($oEdge->GetSourceNode()->GetId() !== $this->GetId()) { - $oNewEdge = new DisplayableEdge($oGraph, $oEdge->GetId().'::'.$sClass, $oEdge->GetSourceNode(), $oNewNode); + try + { + $oNewEdge = new DisplayableEdge($oGraph, $oEdge->GetId().'::'.$sClass, $oEdge->GetSourceNode(), $oNewNode); + } + catch(Exception $e) + { + // ignore this edge + } } } foreach($oNode->GetOutgoingEdges() as $oEdge) @@ -825,7 +848,6 @@ class DisplayableGraph extends SimpleGraph { throw new Exception($sDot); } - $sDot = preg_replace('/.*label=.*,/', '', $sDot); // Get rid of label lines since they may contain weird characters than can break the split and pattern matching below $aChunks = explode(";", $sDot); foreach($aChunks as $sChunk) @@ -975,7 +997,7 @@ class DisplayableGraph extends SimpleGraph $yMin = $aRemainingArea['ymin']; $yMax = $aRemainingArea['ymax']; - //$oPdf->Rect($xMin, $yMin, $xMax - $xMin, $yMax - $yMin, 'D', array(), array(225, 225, 225)); + //$oPdf->Rect($xMin, $yMin, $xMax - $xMin, $yMax - $yMin, 'D', array(), array(225, 50, 50)); $fPageW = $xMax - $xMin; $fPageH = $yMax - $yMin; @@ -1093,7 +1115,7 @@ class DisplayableGraph extends SimpleGraph $yMax = $yPos - $fPadding; } - return array('xmin' => $fMaxWidth + $fIconSize + 4*$fPadding, 'xmax' => $xMax, 'ymin' => $yMin, 'ymax' => $yMax); + return array('xmin' => $xMin + $fMaxWidth + $fIconSize + 4*$fPadding, 'xmax' => $xMax, 'ymin' => $yMin, 'ymax' => $yMax); } /** diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 3b1b1194f..5e0433750 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -1403,10 +1403,11 @@ abstract class MetaModel $aQueries[$sRemoteClass]['down'][$sLocalClass]['sQueryUp'] = $aNeighbourData['sQueryUp']; $aQueries[$sRemoteClass]['down'][$sLocalClass]['sDirection'] = 'both'; } - else - { - throw new Exception("Legacy definition of the relation '$sRelCode/$sRevertCode', defined on $sLocalClass (relation: $sRevertCode, inherited to $sClass), missing the counterpart query on class $sRemoteClass ($sRelCode)"); - } + // Be silent in order to transparently support legacy data models where the counterpart query does not always exist + //else + //{ + // throw new Exception("Legacy definition of the relation '$sRelCode/$sRevertCode', defined on $sLocalClass (relation: $sRevertCode, inherited to $sClass), missing the counterpart query on class $sRemoteClass ($sRelCode)"); + //} } } } diff --git a/core/relationgraph.class.inc.php b/core/relationgraph.class.inc.php index cbcd72727..b7dbddeca 100644 --- a/core/relationgraph.class.inc.php +++ b/core/relationgraph.class.inc.php @@ -48,7 +48,7 @@ class RelationObjectNode extends GraphNode /** * Formatting for GraphViz */ - public function GetDotAttributes() + public function GetDotAttributes($bNoLabel = false) { $sDot = parent::GetDotAttributes(); if ($this->GetProperty('developped', false)) @@ -114,7 +114,7 @@ class RelationRedundancyNode extends GraphNode /** * Formatting for GraphViz */ - public function GetDotAttributes() + public function GetDotAttributes($bNoLabel = false) { $sDisplayThreshold = sprintf('%.1f', $this->GetProperty('threshold')); $sDot = 'shape=doublecircle,fillcolor=indianred,fontcolor=papayawhip,label="'.$sDisplayThreshold.'"'; diff --git a/core/simplegraph.class.inc.php b/core/simplegraph.class.inc.php index 49e5ec0b2..1dffe56af 100644 --- a/core/simplegraph.class.inc.php +++ b/core/simplegraph.class.inc.php @@ -128,10 +128,14 @@ class GraphNode extends GraphElement $oGraph->_AddNode($this); } - public function GetDotAttributes() + public function GetDotAttributes($bNoLabel = false) { - $sLabel = addslashes($this->GetProperty('label', $this->GetId())); - $sDot = 'label="'.$sLabel.'"'; + $sDot = ''; + if (!$bNoLabel) + { + $sLabel = addslashes($this->GetProperty('label', $this->GetId())); + $sDot = 'label="'.$sLabel.'"'; + } return $sDot; } @@ -264,10 +268,14 @@ class GraphEdge extends GraphElement return $this->oSinkNode; } - public function GetDotAttributes() + public function GetDotAttributes($bNoLabel = false) { - $sLabel = addslashes($this->GetProperty('label', '')); - $sDot = 'label="'.$sLabel.'"'; + $sDot = ''; + if (!$bNoLabel) + { + $sLabel = addslashes($this->GetProperty('label', '')); + $sDot = 'label="'.$sLabel.'"'; + } return $sDot; } } @@ -443,9 +451,10 @@ class SimpleGraph /** * Get the description of the graph as a text string in the graphviz 'dot' language + * @param $bNoLabel bool Whether or not to include the labels in the dot file * @return string */ - public function GetDotDescription() + public function GetDotDescription($bNoLabel = false) { $sDot = << $oNode) { - $sDot .= "\t\"".$oNode->GetId()."\" [ ".$oNode->GetDotAttributes()." ];\n"; + $sDot .= "\t\"".$oNode->GetId()."\" [ ".$oNode->GetDotAttributes($bNoLabel)." ];\n"; if (count($oNode->GetOutgoingEdges()) > 0) { foreach($oNode->GetOutgoingEdges() as $oEdge) { - $sDot .= "\t\"".$oNode->GetId()."\" -> \"".$oEdge->GetSinkNode()->GetId()."\" [ ".$oEdge->GetDotAttributes()." ];\n"; + $sDot .= "\t\"".$oNode->GetId()."\" -> \"".$oEdge->GetSinkNode()->GetId()."\" [ ".$oEdge->GetDotAttributes($bNoLabel)." ];\n"; } } } @@ -556,7 +565,7 @@ EOF @mkdir(APPROOT."data/tmp"); } $sXdotFilePath = tempnam(APPROOT."data/tmp", 'xdot-'); - $sDotDescription = $this->GetDotDescription(); + $sDotDescription = $this->GetDotDescription(true); // true => don't put (localized) labels in the file, since it makes it harder to parse $sDotFilePath = tempnam(APPROOT."data/tmp", 'dot-'); $rFile = @fopen($sDotFilePath, "w"); @@ -572,13 +581,14 @@ EOF $sHtml .= "

Error:

"; $sHtml .= "

The command:

$CommandLine
returned $iRetCode

"; $sHtml .= "

The output of the command is:

\n".implode("\n", $aOutput)."

"; + IssueLog::Error($sHtml); } else { $sHtml = '
'.file_get_contents($sXdotFilePath).'
'; - @unlink($sImageFilePath); + @unlink($sXdotFilePath); } - @unlink($sXdotFilePath); + @unlink($sDotFilePath); } else { 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 0d7da474a..cafb6ca21 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 @@ -103,7 +103,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', array( 'UI-ChangeManagementOverview-ChangeByDomain-last-7-days' => 'Changes der letzten sieben Tage nach Typ', 'UI-ChangeManagementOverview-ChangeByStatus-last-7-days' => 'Changes der letzten sieben Tage nach Status', 'Tickets:Related:OpenChanges' => 'Open changes~~', - 'Tickets:Related:RecentChanges' => 'Recent changes~~', + 'Tickets:Related:RecentChanges' => 'Recent changes (72h)~~', 'Class:Change/Attribute:changemanager_email' => 'Change Manager Email', 'Class:Change/Attribute:changemanager_email+' => '', 'Class:Change/Attribute:parent_name' => 'Parent Change ref', 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 76e5bb7b0..356562158 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 @@ -47,7 +47,7 @@ Dict::Add('EN US', 'English', 'English', array( 'UI-ChangeManagementOverview-ChangeByDomain-last-7-days' => 'Changes by domain for the last 7 days', 'UI-ChangeManagementOverview-ChangeByStatus-last-7-days' => 'Changes by status for the last 7 days', 'Tickets:Related:OpenChanges' => 'Open changes', - 'Tickets:Related:RecentChanges' => 'Recent changes', + 'Tickets:Related:RecentChanges' => 'Recent changes (72h)', )); // Dictionnay conventions 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 6d5c13d7d..ac6b60b3b 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 @@ -126,6 +126,6 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI-ChangeManagementOverview-ChangeByStatus-last-7-days' => 'Changements par statut', 'UI:ChangeMgmtMenuOverview:Title' => 'Tableau de bord des changements pour les 7 derniers jours', 'Tickets:Related:OpenChanges' => 'Changements en cours', - 'Tickets:Related:RecentChanges' => 'Changements récents', + 'Tickets:Related:RecentChanges' => 'Changements récents (72h)', )); ?> diff --git a/js/simple_graph.js b/js/simple_graph.js index 0b2ae4f3a..c12fa4c5e 100644 --- a/js/simple_graph.js +++ b/js/simple_graph.js @@ -269,10 +269,10 @@ $(function() this.options.ymax = -9999; for(var k in this.aNodes) { - this.options.xmin = Math.min(this.aNodes[k].x + this.aNodes[k].tx, this.options.xmin); - this.options.xmax = Math.max(this.aNodes[k].x + this.aNodes[k].tx, this.options.xmax); - this.options.ymin = Math.min(this.aNodes[k].y + this.aNodes[k].ty, this.options.ymin); - this.options.ymax = Math.max(this.aNodes[k].y + this.aNodes[k].ty, this.options.ymax); + this.options.xmin = Math.min(this.aNodes[k].x + this.aNodes[k].tx - this.aNodes[k].width/2, this.options.xmin); + this.options.xmax = Math.max(this.aNodes[k].x + this.aNodes[k].tx + this.aNodes[k].width/2, this.options.xmax); + this.options.ymin = Math.min(this.aNodes[k].y + this.aNodes[k].ty - this.aNodes[k].width/2, this.options.ymin); + this.options.ymax = Math.max(this.aNodes[k].y + this.aNodes[k].ty + this.aNodes[k].width/2, this.options.ymax); } }, _get_edge_path: function(oEdge) @@ -617,8 +617,10 @@ $(function() this.element.closest('.ui-tabs').tabs({ heightStyle: "fill" }); this._close_all_tooltips(); this.oPaper.rect(0, 0, this.element.width(), this.element.height()).attr({fill: '#000', opacity: 0.4, 'stroke-width': 0}); + $('#'+sId+'_refresh_btn').button('disable'); $.post(sUrl, {excluded_classes: this.options.excluded_classes, g: this.options.grouping_threshold, sources: this.options.sources, excluded: this.options.excluded, contexts: aContexts, context_key: this.options.context_key }, function(data) { me.load(data); + $('#'+sId+'_refresh_btn').button('enable'); }, 'json'); }, export_as_attachment: function() @@ -665,13 +667,22 @@ $(function() $.post(sUrl, oParams, function(data) { var sDownloadLink = GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?operation=download_document&class=Attachment&id='+data.att_id+'&field=contents'; var sIcon = GetAbsoluteUrlModulesRoot()+'itop-attachments/icons/pdf.png'; - $('#attachments').append('

'+sTitle+'.pdf

'); if (jTab != null) { var re = /^([^(]+)\(([0-9]+)\)(.*)$/; var aParts = re.exec(sTabText); - var iPrevCount = parseInt(aParts[2], 10); - jTab.find('span').html(aParts[1]+'('+(1 + iPrevCount)+')'+aParts[3]); + if (aParts == null) + { + // First attachment + $('#attachments').html('

'+sTitle+'.pdf

'); + jTab.find('span').html(sTabText +' (1)'); + } + else + { + $('#attachments').append('

'+sTitle+'.pdf

'); + var iPrevCount = parseInt(aParts[2], 10); + jTab.find('span').html(aParts[1]+'('+(1 + iPrevCount)+')'+aParts[3]); + } } }, 'json'); return false; @@ -691,15 +702,15 @@ $(function() return sTooltipContent; }, items: '.popupMenuTarget', + tooltipClass: 'tooltip-simple-graph', position: { my: "center bottom-10", - at: "center top", + at: "center top", using: function( position, feedback ) { $(this).css( position ); $( "
" ) .addClass( "arrow" ) .addClass( feedback.vertical ) - .addClass( feedback.horizontal ) .appendTo( this ); } } @@ -739,6 +750,11 @@ $(function() var sDataId = $(this).attr('data-id'); $('.popupMenuTarget[data-id="'+sDataId+'"]').tooltip('close'); }); + this.element.on("click", ":not(.tooltip-simple-graph *,.tooltip-simple-graph)", function(){ + $('.popupMenuTarget').each(function (i) { + $(this).tooltip("close"); + }); + }); }, _get_tooltip_content: function(sNodeId) { diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 1a0b6d54e..e9398eadd 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -1851,6 +1851,7 @@ EOF } $oPage->get_tcpdf()->AddPage(); + $oPage->get_tcpdf()->SetFont('dejavusans', '', 10, '', true); // Reset the font size to its default $oPage->add(''); $iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop'); foreach($aResults as $sListClass => $aObjects)