twb 2.2.1 → 3.7.2

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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/lib/twb.rb +13 -1
  3. data/lib/twb/action.rb +5 -1
  4. data/lib/twb/analysis/AnnotatedFieldsCSVEmitter.rb +3 -0
  5. data/lib/twb/analysis/CalculatedFields/CalculatedFieldsAnalyzer.rb +276 -287
  6. data/lib/twb/analysis/CalculatedFields/MarkdownEmitter.rb +48 -34
  7. data/lib/twb/analysis/DataSources/DataSourceFieldsCSVEmitter.rb +103 -103
  8. data/lib/twb/analysis/DataSources/googlesheetdatasourcesanalyzer.rb +79 -0
  9. data/lib/twb/analysis/DocumentedFieldsMarkdownEmitter.rb +1 -1
  10. data/lib/twb/analysis/Sheets/sheetfieldsanalyzer.rb +82 -0
  11. data/lib/twb/analysis/Sheets/sheetfiltersanalyzer.rb +214 -0
  12. data/lib/twb/calculatedfield.rb +20 -5
  13. data/lib/twb/codedfield.rb +87 -0
  14. data/lib/twb/columnfield.rb +21 -2
  15. data/lib/twb/connection.rb +33 -0
  16. data/lib/twb/dashboard.rb +5 -1
  17. data/lib/twb/datasource.rb +131 -20
  18. data/lib/twb/dbfield.rb +4 -0
  19. data/lib/twb/field.rb +5 -1
  20. data/lib/twb/fieldcalculation.rb +134 -78
  21. data/lib/twb/localfield.rb +5 -1
  22. data/lib/twb/mappedfield.rb +5 -1
  23. data/lib/twb/metadatafield.rb +5 -1
  24. data/lib/twb/storyboard.rb +5 -1
  25. data/lib/twb/tabclass.rb +71 -0
  26. data/lib/twb/tabtest.rb +31 -0
  27. data/lib/twb/tabtool.rb +63 -0
  28. data/lib/twb/twbcodedfield.rb +87 -0
  29. data/lib/twb/util/cypher.rb +112 -0
  30. data/lib/twb/util/cypherpython.rb +128 -0
  31. data/lib/twb/util/docprep.rb +46 -0
  32. data/lib/twb/util/fielddomainloader.rb +108 -0
  33. data/lib/twb/util/gml.rb +144 -0
  34. data/lib/twb/util/gmledge.rb +73 -0
  35. data/lib/twb/util/graph.rb +30 -0
  36. data/lib/twb/util/graphedge.rb +8 -9
  37. data/lib/twb/util/graphnode.rb +46 -29
  38. data/lib/twb/util/tabgraph.rb +30 -0
  39. data/lib/twb/window.rb +5 -1
  40. data/lib/twb/workbook.rb +18 -5
  41. data/lib/twb/worksheet.rb +5 -1
  42. data/test/fieldAliases.rb +10 -0
  43. data/test/testFieldAliases.rb +65 -0
  44. data/test/testFieldDomainLoaded.rb +14 -0
  45. data/test/testFieldDomainLoader.rb +131 -0
  46. metadata +22 -1
