twb 1.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d8e5620e8ff52252576b9ba62c2c274341859698
4
- data.tar.gz: de116f608f42f388d3527c09f23a2786c7193918
3
+ metadata.gz: 8aae5b5038ecc0eade737b33b274b8a05dd3cc94
4
+ data.tar.gz: 161e0430d5558f2151f04be57a79a1ddb9e8c02a
5
5
  SHA512:
6
- metadata.gz: 65e5bda53ed6f4b1dc53e6dfce49fd6a70483369a5738ddd8fe8000d8aa997a4a4f09d5aff7082f96e71d0358f0c8dacfb68c6d46223d0f0acec3ee28defb7eb
7
- data.tar.gz: fc675deeba6612bbaa5eb8523494abd580d65e3bceff019525e4a6519f5a66dedcd970dcf6c95b5ce340d86d5fa61ca0dbc504a638784ec4d2eb8451f50dff61
6
+ metadata.gz: 297a5b3feadd0ad310843a4308883e7a74971ecb200b31450cabd568cba460fbd7b7b1dfb09a401f8f82e3612618310ded0c1c7049217d84f26f4592418ef44b
7
+ data.tar.gz: 8ae0b5c9b192d3f9796fe7f9bee9cc09cd2b7bd415692903d6861d34d24c39239c0fc230b69b60b0edef7d6b2e842b745ae62d3b1968b0b2b50f8baffde4b9a6
data/lib/twb.rb CHANGED
@@ -34,9 +34,11 @@ require_relative 'twb/util/xraydashboards'
34
34
  require_relative 'twb/util/graphnode'
35
35
  require_relative 'twb/util/graphedge'
36
36
  require_relative 'twb/util/graphedges'
37
+ require_relative 'twb/analysis/calculatedfieldsanalyzer'
38
+
37
39
 
38
40
  # Represents Tableau Workbooks and their contents.
39
41
  #
40
42
  module Twb
41
- VERSION = '1.0'
43
+ VERSION = '1.0.1'
42
44
  end
