twb 4.9.4 → 5.2.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.
@@ -0,0 +1,139 @@
1
+ # dotanalyzer.rb - this Ruby script Copyright 2017, 2018 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
+
17
+ module Twb
18
+ module Analysis
19
+ module CalculatedFields
20
+
21
+ class DotAnalyzer
22
+ include TabTool
23
+
24
+ attr_reader :docFileName
25
+
26
+ @@gvDotLocation = 'C:\\tech\\graphviz\\Graphviz2.38\\bin\\dot.exe'
27
+ @@imageTypes = ['pdf', 'png', 'svg']
28
+
29
+ def initialize(**args)
30
+ @args = args
31
+ init
32
+ @funcdoc = {:class=>self.class, :blurb=>'Create Dot files documenting Calculated Fields', :description=>'Analyze Calculated Fields - create Dot files' }
33
+ @metrics = {}
34
+ @imageFiles = Array.new
35
+ end
36
+
37
+ def processTWB twb
38
+ # twb = File.basename(twb)
39
+ @twb = twb #Twb::Workbook.new twb
40
+ addDocFile @dotFile, @dotFileName, "Dot file of Calculated fields for Workbook '#{@twb.name}'"
41
+ @twb.datasources.each do |ds|
42
+ unless ds.calculatedFields.empty?
43
+ initDotFile ds.uiname
44
+ # @dotFile.puts "\n ## #{ds.uiname} \n "
45
+ # @dotFile.puts "__has #{ds.calculatedFields.length} calculated fields__\n "
46
+ @cfCnt = 0
47
+ calcFields = Set.new
48
+ refFields = Set.new
49
+ edges = Set.new
50
+ ds.calculatedFields.each do |cf|
51
+ @cfCnt += 1
52
+ calcFields << cf.uiname
53
+ edges << " \"#{ds.uiname}\" -> \"#{cf.uiname}\" [tailport=e, headport=w] "
54
+ cf.referencedFields.each do |rf|
55
+ refFields << rf.uiname
56
+ edges << " \"#{cf.uiname}\" -> \"#{rf.uiname}\" [tailport=e, headport=w] "
57
+ end
58
+ end # ds.calculatedFields.each
59
+ # "federated.17h7owt0rsacke17cql8o0w2ittk" -> "New AO Actuals Query in PP+ (AO Variance Data)::vs Prior Year [YTD]"
60
+ # "federated.01s5lca037ted31gxs9sg0t9mnnt" [label="Controls" ]
61
+ edges.each do |edge|
62
+ @dotFile.puts "\t #{edge.strip}"
63
+ end
64
+ @dotFile.puts " "
65
+ allFields = calcFields + refFields
66
+ allFields.each do |f|
67
+ @dotFile.puts "\t \"#{f}\" [label=\"#{f}\"]"
68
+ end
69
+ endPointFields = allFields - calcFields
70
+ rankSame(endPointFields) unless endPointFields.nil? || endPointFields.empty?
71
+ closeDotFile
72
+ @@imageTypes.each do |type|
73
+ renderDot type
74
+ end
75
+ end
76
+ end # twb.datasources.each
77
+ finis
78
+ end # def processTwb twb
79
+
80
+ private
81
+
82
+ def initDotFile dsName
83
+ @dotFileName = './ttdoc/' + @twb.name + '.' + dsName + '.CalculatedFields.dot'
84
+ @dotFile = File.open(@dotFileName,'w')
85
+ # @dotFile.puts @@dotHeader
86
+ @dotFile.puts ' digraph g {'
87
+ @dotFile.puts ' graph [rankdir="LR" splines=line];'
88
+ @dotFile.puts ' node [shape="box" width="2"];'
89
+ @dotFile.puts ' '
90
+ @dotFile.puts ' subgraph cluster_0 {'
91
+ end
92
+
93
+ def closeDotFile
94
+ # @dotFile.puts "\n # counted #{@cfCnt} calculated fields\n "
95
+ # @dotFile.puts "\n }"
96
+ @dotFile.puts ' }'
97
+ @dotFile.puts ' '
98
+ @dotFile.puts '// -------------------------------------------------------------'
99
+ @dotFile.puts ' '
100
+ @dotFile.puts ' subgraph cluster_1 {'
101
+ #@dotFile.puts ' color=white;'
102
+ @dotFile.puts ' style=invis;'
103
+ #@dotFile.puts ' border=0;'
104
+ @dotFile.puts ' node [border=blue];'
105
+ @dotFile.puts ' '
106
+ @dotFile.puts ' "" [style=invis]'
107
+ @dotFile.puts " \"Tableau Tools\\nCalculated Fields Map\\nWorkbook '#{@twb.name}'\\n#{Time.new.ctime}\" [penwidth=0]"
108
+ #@dotFile.puts " \"Tableau Tools Workbook Calculated Fields Map\\n#{Time.new.ctime}\" -> \"\" [style=invis]"
109
+ @dotFile.puts ' '
110
+ @dotFile.puts ' }'
111
+ @dotFile.puts ' '
112
+ @dotFile.puts '}'
113
+ @dotFile.close
114
+ end
115
+
116
+ def rankSame fields
117
+ @dotFile.puts "\n {rank=same "
118
+ fields.each do |f|
119
+ @dotFile.puts "\t \"#{f}\" "
120
+ end
121
+ @dotFile.puts " } "
122
+ end
123
+
124
+ def renderDot format
125
+ imageType = '-T' + format
126
+ imageFile = @dotFileName + '.Graph.' + format
127
+ imageParam = '-o"' + imageFile + '"'
128
+ emit "system #{@@gvDotLocation} #{imageType} #{imageParam} \"#{@dotFileName}\""
129
+ system "#{@@gvDotLocation} #{imageType} #{imageParam} \"#{@dotFileName}\""
130
+ # emit " - #{imageFile}"
131
+ @imageFiles << imageFile
132
+ return imageFile
133
+ end
134
+
135
+ end # class DotAnalyzer
136
+
137
+ end # nodule CalculatedFields
138
+ end # module Analysis
139
+ end # module Twb
@@ -52,10 +52,10 @@ module CalculatedFields
52
52
  @docFile.puts "#{l.gsub('<<','[').gsub('>>',']')}"