@@ -0,0 +1,82 @@
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
+ require 'set'
19
+
20
+ module Twb
21
+ module Analysis
22
+
23
+ class SheetFieldsAnalyzer
24
+
25
+ include TabTool
26
+
27
+ attr_accessor :localEmit
28
+
29
+ def initialize
30
+ @twbCnt = 0
31
+ @sheetCnt = 0
32
+ @filterCnt = 0
33
+ @funcdoc = {:class=>self.class, :blurb=>'Analyzing Sheet Fields from Tableau Workbooks.', :description=>nil,}
34
+ docFileName = docFile('TWBWorksheetFields.csv')
35
+ @sheetFieldsCSV = CSV.open(docFileName,'w')
36
+ @sheetFieldsCSV << ['Workbook','Worksheet','Data Source','Data Source (tech)','Field','Field (tech)']
37
+ @docfiles = [{:name=>docFileName,:desc=>"CSV File containing the data relating Workbooks,Worksheets, and the sheets' Data Sources and Fields"}]
38
+ end
39
+
40
+ def processTWB twb
41
+ @twb = twb
42
+ emit " -- #{@twb.name}"
43
+ @twbCnt += 1
44
+ @twbDomainsLoaded = false
45
+ parseSheets
46
+ end
47
+
48
+
49
+ def parseSheets
50
+ @worksheets = @twb.worksheets
51
+ @worksheets.each do |sheet|
52
+ emit "SHEET: #{sheet.name}"
53
+ showFields sheet unless sheet.datasourceFields.nil?
54
+ end
55
+ end
56
+
57
+ def showFields sheet
58
+ fields = sheet.datasourceFields
59
+ emit " #FIELDS: #{fields.length}"
60
+ if fields.nil?
61
+ @sheetFieldsCSV << [@twb.name, sheet.name, nil, nil, nil, nil]
62
+ end
63
+ fields.each do |dsName, dsfields|
64
+ ds = @twb.datasource dsName
65
+ emit " ds: #{dsName}"
66
+ emit " - #{ds.uiname}"
67
+ emit " : #{ds.class}"
68
+ dsfields.each do |sheetField|
69
+ emit " f: #{sheetField}"
70
+ emit " c: #{sheetField.class}"
71
+ fuiName = ds.fieldUIName sheetField #Fields[sheetField]
72
+ @sheetFieldsCSV << [@twb.name, sheet.name, ds.uiname, dsName, sheetField.uiname, sheetField.name]
73
+ # emit true, " : #{dsFields[field].class}"
74
+ end
75
+ end
76
+ end
77
+
78
+
79
+ end #class SheetFieldsAnalyzer
80
+
81
+ end # module Analysis
82
+ end # module Twb
@@ -0,0 +1,214 @@
1
+ # WorksheetFields.rb Copyright (C) 2017, 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
+ require 'set'
19
+
20
+ module Twb
21
+ module Analysis
22
+
23
+ class SheetFiltersAnalyzer
24
+
25
+ include TabTool
26
+
27
+ attr_accessor :localEmit
28
+
29
+ def initialize
30
+ @twbCnt = 0
31
+ @sheetCnt = 0
32
+ @filterCnt = 0
33
+ # @ttlogfile = File.new('./ttdoc/SheetFiltersAnalyzer.ttlog','w')
34
+ $sheetFieldsCSV = CSV.open(docFile('TWBWorksheetFilters.csv') ,'w')
35
+ $sheetFieldsCSV << ['Workbook','Worksheet','Filter Type','Data Source','Field','Value','Alias', 'Alias?']
36
+ end
37
+
38
+ def processTWB twb
39
+ @twb = twb
40
+ emit " -- #{@twb.name}"
41
+ @twbCnt += 1
42
+ @twbDomainsLoaded = false
43
+ parseFilters
44
+ end
45
+
46
+
47
+ def parseFilters
48
+ @worksheets = @twb.worksheets
49
+ @worksheets.each do |sheet|
50
+ emit "\n\nSHEET: #{sheet.name}"
51
+ @sheetCnt += 1
52
+ filters = sheet.node.xpath('.//filter[@column]')
53
+ filters.each do |filter|
54
+ emit "\nFILTER:\n#{filter}\n====="
55
+ fieldCode = filter.attribute('column').text
56
+ codedField = Twb::CodedField.new(fieldCode)
57
+ fieldTech = codedField.name
58
+ srcTech = codedField.dataSource
59
+ dataSource = @twb.datasource(srcTech)
60
+ @dsAliases = dataSource.aliases
61
+ field = dataSource.fieldUIName fieldTech
62
+ emit "\n:: FIELD :: #{field} -- #{fieldTech} -- #{codedField.rawCode}"
63
+ filterValues = collectFilterValues sheet, dataSource, field, filter
64
+ end
65
+ end
66
+ end
67
+
68
+ def collectFilterValues sheet, dataSource, field, node
69
+ emit "collectFilterValues"
70
+ emit " sheet: #{sheet.name}"
71
+ emit " ds: #{dataSource.uiname}"
72
+ emit " field: #{field}"
73
+ emit " node: #{node}"
74
+ emit " "
75
+ firstChild = node.at_xpath('./groupfilter')
76
+ results = {}
77
+ unless firstChild.nil?
78
+ function = firstChild.attribute('function').text
79
+ emit "function: #{function}"
80
+ emit node.to_s
81
+ #-- single element filter
82
+ if 'member'.eql? function
83
+ emit "HANDLING SINGLE MEMBER FILTER"
84
+ value = firstChild.attribute('member').text.gsub(/^"|"$/,'')
85
+ alia = dataSource.deAlias(field,value)
86
+ emit "value :%-25s => alias: %-s" % [value, alia]
87
+ $sheetFieldsCSV << [ @twb.name, sheet.name, 'single', dataSource.uiname, field, value, alia ]
88
+ return {value => alia}
89
+ end
90
+ #-- otherwise filter contains multiple elements
91
+ #-- handle individual member elements
92
+ elements = firstChild.xpath('.//groupfilter')
93
+ elements.each do |element|
94
+ function = element.attribute('function').text
95
+ emit "firstCHild\nfunction: #{function}\n node:\n#{element}"
96
+ if 'member'.eql? function
97
+ name = element.attribute('member').text.gsub(/^"|"$/,'')
98
+ emit "%%%% NAME:: #{name}"
99
+ alia = dataSource.fieldAlias(field,name) # $TableNameAliases[name]
100
+ results[name] = alia
101
+ end
102
+ if 'range'.eql? function
103
+ t = filtersFromRangeNode(dataSource, field, element)
104
+ t.each do |name,alia|
105
+ emit "RANGE ELEMENT Name: %-20s ALIAS: %-s " % [name, alia]
106
+ results[name] = dataSource.fieldAlias(field,name)
107
+ end
108
+ end
109
+ end
110
+ results.each do |name,alia|
111
+ $sheetFieldsCSV << [ @twb.name, sheet.name, 'single', dataSource.uiname, field, name, alia, !name.eql?(alia) ]
112
+ end
113
+ end
114
+ return results
115
+ end
116
+
117
+ def filtersFromRangeNode dataSource, field, node
118
+ unless @twbDomainsLoaded
119
+ loadDomains
120
+ end
121
+ emit "filtersFromRangeNode"
122
+ emit " from: #{node.attribute('from')}"
123
+ emit " to. : #{node.attribute('to')}"
124
+ from = node.attribute('from').text.gsub(/^"|"$/,'')
125
+ to = node.attribute( 'to').text.gsub(/^"|"$/,'')
126
+ emit " from: #{from}"
127
+ emit " to. : #{to}"
128
+ filtersInRange dataSource, field, from, to
129
+ end
130
+
131
+ def filtersInRange dataSource, field, from, to
132
+ emit "filtersInRange"
133
+ results = {}
134
+ dsFields = @twbFielddomains[dataSource.uiname]
135
+ emit "dsFields : #{dsFields}"
136
+ if dsFields.nil? || dsFields.empty?
137
+ emit "#### ALERT - FIELD DOMAIN VALUES FOR DATASOURCE #{dataSource.uiname} NOT LOADED ####"
138
+ emit @twbFieldDomains
139
+ emit "==========="
140
+ emit dsFields
141
+ emit "==========="
142
+ else
143
+ fieldVals = dsFields[field].to_a
144
+ if dataSource.fieldHasAliases field
145
+ #-- resolve aliases
146
+ dbValues = SortedSet.new
147
+ aliases = dataSource.fieldAliases field
148
+ fieldVals.each do |fv|
149
+ fvAliased = aliases.has_value? fv
150
+ if fvAliased
151
+ dbValues << aliases.key(fv)
152
+ else
153
+ dbValues << fv
154
+ end
155
+ end
156
+ $fieldVals = dbValues.to_a
157
+ else
158
+ #-- use domain values as returned
159
+ $fieldVals = dsFields[field].to_a
160
+ end
161
+ # domainVals = dsFields[field].to_a
162
+ # #-- need to dealias (unalias?) the field domain values
163
+ # dbVals = SortedSet.new
164
+ # domainVals.each do |dv|
165
+ emit "field values: #{$fieldVals}"
166
+ unless $fieldVals.nil? || $fieldVals.empty?
167
+ values = $fieldVals
168
+ f_i = values.index from
169
+ t_i = values.index to
170
+ emit " from: #{f_i} :: '#{from}'"
171
+ emit " to: #{t_i} :: '#{to}'"
172
+ badRange = f_i.nil? || t_i.nil?
173
+ emit "badRange: #{badRange}"
174
+ if badRange
175
+ emit "BAD RANGE"
176
+ else
177
+ tnames = values[f_i..t_i]
178
+ tnames.each do |tname|
179
+ results[tname] = dataSource.deAlias(field,tname) # $TableNameAliases[tname]
180
+ end
181
+ emit " filtersInRange f:%-35s t:%-s " % ["'#{from}:#{f_i}'", "'#{to}:#{t_i}'"]
182
+ emit " names: #{results.keys}"
183
+ emit " aliases: #{results.values}"
184
+ end
185
+ end
186
+ end
187
+ return results
188
+ end
189
+
190
+ def loadDomains
191
+ emit "def loadDomains\n=="
192
+ loader = Twb::Util::FieldDomainLoader.new
193
+ @twbFielddomains = loader.loadWorkbook @twb
194
+ emit "#{@twbFielddomains}\n=="
195
+ @twbDomainsLoaded = true
196
+ end
197
+
198
+ # def emit(local=@localEmit, stuff)
199
+ # if stuff.is_a? String then
200
+ # lines = stuff.split(/\n/)
201
+ # lines.each do |line|
202
+ # @ttlogfile.puts "#{$emitPrefix}#{line}"
203
+ # puts "#{$emitPrefix}#{line}" if local
204
+ # end
205
+ # else
206
+ # @ttlogfile.puts "#{$emitPrefix}#{stuff}"
207
+ # puts "#{$emitPrefix}#{stuff}" if local
208
+ # end
209
+ # end
210
+
211
+ end # class
212
+
213
+ end # module Twb
214
+ end # module Analysis
@@ -14,15 +14,16 @@
14
14
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
15
 
