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,30 @@
1
+ # 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
+ module Twb
17
+ module Util
18
+
19
+ class Graph
20
+ attr_accessor :nodes, :edges
21
+ def nodes
22
+ @nodes ||= @nodes = []
23
+ end
24
+ def edges
25
+ @edges ||= @edges = []
26
+ end
27
+ end
28
+
29
+ end # module Util
30
+ end # module Twb
@@ -24,7 +24,7 @@ module Util
24
24
  # @properties - useful for categorizing the edge
25
25
  attr_reader :from, :to, :relationship
26
26
  attr_accessor :properties
27
- attr_reader :cypherCreate
27
+ # attr_reader :cypherCreate
28
28
 
29
29
  # Neo4J cypher variable quote character: `
30
30
 
@@ -35,7 +35,7 @@ module Util
35
35
  @to = to
36
36
  @relationship = relationship
37
37
  @properties = properties
38
- @cypherCreate = "CREATE #{cypher_s}"
38
+ # @cypherCreate = "CREATE #{cypher_s}"
39
39
  end
40
40
 
41
41
  def eql? other
@@ -51,17 +51,16 @@ module Util
51
51
  end
52
52
 
53
53
  def dot
54
- "%s -> %s" % [dotquote(from.id), dotquote(to.id)]
54
+ "%s -> %s" % [from.dotid, to.dotid]
55
55
  end
56
56
 