53
53
  end
54
54
  @docFile.puts "```"
55
- if cf.calcFields.length > 0
55
+ if cf.referencedFields.length > 0
56
56
  fieldsRefOrder = []
57
57
  fieldsSortSet = SortedSet.new
58
- cf.calcFields.each do |field|
58
+ cf.referencedFields.each do |field|
59
59
  fieldsRefOrder.push field.uiname
60
60
  fieldsSortSet << field.uiname
61
61
  end
@@ -0,0 +1,6 @@
1
+ require 'twb'
2
+
3
+ twb = Twb::Workbook.new 'MS.twb'
4
+ ds = twb.datasources.to_a.last
5
+ cfs = ds.calculatedFields
6
+ cfs.each { |cf| puts "\n\n--\n#{cf.calculation.formula}\n--"; cf.calcFields.each { |f| puts " %-12s %s " % [f[:ds], f[:field] ] } }
@@ -14,6 +14,7 @@
14
14
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
15
 
16
16
  require 'csv'
17
+ require 'pry'
17
18
 
18
19
  module Twb
19
20
  module Analysis
@@ -32,6 +33,7 @@ module DataSources
32
33
  'Data Source',
33
34
  'File Name',
34
35
  'File Dir',
36
+ 'File Exists?',
35
37
  'File Modified',
36
38
  'File Size'
37
39
  ]
@@ -55,7 +57,7 @@ module DataSources
55
57
 
56
58
  def processTWB twb
57
59
  @twbName = twb.name
58
- @twDir = twb.dir
60
+ @twbDir = twb.dir
59
61
  twb.datasources.each do |ds|
60
62
  processFiles ds
61
63
  end
@@ -69,9 +71,10 @@ module DataSources
69
71
  fqFileName = twbFileDir.nil? ? twbFileName : twbFileDir + '/' + twbFileName
