twb 1.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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