twb 3.7.5 → 3.9.3
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 +4 -4
- data/lib/twb.rb +4 -1
- data/lib/twb/analysis/CalculatedFields/CalculatedFieldsAnalyzer.rb +355 -337
- data/lib/twb/analysis/CalculatedFields/MarkdownEmitter.rb +6 -4
- data/lib/twb/analysis/CalculatedFields/groupfieldsanalyzer.rb +93 -0
- data/lib/twb/analysis/DataSources/googlesheetdatasourcesanalyzer.rb +18 -9
- data/lib/twb/analysis/Sheets/analyzeDashboardSheets.rb +50 -0
- data/lib/twb/analysis/Sheets/dashsheetsanalyzer.rb +75 -0
- data/lib/twb/analysis/Sheets/sheetfieldsanalyzer.rb +20 -6
- data/lib/twb/analysis/Sheets/sheetfiltersanalyzer.rb +210 -52
- data/lib/twb/calculatedfield.rb +42 -4
- data/lib/twb/codedfield.rb +14 -13
- data/lib/twb/datasource.rb +4 -0
- data/lib/twb/tabtool.rb +60 -44
- data/lib/twb/util/fielddomainloader.rb +3 -1
- data/lib/twb/worksheet.rb +16 -1
- metadata +5 -2
data/lib/twb/calculatedfield.rb
CHANGED
@@ -21,10 +21,11 @@ module Twb
|
|
21
21
|
class CalculatedField < TabClass
|
22
22
|
|
23
23
|
attr_reader :dataSource
|
24
|
-
attr_reader :node,
|
25
|
-
attr_reader :caption,
|
26
|
-
attr_reader :datatype,
|
27
|
-
attr_reader :calculation, :calcFields
|
24
|
+
attr_reader :node, :properties
|
25
|
+
attr_reader :caption, :name, :uiname
|
26
|
+
attr_reader :datatype, :role, :propType
|
27
|
+
attr_reader :calculation, :calcFields
|
28
|
+
attr_reader :isGroup, :groupMembers
|
28
29
|
attr_reader :hidden
|
29
30
|
|
30
31
|
def initialize(calcNode, datasource=nil)
|
@@ -42,6 +43,7 @@ module Twb
|
|
42
43
|
@calculation = Twb::FieldCalculation.new(self, datasource)
|
43
44
|
# --
|
44
45
|
@hidden = true if calcNode.has_attribute?('caption')
|
46
|
+
@isGroup = !@node.at_xpath('./calculation[@class="categorical-bin"]').nil?
|
45
47
|
end
|
46
48
|
|
47
49
|
def properties
|
@@ -64,6 +66,22 @@ module Twb
|
|
64
66
|
@calculation.formulaResolved
|
65
67
|
end
|
66
68
|
|
69
|
+
def groupMembers
|
70
|
+
@groupMembers ||= loadgroupmembers
|
71
|
+
end
|
72
|
+
|
73
|
+
def binValues binName
|
74
|
+
values = []
|
75
|
+
binValue = "\"#{binName}\""
|
76
|
+
binNode = @node.at_xpath("./calculation[@class='categorical-bin']/bin[@value='#{binValue}']")
|
77
|
+
unless binNode.nil?
|
78
|
+
binNode.xpath('.//value').each do |v|
|
79
|
+
values << v.text.gsub(/^['"]|['"]$/,'')
|
80
|
+
end
|
81
|
+
end
|
82
|
+
return values
|
83
|
+
end
|
84
|
+
|
67
85
|
def loadProperties
|
68
86
|
@properties= {}
|
69
87
|
@node.attributes.each do |name,attr|
|
@@ -91,5 +109,25 @@ module Twb
|
|
91
109
|
"%s(%s) => %s" % [uiname, name, @calculation.formulaFlat]
|
92
110
|
end
|
93
111
|
|
112
|
+
private
|
113
|
+
|
114
|
+
def loadgroupmembers
|
115
|
+
@groupMembers = Hash.new { |h,k| h[k] = [] }
|
116
|
+
if @isGroup
|
117
|
+
groupLeads = @node.xpath('./calculation[@class="categorical-bin"]//bin')
|
118
|
+
groupLeads.each do |gl|
|
119
|
+
lead = gl['value']
|
120
|
+
groupAlias = @node.at_xpath("./aliases/alias[@key='#{lead}']")
|
121
|
+
groupNamex = groupAlias.nil? ? lead : groupAlias['value']
|
122
|
+
groupName = groupNamex.gsub(/^['"]|['"]$/,'')
|
123
|
+
values = gl.xpath('./value')
|
124
|
+
values.each do |v|
|
125
|
+
@groupMembers[groupName] << v.text.gsub(/^['"]|['"]$/,'').gsub('\"','"')
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
return @groupMembers
|
130
|
+
end
|
131
|
+
|
94
132
|
end # class
|
95
133
|
end # module
|
data/lib/twb/codedfield.rb
CHANGED
@@ -24,12 +24,13 @@ module Twb
|
|
24
24
|
include Comparable
|
25
25
|
|
26
26
|
attr_reader :dataSource
|
27
|
-
attr_reader :name, :techCode, :rawCode
|
27
|
+
attr_reader :name, :techCode, :function, :rawCode
|
28
28
|
|
29
29
|
def initialize code
|
30
30
|
#puts "\n\nCodedField :: #{code}"
|
31
31
|
@rawCode = code
|
32
|
-
trimmed = code.gsub(/^"|"$/,'').gsub(/^\[|\]$/,'')
|
32
|
+
trimmed = code.gsub(/^['"]|['"]$/,'').gsub(/^\[|\]$/,'')
|
33
|
+
# puts "trimmed: #{trimmed}"
|
33
34
|
parts = trimmed.split('].[')
|
34
35
|
#puts "Field: #{code} parts: #{parts.length} - #{parts.inspect}"
|
35
36
|
#puts " p1: #{parts[0]}"
|
@@ -38,12 +39,12 @@ module Twb
|
|
38
39
|
#puts '==1'
|
39
40
|
@name = parts[0]
|
40
41
|
@techCode = "[#{@name}]"
|
41
|
-
|
42
|
+
elsif # parts.length <> 1
|
42
43
|
#puts '<>1'
|
43
44
|
#puts "p[0]: #{parts[0]}"
|
44
45
|
#puts "p[1]: #{parts[1]}"
|
45
|
-
@dataSource
|
46
|
-
fldName
|
46
|
+
@dataSource = parts[0]
|
47
|
+
fldName = parts[1]
|
47
48
|
if fldName.start_with?(':') && fldName.count(':') == 1
|
48
49
|
@name = fldName
|
49
50
|
else
|
@@ -56,21 +57,21 @@ module Twb
|
|
56
57
|
def parseField str
|
57
58
|
# puts "parseField: #{str}"
|
58
59
|
parts = str.split(':')
|
59
|
-
# puts "parseField: #{str}"
|
60
|
-
# puts " parts : #{parts}"
|
61
|
-
# puts " partsl: #{parts.length}"
|
62
|
-
# puts " p[0]: #{parts[0]}"
|
63
|
-
# puts " p[1]: #{parts[1]}"
|
64
60
|
case parts.length
|
65
61
|
when 1
|
66
|
-
@name
|
62
|
+
@name = parts[0]
|
67
63
|
else
|
68
|
-
@name
|
64
|
+
@name = parts[-2]
|
65
|
+
@function = parts[0]
|
69
66
|
end
|
70
67
|
end
|
71
68
|
|
72
69
|
def id
|
73
|
-
@id ||= @id = "#{@
|
70
|
+
@id ||= @id = "#{@dataSource}::#{@uiname}"
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_s
|
74
|
+
"ds:#{dataSource}|n:#{name}|tc:#{techCode}"
|
74
75
|
end
|
75
76
|
|
76
77
|
def <=>(other)
|
data/lib/twb/datasource.rb
CHANGED
data/lib/twb/tabtool.rb
CHANGED
@@ -17,29 +17,63 @@ require 'logger'
|
|
17
17
|
|
18
18
|
module TabTool
|
19
19
|
|
20
|
-
|
21
|
-
|
20
|
+
@@licensed = false
|
21
|
+
|
22
|
+
TTDOCDIR = './ttdoc'
|
22
23
|
|
23
|
-
|
24
|
+
attr_accessor :ttdocdir, :logger, :loglevel, :logfilename
|
25
|
+
attr_reader :licensed, :funcdoc, :docfiles, :metrics, :alerts
|
24
26
|
|
25
27
|
@funcdoc = {:class=>nil, :blurb=>nil, :description=>nil,}
|
26
28
|
@docfiles = [] # should be of form [{:name=>'docFileName',:description=>'doc file description'}]
|
27
29
|
|
28
|
-
@
|
29
|
-
|
30
|
-
@
|
31
|
-
@
|
32
|
-
@docDirSet = false
|
30
|
+
# @logfilename = 'TabTools.ttlog'
|
31
|
+
@@loglevel = Logger::INFO
|
32
|
+
# @localEmit = false
|
33
|
+
# @docDirSet = false
|
33
34
|
|
34
|
-
def
|
35
|
-
@
|
35
|
+
def init
|
36
|
+
@alerts = []
|
37
|
+
initDocDir
|
38
|
+
# initLogger
|
39
|
+
end
|
40
|
+
|
41
|
+
def initDocDir
|
42
|
+
# return if TTDOCDIR.nil?
|
43
|
+
# return if ''.eql?($ttdocdir) && ''.eql?(TTDOCDIR)
|
44
|
+
@docDir = TTDOCDIR
|
45
|
+
return if Dir.exists?(@docDir)
|
46
|
+
if File.exists? @docDir
|
47
|
+
@docDir = ''
|
48
|
+
else
|
49
|
+
Dir.mkdir @docDir
|
50
|
+
end
|
51
|
+
return @docDir
|
52
|
+
end
|
53
|
+
|
54
|
+
def initLogger
|
55
|
+
logFileName = docFile("#{self.class.to_s.split('::').last}.ttlog")
|
56
|
+
@logger = Logger.new(logFileName, 2, 100*1024)
|
57
|
+
@logger.level = Logger::INFO
|
58
|
+
return @logger
|
36
59
|
end
|
37
60
|
|
38
61
|
def docFile name
|
39
|
-
initDocDir unless @docDirSet
|
40
62
|
@docDir.nil? ? name : "#{@docDir}/#{name}"
|
41
63
|
end
|
42
64
|
|
65
|
+
def license= fileName
|
66
|
+
@@licensed = fileName.is_a?(String) && File.exist?(fileName)
|
67
|
+
end
|
68
|
+
|
69
|
+
def licensed?
|
70
|
+
@@licensed
|
71
|
+
end
|
72
|
+
|
73
|
+
def funcdoc
|
74
|
+
@funcdoc.nil? ? {:class=>'n/a', :blurb=>'generic TabTool blurb', :description=>'A useful Tableau Tool.'} : @funcdoc
|
75
|
+
end
|
76
|
+
|
43
77
|
def docfiles
|
44
78
|
@docfiles ||= @docfiles = []
|
45
79
|
end
|
@@ -66,16 +100,18 @@ module TabTool
|
|
66
100
|
lines << " - %-#{nameLen}s %-s " % [ dfi[:name], dfi[:description] ]
|
67
101
|
end
|
68
102
|
end
|
69
|
-
docLines = lines.empty? ? [] : [' ','For documentation and generated data see the following:',' ']
|
103
|
+
docLines = lines.empty? ? [] : [' ',' For documentation and generated data see the following:',' ']
|
70
104
|
lines.each do |l|
|
71
105
|
docLines << l
|
72
106
|
end
|
73
107
|
return docLines
|
74
108
|
end
|
75
109
|
|
110
|
+
# def metrics
|
111
|
+
# {}
|
112
|
+
# end
|
113
|
+
|
76
114
|
def initCSV(fileName, desc=nil, header=nil)
|
77
|
-
# puts 'initCSV'
|
78
|
-
# puts " @docDirSet: #{@docDirSet} "
|
79
115
|
csvName = docFile(fileName)
|
80
116
|
emit "init CSV #{csvName}"
|
81
117
|
csvFile = CSV.open(csvName, 'w')
|
@@ -85,48 +121,28 @@ module TabTool
|
|
85
121
|
end
|
86
122
|
|
87
123
|
def emit(local=@localEmit, stuff)
|
88
|
-
|
89
|
-
|
124
|
+
# if @logger.closed?
|
125
|
+
# @logger.reopen
|
126
|
+
# end
|
90
127
|
if stuff.is_a? String then
|
91
128
|
lines = stuff.split(/\n/)
|
92
129
|
lines.each do |line|
|
93
|
-
@logger.debug "#{@emitPrefix}#{line}"
|
130
|
+
@logger.debug "#{@emitPrefix}#{line}" unless @logger.nil?
|
94
131
|
puts "#{@emitPrefix}#{line}" if local
|
95
132
|
end
|
96
133
|
else
|
97
|
-
@logger.debug "#{@emitPrefix}#{stuff}"
|
134
|
+
@logger.debug "#{@emitPrefix}#{stuff}" unless @logger.nil?
|
98
135
|
puts "#{@emitPrefix}#{stuff}" if local
|
99
136
|
end
|
100
137
|
end
|
101
138
|
|
102
|
-
def
|
103
|
-
|
104
|
-
|
105
|
-
logFileName = docFile("#{self.class.to_s.split('::').last}.ttlog")
|
106
|
-
@logger = Logger.new(logFileName)
|
107
|
-
@logger.level = Logger::DEBUG
|
139
|
+
def alert str
|
140
|
+
@alerts << str
|
141
|
+
emit "#{self.class} :: #{str}"
|
108
142
|
end
|
109
143
|
|
110
|
-
def
|
111
|
-
#
|
112
|
-
# puts " @docDirSet: #{@docDirSet} "
|
113
|
-
# puts " TTDOCDIR : #{TTDOCDIR} "
|
114
|
-
# puts " $ttdocdir : #{$ttdocdir} "
|
115
|
-
# puts " @docDir : #{@docDir} "
|
116
|
-
return if @docDirSet
|
117
|
-
return if TTDOCDIR.nil? && $ttdocdir.nil?
|
118
|
-
return if ''.eql?($ttdocdir) && ''.eql?(TTDOCDIR)
|
119
|
-
@docDir = $ttdocdir.nil? ? TTDOCDIR : $ttdocdir
|
120
|
-
return if Dir.exists?(@docDir)
|
121
|
-
if File.exists? @docDir
|
122
|
-
@docDir = nil
|
123
|
-
return
|
124
|
-
end
|
125
|
-
Dir.mkdir @docDir
|
126
|
-
@docDirSet = true
|
127
|
-
# puts " @docDirSet: #{@docDirSet} "
|
128
|
-
# puts " @docDir : #{@docDir} "
|
129
|
-
# puts 'initDocDir - end'
|
144
|
+
def finis
|
145
|
+
# @logger.close unless @logger.nil? || @logger.closed?
|
130
146
|
end
|
131
147
|
|
132
148
|
|
@@ -20,6 +20,7 @@ require 'creek'
|
|
20
20
|
require 'csv'
|
21
21
|
|
22
22
|
class FieldDomainLoader
|
23
|
+
include TabTool
|
23
24
|
|
24
25
|
@@xmlLocation = './ttdoc'
|
25
26
|
|
@@ -27,6 +28,7 @@ require 'csv'
|
|
27
28
|
attr_accessor :xmllocation, :workboook, :datasource, :csvOption
|
28
29
|
|
29
30
|
def initialize
|
31
|
+
init
|
30
32
|
reset
|
31
33
|
end
|
32
34
|
|
@@ -82,7 +84,7 @@ require 'csv'
|
|
82
84
|
end
|
83
85
|
end
|
84
86
|
else
|
85
|
-
|
87
|
+
alert "#### ALERT #### Twb:'#{@workbook}' DS: '#{@datasource}' DomRef: #{fileName}' file does not exist."
|
86
88
|
end
|
87
89
|
return fieldDomains
|
88
90
|
end
|
data/lib/twb/worksheet.rb
CHANGED
@@ -25,6 +25,7 @@ module Twb
|
|
25
25
|
attr_reader :node, :name, :datasourcenames, :datasources
|
26
26
|
attr_reader :panesCount
|
27
27
|
attr_reader :fields, :rowFields, :colFields, :paneFields, :datasourceFields
|
28
|
+
attr_reader :hidden, :visible
|
28
29
|
|
29
30
|
def initialize sheetNode
|
30
31
|
@node = sheetNode
|
@@ -64,6 +65,14 @@ module Twb
|
|
64
65
|
# @fields.nil? ? loadFields : @fields
|
65
66
|
end
|
66
67
|
|
68
|
+
def hidden
|
69
|
+
@hidden ||= resolveHidden
|
70
|
+
end
|
71
|
+
|
72
|
+
def visible
|
73
|
+
@visible ||= !hidden
|
74
|
+
end
|
75
|
+
|
67
76
|
def loadFields
|
68
77
|
# puts "WORKSHEET loadFields"
|
69
78
|
@fields = {}
|
@@ -121,7 +130,13 @@ module Twb
|
|
121
130
|
def datasourcenames
|
122
131
|
@datasources.keys
|
123
132
|
end
|
124
|
-
|
133
|
+
|
134
|
+
def resolveHidden
|
135
|
+
windowNode = node.at_xpath("//windows/window[@name='#{@name}']")
|
136
|
+
@hidden = !windowNode.nil? && 'true' == windowNode['hidden']
|
137
|
+
end
|
138
|
+
|
139
|
+
end # class Worksheet
|
125
140
|
|
126
141
|
class WorksheetDataSource
|
127
142
|
# --
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: twb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.9.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Gerrard
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-08-31 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: A sollection of Ruby classes designed for accessing and recording information
|
14
14
|
about, and for manipulating, Tableau Workbooks and their contents.
|
@@ -27,12 +27,15 @@ files:
|
|
27
27
|
- lib/twb/analysis/CalculatedFields/CSVEmitter.rb
|
28
28
|
- lib/twb/analysis/CalculatedFields/CalculatedFieldsAnalyzer.rb
|
29
29
|
- lib/twb/analysis/CalculatedFields/MarkdownEmitter.rb
|
30
|
+
- lib/twb/analysis/CalculatedFields/groupfieldsanalyzer.rb
|
30
31
|
- lib/twb/analysis/DataSources/DataSourceFieldsCSVEmitter.rb
|
31
32
|
- lib/twb/analysis/DataSources/DataSourceTableFieldsCSVEmitter.rb
|
32
33
|
- lib/twb/analysis/DataSources/googlesheetdatasourcesanalyzer.rb
|
33
34
|
- lib/twb/analysis/DocumentedFieldsCSVEmitter.rb
|
34
35
|
- lib/twb/analysis/DocumentedFieldsMarkdownEmitter.rb
|
35
36
|
- lib/twb/analysis/Sheets/WorksheetDataStructureCSVEmitter.rb
|
37
|
+
- lib/twb/analysis/Sheets/analyzeDashboardSheets.rb
|
38
|
+
- lib/twb/analysis/Sheets/dashsheetsanalyzer.rb
|
36
39
|
- lib/twb/analysis/Sheets/sheetfieldsanalyzer.rb
|
37
40
|
- lib/twb/analysis/Sheets/sheetfiltersanalyzer.rb
|
38
41
|
- lib/twb/calculatedfield.rb
|