70
72
  fileName = File.basename fqFileName
71
73
  fileDir = File.dirname fqFileName
72
- modtime = File.mtime fqFileName
73
- size = File.size fqFileName
74
- data = [$recNum+=1, twbName, twb.dir, ds.uiname, fileName, fileDir, modtime, size]
74
+ exists = File.exist? fqFileName
75
+ modtime = exists ? File.mtime(fqFileName) : nil
76
+ size = exists ? File.size(fqFileName) : nil
77
+ data = [@recNum+=1, @twbName, @twbDir, ds.uiname, fileName, fileDir, exists, modtime, size]
75
78
  @csvFile << data
76
79
  end
77
80
  end
@@ -0,0 +1,94 @@
1
+ # sheetfieldsanalyzer.rb Copyright (C) 2018 Chris 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 'twb'
17
+ require 'csv'
18
+
19
+ module Twb
20
+ module Analysis
21
+
22
+ class DashboardsSummarizer
23
+
24
+ include TabTool
25
+
26
+ attr_accessor :localEmit
27
+
28
+ def initialize(**args)
29
+ @args = args
30
+ @recordDir = !@args.nil? && @args[:recordDir] == true
31
+ @ttdocdir = @args[:ttdocdir]
32
+ @csvAdd = !@args.nil? && args[:csvMode] == :add
33
+ @csvMode = @csvAdd ? 'a' : 'w'
34
+ init
35
+ @funcdoc = {:class=>self.class, :blurb=>'Analyze Dashboard Worksheets', :description=>'Identifies the Worksheets present in Dashboards.',}
36
+ #--
37
+ docFileName = docFile('DashboardSummaries.csv')
38
+ @dashboardsCSV = CSV.open(docFileName,@csvMode)
39
+ unless @csvAdd
40
+ if @recordDir
41
+ @dashboardsCSV << ['Rec #','Workbook','Dashboard','# Worksheets','Workbook Dir']
42
+ else
43
+ @dashboardsCSV << ['Rec #','Workbook','Dashboard','# Worksheets' ]
44
+ end
45
+ end
46
+ addDocFile @dashboardsCSV, docFileName, "Workbooks and their Dashboards' summaries"
47
+ #--
48
+ @twbCount = 0
49
+ @dashCount = 0
50
+ @recNum = 0
51
+ end
52
+
53
+ def metrics
54
+ {
55
+ # '# of Workbooks' => @twbCount,
56
+ '# of Dashboards' => @dashCount,
57
+ }
58
+ end
59
+
60
+ def processTWB twb
61
+ @twb = twb
62
+ @twbName = @twb.name
63
+ @twbDir = @twb.dir
64
+ @modTime = @twb.modtime
65
+ emit " -- #{@twbName}"
66
+ @twbCount += 1
67
+ parseDashes
68
+ finis
69
+ end
70
+
71
+ def parseDashes
72
+ @dashboards = @twb.dashboards
73
+ @dashboards.each do |dash|
74
+ emit "DASH:: #{dash.name}"
75
+ @dashCount += 1
76
+ recordCSV [@twbName, dash.name, dash.worksheets.length]
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def recordCSV record
83
+ numberedRec = [@recNum+=1] + record
84
+ if @recordDir
85
+ @dashboardsCSV << numberedRec.push(@twbDir)
86
+ else
87
+ @dashboardsCSV << numberedRec
88
+ end
89
+ end
90
+
91
+ end #class SheetFieldsAnalyzer
92
+
93
+ end # module Analysis
94
+ end # module Twb
@@ -37,7 +37,7 @@ module Analysis
37
37
  docFileName = docFile('WorkbookSummary.csv')
38
38
  @csvFile = CSV.open(docFileName,@csvMode)
39
39
  unless @csvAdd
40
- csvHeader = ['Rec #', 'Workbook','Type','Version','Build','Platform','Modified','# Data Sources','# Dashboards','# Worksheets']
40
+ csvHeader = ['Rec #', 'Workbook','Directory','Type','Version','Build','Platform','Modified','# Data Sources','# Dashboards','# Worksheets']
41
41
  if @recordDir
