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,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'