diff --git a/core/displayablegraph.class.inc.php b/core/displayablegraph.class.inc.php index cafebaecc..27021266e 100644 --- a/core/displayablegraph.class.inc.php +++ b/core/displayablegraph.class.inc.php @@ -1001,14 +1001,27 @@ class DisplayableGraph extends SimpleGraph { $aContextDefs = $this->GetContextDefinitions($sContextKey, false); - $aData = array('nodes' => array(), 'edges' => array()); + $aData = array('nodes' => array(), 'edges' => array(), 'groups' => array()); $iGroupIdx = 0; $oIterator = new RelationTypeIterator($this, 'Node'); foreach($oIterator as $sId => $oNode) { if ($oNode instanceof DisplayableGroupNode) { - $aGroups[] = $oNode->GetObjects(); + // The contents of the "Groups" tab will be rendered + // using a separate ajax call, since the content of + // the page is made of a mix of HTML / CSS / JS which + // cannot be conveyed easily in the JSON structure + // So we just pass a list of groups, each being defined by a class and a list of keys + // in order to avoid redoing the impact computation which is expensive + $aObjects = $oNode->GetObjects(); + $aKeys = array(); + foreach($aObjects as $oObj) + { + $sClass = get_class($oObj); + $aKeys[] = $oObj->GetKey(); + } + $aData['groups'][$iGroupIdx] = array('class' => $sClass, 'keys' => $aKeys); $oNode->SetProperty('group_index', $iGroupIdx); $iGroupIdx++; } diff --git a/js/simple_graph.js b/js/simple_graph.js index e4216b146..69bb9a594 100644 --- a/js/simple_graph.js +++ b/js/simple_graph.js @@ -61,6 +61,7 @@ $(function() this.iTextHeight = 12; this.fSliderZoom = 1.0; this.bInUpdateSliderZoom = false; + this.bRedrawNeeded = false; this.oPaper = Raphael(this.element.get(0), 16*this.element.width(), 16*this.element.height()); @@ -71,6 +72,17 @@ $(function() this._create_toolkit_menu(); this._build_context_menus(); + this.sTabId = null; + var jTabPanel = this.element.closest('.ui-tabs-panel'); + if (jTabPanel.length > 0) + { + // We are inside a tab, find out which one and hook its activation + this.sTabId = jTabPanel.attr('id'); + var jTabs = this.element.closest('.ui-tabs'); + jTabs.on( "tabsactivate", function( event, ui ) { + me._on_tabs_activate(ui); + }); + } $(window).bind('resized', function() { var that = me; window.setTimeout(function() { that._on_resize(); }, 50); } ); this.element.bind('mousewheel', function(event, delta, deltaX, deltaY) { return me._on_mousewheel(event, delta, deltaX, deltaY); @@ -341,9 +353,8 @@ $(function() } return null; }, - auto_scale: function() + adjust_height: function() { - var fMaxZoom = 1.5; var maxHeight = this.element.parent().height(); // Compute the available height var element = this.element; @@ -355,6 +366,12 @@ $(function() }); this.element.height(maxHeight - 20); + this.oPaper.setSize(this.element.width(), this.element.height()); + }, + auto_scale: function() + { + var fMaxZoom = 1.5; + this.adjust_height(); iMargin = 10; xmin = this.options.xmin - iMargin; @@ -639,10 +656,24 @@ $(function() { this.element.closest('.ui-tabs').tabs({ heightStyle: "fill" }); this.auto_scale(); - this.oPaper.setSize(this.element.width(), this.element.height()); this._close_all_tooltips(); this.draw(); }, + _on_tabs_activate: function(ui) + { + if (ui.newPanel.selector == ('#'+this.sTabId)) + { + if (this.bRedrawNeeded) + { + this._updateBBox(); + this.auto_scale(); + this.oPaper.setSize(this.element.width(), this.element.height()); + this._reset_pan_and_zoom(); + this.draw(); + bRedrawNeeded = false; + } + } + }, load: function(oData) { var me = this; @@ -657,11 +688,43 @@ $(function() { this.add_edge(oData.edges[k]); } - this._updateBBox(); - this.auto_scale(); - this.oPaper.setSize(this.element.width(), this.element.height()); - this._reset_pan_and_zoom(); - this.draw(); + if (oData.groups) + { + this.refresh_groups(oData.groups); + } + if (this.element.is(':visible')) + { + this._updateBBox(); + this.auto_scale(); + this._reset_pan_and_zoom(); + this.draw(); + } + else + { + this.bRedrawNeeded = true; + } + }, + refresh_groups: function(aGroups) + { + if ($('#impacted_groups').length > 0) + { + + // The "Groups" tab is present, refresh it + if (aGroups.length == 0) + { + this.element.closest('.ui-tabs').tabs("disable", 2); + $('#impacted_groups').html(''); + } + else + { + this.element.closest('.ui-tabs').tabs("enable", 2); + $('#impacted_groups').html(''); + var sUrl = GetAbsoluteUrlAppRoot()+'pages/ajax.render.php'; + $.post(sUrl, { operation: 'relation_groups', groups: aGroups }, function(data) { + $('#impacted_groups').html(data); + }); + } + } }, _reset_pan_and_zoom: function() { @@ -688,11 +751,12 @@ $(function() 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.adjust_height(); this._close_all_tooltips(); this.oPaper.rect(this.xPan, this.yPan, this.element.width(), this.element.height()).attr({fill: '#000', opacity: 0.4, 'stroke-width': 0}); this.oPaper.rect(this.xPan + this.element.width()/2 - 100, this.yPan + this.element.height()/2 - 10, 200, 20) .attr({fill: 'url(../setup/orange-progress.gif)', stroke: '#000', 'stroke-width': 1}); - this.oPaper.text(this.xPan + this.element.width()/2, this.yPan + this.element.height()/2 - 20, this.options.labels.loading); + this.oPaper.text(this.xPan + this.element.width()/2, this.yPan + this.element.height()/2 - 20, this.options.labels.loading); $('#'+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) { diff --git a/pages/UI.php b/pages/UI.php index f54480c97..15d607597 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -244,27 +244,14 @@ function DisplayNavigatorListTab($oP, $aResults, $sRelation, $sDirection, $oObj) $oP->add(""); } -function DisplayNavigatorGroupTab($oP, $aGroups, $sRelation, $oObj) +function DisplayNavigatorGroupTab($oP) { - if (count($aGroups) > 0) - { - $oP->SetCurrentTab(Dict::S('UI:RelationGroups')); - $oP->add("
"); - $iBlock = 1; // Zero is not a valid blockid - foreach($aGroups as $idx => $aObjects) - { - $sListClass = get_class(current($aObjects)); - $oSet = CMDBObjectSet::FromArray($sListClass, $aObjects); - $oP->add("

".Dict::Format('UI:RelationGroupNumber_N', (1+$idx))."

\n"); - $oP->add("
\n"); - $oP->add("

".MetaModel::GetClassIcon($sListClass)." ".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aObjects), Metamodel::GetName($sListClass))."

\n"); - $oP->add("
\n"); - $oBlock = DisplayBlock::FromObjectSet($oSet, 'list'); - $oBlock->Display($oP, 'group_'.$iBlock++); - $oP->p(' '); // Some space ? - } - $oP->add("
"); - } + $oP->SetCurrentTab(Dict::S('UI:RelationGroups')); + $oP->add("
"); + /* + * Content is rendered asynchronously via pages/ajax.render.php?operation=relation_groups + */ + $oP->add("
"); } /*********************************************************************************** @@ -1468,6 +1455,9 @@ EOF $oObj = MetaModel::GetObject($sClass, $id); $iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth', 20); $aSourceObjects = array($oObj); + + $oP->set_title(MetaModel::GetRelationDescription($sRelation).' '.$oObj->GetName()); + if ($sRelation == 'depends on') { $sRelation = 'impacts'; @@ -1484,20 +1474,7 @@ EOF $aResults = $oRelGraph->GetObjectsByClass(); - $oDisplayGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down')); - - $aGroups = array(); - $iGroupIdx = 0; - $oIterator = new RelationTypeIterator($oDisplayGraph, 'Node'); - foreach($oIterator as $oNode) - { - if ($oNode instanceof DisplayableGroupNode) - { - $aGroups[] = $oNode->GetObjects(); - $oNode->SetProperty('group_index', $iGroupIdx); - $iGroupIdx++; - } - } + $oDisplayGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down')); $oP->AddTabContainer('Navigator'); $oP->SetCurrentTabContainer('Navigator'); @@ -1526,14 +1503,14 @@ EOF DisplayNavigatorListTab($oP, $aResults, $sRelation, $sDirection, $oObj); $oP->SetCurrentTab(Dict::S('UI:RelationshipGraph')); $oDisplayGraph->Display($oP, $aResults, $sRelation, $oAppContext, array(), $sClassForAttachment, $iIdForAttachment, $sContextKey, array('this' => $oObj)); - DisplayNavigatorGroupTab($oP, $aGroups, $sRelation, $oObj); + DisplayNavigatorGroupTab($oP); } else { $oP->SetCurrentTab(Dict::S('UI:RelationshipGraph')); $oDisplayGraph->Display($oP, $aResults, $sRelation, $oAppContext, array(), $sClassForAttachment, $iIdForAttachment, $sContextKey, array('this' => $oObj)); DisplayNavigatorListTab($oP, $aResults, $sRelation, $sDirection, $oObj); - DisplayNavigatorGroupTab($oP, $aGroups, $sRelation, $oObj); + DisplayNavigatorGroupTab($oP); } $oP->SetCurrentTab(''); @@ -1622,7 +1599,7 @@ catch(Exception $e) require_once(APPROOT.'/setup/setuppage.class.inc.php'); $oP = new SetupPage(Dict::S('UI:PageTitle:FatalError')); $oP->add("

".Dict::S('UI:FatalErrorMessage')."

\n"); - $oP->error(Dict::Format('UI:Error_Details', $e->getMessage())); + $oP->error(Dict::Format('UI:Error_Details', $e->getMessage())); $oP->output(); if (MetaModel::IsLogEnabledIssue()) diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 659bdc32b..bb17962fa 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -1992,6 +1992,24 @@ EOF $oPage->SetContentType('application/json'); break; + case 'relation_groups': + $aGroups = utils::ReadParam('groups'); + $iBlock = 1; // Zero is not a valid blockid + foreach($aGroups as $idx => $aDefinition) + { + $sListClass = $aDefinition['class']; + $oSearch = new DBObjectSearch($sListClass); + $oSearch->AddCondition('id', $aDefinition['keys'], 'IN'); + $oPage->add("

".Dict::Format('UI:RelationGroupNumber_N', (1+$idx))."

\n"); + $oPage->add("
\n"); + $oPage->add("

".MetaModel::GetClassIcon($sListClass)." ".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aDefinition['keys']), Metamodel::GetName($sListClass))."

\n"); + $oPage->add("
\n"); + $oBlock = new DisplayBlock($oSearch, 'list'); + $oBlock->Display($oPage, 'group_'.$iBlock++); + $oPage->p(' '); // Some space ? + } + break; + case 'ticket_impact': require_once(APPROOT.'core/simplegraph.class.inc.php'); require_once(APPROOT.'core/relationgraph.class.inc.php');