42
42
  csvHeader.push('Workbook Directory')
43
43
  end
@@ -65,6 +65,7 @@ module Analysis
65
65
  @twb = twb
66
66
  emit " -- #{@twbName}"
67
67
  recordCSV [ @twb.name,
68
+ @twb.dir,
68
69
  @twb.type,
69
70
  @twb.version,
70
71
  @twb.build,
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2014, 2015, 2017 Chris Gerrard
1
+ # Copyright (C) 2014, 2015, 2020 Chris Gerrard
2
2
  #
3
3
  # This program is free software: you can redistribute it and/or modify
4
4
  # it under the terms of the GNU General Public License as published by
@@ -26,7 +26,7 @@ module Twb
26
26
  attr_reader :node, :properties
27
27
  attr_reader :caption, :name, :uiname
28
28
  attr_reader :datatype, :role, :propType
29
- attr_reader :calculation, :calcFields
29
+ attr_reader :calculation, :referencedFields
30
30
  attr_reader :isGroup, :groupMembers
31
31
  attr_reader :hidden
32
32
 
@@ -52,8 +52,8 @@ module Twb
52
52
  @properties ||= loadProperties
53
53
  end
54
54
 
55
- def calcFields
56
- @calculation.calcFields
55
+ def referencedFields
56
+ @calculation.referencedFields
57
57
  end
58
58
 
59
59
  def formulaLines
@@ -14,6 +14,7 @@
14
14
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
15
 
16
16
  require 'nokogiri'
17
+ require 'pry'
17
18
 
18
19
  module Twb
19
20
 
@@ -85,7 +86,6 @@ module Twb
85
86
  @aggregation = load 'aggregation'
86
87
  @autoColumn = load 'auto-column'
87
88
  @datatypeCustomized = load 'datatype-customized'
88
- # @calcField = loadCalcField
89
89
  end
90
90
 
91
91
  def id
@@ -94,7 +94,7 @@ module Twb
94
94
 
95
95
  def load nodeName
96
96
  attr = @node.attribute(nodeName)
97
- val = attr.nil? ? nil : attr.text.strip.gsub(/^\[|\]$/,'')
97
+ val = attr.nil? ? nil : attr.text.strip
98
98
  return val
99
99
  end
100
100
 
@@ -56,7 +56,7 @@ module Twb
56
56
  attr_reader :tableFieldsMap
57
57
  attr_reader :fieldUINames
58
58
  attr_reader :aliases
59
- attr_reader :calculatedFields, :calculatedFieldNamesMap, :calculatedFieldNames, :calculatedField
59
+ attr_reader :calculatedFields, :calculatedFieldsMap, :calculatedFieldNames, :calculatedField
60
60
  attr_reader :allFields
61
61
  attr_reader :groups
62
62
  attr_reader :filters
@@ -391,40 +391,6 @@ module Twb
391
391
  return @fieldUINames
392
392
  end
393
393
 
394
- def loadFieldUINames
395
- # puts 'loadFieldUINames'
396
- @fieldUINames = {}
397
- # puts 'metadataFields'
398
- metadataFields.each do |fld|
399
- # puts " - name:%-45s | uiname:%-45s | localName:%-45s " % [ "'#{fld.name}'", "'#{fld.uiname}'", "'#{fld.localName}'"]
400
- @fieldUINames[fld.uiname] = fld.uiname
401
- @fieldUINames[fld.localName] = fld.uiname unless fld.localName.nil?
402
- @fieldUINames[fld.name] = fld.uiname unless fld.name.nil?
403
- end
404
- # puts 'calculatedFields'
405
- calculatedFields.each do |fld|
406
- # puts " - name:%-45s | uiname:%-45s " % [ "'#{fld.name}'", "'#{fld.uiname}'"]
407
- @fieldUINames[fld.name] = fld.uiname
408
- @fieldUINames[fld.uiname] = fld.uiname
409
- end
410
- # puts 'localFields'
411
- localFields.each do |fld|
412
- # puts " - name:%-45s | uiname:%-45s " % [ "'#{fld.name}'", "'#{fld.uiname}'"]
413
- @fieldUINames[fld.name] = fld.uiname
414
- @fieldUINames[fld.uiname] = fld.uiname
415
- end
416
- # puts "columnFields: #{columnFields.length}"
417
- columnFields.each do |fld|
418
- @fieldUINames[fld.name] = fld.uiname
419
- @fieldUINames[fld.uiname] = fld.uiname
420
- end
421
- groups.each do |fld|
422
- @fieldUINames[fld.name] = fld.uiname
423
- @fieldUINames[fld.uiname] = fld.uiname
424
- end
425
- return @fieldUINames
426
- end
427
-
428
394
  def tableFields
429
395
  @tableFieldsMap.values
430
396
  end
@@ -482,8 +448,7 @@ module Twb
482
448
 
483
449
  def loadAllFields
484
450
  @allFields = SortedSet.new
485
- dbf = dbFields
486
- @allFields << dbf
451
+ @allFields << dbFields
487
452
  @allFields << calculatedFieldNames
488
453
  end
489
454
 
@@ -532,6 +497,53 @@ module Twb
532
497
 
533
498
  private
534
499
 
500
+ def loadFieldUINames
501
+ @fieldUINames = {}
502
+ # puts"metadataFields: #{metadataFields.length}"
503
+ metadataFields.each do |fld|
504
+ # puts" - name:%-45s | caption:%-45s | uiname:%-45s " % [ "'#{fld.name}'", "'#{fld.caption}'", "'#{fld.uiname}'"]
505
+ unless fld.name.nil?
506
+ @fieldUINames[fld.uiname] = fld.uiname
507
+ @fieldUINames[fld.localName] = fld.uiname unless fld.localName.nil?
508
+ @fieldUINames[fld.name] = fld.uiname
509
+ end
510
+ end
511
+ # puts"localFields: #{localFields.length}"
512
+ localFields.each do |fld|
513
+ # puts" - name:%-45s | caption:%-45s | uiname:%-45s " % [ "'#{fld.name}'", "'#{fld.caption}'", "'#{fld.uiname}'"]
514
+ unless fld.caption.nil?
515
+ @fieldUINames[fld.name] = fld.caption
516
+ @fieldUINames[fld.uiname] = fld.caption
517
+ end
518
+ end
519
+ # puts"groups: #{groups.length}"
520
+ groups.each do |fld|
521
+ # puts" - name:%-45s | caption:%-45s | uiname:%-45s " % [ "'#{fld.name}'", "'#{fld.caption}'", "'#{fld.uiname}'"]
522
+ unless fld.caption.nil?
523
+ @fieldUINames[fld.name] = fld.caption
524
+ @fieldUINames[fld.uiname] = fld.caption
525
+ end
526
+ end
527
+ # puts"calculatedFields: #{calculatedFields.length}"
528
+ calculatedFields.each do |fld|
529
+ # puts" - name:%-45s | caption:%-45s | uiname:%-45s " % [ "'#{fld.name}'", "'#{fld.caption}'", "'#{fld.uiname}'"]
530
+ unless fld.caption.nil?
531
+ @fieldUINames[fld.name] = fld.caption
532
+ @fieldUINames[fld.uiname] = fld.caption
533
+ end
534
+ end
535
+ # puts"columnFields: #{columnFields.length}"
536
+ columnFields.each do |fld|
537
+ # puts" - name:%-45s | caption:%-45s | uiname:%-45s " % [ "'#{fld.name}'", "'#{fld.caption}'", "'#{fld.uiname}'"]
538
+ unless fld.caption.nil?
539
+ # @fieldUINames[fld.name] = fld.caption
540
+ @fieldUINames[fld.name.gsub(/^[\[]|[\]]$/,'')] = fld.caption
541
+ @fieldUINames[fld.uiname] = fld.caption
542
+ end
543
+ end
544
+ return @fieldUINames
545
+ end
546
+
535
547
  def loadGroups
536
548
  @groups = []
537
549
  groupNodes = @node.xpath('.//group')