@@ -0,0 +1,509 @@
1
+ # TTC_CalculatedFields.rb - this Ruby script Copyright 2017 Christopher Gerrard
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ require 'nokogiri'
17
+ require 'twb'
18
+ require 'set'
19
+ require 'csv'
20
+ require 'logger'
21
+
22
+ module Twb
23
+ module Analysis
24
+
25
+ class CalculatedFieldsAnalyzer
26
+
27
+ attr_reader :calculatedFieldsCount, :formulaFieldsCount
28
+
29
+ @@ttlogfile = 'CalculatedFieldsAnalyzer.ttlog'
30
+ @@gvDotLocation = 'C:\\tech\\graphviz\\Graphviz2.38\\bin\\dot.exe'
31
+ @@processName = '.CalculatedFields'
32
+
33
+ @@calcFieldsCSVFileName = 'TwbCalculatedFields.csv'
34
+ @@calcFieldsCSVFileHeader = ['Record #',
35
+ 'Workbook', 'Workbook Dir',
36
+ 'Data Source', 'Data Source Caption', 'Data Source Name (tech)',
37
+ 'Field Name', 'Field Caption', 'Field Name (tech)',
38
+ 'Data Source + Field Name (tech)',
39
+ 'Data Type', 'Role', 'Type',
40
+ 'Class',
41
+ 'Scope Isolation',
42
+ 'Formula Length',
43
+ 'Formula Code',
44
+ 'Formula',
45
+ 'Formula Comments',
46
+ 'Formula LOD?'
47
+ ]
48
+
49
+ @@formFieldsCSVFileName = 'TwbFormulaFields.csv'
50
+ @@formFieldsCSVFileHeader = ['Rec #',
51
+ 'Workbook', 'Workbook Dir',
52
+ 'Data Source',
53
+ 'Field - Calculated',
54
+ 'Data Source - Formula (tech)',
55
+ 'Data Source - Formula',
56
+ 'Field - Formula (tech)',
57
+ 'Field - Formula',
58
+ 'Data Source + Field - Calculated',
59
+ 'Table'
60
+ ]
61
+
62
+ @techUINames = {}
63
+ @fieldTables = {}
64
+
65
+
66
+ @@dotHeader = <<DOTHEADER
67
+ digraph g {
68
+ graph [rankdir="LR" splines=line];
69
+ node [shape="box" width="2"];
70
+
71
+ DOTHEADER
72
+
73
+ def initialize
74
+ #-- Logging setup --
75
+ @logger = Logger.new(@@ttlogfile)
76
+ @logger.level = Logger::DEBUG
77
+ #-- CSV files setup --
78
+ @calcFieldsCSVFile = CSV.open(@@calcFieldsCSVFileName,'w')
79
+ @calcFieldsCSVFile << @@calcFieldsCSVFileHeader
80
+ # --
81
+ @formFieldsCSVFile = CSV.open(@@formFieldsCSVFileName ,'w')
82
+ @formFieldsCSVFile << @@formFieldsCSVFileHeader
83
+ #-- Counters setup --
84
+ @twbCount = 0
85
+ @calculatedFieldsCount = 0
86
+ @formulaFieldsCount = 0
87
+ # --
88
+ @referencedFields = SortedSet.new
89
+ # --
90
+ @localEmit = false
91
+ emit "\n\nLogging activity to: #{File.basename(@@ttlogfile)}"
92
+ @imageFiles = []
93
+ end
94
+
95
+ def loadFieldTables dataSource
96
+ emit "FIELD TABLES"
97
+ @records = CSV.read('C:\Professional\Clients\Incapsulate\Internal Project Monitoring\Project Portfolio v2\Salesforce Fields.csv')
98
+ @records.each do |rec|
99
+ emit "-- #{rec}"
100
+ m = {}
101
+ m['table'] = rec[1]
102
+ m['dbFieldName'] = rec[2]
103
+ @fieldTables[rec[0]] = m
104
+ end
105
+ emit "=========="
106
+ emit @fieldTables
107
+ emit "=========="
108
+ emit "FIELD TABLES"
109
+ end
110
+
111
+ def processTWB twbWithDir
112
+ twb = File.basename(twbWithDir)
113
+ @twb = Twb::Workbook.new twbWithDir
114
+ puts " - #{twbWithDir}"
115
+ emit "- Workbook: #{twbWithDir}"
116
+ emit " version: #{@twb.version}"
117
+ return if twbWithDir.end_with? == "Tableau Calculated Fields Analyses.twb"
118
+ twbDir = File.dirname(File.expand_path(twbWithDir))
119
+ edges = Set.new
120
+ # -- processing
121
+ dss = @twb.datasources
122
+ twbRootFields = Set.new
123
+ dss.each do |ds|
124
+ emit "Datasource: '#{ds.uiname}' -> #{ds.Parameters?}"
125
+ next if ds.Parameters? # don't process the Parameters data source
126
+ # it requires special handling, has different XML structure
127
+ #-- For tracking unreferenced (root) calculated fields = calculatedFields - referencedFields
128
+ calculatedFields = SortedSet.new
129
+ referencedFields = SortedSet.new
130
+ #--
131
+ dsTechName = ds.name
132
+ dsCaption = ds.caption
133
+ dsName = ds.uiname
134
+ dsID = dsTechName + ':::' + dsName
135
+ emit "\n\n "
136
+ emit "======================================================"
137
+ emit "======================================================"
138
+ emit "======= DATA SOURCE: #{ds.uiname} ====== "
139
+ emit "======================================================"
140
+ emit "======================================================\n\n "
141
+ dsGraphNode = Twb::Util::Graphnode.new(name: dsName, id: dsID, type: :TwbDataConnection, properties: {workbook: twbWithDir})
142
+ emit "\t dsgnode: #{dsGraphNode}"
143
+ fieldUINames = ds.fieldUINames
144
+ calculationNodes = ds.calculatedFields
145
+ emit "calculationNodes : nil? '#{calculationNodes.nil?}'" # - len '#{calculationNodes.length}'"
146
+ calculationNodes.each do |calcNode|
147
+ calculation = Twb::FieldCalculation.new calcNode
148
+ emit "HANDLING CALCULATION NODE:"
149
+ emit calcNode.attributes
150
+ #-- field names --
151
+ fldCaption, = calcNode.xpath('../@caption').text
152
+ fldTechName = calcNode.xpath('../@name').text.gsub(/^\[/,'').gsub(/\]$/,'')
153
+ fldName = if fldCaption == ''
154
+ then fldTechName
155
+ else fldCaption
156
+ end
157
+ emit "\t Field : #{fldName}"
158
+ emit "\t Formula : #{calcNode.attribute('formula')}"
159
+ dataType = calcNode.xpath('../@datatype').text
160
+ role = calcNode.xpath('../@role').text
161
+ type = calcNode.xpath('../@type').text
162
+ fieldID = fldTechName+'::'+dsName
163
+ calculatedFields.add fieldID
164
+ srcGraphNode = Twb::Util::Graphnode.new(name: fldName, id: fieldID, type: :CalculatedField, properties: {:DataSource => dsName})
165
+ dsFieldEdge = Twb::Util::Graphedge.new(from: dsGraphNode, to: srcGraphNode, relationship: 'contains')
166
+ edges.add dsFieldEdge
167
+ hasFormula = calcNode.has_attribute?('formula')
168
+ if hasFormula
169
+ formulaText = calcNode.attribute('formula').text
170
+ emit "\t Formula: #{formulaText}"
171
+ #-- field attributes --
172
+ # fldDispLabel = "#{fldName}\n--\n#{formulaText.gsub('"', "'")}" #fldName + '\n--' + formulaText.to_s
173
+ emit "\t srfnode: #{srcGraphNode} "
174
+ emit "\t dsfedge: #{dsFieldEdge} "
175
+ emit " "
176
+ emit "\tFIELD cap: #{fldCaption} "
177
+ emit "\t name: #{fldTechName} "
178
+ emit "\t uiname: #{fldName} "
179
+ emit "\t------------------------------------------------------------"
180
+ #-- calculation --
181
+ formulaFlat = formulaText.gsub(/\r\n/, ' ## ').gsub(/\n/, ' ## ').gsub(/[ ]+/,' ')
182
+ formulaFlatFlat = formulaFlat.upcase
183
+ formulaLOD = formulaFlatFlat.include?('{FIXED') || formulaFlatFlat.include?('{INCLUDE') || formulaFlatFlat.include?('{EXCLUDE')
184
+ formulaLength = formulaText.length
185
+ emit "\tFORMULA TEXT: #{formulaText} "
186
+ emit "\t FLAT: #{formulaFlat}"
187
+ emit "\t------------------------------------------------------------"
188
+ comments = calculation.comments # getComments( formulaText )
189
+ calcClass = calculation.class # d.xpath('./@class').text
190
+ scopeIsolation = calcNode.xpath('./@scope-isolation').text
191
+ # -- resolved fields: {internal field name => datasource}
192
+ # -- datasource is only present for fields located in other data sources
193
+ resolvedFields = calculation.resolvedFields
194
+ # prepare UI formula, replacing technical field names with their UI forms
195
+ uiFormula = formulaFlat.gsub(' XX ',' ')
196
+ resolvedFields.each do |rf|
197
+ emit "\tRESOLVED FLD: #{rf.inspect}"
198
+ calcFieldName = rf[:field]
199
+ if rf[:source].nil?
200
+ calcFieldRef = "[%s]" % [ calcFieldName ]
201
+ dispFieldRef = "[%s]" % [ ds.fieldUIName(calcFieldName) ]
202
+ else
203
+ remoteDS = @twb.datasource(rf[:source])
204
+ remoteDSName = remoteDS.uiname
205
+ remoteDSFld = remoteDS.fieldUIName(calcFieldName)
206
+ calcFieldRef = "[%s].[%s]" % [ rf[:source], calcFieldName ]
207
+ dispFieldRef = "[%s].[%s]" % [ remoteDSName, remoteDSFld ]
208
+ end
209
+ emit "\tcalcFieldRef: #{calcFieldRef}"
210
+ emit "\tdispFieldRef: #{dispFieldRef}"
211
+ uiFormula = uiFormula.gsub(calcFieldRef, dispFieldRef)
212
+ end
213
+ emit "\t FLAT: #{formulaFlat}"
214
+ emit "\t Resolved: #{uiFormula}\n\t--"
215
+ @calcFieldsCSVFile << [
216
+ @calculatedFieldsCount += 1,
217
+ twb, twbDir,
218
+ dsName, dsCaption, dsTechName,
219
+ fldName, fldCaption, fldTechName,
220
+ dsTechName + '::' + fldTechName,
221
+ dataType, role, type,
222
+ calcClass, scopeIsolation,
223
+ formulaLength, formulaFlat, uiFormula,
224
+ comments,
225
+ formulaLOD
226
+ ]
227
+ resolvedFields.each do |rf|
228
+ emit "\t\t res field : #{rf[:field]} "
229
+ emit "\t\t res source: #{rf[:source]}"
230
+ calcFieldName = rf[:field]
231
+ calcDataSource = rf[:source]
232
+ localDataSource = rf[:source].nil? # if there isn't a rf[:source] value
233
+ # the field is from this data source
234
+ # else the field is from an alien data source (in the same workbook)
235
+ refDataSource = localDataSource ? ds : @twb.datasource(calcDataSource)
236
+ dispFieldName = refDataSource.fieldUIName(calcFieldName)
237
+ calcFieldTable = refDataSource.fieldTable(calcFieldName)
238
+ emit "\t\t calc field : #{dispFieldName} nil?<#{dispFieldName.nil?}>"
239
+ emit "\t\t data source: #{refDataSource.uiname}"
240
+ emit "\t\t table: #{calcFieldTable} nil?<#{calcFieldTable.nil?}>"
241
+ properties = {'DataSource' => dsName, 'DataSourceReference' => 'local'}
242
+ if dispFieldName.nil?
243
+ dispFieldName = "<#{calcFieldName}>::<#{calcDataSource}> UNDEFINED"
244
+ properties['status'] = 'UNDEFINED'
245
+ end
246
+ calcFieldID = "#{calcFieldName}::#{refDataSource.uiname}"
247
+ if !localDataSource
248
+ calcFieldID = "#{calcFieldName}:LDS:#{ds.uiname}:RDS:#{refDataSource.uiname}"
249
+ properties['DataSourceReference'] = 'remote'
250
+ end
251
+ calcFieldTable = ds.fieldTable(calcFieldName)
252
+ calcFieldType = calcFieldTable.nil? ? :CalculatedField : :DatabaseField
253
+ calcFieldNode = Twb::Util::Graphnode.new(name: dispFieldName, id: calcFieldID, type: calcFieldType, properties: properties)
254
+ fieldFieldEdge = Twb::Util::Graphedge.new(from: srcGraphNode, to: calcFieldNode, relationship: 'references')
255
+ edges.add fieldFieldEdge
256
+ referencedFields.add calcFieldID
257
+ # @formulaFieldsCount+=1
258
+ emit "\t\t calcFieldNode: #{calcFieldNode}"
259
+ emit "\t\t graphEdge: #{fieldFieldEdge}"
260
+ fldToDsNode = calcFieldNode
261
+ if !calcFieldTable.nil?
262
+ tableID = calcFieldTable + ':::' + ds.uiname
263
+ tableName = "-[#{calcFieldTable}]-"
264
+ tableNode = Twb::Util::Graphnode.new(name: tableName, id: tableID, type: :DBTable, properties: properties)
265
+ fieldFieldEdge = Twb::Util::Graphedge.new(from: calcFieldNode, to: tableNode, relationship: 'is a field in')
266
+ edges.add fieldFieldEdge
267
+ fldToDsNode = tableNode
268
+ end
269
+ if !localDataSource
270
+ alienDSNode = Twb::Util::Graphnode.new( name: '==>' + refDataSource.uiname,
271
+ id: "#{ds.uiname}::::=>#{refDataSource.uiname}",
272
+ type: :DBTable,
273
+ properties: {'Home Source' => dsName, 'Remote Source' => refDataSource.uiname}
274
+ )
275
+ fieldFieldEdge = Twb::Util::Graphedge.new(from: fldToDsNode, to: alienDSNode, relationship: 'In Remote Data Source')
276
+ edges.add fieldFieldEdge
277
+ end
278
+ @formFieldsCSVFile << [ @formulaFieldsCount+=1,
279
+ twb,
280
+ twbDir,
281
+ dsName,
282
+ fldName,
283
+ refDataSource.name,
284
+ refDataSource.uiname,
285
+ calcFieldName,
286
+ dispFieldName,
287
+ dsName + '::' + dispFieldName,
288
+ 'fieldTable'
289
+ ]
290
+ end
291
+ end # if hasFormula
292
+ end # calculationNodes.each
293
+ dsRootFields = calculatedFields - referencedFields
294
+ @referencedFields.merge referencedFields
295
+ #--
296
+ emit "--\nCalculated Fields\n-----------------"
297
+ calculatedFields.each { |f| emit f }
298
+ emit "--\nReferenced Fields\n-----------------"
299
+ referencedFields.each { |f| emit f }
300
+ emit "--\nDS Root Fields\n-----------------"
301
+ dsRootFields.each { |f| emit f }
302
+ emit "--"
303
+ # --
304
+ twbRootFields.merge dsRootFields
305
+ end # dss.each
306
+ @twbCount += 1
307
+ mapTwb twb, edges, twbRootFields
308
+ graphEdges twb, edges
309
+ emit "#######################"
310
+ return @imageFiles
311
+ end
312
+
313
+
314
+ def mapTwb twb, edges, rootFields
315
+ dotFile = initDot twb
316
+ dotFileName = File.basename dotFile
317
+ dotFile.puts "\n // subgraph cluster_1 {"
318
+ dotFile.puts " // color= grey;"
319
+ dotFile.puts ""
320
+ edgesAsStrings = SortedSet.new
321
+ # this two step process coalesces the edges into a unique set, avoiding duplicating the dot
322
+ # file entries, and can be shrunk when graph edges expose the bits necessary for management by Set
323
+ emit "\n========================\nLoading Edges\n========================\n From DC? Referenced? Edge \n %s %s %s" % ['--------', '-----------', '-'*45]
324
+ edges.each do |e|
325
+ # don't want to emit edge which is from a Data Connection to a
326
+ # Calculated Field which is also referenced by another calculated field
327
+ isFromDC = e.from.type == :TwbDataConnection
328
+ isRefField = @referencedFields.include?(e.to.id)
329
+ edgesAsStrings.add(e.dot) unless isFromDC && isRefField
330
+ end
331
+ emit "------------------------\n "
332
+ edgesAsStrings.each do |es|
333
+ dotFile.puts " #{es}"
334
+ emit " #{es}"
335
+ end
336
+ emit "========================\n "
337
+ dotFile.puts ""
338
+ dotFile.puts " // }"
339
+ dotFile.puts "\n\n // 4--------------------------------------------------------------------"
340
+ # "table::JIRA_HARVEST_Correspondence__c::Jira" [label="JIRA_HARVEST_Correspondence__c"]
341
+ nodes = SortedSet.new
342
+ edges.each do |e|
343
+ nodes.add e.from.dotLabel
344
+ nodes.add e.to.dotLabel
345
+ end
346
+ nodes.each do |n|
347
+ dotFile.puts n
348
+ end
349
+ dotFile.puts "\n\n // 5--------------------------------------------------------------------"
350
+ emitTypes( edges, dotFile )
351
+ rankRootFields( dotFile, rootFields )
352
+ closeDot( dotFile, twb )
353
+ # renderPng(twb.name,dotFileName)
354
+ # renderPdf(twb.name,dotFileName)
355
+ renderDot(twb,dotFileName,'pdf')
356
+ renderDot(twb,dotFileName,'png')
357
+ renderDot(twb,dotFileName,'svg')
358
+ emitEdges edges
359
+ end
360
+
361
+
362
+ def graphEdges twb, edges
363
+ graphFile = File.new(twb + '.cypher', 'w')
364
+ # graphFile.puts "OKEY DOKE, graphing away"
365
+ cypherCode = Set.new
366
+ edges.each do |edge|
367
+ cypherCode.add edge.from.cypherCreate
368
+ cypherCode.add edge.to.cypherCreate
369
+ cypherCode.add edge.cypherCreate
370
+ end
371
+ cypherCode.each do |cc|
372
+ graphFile.puts cc
373
+ end
374
+ graphFile.puts "\nreturn *"
375
+ graphFile.close unless graphFile.nil?
376
+ @imageFiles << File.basename(graphFile)
377
+ end
378
+
379
+ def emitEdges edges
380
+ emit " %-15s %s" % ['type', 'Edge']
381
+ emit " %-15s %s" % ['-'*15, '-'*35]
382
+ edges.each do |edge|
383
+ emit " %-15s %s" % [edge.from.type, edge.from]
384
+ emit " %-15s %s" % [edge.to.type, edge.to]
385
+ emit "\n "
386
+ end
387
+ end
388
+
389
+ def emitTypes edges, dotFile
390
+ typedNodes = {}
391
+ dotFile.puts "\n\n // 2--------------------------------------------------------------------"
392
+ edges.each do |edge|
393
+ emit " EDGE :: #{edge}"
394
+ loadNodeType typedNodes, edge.from
395
+ loadNodeType typedNodes, edge.to
396
+ end
397
+ typedNodes.each do |type, nodes|
398
+ emit "+++++++++ typedNodes of '#{type}'' "
399
+ nodes.each do |node|
400
+ emit " -n- #{node}"
401
+ end
402
+ rankSame(dotFile, type, nodes) unless type == :CalculatedField
403
+ end
404
+ # labelTypes dotFile, edges
405
+ end
406
+
407
+ def loadNodeType set, node
408
+ type = node.type
409
+ set[type] = Set.new unless set.include? type
410
+ set[type].add node
411
+ end
412
+
413
+ def rankSame dotFile, type, nodes
414
+ dotFile.puts "\n // '#{type}' --------------------------------------------------------------------"
415
+ dotFile.puts "\n {rank=same "
416
+ # dotFile.puts " \"#{type}\" [shape=\"box3d\" style=\"filled\" ]" unless ''.eql? type # [shape=\"box3d\" style=\"filled\" ]\"" unless label.equal? ''
417
+ nodes.each do |node|
418
+ dotFile.puts " \"#{node.id}\""
419
+ end
420
+ dotFile.puts " }"
421
+ end
422
+
423
+ def rankRootFields dotFile, dsRootFields
424
+ dotFile.puts "\n // Unreferenced (root) Calculated Fields -----------------------------------------"
425
+ dotFile.puts "\n {rank=same "
426
+ dsRootFields.each do |rf|
427
+ dotFile.puts " \"#{rf}\""
428
+ end
429
+ dotFile.puts " }"
430
+ end
431
+
432
+
433
+ def labelTypes dotFile, edges
434
+ fromTos = Set.new
435
+ edges.each do |edge|
436
+ # fromTos.add "\"Alien Data Source\" -> \"Alien Data Source\""
437
+ fromTos.add "\"#{edge.from.type}\""
438
+ fromTos.add "\"#{edge.to.type}\""
439
+ end
440
+ return if fromTos.empty?
441
+ dotFile.puts "\n // 3--------------------------------------------------------------------"
442
+ dotFile.puts ' subgraph cluster_0 {'
443
+ dotFile.puts ' color=white;'
444
+ dotFile.puts ' node [shape="box3d" style="filled" ];'
445
+ fromTos.each do |ft|
446
+ dotFile.puts " #{ft}"
447
+ end
448
+ dotFile.puts ' }'
449
+ end
450
+
451
+
452
+ def emit(local=@localEmit, stuff)
453
+ #puts "\nstuff.class #{stuff.class} :: #{stuff}" if local
454
+ if stuff.is_a? String then
455
+ lines = stuff.split(/\n/)
456
+ lines.each do |line|
457
+ @logger.debug "#{@emitPrefix}#{line}"
458
+ puts "#{@emitPrefix}#{line}" if local
459
+ end
460
+ else
461
+ @logger.debug "#{@emitPrefix}#{stuff}"
462
+ puts "#{@emitPrefix}#{stuff}" if local
463
+ end
464
+ end
465
+
466
+
467
+ def initDot twb
468
+ dotFile = File.open("#{twb}#{@@processName}.dot",'w')
469
+ dotFile.puts @@dotHeader
470
+ return dotFile
471
+ end
472
+
473
+ def closeDot dotFile, twb
474
+ dotFile.puts ' '
475
+ dotFile.puts '// -------------------------------------------------------------'
476
+ dotFile.puts ' '
477
+ dotFile.puts ' subgraph cluster_1 {'
478
+ # dotFile.puts ' color=white;'
479
+ dotFile.puts ' style=invis;'
480
+ # dotFile.puts ' border=0;'
481
+ dotFile.puts ' node [border=blue];'
482
+ dotFile.puts ' '
483
+ dotFile.puts ' "" [style=invis]'
484
+ dotFile.puts " \"Tableau Tools\\nCalculated Fields Map\\nWorkbook '#{twb}'\\n#{Time.new.ctime}\" [penwidth=0]"
485
+ # dotFile.puts " \"Tableau Tools Workbook Calculated Fields Map\\n#{Time.new.ctime}\" -> \"\" [style=invis]"
486
+ dotFile.puts ' '
487
+ dotFile.puts ' }'
488
+ dotFile.puts ' '
489
+ dotFile.puts '}'
490
+ dotFile.close
491
+ end
492
+
493
+
494
+ def renderDot twb, dot, format
495
+ emit "Rendering DOT file\n - #{twb}\n - #{dot}\n - #{format}"
496
+ imageType = '-T' + format
497
+ imageFile = twb + @@processName + 'Graph.' + format
498
+ imageParam = '-o' + imageFile
499
+ emit "system #{@@gvDotLocation} #{imageType} #{imageParam} #{dot}"
500
+ system @@gvDotLocation, imageType, imageParam, dot
501
+ @imageFiles << imageFile
502
+ return imageFile
503
+ end
504
+
505
+
506
+ end # class
507
+
508
+ end # module Analysis
509
+ end # module Twb
@@ -21,7 +21,23 @@ module Util
21
21
  @@stripChars = /[.: %-\-\(\)=]/
22
22
  @@replChar = '_'
23
23
 
24
-
24
+ @@typeShapes = { :CalculatedField => 'shape=note',
25
+ :DBTable => 'shape=ellipse',
26
+ :TwbDataConnection => '',
27
+ :DatabaseField => ''
28
+ }
29
+
30
+ @@typeColors = { :CalculatedField => 'fillcolor=lightskyblue3',
31
+ :DBTable => 'fillcolor=steelblue',
32
+ :TwbDataConnection => 'fillcolor=cornflowerblue',
33
+ :DatabaseField => 'fillcolor=steelblue',
34
+ }
35
+
36
+ @@typeStyles = { :CalculatedField => 'style=filled',
37
+ :DBTable => 'style=filled',
38
+ :TwbDataConnection => 'style=filled',
39
+ :DatabaseField => 'style=filled'
40
+ }
25
41
 
26
42
  # @name - the visible name
27
43
  # @id - the technical identifier, used to distinquish the node from similarly named nodes
@@ -58,8 +74,9 @@ module Util
58
74
 
59
75
  def dotLabel
60
76
  # "JIRA 1::JIRA 1.csv" [label="JIRA 1.csv"]
61
- filled = @type =~ /Data Source|DB Table|Database Field/ ? "style=filled" : ''
62
- "\"%s\" [label=\"%s\" %s]" % [id, name, filled]
77
+ # style = @type =~ /Data Source|DB Table|Database Field/ ? "style=filled" : ''
78
+ # style = @type =~ /Data Source|DB Table|Database Field/ ? "style=filled" : ''
79
+ "\"%s\" [label=\"%s\" %s %s %s ]" % [id, name, @@typeShapes[@type], 'style=filled', @@typeColors[@type]]
63
80
  end
64
81
 
65
82
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: twb
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.0'
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Gerrard
@@ -24,6 +24,7 @@ files:
24
24
  - lib/twb.rb
25
25
  - lib/twb/TwbTest.rb
26
26
  - lib/twb/action.rb
27
+ - lib/twb/analysis/calculatedfieldsanalyzer.rb
27
28
  - lib/twb/apps/X-Ray Dashboards.rb
28
29
  - lib/twb/countNodes.rb
29
30
  - lib/twb/dashboard.rb