16
16
  require 'nokogiri'
17
+ require 'digest/md5'
17
18
 
18
19
  module Twb
19
20
 
20
- class CalculatedField
21
+ class CalculatedField < TabClass
21
22
 
22
23
  attr_reader :dataSource
23
24
  attr_reader :node, :properties
24
- attr_reader :caption, :name, :uiname
25
- attr_reader :datatype, :role, :type
25
+ attr_reader :caption, :name, :uiname
26
+ attr_reader :datatype, :role, :propType
26
27
  attr_reader :calculation, :calcFields
27
28
  attr_reader :hidden
28
29
 
@@ -36,7 +37,7 @@ module Twb
36
37
  # --
37
38
  @datatype = @node.attribute('datatype').text
38
39
  @role = @node.attribute('role').text
39
- @type = @node.attribute('type').text
40
+ @propType = @node.attribute('type').text # n.b. 'type' is used as a proxy for class
40
41
  # --
41
42
  @calculation = Twb::FieldCalculation.new(self, datasource)
42
43
  # --
@@ -47,7 +48,7 @@ module Twb
47
48
  @properties ||= loadProperties
48
49
  end
49
50
 
50
- def calcLines
51
+ def calcFields
51
52
  @calculation.calcFields
52
53
  end
53
54
 
@@ -69,9 +70,23 @@ module Twb
69
70
  @properties[name] = attr.value
