twb 2.2.1 → 3.7.2

Sign up to get free protection for your applications and to get access to all the features.
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