57
- def dotquote str
58
- ns = str.gsub(/(["])/,'\\"')
59
- return "\"#{ns}\""
57
+ def gml
58
+ "edge [ id %s\n source \"%s\"\n target \"%s\"\n ]" % [@from, @to]
60
59
  end
61
60
 
62
- def cypher_s
63
- "(%s)-[:`%s`]->(%s)" % [@from.cypherID,@relationship,@to.cypherID]
64
- end
61
+ # def cypher_s
62
+ # "(%s)-[:`%s`]->(%s)" % [@from.cypherID,@relationship,@to.cypherID]
63
+ # end
65
64
 
66
65
  end
67
66
 
@@ -13,10 +13,16 @@
13
13
  # You should have received a copy of the GNU General Public License
14
14
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
15
 
16
+ require 'securerandom'
17
+
16
18
  module Twb
17
19
  module Util
18
20
 
19
- class Graphnode
21
+ class Graphnode #< TabClass
22
+
23
+ include Comparable
24
+
25
+ @@hasher = Digest::SHA256.new
20
26
 
21
27
  @@stripChars = /[.: %-\-\(\)=]/
22
28
  @@replChar = '_'
@@ -33,77 +39,88 @@ module Util
33
39
  :DatabaseField => 'fillcolor=grey',
34
40
  }
35
41
 
36
- # not currently used
37
42
  @@typeStyles = { :CalculatedField => 'style=filled',
38
43
  :DBTable => 'style=filled',
39
44
  :TwbDataConnection => 'style=filled',
40
45
  :DatabaseField => 'style=filled'
41
46
  }
42
47
 
43
- # @id - the technical fully qualified identifier, distinquishes the node from similarly named nodes
44
- # @name - the visible name
45
- # @type - useful for categorizing the node
46
- attr_reader :id, :type, :name
47
- attr_reader :cypherID, :cypherCreate
48
- # attr_reader :cypherName, :cypherID, :cypherNodeID, :cypherType, :cypherCreate
48
+ attr_reader :id, :uuid, :type, :name, :dotid
49
49
  attr_accessor :properties
50
50
 
51
- # Neo4J cypher variable quote character: `
52
-
53
51
  def initialize (name:, id:, type:, properties: {})
52
+ # puts "graphNode : t: %-20s tc: %s " % [type, type.class]
54
53
  @id = id
55
- @type = type
54
+ @uuid = SecureRandom.uuid
55
+ # @type = type.instance_of?(Class) ? type : type.class
56
+ @type = resolveType type
57
+ # puts " : t: %-20s tc: %s \n " % [@type, @type.class]
56
58
  @name = name
57
59
  @properties = properties
58
- # --
59
- @cypherID = '`' + id + '`'
60
- # @cypherType = type
61
- # @cypherName = name
62
60
  @properties['name'] = name unless @properties.key? 'name'
63
- @cypherCreate = "CREATE (%s:`%s` {%s})" % [@cypherID,type,props_s]
61
+ if @name.nil?
62
+ raise ArgumentError, "Graphnode Initialize Error - name:'#{@name}' id:'#{@id}' type:'#{type}' properties:'@properties.inspect'"
63
+ end
64
64
  end
65
65
 
66
+ def resolveType type
67
+ if type.instance_of?(Class)
68
+ deModule(type)
69
+ elsif type.instance_of?(Symbol)
70
+ type.to_s
71
+ elsif type.instance_of?(String)
72
+ type
73
+ else deModule(type)
74
+ end
75
+ end
66
76
 
67
- def cypherize str
68
- enquote str.gsub(@@stripChars,@@replChar)
77
+ def deModule fqClass
78
+ fqClass.class.name.split('::').last
69
79
  end
70
80
 
71
81
  def enquote str
72
82
  str.gsub("'","\\\\'")
73
83
  end
74
84
 
75
-
76
85
  def dotLabel
77
- "\"%s\" [label=\"%s\" %s %s ]" % [id, name.gsub('"','\"'), @@typeShapes[@type], @@typeColors[@type]]
86
+ "%s [label=\"%s\" %s %s ]" % [dotid, name.gsub('"','\"'), @@typeShapes[@type], @@typeColors[@type]]
78
87
  end
79
88
 
89
+ def dotid
90
+ @dotid ||= @dotid = dotquote(@id)
91
+ end
92
+
93
+ def dotquote str
94
+ ns = str.instance_of?(String) ? str.gsub(/(["])/,'\\"') : str.to_s
95
+ return "\"#{ns}\""
96
+ end
80
97
 
81
98
  def eql? other
82
99
  @name == other.name && @id == other.id && @type == other.type && @properties == other.properties
83
100
  end
84
101
 
85
-
86
102
  def hash
87
103
  [@name, @id, @type, @properties].hash
88
104
  end
89
105
 
106
+ # def uuid
107
+ # @uuid ||= @uuid = SecureRandom.uuid
108
+ # # @uuid ||= Digest::MD5.hexdigest(id)
109
+ # end
90
110
 
91
111
  def to_s
92
112
  "name:'%s' id:'%s' t:'%s' p:%s" % [@name, @id, @type, @properties.to_s]
93
113
  end
94
114
 
95
-
96
- def cypher_s
97
- "name:%s id:%s t:%s p:{%s}" % [name, @cypherID, @type, props_s]
98
- end
99
-
100
-
101
115
  def props_s
102
- @properties.map{|k,v| "#{k}: #{v.inspect}"}.join(', ')
116
+ @props_s ||= @properties.map{|k,v| "#{k}: #{v.inspect}"}.join(', ')
103
117
  end
104
118
 
119
+ def <=>(other)
120
+ uuid <=> other.uuid
121
+ end
105
122
 
106
- end
123
+ end # class Graphnode
107
124
 
108
125
  end # module Util
109
126
  end # module Twb
@@ -0,0 +1,30 @@
1
+ # 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
+ module Twb
17
+ module Util
18
+
19
+ class TabGraph
20
+ attr_accessor :nodes, :edges
21
+ def nodes
22
+ @nodes ||= @nodes = []
23
+ end
24
+ def edges
25
+ @edges ||= @edges = []
26
+ end
27
+ end
28
+
29
+ end # module Util
30
+ end # module Twb
@@ -17,7 +17,7 @@ require 'nokogiri'
17
17
 
18
18
  module Twb
19
19
 
20
- class Window
20
+ class Window < TabClass
21
21
 
22
22
  attr_reader :node, :name
23
23
 
@@ -26,6 +26,10 @@ module Twb
26
26
  @name = @node.attr('name')
27
27
  end
28
28
 
29
+ def id
30
+ @id ||= @id = @name.hash
31
+ end
32
+
29
33
  end
30
34
 
31
35
  end
@@ -22,7 +22,7 @@ module Twb
22
22
  ##
23
23
  # A Tableau Workbook and its parts.
24
24
  #
25
- class Workbook
25
+ class Workbook < TabClass
26
26
 
27
27
  attr_reader :workbooknode
28
28
  attr_reader :name, :dir
@@ -41,7 +41,8 @@ module Twb
41
41
  #
42
42
  def initialize twbWithDir
43
43
  raise ArgumentError.new("ERROR in Workbok creation: '#{twbWithDir}' must be a String, is a #{twbWithDir.class} \n ") unless twbWithDir.is_a? String
44
- raise ArgumentError.new("ERROR in Workbok creation: '#{twbWithDir}' must have an extension of .twb or .twbx \n ") unless twbWithDir.upcase.end_with?(".TWB", ".TWBX")
44
+ raise ArgumentError.new("ERROR in Workbok creation: '#{twbWithDir}' must have an extension of .twb or .twbx \n ") unless twbWithDir.upcase.end_with?(".TWB", ".TWBX")
45
+ raise ArgumentError.new("ERROR in Workbok creation: '#{twbWithDir}' must must be a file, is a Directory\\Folder \n ") if File.directory?(twbWithDir)
45
46
  raise ArgumentError.new("ERROR in Workbok creation: '#{twbWithDir}' cannot be found, must be a Tableau Workbook file. \n ") unless File.file?(twbWithDir)
46
47
  @valid = false
47
48
  if File.file?(twbWithDir) then
@@ -55,6 +56,10 @@ module Twb
55
56
  end
56
57
  end
57
58
 
59
+ def id
60
+ @id ||= @id = @name.hash
61
+ end
62
+
58
63
  def processTWBX(twbxWithDir)
59
64
  Zip::File.open(twbxWithDir) do |zip_file|
60
65
  twb = zip_file.glob('*.twb').first
@@ -84,13 +89,21 @@ module Twb
84
89
  @build ||= loadBuild
85
90
  end
86
91
 
92
+ def release
93
+ @build ||= loadBuild
94
+ end
95
+
87
96
  def loadBuild
88
97
  # - earlier Version, need to confirm when source-build began
89
98
  # @build = @ndoc.xpath('/workbook/comment()').text.gsub(/^[^0-9]+/,'').strip
90
- @build = if !@ndoc.xpath('/workbook/@source-build').nil?
91
- @ndoc.xpath('/workbook/@source-build').first.text
99
+ @build = if !@ndoc.at_xpath('/workbook/@source-build').nil?
100
+ @ndoc.at_xpath('/workbook/@source-build').text
92
101
  else
93
- @ndoc.xpath('/workbook/comment()').text.gsub(/^[^0-9]+/,'').strip
102
+ if @ndoc.at_xpath('/workbook/comment()').nil?
103
+ 'not found'
104
+ else
105
+ @ndoc.at_xpath('/workbook/comment()').text.gsub(/^[^0-9]+/,'').strip
106
+ end
94
107
  end
95
108
  end
96
109
 
@@ -18,7 +18,7 @@ require 'digest/md5'
18
18
 
19
19
  module Twb
20
20
 
21
- class Worksheet
21
+ class Worksheet < TabClass
22
22
 
23
23
  @@hasher = Digest::SHA256.new
24
24
 
@@ -34,6 +34,10 @@ module Twb
34
34
  return self
35
35
  end
36
36
 
37
+ def id
38
+ @id ||= @id = @name.hash
39
+ end
40
+
37
41
  def loadDataSourceNames
38
42
  @datasources = {}
39
43
  dsNodes = @node.xpath('.//datasource')
@@ -0,0 +1,10 @@
1
+ require 'twb'
2
+
3
+ twb = Twb::Workbook.new 'Sheet43.twb'
4
+ dss = twb.datasources
5
+ p dss.class
6
+
7
+ dss = twb.datasources.to_a
8
+ ds = dss[1]
9
+ p ds.aliases
10
+
@@ -0,0 +1,65 @@
1
+ system 'cls'
2
+
3
+ require 'twb'
4
+
5
+ twb = Twb::Workbook.new 'Sheet43.twb'
6
+ dss = twb.datasources
7
+ p dss.class
8
+
9
+ dss = twb.datasources.to_a
10
+ # ds = dss[1]
11
+ # p ds.aliases
12
+
13
+ dss.each do |ds|
14
+ puts "\n\nDS :: #{ds.uiname}\n======================================"
15
+ ds.aliases.each do |field,aliases|
16
+ puts "\nFIELD :: #{field}"
17
+ aliases.each do |value,alia|
18
+ puts " :: %-25s -> %-s" % [value,alia]
19
+ end
20
+ end
21
+ end
22
+
23
+ # xml = <<XML
24
+ # <column datatype='string' name='[Business Line (copy)]' role='dimension' type='nominal'>
25
+ # <calculation class='tableau' formula='[Business Line]' />
26
+ # <aliases>
27
+ # <alias key='&quot;Government - Fed&quot;' value='Federal' />
28
+ # <alias key='&quot;Government - S&amp;L&quot;' value='State &amp; Local' />
29
+ # </aliases>
30
+ # </column>
31
+ # XML
32
+
33
+ # puts "XML::\n#{xml}\n--"
34
+
35
+ # $node = Nokogiri::XML(xml)
36
+
37
+ # puts $node
38
+
39
+ # def loadAliases
40
+ # @aliases = Hash.new {|field,aliases| aliases={}}
41
+ # # puts $node.xpath('.//column/aliases/..').length
42
+ # $node.xpath('.//column/aliases/..').each do |anode|
43
+ # # puts "anode:: #{anode}"
44
+ # name = anode.attribute('name').text.gsub(/^\[|\]$/,'')
45
+ # # puts " name:: #{name}"
46
+ # # puts "#alia:: #{anode.xpath('.//alias').length}"
47
+ # aliasMap = {}
48
+ # anode.xpath('.//alias').each do |vnode|
49
+ # key = vnode.attribute('key').text[1..-2]
50
+ # alia = vnode.attribute('value').text
51
+ # # puts " key:: #{key}"
52
+ # # puts "alias:: #{alia}"
53
+ # aliasMap[key] = alia
54
+ # end
55
+ # @aliases[name] = aliasMap
56
+ # end
57
+ # return @aliases
58
+ # end
59
+
60
+ # aliases = loadAliases
61
+
62
+ # puts '-----'
63
+ # puts aliases
64
+
65
+
@@ -0,0 +1,14 @@
1
+ require 'twb'
2
+
3
+
4
+ loader = Twb::Util::FieldDomainLoader.new
5
+ puts "xml location: #{loader.xmllocation}"
6
+
7
+ loader.xmllocation = './ttdoc/data'
8
+ puts "xml location: #{loader.xmllocation}"
9
+
10
+ loader.reset
11
+ puts "xml location: #{loader.xmllocation}"
12
+
13
+ loader.loadWorkbook
14
+ loader.loadDataSource
@@ -0,0 +1,131 @@
1
+ require 'twb'
2
+ require 'csv'
3
+ # require 'roo'
4
+ # require 'creek'
5
+
6
+ system 'cls'
7
+
8
+ puts "\n\n"
9
+ loader = Twb::Util::FieldDomainLoader.new
10
+ puts "xml location (init) : #{loader.xmllocation}"
11
+
12
+ loader.xmllocation = './ttdoc/data'
13
+ puts "xml location (set) : #{loader.xmllocation}"
14
+
15
+ loader.reset
16
+ puts "xml location (reset): #{loader.xmllocation}"
17
+
18
+ puts "\n\nLoading..."
19
+ loader.load
20
+
21
+
22
+ twb = Twb::Workbook.new 'Sheet43.twb'
23
+
24
+ puts "\n\nLoading TWB '#{twb.name}'..."
25
+ domains = loader.loadWorkbook twb
26
+ # puts domains
27
+ $csv = CSV.open('TwbFieldDomainValues.csv', 'w')
28
+ $csv << ['Workbook','Data Source','Field Name','Value']
29
+ domains.each do |ds,fieldVals|
30
+ puts "== #{ds}"
31
+ fieldVals.each do |fname,vals|
32
+ puts "-- #{fname}"
33
+ vals.each do |val|
34
+ puts " #{val}"
35
+ end
36
+ end
37
+ end
38
+
39
+ puts "\n\n"
40
+ loader.loadDataSource twb.datasources.first.uiname
41
+
42
+ puts "\n\nLoading 'testFieldDomains.xlsx'..."
43
+ loader.loadxlsx 'testFieldDomains.xlsx'
44
+
45
+ puts "\n\nLoading 'ttdoc\\testFieldDomains.xlsx'..."
46
+ loader.loadxlsx 'ttdoc\\testFieldDomains.xlsx'
47
+
48
+
49
+
50
+ # def parseRows rows
51
+ # fieldValues = {}
52
+ # # rows = sheetRows.to_a
53
+ # unless rows.empty?
54
+ # firstRow = rows[0].to_a[0]
55
+ # fieldName = firstRow[1].to_s
56
+ # fieldValues[fieldName] = []
57
+ # values = rows[1..-1]
58
+ # values.each do |row|
59
+ # value = row.to_a[0][1].to_s
60
+ # fieldValues[fieldName] << value
61
+ # end
62
+ # end
63
+ # return fieldValues
64
+ # end
65
+
66
+ # def parseXLSX fileName
67
+ # fieldDomains = {}
68
+ # xlsx = Creek::Book.new fileName
69
+ # sheets = xlsx.sheets
70
+ # sheets.each do |sheet|
71
+ # rows = sheet.rows.to_a
72
+ # # puts "ROWS methods: #{rows.methods.sort}"
73
+ # unless rows.empty?
74
+ # fieldValues = parseRows(rows)
75
+ # # puts "...#{fieldValues.empty?}"
76
+ # # puts "...#{fieldValues.keys.first}"
77
+ # # puts "...#{fieldValues.values.first}"
78
+ # unless fieldValues.empty? || fieldValues.values.first.empty?
79
+ # fieldDomains[fieldValues.keys.first] = fieldValues.values.first
80
+ # end
81
+ # end
82
+ # end
83
+ # return fieldDomains
84
+ # end
85
+
86
+ # fds = parseXLSX 'ttdoc/testFieldDomains.xlsx'
87
+ # puts "\n\nfds: #{fds}"
88
+
89
+
90
+ # puts "\n\n "
91
+ # fds.each do |field,values|
92
+ # puts field.to_s
93
+ # puts '-----------'
94
+ # values.each { |v| puts " #{v}"}
95
+ # puts ' '
96
+ # end
97
+
98
+
99
+ # xlsx = Roo::Excelx.new("ttdoc/testFieldDomains.xlsx")
100
+
101
+ # # puts xlsx.methods.sort
102
+
103
+ # xlsx.sheets.each do |sheet|
104
+ # puts sheet
105
+ # end
106
+
107
+
108
+ # loader = Twb::Util::FieldDomainLoader.new
109
+ # puts "xml location: #{loader.xmllocation}"
110
+
111
+ # loader.xmllocation = './ttdoc/data'
112
+ # puts "xml location: #{loader.xmllocation}"
113
+
114
+ # puts "\n\n--"
115
+ # loader.reset
116
+ # puts "xml location: #{loader.xmllocation}"
117
+
118
+ # puts "\n\n--"
119
+ # loader.load
120
+
121
+
122
+ # twb = Twb::Workbook.new 'Sheet43.twb'
123
+
124
+ # puts "\n\n--"
125
+ # loader.loadWorkbook twb
126
+
127
+
128
+ # puts "\n\n--"
129
+ # loader.loadDataSource twb.datasources.first
130
+
131
+ # loader.loadxlsx 'testFieldDomains.xlsx'