70
71
  end
71
72
  @properties[:uiname] = @uiname
73
+ @properties[:uuid] = uuid
72
74
  return @properties
73
75
  end
74
76
 
77
+ def id
78
+ @id ||= @id = "#{@dataSource.uiname}::#{@uiname}"
79
+ end
80
+
81
+ # def uuid
82
+ # @uuid ||= loadUUID
83
+ # end
84
+
85
+ # def loadUUID
86
+ # dsn = @dataSource.nil? ? 'NO DATASOURCE' : dataSource.uuid
87
+ # @uuid = Digest::MD5.hexdigest("#{dsn}::@name").hash
88
+ # end
89
+
75
90
  def to_s
76
91
  "%s(%s) => %s" % [uiname, name, @calculation.formulaFlat]
77
92
  end
@@ -0,0 +1,87 @@
1
+ # Copyright (C) 2014, 2015, 2017 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 'nokogiri'
17
+ require 'digest/md5'
18
+ require 'csv'
19
+
20
+ module Twb
21
+
22
+ class CodedField
23
+
24
+ include Comparable
25
+
26
+ attr_reader :dataSource
27
+ attr_reader :name, :techCode, :rawCode
28
+
29
+ def initialize code
30
+ #puts "\n\nCodedField :: #{code}"
31
+ @rawCode = code
32
+ trimmed = code.gsub(/^"|"$/,'').gsub(/^\[|\]$/,'')
33
+ parts = trimmed.split('].[')
34
+ #puts "Field: #{code} parts: #{parts.length} - #{parts.inspect}"
35
+ #puts " p1: #{parts[0]}"
36
+ #puts " p2: #{parts[1]}"
37
+ if parts.length == 1
38
+ #puts '==1'
39
+ @name = parts[0]
40
+ @techCode = "[#{@name}]"
41
+ else # parts.length <> 1
42
+ #puts '<>1'
43
+ #puts "p[0]: #{parts[0]}"
44
+ #puts "p[1]: #{parts[1]}"
45
+ @dataSource = parts[0]
46
+ fldName = parts[1]
47
+ if fldName.start_with?(':') && fldName.count(':') == 1
48
+ @name = fldName
49
+ else
50
+ parseField fldName
51
+ end
52
+ @techCode = "[#{@dataSource}].[#{@name}]"
53
+ end
54
+ end # initialize
55
+
56
+ def parseField str
57
+ # puts "parseField: #{str}"
58
+ 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
+ case parts.length
65
+ when 1
66
+ @name = parts[0]
67
+ else
68
+ @name = parts[1]
69
+ end
70
+ end
71
+
72
+ def id
73
+ @id ||= @id = "#{@dataSourceName}::#{@uiname}"
74
+ end
75
+
76
+ def <=>(other)
77
+ # myName = @uiname.nil? ? '' : @uiname
78
+ # otherName = other.uiName.nil? ? "" : other.uiName
79
+ # ##puts "#{@uiname} / #{myName} <=> #{otherName} / #{other.uiName}"
80
+ # ##puts "#{@uiname.nil?} // #{other.uiName.nil?}"
81
+ # myName <=> otherName
82
+ @fqName <=> other.techCode
83
+ end
84
+
85
+ end # class CalculationField
86
+
87
+ end # module Twb
@@ -17,7 +17,7 @@ require 'nokogiri'
17
17
 
