From 7ca7cb39ae3271801263a1991f1d1f9a9e043e88 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 15 May 2015 13:49:25 +0000 Subject: [PATCH] Integration of the new impact analysis into the tickets. SVN:trunk[3578] --- application/itopwebpage.class.inc.php | 41 +-- application/pdfpage.class.inc.php | 4 +- core/displayablegraph.class.inc.php | 277 ++++++++++++------ core/metamodel.class.php | 5 +- core/relationgraph.class.inc.php | 45 ++- .../datamodel.itop-change-mgmt-itil.xml | 51 ++-- .../datamodel.itop-change-mgmt.xml | 41 +-- .../datamodel.itop-request-mgmt-itil.xml | 65 +--- .../datamodel.itop-request-mgmt.xml | 66 ++--- .../itop-tickets/datamodel.itop-tickets.xml | 35 ++- .../2.x/itop-tickets/de.dict.itop-tickets.php | 13 +- .../2.x/itop-tickets/en.dict.itop-tickets.php | 13 +- .../2.x/itop-tickets/fr.dict.itop-tickets.php | 13 +- .../2.x/itop-tickets/main.itop-tickets.php | 120 ++++++++ .../2.x/itop-tickets/module.itop-tickets.php | 4 +- dictionaries/de.dictionary.itop.ui.php | 1 + dictionaries/dictionary.itop.ui.php | 1 + dictionaries/fr.dictionary.itop.ui.php | 7 +- js/simple_graph.js | 72 ++++- pages/UI.php | 185 +----------- pages/ajax.render.php | 146 +++++++-- 21 files changed, 700 insertions(+), 505 deletions(-) diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index 0d63733b0..dc94392ce 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -218,7 +218,7 @@ EOF; }, beforeLoad: function( event, ui ) { if ( ui.tab.data('loaded') && (ui.tab.attr('data-cache') == 'true')) { - event.defaultPrevented = true; + event.preventDefault(); return; } ui.panel.html('
'); @@ -867,26 +867,29 @@ EOF } else if ($this->GetOutputFormat() == 'pdf' && $this->IsOutputFormatAvailable('pdf') ) { - require_once(APPROOT.'lib/MPDF/mpdf.php'); - $oMPDF = new mPDF('c'); - $oMPDF->mirroMargins = false; - if ($this->a_base['href'] != '') + if (@is_readable(APPROOT.'lib/MPDF/mpdf.php')) { - $oMPDF->setBasePath($this->a_base['href']); // Seems that the tag is not recognized by mPDF... + require_once(APPROOT.'lib/MPDF/mpdf.php'); + $oMPDF = new mPDF('c'); + $oMPDF->mirroMargins = false; + if ($this->a_base['href'] != '') + { + $oMPDF->setBasePath($this->a_base['href']); // Seems that the tag is not recognized by mPDF... + } + $oMPDF->showWatermarkText = true; + if ($this->GetOutputOption('pdf', 'template_path')) + { + $oMPDF->setImportUse(); // Allow templates + $oMPDF->SetDocTemplate ($this->GetOutputOption('pdf', 'template_path'), 1); + } + $oMPDF->WriteHTML($sHtml); + $sOutputName = $this->s_title.'.pdf'; + if ($this->GetOutputOption('pdf', 'output_name')) + { + $sOutputName = $this->GetOutputOption('pdf', 'output_name'); + } + $oMPDF->Output($sOutputName, 'I'); } - $oMPDF->showWatermarkText = true; - if ($this->GetOutputOption('pdf', 'template_path')) - { - $oMPDF->setImportUse(); // Allow templates - $oMPDF->SetDocTemplate ($this->GetOutputOption('pdf', 'template_path'), 1); - } - $oMPDF->WriteHTML($sHtml); - $sOutputName = $this->s_title.'.pdf'; - if ($this->GetOutputOption('pdf', 'output_name')) - { - $sOutputName = $this->GetOutputOption('pdf', 'output_name'); - } - $oMPDF->Output($sOutputName, 'I'); } MetaModel::RecordQueryTrace(); ExecutionKPI::ReportStats(); diff --git a/application/pdfpage.class.inc.php b/application/pdfpage.class.inc.php index a6fee6190..56430f285 100644 --- a/application/pdfpage.class.inc.php +++ b/application/pdfpage.class.inc.php @@ -29,8 +29,8 @@ class iTopPDF extends TCPDF $aMargins = $this->getMargins(); // Display the title (centered) - $this->SetY(0); - $this->MultiCell($this->getPageWidth() - $aMargins['left'] - $aMargins['right'] - $iPageNumberWidth, 15, $this->sDocumentTitle, 0, 'C', false, 0 /* $ln */, '', '', true, 0, false, true, 15, 'M' /* $valign */); + $this->SetXY($aMargins['left'] + $iPageNumberWidth, 0); + $this->MultiCell($this->getPageWidth() - $aMargins['left'] - $aMargins['right'] - 2*$iPageNumberWidth, 15, $this->sDocumentTitle, 0, 'C', false, 0 /* $ln */, '', '', true, 0, false, true, 15, 'M' /* $valign */); $this->SetFont('dejavusans', '', 10); // Display the page number (right aligned) diff --git a/core/displayablegraph.class.inc.php b/core/displayablegraph.class.inc.php index 6ad902850..66949be57 100644 --- a/core/displayablegraph.class.inc.php +++ b/core/displayablegraph.class.inc.php @@ -560,13 +560,17 @@ class DisplayableGroupNode extends DisplayableNode */ class DisplayableGraph extends SimpleGraph { - protected $sDirection; + protected $bDirectionDown; protected $aTempImages; + protected $aSourceObjects; + protected $aSinkObjects; public function __construct() { parent::__construct(); $this->aTempImages = array(); + $this->aSourceObjects = array(); + $this->aSinkObjects = array(); } public function GetTempImageName() @@ -594,6 +598,7 @@ class DisplayableGraph extends SimpleGraph public static function FromRelationGraph(RelationGraph $oGraph, $iGroupingThreshold = 20, $bDirectionDown = true) { $oNewGraph = new DisplayableGraph(); + $oNewGraph->bDirectionDown = $bDirectionDown; $oNodesIter = new RelationTypeIterator($oGraph, 'Node'); foreach($oNodesIter as $oNode) @@ -603,16 +608,27 @@ class DisplayableGraph extends SimpleGraph case 'RelationObjectNode': $oNewNode = new DisplayableNode($oNewGraph, $oNode->GetId(), 0, 0); + $oObj = $oNode->GetProperty('object'); + $sClass = get_class($oObj); if ($oNode->GetProperty('source')) { + if (!array_key_exists($sClass, $oNewGraph->aSourceObjects)) + { + $oNewGraph->aSourceObjects[$sClass] = array(); + } + $oNewGraph->aSourceObjects[$sClass][] = $oObj->GetKey(); $oNewNode->SetProperty('source', true); } if ($oNode->GetProperty('sink')) { + if (!array_key_exists($sClass, $oNewGraph->aSinkObjects)) + { + $oNewGraph->aSinkObjects[$sClass] = array(); + } + $oNewGraph->aSinkObjects[$sClass][] = $oObj->GetKey(); $oNewNode->SetProperty('sink', true); } - $oObj = $oNode->GetProperty('object'); - $oNewNode->SetProperty('class', get_class($oObj)); + $oNewNode->SetProperty('class', $sClass); $oNewNode->SetProperty('object', $oObj); $oNewNode->SetProperty('icon_url', $oObj->GetIcon(false)); $oNewNode->SetProperty('label', $oObj->GetRawName()); @@ -791,71 +807,6 @@ class DisplayableGraph extends SimpleGraph } } } - - /** - * Renders as a suite of Javascript instructions to display the graph using the simple_graph widget - * @param WebPage $oP - * @param string $sId - * @param string $sExportAsPdfURL - * @param string $sExportAsDocumentURL - * @param string $sDrillDownURL - */ - function RenderAsRaphael(WebPage $oP, $sId = null, $sExportAsPdfURL, $sExportAsDocumentURL, $sDrillDownURL) - { - if ($sId == null) - { - $sId = 'graph'; - } - $oP->add('
'); - $aParams = array( - 'export_as_pdf' => array('url' => $sExportAsPdfURL, 'label' => Dict::S('UI:Relation:ExportAsPDF')), - 'export_as_document' => array('url' => $sExportAsDocumentURL, 'label' => Dict::S('UI:Relation:ExportAsDocument')), - 'drill_down' => array('url' => $sDrillDownURL, 'label' => Dict::S('UI:Relation:DrillDown')), - 'labels' => array( - 'export_pdf_title' => Dict::S('UI:Relation:PDFExportOptions'), - 'export' => Dict::S('UI:Relation:PDFExportOptions'), - 'cancel' => Dict::S('UI:Button:Cancel'), - ), - 'page_format' => array( - 'label' => Dict::S('UI:Relation:PDFExportPageFormat'), - 'values' => array( - 'A3' => Dict::S('UI:PageFormat_A3'), - 'A4' => Dict::S('UI:PageFormat_A4'), - 'Letter' => Dict::S('UI:PageFormat_Letter'), - ), - ), - 'page_orientation' => array( - 'label' => Dict::S('UI:Relation:PDFExportPageOrientation'), - 'values' => array( - 'P' => Dict::S('UI:PageOrientation_Portrait'), - 'L' => Dict::S('UI:PageOrientation_Landscape'), - ), - ), - ); - $oP->add_ready_script("var oGraph = $('#$sId').simple_graph(".json_encode($aParams).");"); - - $oIterator = new RelationTypeIterator($this, 'Node'); - foreach($oIterator as $sId => $oNode) - { - $aNode = $oNode->GetForRaphael(); - $sJSNode = json_encode($aNode); - $oP->add_ready_script("oGraph.simple_graph('add_node', $sJSNode);"); - } - $oIterator = new RelationTypeIterator($this, 'Edge'); - foreach($oIterator as $sId => $oEdge) - { - $aEdge = array(); - $aEdge['id'] = $oEdge->GetId(); - $aEdge['source_node_id'] = $oEdge->GetSourceNode()->GetId(); - $aEdge['sink_node_id'] = $oEdge->GetSinkNode()->GetId(); - $fOpacity = ($oEdge->GetSinkNode()->GetProperty('is_reached') && $oEdge->GetSourceNode()->GetProperty('is_reached') ? 1 : 0.2); - $aEdge['attr'] = array('opacity' => $fOpacity, 'stroke' => '#000'); - $sJSEdge = json_encode($aEdge); - $oP->add_ready_script("oGraph.simple_graph('add_edge', $sJSEdge);"); - } - - $oP->add_ready_script("oGraph.simple_graph('draw');"); - } /** * Renders as JSON string suitable for loading into the simple_graph widget @@ -964,13 +915,26 @@ class DisplayableGraph extends SimpleGraph $oPdf->SetAlpha(1); } + /** + * Renders (in PDF) the key (legend) of the graphics vertically to the left of the specified zone (xmin,ymin, xmax,ymax). Returns the width used by the legend. + * @param TCPDF $oPdf + * @param string $sComments + * @param float $xMin + * @param float $yMin + * @param float $xMax + * @param float $yMax + * @return number The width used by the legend + */ protected function RenderKey(TCPDF $oPdf, $sComments, $xMin, $yMin, $xMax, $yMax) { + $fFontSize = 7; // in mm + $fIconSize = 6; // in mm + $fPadding = 1; // in mm $oIterator = new RelationTypeIterator($this, 'Node'); - $fMaxWidth = max($oPdf->GetStringWidth(Dict::S('UI:Relation:Key')) - 6, $oPdf->GetStringWidth(Dict::S('UI:Relation:Comments')) - 6); + $fMaxWidth = max($oPdf->GetStringWidth(Dict::S('UI:Relation:Key')) - $fIconSize, $oPdf->GetStringWidth(Dict::S('UI:Relation:Comments')) - $fIconSize); $aClasses = array(); $aIcons = array(); - $oPdf->SetFont('dejavusans', '', 8, '', true); + $oPdf->SetFont('dejavusans', '', $fFontSize, '', true); foreach($oIterator as $sId => $oNode) { if ($sClass = $oNode->GetProperty('class')) @@ -987,26 +951,169 @@ class DisplayableGraph extends SimpleGraph } } } - $oPdf->SetXY($xMin + 1, $yMin +1); - $yPos = $yMin + 1; + $oPdf->SetXY($xMin + $fPadding, $yMin + $fPadding); + $yPos = $yMin + $fPadding; $oPdf->SetFillColor(225, 225, 225); - $oPdf->Cell(7 + $fMaxWidth, 7, Dict::S('UI:Relation:Key'), 0 /* border */, 1 /* ln */, 'C', true /* fill */); - $yPos += 8; + $oPdf->Cell($fIconSize + $fPadding + $fMaxWidth, $fIconSize + $fPadding, Dict::S('UI:Relation:Key'), 0 /* border */, 1 /* ln */, 'C', true /* fill */); + $yPos += $fIconSize + 2*$fPadding; foreach($aClasses as $sClass => $sLabel) { - $oPdf->SetX($xMin + 7); - $oPdf->Cell(0, 8, $sLabel, 0 /* border */, 1 /* ln */); - $oPdf->Image($aIcons[$sClass], $xMin+1, $yPos, 6, 6); - $yPos += 8; + $oPdf->SetX($xMin + $fIconSize + $fPadding); + $oPdf->Cell(0, $fIconSize + 2*$fPadding, $sLabel, 0 /* border */, 1 /* ln */); + $oPdf->Image($aIcons[$sClass], $xMin+1, $yPos, $fIconSize, $fIconSize); + $yPos += $fIconSize + 2*$fPadding; } - $oPdf->Rect($xMin, $yMin, $fMaxWidth+9, $yPos - $yMin, 'D'); - $oPdf->Rect($xMin, $yPos, $fMaxWidth+9, $yMax - $yPos, 'D'); + $oPdf->Rect($xMin, $yMin, $fMaxWidth + $fIconSize + 3*$fPadding, $yPos - $yMin, 'D'); + $oPdf->Rect($xMin, $yPos, $fMaxWidth + $fIconSize + 3*$fPadding, $yMax - $yPos, 'D'); $yPos +=1; - $oPdf->SetXY($xMin + 1, $yPos); - $oPdf->Cell(7 + $fMaxWidth, 7, Dict::S('UI:Relation:Comments'), 0 /* border */, 1 /* ln */, 'C', true /* fill */); - $yPos += 8; + $oPdf->SetXY($xMin + $fPadding, $yPos); + $oPdf->Cell($fIconSize + $fPadding + $fMaxWidth, $fIconSize + $fPadding, Dict::S('UI:Relation:Comments'), 0 /* border */, 1 /* ln */, 'C', true /* fill */); + $yPos += $fIconSize + 2*$fPadding; $oPdf->SetX($xMin); - $oPdf->writeHTMLCell(8 + $fMaxWidth, $yMax - $yPos, $xMin, $yPos, '

'.str_replace("\n", '
', htmlentities($sComments, ENT_QUOTES, 'UTF-8')).'

', 0 /* border */, 1 /* ln */); - return $fMaxWidth + 10; + $oPdf->writeHTMLCell($fIconSize + 2*$fPadding + $fMaxWidth, $yMax - $yPos, $xMin, $yPos, '

'.str_replace("\n", '
', htmlentities($sComments, ENT_QUOTES, 'UTF-8')).'

', 0 /* border */, 1 /* ln */); + return $fMaxWidth + $fIconSize + 4*$fPadding; } + + /** + * Display the graph inside the given page, with the "filter" drawer above it + * @param WebPage $oP + * @param hash $aResults + * @param string $sRelation + * @param ApplicationContext $oAppContext + * @param array $aExcludedObjects + */ + function Display(WebPage $oP, $aResults, $sRelation, ApplicationContext $oAppContext, $aExcludedObjects) + { + $aExcludedByClass = array(); + foreach($aExcludedObjects as $oObj) + { + if (!array_key_exists(get_class($oObj), $aExcludedByClass)) + { + $aExcludedByClass[get_class($oObj)] = array(); + } + $aExcludedByClass[get_class($oObj)][] = $oObj->GetKey(); + } + $oP->add("
\n"); + $oP->add_ready_script( +<< $aObjects) + { + foreach($aObjects as $oCurrObj) + { + $sSubClass = get_class($oCurrObj); + $aSortedElements[$sSubClass] = MetaModel::GetName($sSubClass); + } + } + + asort($aSortedElements); + $idx = 0; + foreach($aSortedElements as $sSubClass => $sClassName) + { + $oP->add(" "); + $idx++; + } + $oP->add("

"); + $oP->add("
\n"); + $oP->add("
\n"); + $oP->add("
".Dict::S('UI:ElementsDisplayed')."
\n"); + + $sDirection = utils::ReadParam('d', 'horizontal'); + $iGroupingThreshold = utils::ReadParam('g', 5); + + $oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/fraphael.js'); + $oP->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/jquery.contextMenu.css'); + $oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.contextMenu.js'); + $oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/simple_graph.js'); + try + { + $this->InitFromGraphviz(); + $sExportAsPdfURL = ''; + if (extension_loaded('gd')) + { + $sExportAsPdfURL = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=relation_pdf&relation='.$sRelation.'&direction='.($this->bDirectionDown ? 'down' : 'up'); + } + $oAppcontext = new ApplicationContext(); + $sContext = $oAppContext->GetForLink(); + $sDrillDownURL = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=details&class=%1$s&id=%2$s&'.$sContext; + $sExportAsDocumentURL = ''; + $sLoadFromURL = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=relation_json&relation='.$sRelation.'&direction='.($this->bDirectionDown ? 'down' : 'up'); + + $sId = 'graph'; + $oP->add('
'); + $aParams = array( + 'source_url' => $sLoadFromURL, + 'sources' => ($this->bDirectionDown ? $this->aSourceObjects : $this->aSinkObjects), + 'excluded' => $aExcludedByClass, + 'grouping_threshold' => $iGroupingThreshold, + 'export_as_pdf' => array('url' => $sExportAsPdfURL, 'label' => Dict::S('UI:Relation:ExportAsPDF')), + //'export_as_document' => array('url' => $sExportAsDocumentURL, 'label' => Dict::S('UI:Relation:ExportAsDocument')), + 'drill_down' => array('url' => $sDrillDownURL, 'label' => Dict::S('UI:Relation:DrillDown')), + 'labels' => array( + 'export_pdf_title' => Dict::S('UI:Relation:PDFExportOptions'), + 'export' => Dict::S('UI:Button:Export'), + 'cancel' => Dict::S('UI:Button:Cancel'), + 'title' => Dict::S('UI:RelationOption:Title'), + 'include_list' => Dict::S('UI:RelationOption:IncludeList'), + 'comments' => Dict::S('UI:RelationOption:Comments'), + 'grouping_threshold' => Dict::S('UI:RelationOption:GroupingThreshold'), + 'refresh' => Dict::S('UI:Button:Refresh'), + ), + 'page_format' => array( + 'label' => Dict::S('UI:Relation:PDFExportPageFormat'), + 'values' => array( + 'A3' => Dict::S('UI:PageFormat_A3'), + 'A4' => Dict::S('UI:PageFormat_A4'), + 'Letter' => Dict::S('UI:PageFormat_Letter'), + ), + ), + 'page_orientation' => array( + 'label' => Dict::S('UI:Relation:PDFExportPageOrientation'), + 'values' => array( + 'P' => Dict::S('UI:PageOrientation_Portrait'), + 'L' => Dict::S('UI:PageOrientation_Landscape'), + ), + ), + ); + $oP->add_ready_script("$('#$sId').simple_graph(".json_encode($aParams).");"); + } + catch(Exception $e) + { + $oP->add('
'.$e->getMessage().'
'); + } + $oP->add_script( +<<AddSourceObject($oObject); } - $oGraph->ComputeRelatedObjectsDown($sRelCode, $iMaxDepth, $bEnableRedundancy); + $oGraph->ComputeRelatedObjectsDown($sRelCode, $iMaxDepth, $bEnableRedundancy, $aUnreachable); return $oGraph; } diff --git a/core/relationgraph.class.inc.php b/core/relationgraph.class.inc.php index 292aa02cd..5ed1e0326 100644 --- a/core/relationgraph.class.inc.php +++ b/core/relationgraph.class.inc.php @@ -75,11 +75,11 @@ class RelationObjectNode extends GraphNode } /** - * Recursively mark the objects nodes as reached, unless we get stopped by a redundancy node + * Recursively mark the objects nodes as reached, unless we get stopped by a redundancy node or a 'not allowed' node */ public function ReachDown($sProperty, $value) { - if (is_null($this->GetProperty($sProperty))) + if (is_null($this->GetProperty($sProperty)) && ($this->GetProperty($sProperty.'_allowed') !== false)) { $this->SetProperty($sProperty, $value); foreach ($this->GetOutgoingEdges() as $oOutgoingEdge) @@ -201,7 +201,7 @@ class RelationGraph extends SimpleGraph /** * Build the graph downstream, and mark the nodes that can be reached from the source node */ - public function ComputeRelatedObjectsDown($sRelCode, $iMaxDepth, $bEnableRedundancy) + public function ComputeRelatedObjectsDown($sRelCode, $iMaxDepth, $bEnableRedundancy, $aUnreachableObjects = array()) { //echo "
Sources only...
\n".$this->DumpAsHtmlImage()."
\n"; // Build the graph out of the sources @@ -210,6 +210,21 @@ class RelationGraph extends SimpleGraph $this->AddRelatedObjects($sRelCode, true, $oSourceNode, $iMaxDepth, $bEnableRedundancy); //echo "
After processing of {$oSourceNode->GetId()}
\n".$this->DumpAsHtmlImage()."
\n"; } + + // Mark the unreachable nodes + foreach ($aUnreachableObjects as $oObj) + { + $sNodeId = RelationObjectNode::MakeId($oObj); + $oNode = $this->GetNode($sNodeId); + if($oNode) + { + $oNode->SetProperty('is_reached_allowed', false); + } + else + { + } + } + // Determine the reached nodes foreach ($this->aSourceNodes as $oSourceNode) { @@ -437,4 +452,28 @@ class RelationGraph extends SimpleGraph } return $oRet; } + + /** + * Get the objects referenced by the graph as a hash array: 'class' => array of objects + * @return Ambigous + */ + public function GetObjectsByClass() + { + $aResults = array(); + $oIterator = new RelationTypeIterator($this, 'Node'); + foreach($oIterator as $oNode) + { + $oObj = $oNode->GetProperty('object'); // Some nodes (Redundancy Nodes and Group) do not contain an object + if ($oObj) + { + $sObjClass = get_class($oObj); + if (!array_key_exists($sObjClass, $aResults)) + { + $aResults[$sObjClass] = array(); + } + $aResults[$sObjClass][] = $oObj; + } + } + return $aResults; + } } 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 626740182..78e8d0369 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 @@ -932,47 +932,32 @@ false protected Overload-DBObject - Get('contacts_list'); - $oToImpact = $this->Get('functionalcis_list'); - - $oImpactedInfras = DBObjectSet::FromLinkSet($this, 'functionalcis_list', 'functionalci_id'); - - $aComputed = $oImpactedInfras->GetRelatedObjects('impacts', 10); - - if (isset($aComputed['FunctionalCI']) && is_array($aComputed['FunctionalCI'])) - { - foreach($aComputed['FunctionalCI'] as $iKey => $oObject) - { - $oNewLink = new lnkFunctionalCIToTicket(); - $oNewLink->Set('functionalci_id', $iKey); - $oToImpact->AddObject($oNewLink); - } - } - if (isset($aComputed['Contact']) && is_array($aComputed['Contact'])) - { - foreach($aComputed['Contact'] as $iKey => $oObject) - { - $oNewLink = new lnkContactToTicket(); - $oNewLink->Set('contact_id', $iKey); - $oNewLink->Set('role', 'contact automatically computed'); - $oToNotify->AddObject($oNewLink); - } - } - + parent::OnInsert(); + $this->UpdateImpactedItems(); $this->Set('creation_date', time()); $this->Set('last_update', time()); - }]]> + }]]> + false protected Overload-DBObject - Set('last_update', time()); - }]]> + ListChanges(); + if (array_key_exists('functionalcis_list', $aChanges)) + { + $this->UpdateImpactedItems(); + } + $this->Set('last_update', time()); + }]]> + diff --git a/datamodels/2.x/itop-change-mgmt/datamodel.itop-change-mgmt.xml b/datamodels/2.x/itop-change-mgmt/datamodel.itop-change-mgmt.xml index daa4a650a..48cc23167 100755 --- a/datamodels/2.x/itop-change-mgmt/datamodel.itop-change-mgmt.xml +++ b/datamodels/2.x/itop-change-mgmt/datamodel.itop-change-mgmt.xml @@ -498,35 +498,11 @@ false protected Overload-DBObject - Get('contacts_list'); - $oToImpact = $this->Get('functionalcis_list'); - - $oImpactedInfras = DBObjectSet::FromLinkSet($this, 'functionalcis_list', 'functionalci_id'); - - $aComputed = $oImpactedInfras->GetRelatedObjects('impacts', 10); - - if (isset($aComputed['FunctionalCI']) && is_array($aComputed['FunctionalCI'])) - { - foreach($aComputed['FunctionalCI'] as $iKey => $oObject) - { - $oNewLink = new lnkFunctionalCIToTicket(); - $oNewLink->Set('functionalci_id', $iKey); - $oToImpact->AddObject($oNewLink); - } - } - if (isset($aComputed['Contact']) && is_array($aComputed['Contact'])) - { - foreach($aComputed['Contact'] as $iKey => $oObject) - { - $oNewLink = new lnkContactToTicket(); - $oNewLink->Set('contact_id', $iKey); - $oNewLink->Set('role', 'contact automatically computed'); - $oToNotify->AddObject($oNewLink); - } - } - + parent::OnInsert(); + $this->UpdateImpactedItems(); $this->Set('creation_date', time()); $this->Set('last_update', time()); }]]> @@ -535,8 +511,15 @@ false protected Overload-DBObject - ListChanges(); + if (array_key_exists('functionalcis_list', $aChanges)) + { + $this->UpdateImpactedItems(); + } $this->Set('last_update', time()); }]]> diff --git a/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml b/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml index 610d385a7..e542f87c8 100755 --- a/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml +++ b/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml @@ -1361,61 +1361,21 @@ false public LifecycleAction - Get('contacts_list'); - $oToImpact = $this->Get('functionalcis_list'); - - $oImpactedInfras = DBObjectSet::FromLinkSet($this, 'functionalcis_list', 'functionalci_id'); - - $oGraph = $oImpactedInfras->GetRelatedObjectsDown('impacts',10, true /* bEnableRedundancy */); - $oIterator = new RelationTypeIterator($oGraph, 'Node'); - foreach($oIterator as $oNode) - { - if($oNode instanceof RelationObjectNode) - { - if ($oNode->GetProperty('is_reached') && (!$oNode->GetProperty('source'))) - { - $oObj = $oNode->GetProperty('object'); - $sRootClass = MetaModel::GetRootClass(get_class($oObj)); - if (!array_key_exists($sRootClass, $aComputed)) - { - $aComputed[$sRootClass] = array(); - } - $aComputed[$sRootClass][$oObj->GetKey()] = $oObj; - } - } - } - - if (isset($aComputed['FunctionalCI']) && is_array($aComputed['FunctionalCI'])) - { - foreach($aComputed['FunctionalCI'] as $iKey => $oObject) - { - $oNewLink = new lnkFunctionalCIToTicket(); - $oNewLink->Set('functionalci_id', $iKey); - $oNewLink->Set('impact', 'potentially impacted (automatically computed)'); - $oToImpact->AddObject($oNewLink); - } - } - if (isset($aComputed['Contact']) && is_array($aComputed['Contact'])) - { - foreach($aComputed['Contact'] as $iKey => $oObject) - { - $oNewLink = new lnkContactToTicket(); - $oNewLink->Set('contact_id', $iKey); - $oNewLink->Set('role', 'contact automatically computed'); - $oToNotify->AddObject($oNewLink); - } - } - parent::OnInsert(); + // This method is kept for backward compatibility + // in case a delta redefines it, but you may call + // UpdateImpactedItems directly + $this->UpdateImpactedItems(); }]]> false protected Overload-DBObject - ComputeImpactedItems(); $this->Set('last_update', time()); @@ -1426,8 +1386,15 @@ false protected Overload-DBObject - ListChanges(); + if (array_key_exists('functionalcis_list', $aChanges)) + { + $this->UpdateImpactedItems(); + } $this->Set('last_update', time()); $this->UpdateChildRequestLog(); }]]> 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 7b238551c..52241a102 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 @@ -1363,62 +1363,23 @@ false public LifecycleAction - Get('contacts_list'); - $oToImpact = $this->Get('functionalcis_list'); - - $oImpactedInfras = DBObjectSet::FromLinkSet($this, 'functionalcis_list', 'functionalci_id'); - - $oGraph = $oImpactedInfras->GetRelatedObjectsDown('impacts',10, true /* bEnableRedundancy */); - $oIterator = new RelationTypeIterator($oGraph, 'Node'); - foreach($oIterator as $oNode) - { - if($oNode instanceof RelationObjectNode) - { - if ($oNode->GetProperty('is_reached') && (!$oNode->GetProperty('source'))) - { - $oObj = $oNode->GetProperty('object'); - $sRootClass = MetaModel::GetRootClass(get_class($oObj)); - if (!array_key_exists($sRootClass, $aComputed)) - { - $aComputed[$sRootClass] = array(); - } - $aComputed[$sRootClass][$oObj->GetKey()] = $oObj; - } - } - } - - if (isset($aComputed['FunctionalCI']) && is_array($aComputed['FunctionalCI'])) - { - foreach($aComputed['FunctionalCI'] as $iKey => $oObject) - { - $oNewLink = new lnkFunctionalCIToTicket(); - $oNewLink->Set('functionalci_id', $iKey); - $oNewLink->Set('impact', 'potentially impacted (automatically computed)'); - $oToImpact->AddObject($oNewLink); - } - } - if (isset($aComputed['Contact']) && is_array($aComputed['Contact'])) - { - foreach($aComputed['Contact'] as $iKey => $oObject) - { - $oNewLink = new lnkContactToTicket(); - $oNewLink->Set('contact_id', $iKey); - $oNewLink->Set('role', 'contact automatically computed'); - $oToNotify->AddObject($oNewLink); - } - } - parent::OnInsert(); + // This method is kept for backward compatibility + // in case a delta redefines it, but you may call + // UpdateImpactedItems directly + $this->UpdateImpactedItems(); }]]> false protected Overload-DBObject - ComputeImpactedItems(); $this->Set('last_update', time()); $this->Set('start_date', time()); @@ -1428,8 +1389,15 @@ false protected Overload-DBObject - ListChanges(); + if (array_key_exists('functionalcis_list', $aChanges)) + { + $this->UpdateImpactedItems(); + } $this->Set('last_update', time()); $this->UpdateChildRequestLog(); }]]> diff --git a/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml b/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml index 3b7f09976..db2556d2d 100755 --- a/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml +++ b/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml @@ -17,6 +17,7 @@ cmdbAbstractObject + _Ticket true + + + manual + computed + do_not_notify + + impact_code + manual + false + list + @@ -377,7 +389,7 @@ 20 - + 30 @@ -390,7 +402,7 @@ 20 - + 30 @@ -403,7 +415,7 @@ 20 - + 30 @@ -465,6 +477,17 @@ true + + + manual + computed + not_impacted + + impact_code + manual + false + list + @@ -476,7 +499,7 @@ 20 - + 30 @@ -489,7 +512,7 @@ 20 - + 30 @@ -502,7 +525,7 @@ 20 - + 30 diff --git a/datamodels/2.x/itop-tickets/de.dict.itop-tickets.php b/datamodels/2.x/itop-tickets/de.dict.itop-tickets.php index 5890e0a67..093e757ea 100755 --- a/datamodels/2.x/itop-tickets/de.dict.itop-tickets.php +++ b/datamodels/2.x/itop-tickets/de.dict.itop-tickets.php @@ -58,22 +58,31 @@ Dict::Add('DE DE', 'German', 'Deutsch', array( 'Class:Ticket/Attribute:workorders_list+' => '', 'Class:Ticket/Attribute:finalclass' => 'Typ', 'Class:Ticket/Attribute:finalclass+' => '', + 'Ticket:ImpactAnalysis' => 'Impact Analysis~~', 'Class:lnkContactToTicket' => 'Verknüpfung Kontakt/Ticket', 'Class:lnkContactToTicket+' => '', 'Class:lnkContactToTicket/Attribute:ticket_id' => 'Ticket', 'Class:lnkContactToTicket/Attribute:ticket_id+' => '', 'Class:lnkContactToTicket/Attribute:contact_id' => 'Kontakt', 'Class:lnkContactToTicket/Attribute:contact_id+' => '', - 'Class:lnkContactToTicket/Attribute:role' => 'Rolle', + 'Class:lnkContactToTicket/Attribute:role' => 'Rolle (Text)', 'Class:lnkContactToTicket/Attribute:role+' => '', + 'Class:lnkContactToTicket/Attribute:role_code' => 'Rolle', + 'Class:lnkContactToTicket/Attribute:role_code/Value:manual' => 'Added manually~~', + 'Class:lnkContactToTicket/Attribute:role_code/Value:computed' => 'Computed~~', + 'Class:lnkContactToTicket/Attribute:role_code/Value:do_not_notify' => 'Do not notify~~', 'Class:lnkFunctionalCIToTicket' => 'Verknüpfung FunctionalCI/Ticket', 'Class:lnkFunctionalCIToTicket+' => '', 'Class:lnkFunctionalCIToTicket/Attribute:ticket_id' => 'Ticket', 'Class:lnkFunctionalCIToTicket/Attribute:ticket_id+' => '', 'Class:lnkFunctionalCIToTicket/Attribute:functionalci_id' => 'CI', 'Class:lnkFunctionalCIToTicket/Attribute:functionalci_id+' => '', - 'Class:lnkFunctionalCIToTicket/Attribute:impact' => 'Auswirkung', + 'Class:lnkFunctionalCIToTicket/Attribute:impact' => 'Auswirkung (Text)', 'Class:lnkFunctionalCIToTicket/Attribute:impact+' => '', + 'Class:lnkFunctionalCIToTicket/Attribute:impact_code' => 'Auswirkung', + 'Class:lnkFunctionalCIToTicket/Attribute:impact_code/Value:manual' => 'Added manually~~', + 'Class:lnkFunctionalCIToTicket/Attribute:impact_code/Value:computed' => 'Computed~~', + 'Class:lnkFunctionalCIToTicket/Attribute:impact_code/Value:not_impacted' => 'Not impacted~~', 'Class:WorkOrder' => 'Arbeitsauftrag', 'Class:WorkOrder+' => '', 'Class:WorkOrder/Attribute:name' => 'Name', diff --git a/datamodels/2.x/itop-tickets/en.dict.itop-tickets.php b/datamodels/2.x/itop-tickets/en.dict.itop-tickets.php index 26d0b7bc6..2009be71b 100755 --- a/datamodels/2.x/itop-tickets/en.dict.itop-tickets.php +++ b/datamodels/2.x/itop-tickets/en.dict.itop-tickets.php @@ -82,6 +82,7 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:Ticket/Attribute:workorders_list+' => 'All the work orders for this ticket', 'Class:Ticket/Attribute:finalclass' => 'Type', 'Class:Ticket/Attribute:finalclass+' => '', + 'Ticket:ImpactAnalysis' => 'Impact Analysis', )); @@ -100,8 +101,12 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:lnkContactToTicket/Attribute:contact_id+' => '', 'Class:lnkContactToTicket/Attribute:contact_email' => 'Contact Email', 'Class:lnkContactToTicket/Attribute:contact_email+' => '', - 'Class:lnkContactToTicket/Attribute:role' => 'Role', + 'Class:lnkContactToTicket/Attribute:role' => 'Role (text)', 'Class:lnkContactToTicket/Attribute:role+' => '', + 'Class:lnkContactToTicket/Attribute:role_code' => 'Role', + 'Class:lnkContactToTicket/Attribute:role_code/Value:manual' => 'Added manually', + 'Class:lnkContactToTicket/Attribute:role_code/Value:computed' => 'Computed', + 'Class:lnkContactToTicket/Attribute:role_code/Value:do_not_notify' => 'Do not notify', )); // @@ -119,8 +124,12 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:lnkFunctionalCIToTicket/Attribute:functionalci_id+' => '', 'Class:lnkFunctionalCIToTicket/Attribute:functionalci_name' => 'CI Name', 'Class:lnkFunctionalCIToTicket/Attribute:functionalci_name+' => '', - 'Class:lnkFunctionalCIToTicket/Attribute:impact' => 'Impact', + 'Class:lnkFunctionalCIToTicket/Attribute:impact' => 'Impact (text)', 'Class:lnkFunctionalCIToTicket/Attribute:impact+' => '', + 'Class:lnkFunctionalCIToTicket/Attribute:impact_code' => 'Impact', + 'Class:lnkFunctionalCIToTicket/Attribute:impact_code/Value:manual' => 'Added manually', + 'Class:lnkFunctionalCIToTicket/Attribute:impact_code/Value:computed' => 'Computed', + 'Class:lnkFunctionalCIToTicket/Attribute:impact_code/Value:not_impacted' => 'Not impacted', )); diff --git a/datamodels/2.x/itop-tickets/fr.dict.itop-tickets.php b/datamodels/2.x/itop-tickets/fr.dict.itop-tickets.php index e22f9de80..52c959622 100755 --- a/datamodels/2.x/itop-tickets/fr.dict.itop-tickets.php +++ b/datamodels/2.x/itop-tickets/fr.dict.itop-tickets.php @@ -69,6 +69,7 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Class:Ticket/Attribute:workorders_list+' => '', 'Class:Ticket/Attribute:finalclass' => 'Type', 'Class:Ticket/Attribute:finalclass+' => '', + 'Ticket:ImpactAnalysis' => 'Analyse d\'Impact', )); @@ -87,8 +88,12 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Class:lnkContactToTicket/Attribute:contact_id+' => '', 'Class:lnkContactToTicket/Attribute:contact_email' => 'Email Contact', 'Class:lnkContactToTicket/Attribute:contact_email+' => '', - 'Class:lnkContactToTicket/Attribute:role' => 'Rôle', + 'Class:lnkContactToTicket/Attribute:role' => 'Rôle (texte)', 'Class:lnkContactToTicket/Attribute:role+' => '', + 'Class:lnkContactToTicket/Attribute:role_code' => 'Rôle', + 'Class:lnkContactToTicket/Attribute:role_code/Value:manual' => 'Ajouté manuellement', + 'Class:lnkContactToTicket/Attribute:role_code/Value:computed' => 'Calculé', + 'Class:lnkContactToTicket/Attribute:role_code/Value:do_not_notify' => 'Ne pas notifier', )); // @@ -106,8 +111,12 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Class:lnkFunctionalCIToTicket/Attribute:functionalci_id+' => '', 'Class:lnkFunctionalCIToTicket/Attribute:functionalci_name' => 'Nom CI', 'Class:lnkFunctionalCIToTicket/Attribute:functionalci_name+' => '', - 'Class:lnkFunctionalCIToTicket/Attribute:impact' => 'Impact', + 'Class:lnkFunctionalCIToTicket/Attribute:impact' => 'Impact (texte)', 'Class:lnkFunctionalCIToTicket/Attribute:impact+' => '', + 'Class:lnkFunctionalCIToTicket/Attribute:impact_code' => 'Impact', + 'Class:lnkFunctionalCIToTicket/Attribute:impact_code/Value:manual' => 'Ajouté manuellement', + 'Class:lnkFunctionalCIToTicket/Attribute:impact_code/Value:computed' => 'Calculé', + 'Class:lnkFunctionalCIToTicket/Attribute:impact_code/Value:not_impacted' => 'Non impacté', )); diff --git a/datamodels/2.x/itop-tickets/main.itop-tickets.php b/datamodels/2.x/itop-tickets/main.itop-tickets.php index 4cbf2ef6a..9cd0fb949 100755 --- a/datamodels/2.x/itop-tickets/main.itop-tickets.php +++ b/datamodels/2.x/itop-tickets/main.itop-tickets.php @@ -127,4 +127,124 @@ class ResponseTicketTTR extends ResponseTicketSLT implements iMetricComputer } } + +class _Ticket extends cmdbAbstractObject +{ + + public function UpdateImpactedItems() + { + $oContactsSet = $this->Get('contacts_list'); + $oCIsSet = $this->Get('functionalcis_list'); + + $aCIsToImpactCode = array(); + $aSources = array(); + $aExcluded = array(); + + $oCIsSet->Rewind(); + while ($oLink = $oCIsSet->Fetch()) + { + $iKey = $oLink->Get('functionalci_id'); + $aCIsToImpactCode[$iKey] = $oLink->Get('impact_code'); + if ($oLink->Get('impact_code') == 'manual') + { + $oObj = MetaModel::GetObject('FunctionalCI', $iKey); + $aSources[$iKey] = $oObj; + } + else if ($oLink->Get('impact_code') == 'not_impacted') + { + $oObj = MetaModel::GetObject('FunctionalCI', $iKey); + $aExcluded[$iKey] = $oObj; + } + } + + $aContactsToRoleCode = array(); + $oContactsSet->Rewind(); + while ($oLink = $oContactsSet->Fetch()) + { + $iKey = $oLink->Get('contact_id'); + $aContactsToRoleCode[$iKey] = $oLink->Get('role_code'); + if ($oLink->Get('role_code') == 'do_not_notify') + { + $oObj = MetaModel::GetObject('Contact', $iKey); + $aExcluded[$iKey] = $oObj; + } + } + + $oNewCIsSet = DBObjectSet::FromScratch('lnkFunctionalCIToTicket'); + foreach($aCIsToImpactCode as $iKey => $sImpactCode) + { + if ($sImpactCode != 'computed') + { + $oNewLink = new lnkFunctionalCIToTicket(); + $oNewLink->Set('functionalci_id', $iKey); + $oNewLink->Set('impact_code', $sImpactCode); + $oNewCIsSet->AddObject($oNewLink); + } + } + + $oNewContactsSet = DBObjectSet::FromScratch('lnkContactToTicket'); + foreach($aContactsToRoleCode as $iKey => $sImpactCode) + { + if ($sImpactCode != 'computed') + { + $oNewLink = new lnkContactToTicket(); + $oNewLink->Set('contact_id', $iKey); + $oNewLink->Set('role_code', $sImpactCode); + $oNewContactsSet->AddObject($oNewLink); + } + } + + $oContactsSet = DBObjectSet::FromScratch('lnkContactToTicket'); + $oGraph = MetaModel::GetRelatedObjectsDown('impacts', $aSources, 10, true /* bEnableRedundancy */, $aExcluded); + $oIterator = new RelationTypeIterator($oGraph, 'Node'); + foreach ($oIterator as $oNode) + { + if ( ($oNode instanceof RelationObjectNode) && ($oNode->GetProperty('is_reached')) && (!$oNode->GetProperty('source'))) + { + $oObj = $oNode->GetProperty('object'); + $iKey = $oObj->GetKey(); + $sRootClass = MetaModel::GetRootClass(get_class($oObj)); + switch ($sRootClass) + { + case 'FunctionalCI': + // Only link FunctionalCIs which are not already linked to the ticket + if (!array_key_exists($iKey, $aCIsToImpactCode) || ($aCIsToImpactCode[$iKey] != 'not_impacted')) + { + $oNewLink = new lnkFunctionalCIToTicket(); + $oNewLink->Set('functionalci_id', $iKey); + $oNewLink->Set('impact_code', 'computed'); + $oNewCIsSet->AddObject($oNewLink); + } + break; + + case 'Contact': + // Only link Contacts which are not already linked to the ticket + if (!array_key_exists($iKey, $aContactsToRoleCode) || ($aCIsToImpactCode[$iKey] != 'do_not_notify')) + { + $oNewLink = new lnkContactToTicket(); + $oNewLink->Set('contact_id', $iKey); + $oNewLink->Set('role_code', 'computed'); + $oNewContactsSet->AddObject($oNewLink); + } + break; + } + } + } + $this->Set('functionalcis_list', $oNewCIsSet); + $this->Set('contacts_list', $oNewContactsSet); + } + + public function DisplayBareRelations(WebPage $oPage, $bEditMode = false) + { + parent::DisplayBareRelations($oPage, $bEditMode); + if (!$bEditMode) + { + $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/fraphael.js'); + $oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/jquery.contextMenu.css'); + $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.contextMenu.js'); + $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/simple_graph.js'); + $oPage->AddAjaxTab(Dict::S('Ticket:ImpactAnalysis'), utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=ticket_impact&class='.get_class($this).'&id='.$this->GetKey(), true); + } + } +} ?> \ No newline at end of file diff --git a/datamodels/2.x/itop-tickets/module.itop-tickets.php b/datamodels/2.x/itop-tickets/module.itop-tickets.php index 297a9fbbe..af5e43375 100755 --- a/datamodels/2.x/itop-tickets/module.itop-tickets.php +++ b/datamodels/2.x/itop-tickets/module.itop-tickets.php @@ -3,7 +3,7 @@ SetupWebPage::AddModule( __FILE__, - 'itop-tickets/2.1.0', + 'itop-tickets/2.2.0', array( // Identification // @@ -22,8 +22,8 @@ SetupWebPage::AddModule( // Components // 'datamodel' => array( - 'model.itop-tickets.php', 'main.itop-tickets.php', + 'model.itop-tickets.php', ), 'data.struct' => array( // 'data.struct.ta-actions.xml', diff --git a/dictionaries/de.dictionary.itop.ui.php b/dictionaries/de.dictionary.itop.ui.php index eb3ffbcba..8df4bf7ca 100644 --- a/dictionaries/de.dictionary.itop.ui.php +++ b/dictionaries/de.dictionary.itop.ui.php @@ -777,6 +777,7 @@ Wenn Aktionen mit Trigger verknüpft sind, bekommt jede Aktion eine Auftragsnumm 'UI:RelationGroups' => 'Gruppen', 'UI:RelationGroupNumber_N' => 'Gruppe #%1$d~~', 'UI:Relation:ExportAsPDF' => 'Export as PDF...~~', + 'UI:RelationOption:GroupingThreshold' => 'Grouping threshold~~', '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 f34c5c0f3..427e87f77 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -970,6 +970,7 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:ElementsDisplayed' => 'Filtering', 'UI:RelationGroupNumber_N' => 'Group #%1$d', 'UI:Relation:ExportAsPDF' => 'Export as PDF...', + 'UI:RelationOption:GroupingThreshold' => 'Grouping threshold', 'UI:Relation:ExportAsDocument' => 'Export as Document...', '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 73c31b106..aa95460f4 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -812,10 +812,11 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé 'UI:RelationGroups' => 'Groupes', 'UI:ElementsDisplayed' => 'Filtrage', 'UI:RelationGroupNumber_N' => 'Groupe n°%1$d', - 'UI:Relation:ExportAsPDF' => 'Exportation en PDF...', - 'UI:Relation:ExportAsDocument' => 'Exportation comme Document...', + 'UI:Relation:ExportAsPDF' => 'Exporter en PDF...', + 'UI:RelationOption:GroupingThreshold' => 'Seuil de groupage', + 'UI:Relation:ExportAsDocument' => 'Exporter comme Document...', 'UI:Relation:DrillDown' => 'Détails...', - 'UI:Relation:PDFExportOptions' => 'Options de l\'Exportation PDF', + 'UI:Relation:PDFExportOptions' => 'Options de l\'export en PDF', 'UI:Relation:Key' => 'Légende', 'UI:Relation:Comments' => 'Commentaires', 'UI:RelationOption:Title' => 'Titre', diff --git a/js/simple_graph.js b/js/simple_graph.js index 4f1ea7072..c60d77443 100644 --- a/js/simple_graph.js +++ b/js/simple_graph.js @@ -16,13 +16,24 @@ $(function() align: 'center', 'vertical-align': 'middle', source_url: null, + sources: {}, + excluded: {}, export_as_pdf: null, page_format: { label: 'Page Format:', values: { A3: 'A3', A4: 'A4', Letter: 'Letter' }, 'default': 'A4'}, page_orientation: { label: 'Page Orientation:', values: { P: 'Portait', L: 'Landscape' }, 'default': 'L' }, - labels: { export_pdf_title: 'PDF Export Options', cancel: 'Cancel', 'export': 'Export', title: 'Document Title', include_list: 'Include the list of objects', comments: 'Comments' }, + labels: { + export_pdf_title: 'PDF Export Options', + cancel: 'Cancel', 'export': 'Export', + title: 'Document Title', + include_list: 'Include the list of objects', + comments: 'Comments', + grouping_threshold: 'Grouping Threshold', + refresh: 'Refresh' + }, export_as_document: null, drill_down: null, - excluded: [] + grouping_threshold: 10, + excluded_classes: [] }, // the constructor @@ -352,7 +363,10 @@ $(function() _create_toolkit_menu: function() { var sPopupMenuId = 'tk_graph'+this.element.attr('id'); - var sHtml = '
    • '; + var sHtml = '
      '; + var sId = this.element.attr('id'); + sHtml += this.options.labels.grouping_threshold+'  '; + sHtml += '
      '; + sHtml += '
      '; this.element.before(sHtml); - $('#'+sPopupMenuId).popupmenu(); + $('#'+sPopupMenuId+'>ul').popupmenu(); var me = this; $('#'+sPopupMenuId+'_pdf').click(function() { me.export_as_pdf(); }); $('#'+sPopupMenuId+'_document').click(function() { me.export_as_document(); }); - $('#'+sPopupMenuId+'_reload').click(function() { me.reload(); }); - + $('#'+sId+'_grouping_threshold').spinner({ min: 2}); + $('#'+sId+'_refresh_btn').button().click(function() { me.reload(); }); }, _build_context_menus: function() { @@ -400,13 +415,16 @@ $(function() { case 'group': var sGroupIndex = oNode.group_index; - oResult = { - callback: function(key, options) { - var me = $('.itop-simple-graph').data('itopSimple_graph'); // need a live value - me.show_group('relation_group_'+sGroupIndex); - }, - items: { 'show': {name: me.options.drill_down.label } } - }; + if( $('#relation_group_'+sGroupIndex).length > 0) + { + oResult = { + callback: function(key, options) { + var me = $('.itop-simple-graph').data('itopSimple_graph'); // need a live value + me.show_group('relation_group_'+sGroupIndex); + }, + items: { 'show': {name: me.options.drill_down.label } } + }; + } break; case 'icon': @@ -438,10 +456,25 @@ $(function() oPositions[this.aNodes[k].id] = {x: this.aNodes[k].x, y: this.aNodes[k].y }; } var sHtmlForm = '
      '; + sHtmlForm += ''; sHtmlForm += ''; - for(k in this.options.excluded) + for(k in this.options.excluded_classes) { - sHtmlForm += ''; + sHtmlForm += ''; + } + for(var k1 in this.options.sources) + { + for(var k2 in this.options.sources[k1]) + { + sHtmlForm += ''; + } + } + for(var k1 in this.options.excluded) + { + for(var k2 in this.options.excluded[k1]) + { + sHtmlForm += ''; + } } sHtmlForm += ''; sHtmlForm += '
      '+this.options.page_format.label+' "); - $idx++; - } - $oP->add("

      "); - $oP->add("\n"); - $oP->add("
      \n"); - $oP->add("
      ".Dict::S('UI:ElementsDisplayed')."
      \n"); - - $sDirection = utils::ReadParam('d', 'horizontal'); - $iGroupingThreshold = utils::ReadParam('g', 5); - - $oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/fraphael.js'); - $oP->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/jquery.contextMenu.css'); - $oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.contextMenu.js'); - $oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/simple_graph.js'); - try - { - $oDisplayGraph->InitFromGraphviz(); - $sExportAsPdfURL = ''; - if (extension_loaded('gd')) - { - $sExportAsPdfURL = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=relation_pdf&relation='.$sRelation.'&direction='.($bDirectionDown ? 'down' : 'up').'&class='.$sClass.'&id='.$id.'&g='.$iGroupingThreshold; - } - $oAppcontext = new ApplicationContext(); - $sContext = $oAppContext->GetForLink(); - $sDrillDownURL = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=details&class=%1$s&id=%2$s&'.$sContext; - $sExportAsDocumentURL = ''; - $sLoadFromURL = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=relation_json&relation='.$sRelation.'&direction='.($bDirectionDown ? 'down' : 'up').'&class='.$sClass.'&id='.$id.'&g='.$iGroupingThreshold; - - $sId = 'graph'; - $oP->add('
      '); - $aParams = array( - 'source_url' => $sLoadFromURL, - 'export_as_pdf' => array('url' => $sExportAsPdfURL, 'label' => Dict::S('UI:Relation:ExportAsPDF')), - //'export_as_document' => array('url' => $sExportAsDocumentURL, 'label' => Dict::S('UI:Relation:ExportAsDocument')), - 'drill_down' => array('url' => $sDrillDownURL, 'label' => Dict::S('UI:Relation:DrillDown')), - 'labels' => array( - 'export_pdf_title' => Dict::S('UI:Relation:PDFExportOptions'), - 'export' => Dict::S('UI:Button:Export'), - 'cancel' => Dict::S('UI:Button:Cancel'), - 'title' => Dict::S('UI:RelationOption:Title'), - 'include_list' => Dict::S('UI:RelationOption:IncludeList'), - 'comments' => Dict::S('UI:RelationOption:Comments'), - ), - 'page_format' => array( - 'label' => Dict::S('UI:Relation:PDFExportPageFormat'), - 'values' => array( - 'A3' => Dict::S('UI:PageFormat_A3'), - 'A4' => Dict::S('UI:PageFormat_A4'), - 'Letter' => Dict::S('UI:PageFormat_Letter'), - ), - ), - 'page_orientation' => array( - 'label' => Dict::S('UI:Relation:PDFExportPageOrientation'), - 'values' => array( - 'P' => Dict::S('UI:PageOrientation_Portrait'), - 'L' => Dict::S('UI:PageOrientation_Landscape'), - ), - ), - ); - $oP->add_ready_script("$('#$sId').simple_graph(".json_encode($aParams).");"); - } - catch(Exception $e) - { - $oP->add('
      '.$e->getMessage().'
      '); - } - $oP->add_script( -<<add_ready_script( -<<GetProperty('object'); // Some nodes (Redundancy Nodes and Group) do not contain an object - if ($oObj) - { - $sObjClass = get_class($oObj); - if (!array_key_exists($sObjClass, $aResults)) - { - $aResults[$sObjClass] = array(); - } - $aResults[$sObjClass][] = $oObj; - } - } - + $aResults = $oRelGraph->GetObjectsByClass(); $oDisplayGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down')); + $aGroups = array(); + $iGroupIdx = 0; $oIterator = new RelationTypeIterator($oDisplayGraph, 'Node'); foreach($oIterator as $oNode) { @@ -1646,12 +1479,14 @@ EOF if ($sFirstTab == 'list') { DisplayNavigatorListTab($oP, $aResults, $sRelation, $oObj); - DisplayNavigatorGraphicsTab($oP, $aResults, $oDisplayGraph, $sClass, $id, $sRelation, $oAppContext, ($sDirection == 'down')); + $oP->SetCurrentTab(Dict::S('UI:RelationshipGraph')); + $oDisplayGraph->Display($oP, $aResults, $sRelation, $oAppContext, ($sDirection == 'down')); DisplayNavigatorGroupTab($oP, $aGroups, $sRelation, $oObj); } else { - DisplayNavigatorGraphicsTab($oP, $aResults, $oDisplayGraph, $sClass, $id, $sRelation, $oAppContext, ($sDirection == 'down')); + $oP->SetCurrentTab(Dict::S('UI:RelationshipGraph')); + $oDisplayGraph->Display($oP, $aResults, $sRelation, $oAppContext, ($sDirection == 'down')); DisplayNavigatorListTab($oP, $aResults, $sRelation, $oObj); DisplayNavigatorGroupTab($oP, $aGroups, $sRelation, $oObj); } diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 9f9b6126c..a279f21c7 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -1,5 +1,5 @@ $aIDs) + { + $oSearch = new DBObjectSearch($sClass); + $oSearch->AddCondition('id', $aIDs, 'IN'); + $oSet = new DBObjectSet($oSearch); + while($oObj = $oSet->Fetch()) + { + $aSourceObjects[] = $oObj; + } + } + + // Get the list of excluded objects + $aExcluded = utils::ReadParam('excluded', array(), false, 'raw_data'); + $aExcludedObjects = array(); + foreach($aExcluded as $sClass => $aIDs) + { + $oSearch = new DBObjectSearch($sClass); + $oSearch->AddCondition('id', $aIDs, 'IN'); + $oSet = new DBObjectSet($oSearch); + while($oObj = $oSet->Fetch()) + { + $aExcludedObjects[] = $oObj; + } + } + $iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth', 20); - $aSourceObjects = array($oObj); if ($sDirection == 'up') { $oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth); } else { - $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth); + $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects); } // Remove excluded classes from the graph - if (count($aExcluded) > 0) + if (count($aExcludedClasses) > 0) { $oIterator = new RelationTypeIterator($oRelGraph, 'Node'); foreach($oIterator as $oNode) { $oObj = $oNode->GetProperty('object'); - if ($oObj && in_array(get_class($oObj), $aExcluded)) + if ($oObj && in_array(get_class($oObj), $aExcludedClasses)) { $oRelGraph->FilterNode($oNode); } @@ -1798,13 +1822,7 @@ EOF } // First page is the graph $oGraph->RenderAsPDF($oPage, $sComments); - /* - // Experimental QR code at the bottom left of the page... - $sUrl = "r=$sRelation&d=$sDirection&c=$sClass&id=$id"; - $oPdf = $oPage->get_tcpdf(); - $aMargins = $oPdf->getMargins(); - $oPdf->write2DBarcode($sUrl, 'QRCODE,H', $aMargins['left'], $oPdf->getPageHeight() - $aMargins['bottom'] - 35, 25, 25, array(), 'N'); - */ + if ($bIncludeList) { // Then the lists of objects (one table per finalclass) @@ -1864,39 +1882,64 @@ EOF require_once(APPROOT.'core/simplegraph.class.inc.php'); require_once(APPROOT.'core/relationgraph.class.inc.php'); require_once(APPROOT.'core/displayablegraph.class.inc.php'); - $sClass = utils::ReadParam('class', '', false, 'class'); - $id = utils::ReadParam('id', 0); - $sRelation = utils::ReadParam('relation', 'impact'); + $sRelation = utils::ReadParam('relation', 'impacts'); $sDirection = utils::ReadParam('direction', 'down'); $iGroupingThreshold = utils::ReadParam('g', 5); $sPositions = utils::ReadParam('positions', null, false, 'raw_data'); - $aExcluded = utils::ReadParam('excluded', array(), false, 'raw_data'); + $aExcludedClasses = utils::ReadParam('excluded_classes', array(), false, 'raw_data'); $aPositions = null; if ($sPositions != null) { $aPositions = json_decode($sPositions, true); } - $oObj = MetaModel::GetObject($sClass, $id); + // Get the list of source objects + $aSources = utils::ReadParam('sources', array(), false, 'raw_data'); + $aSourceObjects = array(); + foreach($aSources as $sClass => $aIDs) + { + $oSearch = new DBObjectSearch($sClass); + $oSearch->AddCondition('id', $aIDs, 'IN'); + $oSet = new DBObjectSet($oSearch); + while($oObj = $oSet->Fetch()) + { + $aSourceObjects[] = $oObj; + } + } + + // Get the list of excluded objects + $aExcluded = utils::ReadParam('excluded', array(), false, 'raw_data'); + $aExcludedObjects = array(); + foreach($aExcluded as $sClass => $aIDs) + { + $oSearch = new DBObjectSearch($sClass); + $oSearch->AddCondition('id', $aIDs, 'IN'); + $oSet = new DBObjectSet($oSearch); + while($oObj = $oSet->Fetch()) + { + $aExcludedObjects[] = $oObj; + } + } + + // Compute the graph $iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth', 20); - $aSourceObjects = array($oObj); if ($sDirection == 'up') { $oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth); } else { - $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth); + $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects); } // Remove excluded classes from the graph - if (count($aExcluded) > 0) + if (count($aExcludedClasses) > 0) { $oIterator = new RelationTypeIterator($oRelGraph, 'Node'); foreach($oIterator as $oNode) { $oObj = $oNode->GetProperty('object'); - if ($oObj && in_array(get_class($oObj), $aExcluded)) + if ($oObj && in_array(get_class($oObj), $aExcludedClasses)) { $oRelGraph->FilterNode($oNode); } @@ -1913,6 +1956,57 @@ EOF $oPage->SetContentType('application/json'); break; + case 'ticket_impact': + require_once(APPROOT.'core/simplegraph.class.inc.php'); + require_once(APPROOT.'core/relationgraph.class.inc.php'); + require_once(APPROOT.'core/displayablegraph.class.inc.php'); + $sRelation = utils::ReadParam('relation', 'impacts'); + $sDirection = utils::ReadParam('direction', 'down'); + $iGroupingThreshold = utils::ReadParam('g', 5); + $sClass = utils::ReadParam('class', '', false, 'class'); + $sAttCode = utils::ReadParam('attcode', 'functionalcis_list'); + $sImpactAttCode = utils::ReadParam('impact_attcode', 'impact_code'); + $sImpactAttCodeValue = utils::ReadParam('impact_attcode_value', 'manual'); + $iId = (int)utils::ReadParam('id', 0, false, 'integer'); + + // Get the list of source objects + $oTicket = MetaModel::GetObject($sClass, $iId); + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + $sExtKeyToRemote = $oAttDef->GetExtKeyToRemote(); + $oExtKeyToRemote = MetaModel::GetAttributeDef($oAttDef->GetLinkedClass(), $sExtKeyToRemote); + $sRemoteClass = $oExtKeyToRemote->GetTargetClass(); + $oSet = $oTicket->Get($sAttCode); + $aSourceObjects = array(); + $aExcludedObjects = array(); + while($oLnk = $oSet->Fetch()) + { + if ($oLnk->Get($sImpactAttCode) == 'manual') + { + $aSourceObjects[] = MetaModel::GetObject($sRemoteClass, $oLnk->Get($sExtKeyToRemote)); + } + if ($oLnk->Get($sImpactAttCode) == 'not_impacted') + { + $aExcludedObjects[] = MetaModel::GetObject($sRemoteClass, $oLnk->Get($sExtKeyToRemote)); + } + } + + // Compute the graph + $iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth', 20); + if ($sDirection == 'up') + { + $oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth); + } + else + { + $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, $aExcludedObjects); + } + + $aResults = $oRelGraph->GetObjectsByClass(); + $oGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down')); + $oAppContext = new ApplicationContext(); + $oGraph->Display($oPage, $aResults, $sRelation, $oAppContext, $aExcludedObjects); + break; + default: $oPage->p("Invalid query."); }