1
2
3
4
5
6
7
8
9
10
11 """
12 Operations for Reports.
13
14 Available at: /zport/dmd/report_router
15 """
16
17 import logging
18 from itertools import izip_longest
19 from Products.ZenMessaging.audit import audit
20 from Products.ZenUtils.Ext import DirectResponse
21 from Products.Zuul.decorators import require
22 from Products.Zuul.marshalling import Marshaller
23 from Products.Zuul.utils import ZuulMessageFactory as _t
24 from Products.Zuul.routers import TreeRouter
25 from Products import Zuul
26 from Products.ZenModel.ReportClass import ReportClass
27 from Products.ZenModel.BaseReport import BaseReport
28 from Products.Zuul.catalog.interfaces import IModelCatalogTool
29
30 log = logging.getLogger('zen.ReportRouter')
31
32 reportTypes = [
33 'customDeviceReport',
34 'graphReport',
35 'multiGraphReport',
36 ]
37
38 menuText = [
39 _t('Custom Device Report'),
40 _t('Graph Report'),
41 _t('Multi-Graph Report'),
42 ]
43
44 essentialReportOrganizers = [
45 '/zport/dmd/Reports/Custom Device Reports',
46 '/zport/dmd/Reports/Graph Reports',
47 '/zport/dmd/Reports/Multi-Graph Reports',
48 '/zport/dmd/Reports',
49 ]
52 """
53 A JSON/ExtDirect interface to operations on reports
54 """
55
56
58 self.api = Zuul.getFacade('reports')
59 self.context = context
60 self.request = request
61 self.keys = ('deletable', 'edit_url', 'isMultiGraphReport', 'isGraphReport', 'columns')
62 super(ReportRouter, self).__init__(context, request)
63
66
68 """
69 Returns the tree structure of an organizer hierarchy where
70 the root node is the organizer identified by the id parameter.
71
72 @type id: string
73 @param id: Id of the root node of the tree to be returned
74 @rtype: [dictionary]
75 @return: Object representing the tree
76 """
77 facade = self._getFacade()
78 tree = facade.getTree(id)
79 data = Zuul.marshal(tree)
80 return [data]
81
83 """
84 Get the available report types.
85
86 @rtype: DirectResponse
87 @return: B{Properties}:
88 - menuText: ([string]) Human readable list of report types
89 - reportTypes: ([string]) A list of the available report types
90 """
91 return DirectResponse.succeed(reportTypes=reportTypes,
92 menuText=menuText)
93
96
97 @require('Manage DMD')
98 - def addNode(self, nodeType, contextUid, id):
99 """
100 Add a new report or report organizer.
101
102 @type nodeType: string
103 @param nodeType: Type of new node. Can either be 'organizer' or one of
104 the report types returned from getReportTypes()
105 @type contextUid: string
106 @param contextUid: The organizer where the new node should be added
107 @type id: string
108 @param id: The new node's ID
109 @rtype: DirectResponse
110 @return: B{Properties}:
111 - tree: (dictionary) Object representing the new Reports tree
112 - newNode: (dictionary) Object representing the added node
113 """
114 facade = self._getFacade()
115 if nodeType in reportTypes:
116 facade.addReport(nodeType, contextUid, id)
117 audit('UI.Report.Add', contextUid + '/' + id, reporttype=nodeType)
118 else:
119 facade.addOrganizer(contextUid, id, None)
120 audit('UI.Organizer.Add', contextUid + '/' + id)
121
122 return self._getTreeUpdates(contextUid, id)
123
124 @require('Manage DMD')
126 """
127 Remove a report or report organizer.
128
129 @type uid: string
130 @param uid: The UID of the node to delete
131 @rtype: [dictionary]
132 @return: B{Properties}:
133 - tree: (dictionary) Object representing the new Reports tree
134 """
135
136 if uid in essentialReportOrganizers:
137 raise Exception('You cannot delete this organizer')
138
139
140 node = self.context.dmd.unrestrictedTraverse(uid)
141 brains = IModelCatalogTool(node).search((ReportClass,BaseReport))
142 family = []
143 for brain in brains:
144 family.append([brain.getPath(), isinstance(brain.getObject(), ReportClass)])
145
146 self._getFacade().deleteNode(uid)
147
148
149 for name, isOrganizer in family:
150 if isOrganizer:
151 audit('UI.Organizer.Delete', name)
152 else:
153 audit('UI.Report.Delete', name)
154
155 contextUid = '/'.join(uid.split('/')[:-1])
156 return self._getTreeUpdates(contextUid)
157
159 marshalled = self._marshalPath(contextUid, newId, localKeys=self.keys)
160 for parent, child in zip(marshalled[:-1], marshalled[1:]):
161 parent['children'] = [child]
162 result = {'tree': [marshalled[0]]}
163 if newId:
164 result['newNode'] = marshalled[-1]
165 return DirectResponse.succeed(**result)
166
167 @require('Manage DMD')
169 """
170 Move a report or report organizer from one organizer to another.
171
172 @type uid: string
173 @param uid: The UID of node to move
174 @type target: string
175 @param target: The UID of the target Report organizer
176 @rtype: [dictionary]
177 @return: B{Properties}:
178 - tree: (dictionary) Object representing the new Reports tree
179 - newNode: (dictionary) Object representing the moved node
180 """
181 self._getFacade().moveNode(uid, target)
182 audit('UI.Report.Move', uid, target=target)
183 return self._treeMoveUpdates(uid, target)
184
186 oldPathTokens = uid.split('/')
187 oldPath = '/'.join(oldPathTokens[:-1])
188 oldBranch = self._marshalPath(oldPath, localKeys=self.keys + ('id', 'children'))
189 newId = oldPathTokens[-1]
190 newBranch = self._marshalPath(target, newId, localKeys=self.keys + ('id', 'children'))
191 for newParent, newChild, oldParent, oldChild in izip_longest(newBranch[:-1], newBranch[1:], oldBranch[:-1], oldBranch[1:], fillvalue=None):
192 if newParent and oldParent and newParent['id'] != oldParent['id']:
193 newParent['children'] = [newChild]
194 oldParent['children'] = [oldChild]
195 else:
196 parent = newParent if newParent else oldParent
197 if newChild and oldChild and newChild['id'] != oldChild['id']:
198 parent['children'] = [oldChild, newChild]
199 else:
200 child = newChild if newChild else oldChild
201 parent['children'] = [child]
202 tree = [newBranch[0]]
203 if oldBranch[0]['id'] != newBranch[0]['id']:
204 tree.append(oldBranch[0])
205
206 return DirectResponse.succeed()
207
208 - def _marshalPath(self, contextUid, newId=None, localKeys=None):
209 tokens = contextUid.split('/')
210 if newId:
211 tokens.append(newId)
212 paths = []
213
214 for x in range(4, len(tokens) + 1):
215 paths.append('/'.join(tokens[:x]))
216 nodes = [self._getFacade().getTree(id) for id in paths]
217 return [Marshaller(node).marshal(localKeys) for node in nodes]
218
220 data = Zuul.marshal(data)
221
222
223 for row in data:
224 row['title'] = row['contextTitle']
225 return DirectResponse(data=data)
226
228 """
229 Returns the json necessary for rendering graphs with the metric
230 service for each component in the graph definition.
231 @type uid: string
232 @param uid: unique identifier of an object
233 """
234 facade = self._getFacade()
235 return self._correctReportTitles(facade.getGraphReportDefs(uid))
236
238 """
239 Gets the json necessary for rendering graphs with the metric services
240 for multi graph reports.
241 @type uid: string
242 @param uid: unique identifier of an object
243 @param graphGroup: return the graph definition for the specific graph group
244 @type graphGroup: string
245
246 """
247 facade = self._getFacade()
248 return self._correctReportTitles(facade.getMultiGraphReportDefs(uid, graphGroup=graphGroup))
249