18
18
  module Twb
19
19
 
20
- class ColumnField
20
+ class ColumnField < TabClass
21
21
 
22
22
  include Comparable
23
23
 
@@ -54,10 +54,10 @@ module Twb
54
54
  attr_reader :alias, :semanticRole, :aggregation
55
55
  attr_reader :autoColumn, :hidden, :datatypeCustomized
56
56
  attr_reader :calcField
57
+ attr_reader :aliases
57
58
 
58
59
 
59
60
  def initialize(fieldNode, datasource=nil)
60
-
61
61
  @datasource = datasource
62
62
  @node = fieldNode
63
63
  @name = load 'name'
@@ -79,6 +79,10 @@ module Twb
79
79
  @calcField = loadCalcField
80
80
  end
81
81
 
82
+ def id
83
+ @id ||= @id = @name.hash
84
+ end
85
+
82
86
  def load nodeName
83
87
  attr = @node.attribute(nodeName)
84
88
  val = attr.nil? ? nil : attr.text.strip.gsub(/^\[|\]$/,'')
@@ -135,6 +139,21 @@ module Twb
135
139
  @commentLines.empty?
136
140
  end
137
141
 
142
+ def aliases
143
+ @aliases ||= loadAliases
144
+ end
145
+
146
+ def loadAliases
147
+ @aliases = {}
148
+ aliasNodes = @node.xpath('.//alias')
149
+ aliasNodes.each do |node|
150
+ key = node.attribute('key').text.gsub(/^"|"$/,'')
151
+ value = node.attribute('value').text.gsub(/^"|"$/,'')
152
+ @aliases[key] = value
153
+ end
154
+ return @aliases
155
+ end
156
+
138
157
  def properties
139
158
  @properties ||= loadProperties
140
159
  end