twb 4.9.2 → 5.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 593542dc199a3eb14583db83dabd812e7aa431e630a7bd37814d8c48723e7978
4
- data.tar.gz: d57f4a3077c498ab4bd3eb9a58a8daff981f8c16a30e74bd4103024767f79f0d
3
+ metadata.gz: f839625abecd6c9b4ad520856fd216678790a2dace5307e62458db4e07b970cd
4
+ data.tar.gz: f95701f60e3f4f99525113e723392b1d039b361db9ac63eb2689c58ef4a3a490
5
5
  SHA512:
6
- metadata.gz: b7f2ae59171211a551e2214ea2787b0132c3426e86be43803c58e797a3835b99715b01501f1d70f9ae8305777ec315634e567a7c5556a123f4583065bf8e2afa
7
- data.tar.gz: 4f181bf7dbcb73525c49421a73560987ced3b0438b9519debbe0c752bef95bda770eb53b80a73edd00c403c30c5e163a712a91ae4a54b0ec933c1c9e7b806bd2
6
+ metadata.gz: d59954bb21cc6b232402d0792219df4e0e48ddcf8a664088b58409c7e28b30175f3f3304910459304f2d87639bf1ce5a6f7d9b533b43ed0088bfbb1c4f131c8f
7
+ data.tar.gz: c8fa036df0e3e8808f507404f5930a8db20ca19fb7a04251eda1deee7dc1ea09f1e06a811f79539275d2eed9cb473f009239dca5ae1c56c7179f4826ed0201c0
@@ -0,0 +1,311 @@
1
+
2
+ # Copyright (C) 2014, 2020 Chris Gerrard
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ require 'nokogiri'
18
+ require 'zip'
19
+
20
+ module Flow
21
+
22
+
23
+ ##
24
+ # A Tableau Prep Flow file.
25
+ #
26
+ class Flow < TabClass
27
+
28
+ attr_reader :name, :dir, :type, :modtime
29
+ attr_reader :version, :build, :platform, :base
30
+ attr_reader :root
31
+ #--
32
+ attr_reader :datasources, :datasource
33
+ attr_reader :datasourceNames, :datasourceUINames, :dataSourceNamesMap
34
+ # attr_reader :orphanDataSources # i.e. not referenced in any Worksheet
35
+ #--
36
+ # attr_reader :dashboards, :storyboards, :worksheets
37
+ # attr_reader :parameters, :actions
38
+ # attr_reader :valid
39
+
40
+ ##
41
+ # Creates a Flow from its file name.
42
+ #
43
+ # == Parameters:
44
+ # flowWithDir
45
+ # The Flow's file name, the Flow can be a TWB or TWBX file.
46
+ #
47
+ def initialize tflWithDir
48
+ raise ArgumentError.new("ERROR in Flow creation: '#{tflWithDir}' must be a String, is a #{tflWithDir.class} \n ") unless tflWithDir.is_a? String
49
+ raise ArgumentError.new("ERROR in Flow creation: '#{tflWithDir}' must have an extension of .tfl or .tflx \n ") unless tflWithDir.upcase.end_with?(".TFL", ".TFLX")
50
+ raise ArgumentError.new("ERROR in Flow creation: '#{tflWithDir}' must must be a file, is a Directory\\Folder \n ") if File.directory?(tflWithDir)
51
+ raise ArgumentError.new("ERROR in Flow creation: '#{tflWithDir}' cannot be found, must be a Tableau Flow file. \n ") unless File.file?(tflWithDir)
52
+ @valid = false
53
+ if File.file?(tflWithDir) then
54
+ @name = File.basename(tflWithDir)
55
+ @dir = File.dirname(File.expand_path(tflWithDir))
56
+ @modtime = File.new(tflWithDir).mtime.strftime("%Y-%m-%d %H:%M:%S")
57
+ case File.extname(tflWithDir).downcase
58
+ when '.tlf' then processTFL(tflWithDir)
59
+ when '.tflx' then processTFLX(flowWithDir)
60
+ end
61
+ end
62
+ end
63
+
64
+ def id
65
+ @id ||= @id = @name.hash
66
+ end
67
+
68
+ def build
69
+ @build ||= loadBuild
70
+ end
71
+
72
+ def release
73
+ @build ||= loadBuild
74
+ end
75
+
76
+ def worksheets
77
+ @worksheets.values
78
+ end
79
+
80
+ def worksheet name
81
+ @worksheets[name]
82
+ end
83
+
84
+ def worksheetNames
85
+ @worksheets.keys
86
+ end
87
+
88
+ def dashboards
89
+ @dashboards.values
90
+ end
91
+
92
+ def dashboardNames
93
+ @dashboards.keys
94
+ end
95
+
96
+ def dashboard name
97
+ @dashboards[name]
98
+ end
99
+
100
+ def actions
101
+ @actions.values
102
+ end
103
+
104
+ def actionNames
105
+ @actions.keys
106
+ end
107
+
108
+ def datasource name
109
+ @dataSourceNamesMap[name]
110
+ end
111
+
112
+ def parameters
113
+ @parameters ||= loadParameters
114
+ end
115
+
116
+ def orphanDataSources
117
+ @orphanDataSources ||= identifyOrphandatasoUrceS
118
+ end
119
+
120
+ def storyboards
121
+ @storyboards.values
122
+ end
123
+
124
+ def storyboardNames
125
+ @storyboards.keys
126
+ end
127
+
128
+ def storyboard name
129
+ @storyboards[name]
130
+ end
131
+
132
+ private
133
+
134
+ def processTFLX(twbxWithDir)
135
+ Zip::File.open(twbxWithDir) do |zip_file|
136
+ twb = zip_file.glob('*.twb').first
137
+ @ndoc = Nokogiri::XML(twb.get_input_stream)
138
+ @type = :twbx
139
+ processDoc
140
+ end
141
+ end
142
+
143
+ def processTFL(twbFile)
144
+ @ndoc = Nokogiri::XML(open(twbFile))
145
+ @type = :twb
146
+ processDoc
147
+ end
148
+
149
+ def processDoc
150
+ @valid = true
151
+ end
152
+
153
+ def loadBuild
154
+ # - earlier Version, need to confirm when source-build began
155
+ # @build = @ndoc.xpath('/flow/comment()').text.gsub(/^[^0-9]+/,'').strip
156
+ @build = if !@ndoc.at_xpath('/flow/@source-build').nil?
157
+ @ndoc.at_xpath('/flow/@source-build').text
158
+ else
159
+ if @ndoc.at_xpath('/flow/comment()').nil?
160
+ 'not found'
161
+ else
162
+ @ndoc.at_xpath('/flow/comment()').text.gsub(/^[^0-9]+/,'').strip
163
+ end
164
+ end
165
+ end
166
+
167
+ def loaddatasources
168
+ # puts "LOAD DATA SOURCES"
169
+ # @dataSourcesNode = @ndoc.at_xpath('//flow/datasources')
170
+ @datasources = Set.new
171
+ @datasourceNames = SortedSet.new
172
+ @datasourceUINames = SortedSet.new
173
+ @dataSourceNamesMap = {}
174
+ datasourceNodes = @ndoc.xpath('//flow/datasources/datasource')
175
+ # puts "DATASOURCENODES : #{@datasourceNodes.length}"
176
+ datasourceNodes.each do |node|
177
+ datasource = Twb::DataSource.new(node,self)
178
+ @datasources << datasource
179
+ @datasourceNames << datasource.name
180
+ @datasourceNames << datasource.uiname
181
+ @datasourceUINames << datasource.uiname
182
+ @dataSourceNamesMap[datasource.name] = datasource
183
+ @dataSourceNamesMap[datasource.uiname] = datasource
184
+ end
185
+ # puts "DATASOURCES : #{@datasources.length}"
186
+ end
187
+
188
+ def loadWorksheets
189
+ @worksheets = {}
190
+ hiddenSheets = []
191
+ @ndoc.xpath('//flow/windows/window[@hidden="true"]').each do |hs|
192
+ hiddenSheets << hs['name']
193
+ end
194
+ sheets = @ndoc.xpath('//flow/worksheets/worksheet' ).to_a
195
+ sheets.each do |node|
196
+ sheet = Twb::Worksheet.new(node, self)
197
+ sheet.hidden = hiddenSheets.include? sheet.name
198
+ @worksheets[sheet.name] = sheet
199
+ end
200
+ end
201
+
202
+ def loadDashboards
203
+ @dashesNode = @ndoc.at_xpath('//flow/dashboards')
204
+ @dashboards = {}
205
+ dashes = @ndoc.xpath('//flow/dashboards/dashboard').to_a
206
+ dashes.each do |node|
207
+ unless node.attr('type') == 'storyboard' then
208
+ dashboard = Twb::Dashboard.new(node, @worksheets)
209
+ @dashboards[dashboard.name] = dashboard
210
+ end
211
+ end
212
+ end
213
+
214
+ def loadStoryboards
215
+ @storyboards = {}
216
+ boards = @ndoc.xpath("//flow/dashboards/dashboard[@type='storyboard']" ).to_a
217
+ boards.each do |node|
218
+ sheet = Twb::Storyboard.new(node)
219
+ @storyboards[sheet.name] = sheet
220
+ end
221
+ end
222
+
223
+ def loadWindows
224
+ @windowsnode = @ndoc.at_xpath("//flow/windows")
225
+ @windows = {}
226
+ windows = @ndoc.xpath("//flow/windows/window[@name]")
227
+ windows.each do |node|
228
+ window = Twb::Window.new(node)
229
+ @windows[window.name] = window
230
+ end
231
+ end
232
+
233
+ def loadActions
234
+ @actions = {}
235
+ actionNodes = @ndoc.xpath("//flow/actions/action")
236
+ actionNodes.each do |anode|
237
+ action = Twb::Action.new(anode, @flownode)
238
+ @actions[action.uiname] = action
239
+ end
240
+ end
241
+
242
+ def identifyOrphandatasoUrceS
243
+ sheetDataSources = Set.new
244
+ @worksheets.values.each do |sheet|
245
+ sheet.datasources.each do |ds|
246
+ sheetDataSources << ds.uiname
247
+ end
248
+ end
249
+ @orphanDataSources = @datasourceUINames - sheetDataSources
250
+ end
251
+
252
+ def writeTwb(name=@name)
253
+ $f = File.open(name,'w')
254
+ if $f
255
+ $f.puts @ndoc
256
+ $f.close
257
+ end
258
+ return name
259
+ end
260
+
261
+ def writeTwbx(name=@name)
262
+ emit "Writing the Workbook, need implementation"
263
+ end
264
+
265
+ # Make sure that the TWB has a <dashboards> node.
266
+ # It's possible for a TWB to have no dashboards, and therefore no <dashboards> node.
267
+ def ensureDashboardsNodeExists
268
+ if @dashesNode.nil?
269
+ @dashesNode = Nokogiri::XML::Node.new "dashboards", @ndoc
270
+ # TODO fix this @dataSourcesNode.add_next_sibling(@dashesNode)
271
+ end
272
+ end
273
+
274
+ def ensureWindowsNodeExists
275
+ if @windowsnode.nil?
276
+ @windowsnode = Nokogiri::XML::Node.new "windows", @ndoc
277
+ # TODO fix this @dataSourcesNode.add_next_sibling(@windowsnode)
278
+ end
279
+ end
280
+
281
+ def getNewDashboardTitle(t)
282
+ title = t
283
+ if @datasources.include?(title)
284
+ inc = 0
285
+ loop do
286
+ inc+=1
287
+ title = t + ' ' + inc.to_s
288
+ if !@datasources.include?(title)
289
+ break
290
+ end
291
+ end
292
+ end
293
+ return title
294
+ end
295
+
296
+ def loadParameters
297
+ @parameters = {}
298
+ paramsDS = ndoc.at_xpath('./flow/datasources/datasource[@name="Parameters"]')
299
+ unless paramsDS.nil?
300
+ paramNodes = paramsDS.xpath('.//column')
301
+ paramNodes.each do |pn|
302
+ parameter = Twb::Parameter.new pn
303
+ @parameters[parameter.name] = parameter
304
+ end
305
+ end
306
+ return @parameters
307
+ end
308
+
309
+ end # class Flow
310
+
311
+ end # module Twb
data/lib/twb.rb CHANGED
@@ -73,12 +73,12 @@ require_relative 'twb/analysis/sheets/worksheetsummarizer'
73
73
  require_relative 'twb/analysis/sheets/worksheetdatastructurecsvemitter'
74
74
  require_relative 'twb/analysis/sheets/sheetfiltersanalyzer'
75
75
  require_relative 'twb/analysis/sheets/sheetfieldsanalyzer'
76
+ require_relative 'twb/analysis/sheets/dashboardsummarizer'
76
77
  require_relative 'twb/analysis/sheets/dashsheetsanalyzer'
77
78
  require_relative 'twb/analysis/sheets/sheetsintooltipanalyzer'
78
79
 
79
-
80
80
  # Represents Tableau Workbooks, their contents, and classes that analyze and manipulate them.
81
81
  #
82
82
  module Twb
83
- VERSION = '4.9.2'
83
+ VERSION = '5.1.4'
84
84
  end
@@ -249,47 +249,62 @@ DOTHEADER
249
249
  ]
250
250
  end
251
251
  #-- collect fields referenced in formula
252
- emit "# Calculated Fields: #{calculation.calcFields.length}"
253
- calculation.calcFields.each do |rf|
254
- emit " referenced field ::'#{rf}'"
255
- emit " referenced field.name ::'#{rf.name.nil?}' :: '#{rf.name}'"
256
- emit " referenced field.uiname::'#{rf.uiname}'"
257
- # if @doGraph
258
- unless rf.uiname.nil?
259
- properties = {'DataSource' => ds.uiname, 'DataSourceReference' => 'local', :source => rf}
260
- refFieldNode = Twb::Util::Graphnode.new(name: rf.uiname, id: rf.id, type: rf.type, properties: properties)
261
- @nodes.add refFieldNode
262
- fieldFieldEdge = Twb::Util::Graphedge.new(from: calcFieldNode, to: refFieldNode, relationship: 'references')
263
- @edges.add fieldFieldEdge
264
- end
265
- # end
266
- referencedFields.add rf.id
267
- refFieldTable = ds.fieldTable(rf.name)
268
- emit "refFieldTable.nil? : #{refFieldTable.nil?}"
269
- unless refFieldTable.nil?
270
- tableID = refFieldTable + ':::' + ds.uiname
271
- tableName = "||#{refFieldTable}||"
272
- # if @doGraph
273
- tableNode = Twb::Util::Graphnode.new(name: tableName, id: tableID, type: :DBTable, properties: properties)
274
- @nodes.add tableNode
275
- fieldFieldEdge = Twb::Util::Graphedge.new(from: refFieldNode, to: tableNode, relationship: 'is a field in')
276
- @edges.add fieldFieldEdge
277
- # end
278
- # fldToDsNode = tableNode
279
- end
252
+ emit "# Calculated Fields: #{calculation.referencedFields.length}"
253
+ calculation.referencedFields.each do |rf|
254
+ emit " referenced field :: %12s %s " % [ rf.dataSourceName, rf.uiname ]
280
255
  @csvFormulaFields << [
281
256
  @referencedFieldsCount += 1,
282
257
  @twb.name,
283
258
  # @modTime,
284
- ds.uiname,
259
+ rf.dataSourceName, # ds.uiname,
285
260
  calcField.uiname,
286
261
  calculation.formulaFlat,
287
262
  calculation.formulaFlatResolved,
288
263
  rf.name,
289
- rf.uiname,
290
- rf.id,
291
- refFieldTable
264
+ rf.uiname, #.uiname,
265
+ '', # rf.id,
266
+ '', #refFieldTable
292
267
  ]
268
+
269
+
270
+ # emit " referenced field.name ::'#{rf.name.nil?}' :: '#{rf.name}'"
271
+ # emit " referenced field.uiname::'#{rf.uiname}'"
272
+ # if @doGraph
273
+ # unless rf.uiname.nil?
274
+ # properties = {'DataSource' => ds.uiname, 'DataSourceReference' => 'local', :source => rf}
275
+ # refFieldNode = Twb::Util::Graphnode.new(name: rf.uiname, id: rf.id, type: rf.type, properties: properties)
276
+ # @nodes.add refFieldNode
277
+ # fieldFieldEdge = Twb::Util::Graphedge.new(from: calcFieldNode, to: refFieldNode, relationship: 'references')
278
+ # @edges.add fieldFieldEdge
279
+ # end
280
+ # # end
281
+ # referencedFields.add rf.id
282
+ # refFieldTable = ds.fieldTable(rf.name)
283
+ # emit "refFieldTable.nil? : #{refFieldTable.nil?}"
284
+ # unless refFieldTable.nil?
285
+ # tableID = refFieldTable + ':::' + ds.uiname
286
+ # tableName = "||#{refFieldTable}||"
287
+ # # if @doGraph
288
+ # tableNode = Twb::Util::Graphnode.new(name: tableName, id: tableID, type: :DBTable, properties: properties)
289
+ # @nodes.add tableNode
290
+ # fieldFieldEdge = Twb::Util::Graphedge.new(from: refFieldNode, to: tableNode, relationship: 'is a field in')
291
+ # @edges.add fieldFieldEdge
292
+ # # end
293
+ # # fldToDsNode = tableNode
294
+ # end
295
+ # @csvFormulaFields << [
296
+ # @referencedFieldsCount += 1,
297
+ # @twb.name,
298
+ # # @modTime,
299
+ # ds.uiname,
300
+ # calcField.uiname,
301
+ # calculation.formulaFlat,
302
+ # calculation.formulaFlatResolved,
303
+ # rf.name,
304
+ # rf.uiname,
305
+ # rf.id,
306
+ # refFieldTable
307
+ # ]
293
308
  end # resolvedFields.each do
294
309
  end # if calculation.has_formula
295
310
  end # ds.calculatedFields.each
@@ -298,8 +313,8 @@ DOTHEADER
298
313
  @referencedFields.merge referencedFields
299
314
  @twbRootFields.merge dsRootFields
300
315
  if @doGraph
301
- cypher @twb.name
302
- cypherPy @twb.name
316
+ # cypher @twb.name
317
+ # cypherPy @twb.name
303
318
  end
304
319
  emit "#######################"
305
320
  #--
@@ -0,0 +1,6 @@
1
+ require 'twb'
2
+
3
+ twb = Twb::Workbook.new 'MS.twb'
4
+ ds = twb.datasources.to_a.last
5
+ cfs = ds.calculatedFields
6
+ cfs.each { |cf| puts "\n\n--\n#{cf.calculation.formula}\n--"; cf.calcFields.each { |f| puts " %-12s %s " % [f[:ds], f[:field] ] } }
@@ -14,6 +14,7 @@
14
14
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
15
 
16
16
  require 'csv'
17
+ require 'pry'
17
18
 
18
19
  module Twb
19
20
  module Analysis
@@ -32,6 +33,7 @@ module DataSources
32
33
  'Data Source',
33
34
  'File Name',
34
35
  'File Dir',
36
+ 'File Exists?',
35
37
  'File Modified',
36
38
  'File Size'
37
39
  ]
@@ -55,7 +57,7 @@ module DataSources
55
57
 
56
58
  def processTWB twb
57
59
  @twbName = twb.name
58
- @twDir = twb.dir
60
+ @twbDir = twb.dir
59
61
  twb.datasources.each do |ds|
60
62
  processFiles ds
61
63
  end
@@ -69,9 +71,10 @@ module DataSources
69
71
  fqFileName = twbFileDir.nil? ? twbFileName : twbFileDir + '/' + twbFileName
70
72
  fileName = File.basename fqFileName
71
73
  fileDir = File.dirname fqFileName
72
- modtime = File.mtime fqFileName
73
- size = File.size fqFileName
74
- data = [$recNum+=1, twbName, twb.dir, ds.uiname, fileName, fileDir, modtime, size]
74
+ exists = File.exist? fqFileName
75
+ modtime = exists ? File.mtime(fqFileName) : nil
76
+ size = exists ? File.size(fqFileName) : nil
77
+ data = [@recNum+=1, @twbName, @twbDir, ds.uiname, fileName, fileDir, exists, modtime, size]
75
78
  @csvFile << data
76
79
  end
77
80
  end
@@ -0,0 +1,94 @@
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
+
19
+ module Twb
20
+ module Analysis
21
+
22
+ class DashboardsSummarizer
23
+
24
+ include TabTool
25
+
26
+ attr_accessor :localEmit
27
+
28
+ def initialize(**args)
29
+ @args = args
30
+ @recordDir = !@args.nil? && @args[:recordDir] == true
31
+ @ttdocdir = @args[:ttdocdir]
32
+ @csvAdd = !@args.nil? && args[:csvMode] == :add
33
+ @csvMode = @csvAdd ? 'a' : 'w'
34
+ init
35
+ @funcdoc = {:class=>self.class, :blurb=>'Analyze Dashboard Worksheets', :description=>'Identifies the Worksheets present in Dashboards.',}
36
+ #--
37
+ docFileName = docFile('DashboardSummaries.csv')
38
+ @dashboardsCSV = CSV.open(docFileName,@csvMode)
39
+ unless @csvAdd
40
+ if @recordDir
41
+ @dashboardsCSV << ['Rec #','Workbook','Dashboard','# Worksheets','Workbook Dir']
42
+ else
43
+ @dashboardsCSV << ['Rec #','Workbook','Dashboard','# Worksheets' ]
44
+ end
45
+ end
46
+ addDocFile @dashboardsCSV, docFileName, "Workbooks and their Dashboards' summaries"
47
+ #--
48
+ @twbCount = 0
49
+ @dashCount = 0
50
+ @recNum = 0
51
+ end
52
+
53
+ def metrics
54
+ {
55
+ # '# of Workbooks' => @twbCount,
56
+ '# of Dashboards' => @dashCount,
57
+ }
58
+ end
59
+
60
+ def processTWB twb
61
+ @twb = twb
62
+ @twbName = @twb.name
63
+ @twbDir = @twb.dir
64
+ @modTime = @twb.modtime
65
+ emit " -- #{@twbName}"
66
+ @twbCount += 1
67
+ parseDashes
68
+ finis
69
+ end
70
+
71
+ def parseDashes
72
+ @dashboards = @twb.dashboards
73
+ @dashboards.each do |dash|
74
+ emit "DASH:: #{dash.name}"
75
+ @dashCount += 1
76
+ recordCSV [@twbName, dash.name, dash.worksheets.length]
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def recordCSV record
83
+ numberedRec = [@recNum+=1] + record
84
+ if @recordDir
85
+ @dashboardsCSV << numberedRec.push(@twbDir)
86
+ else
87
+ @dashboardsCSV << numberedRec
88
+ end
89
+ end
90
+
91
+ end #class SheetFieldsAnalyzer
92
+
93
+ end # module Analysis
94
+ end # module Twb
@@ -37,7 +37,7 @@ module Analysis
37
37
  docFileName = docFile('WorkbookSummary.csv')
38
38
  @csvFile = CSV.open(docFileName,@csvMode)
39
39
  unless @csvAdd
40
- csvHeader = ['Rec #', 'Workbook','Type','Version','Build','Platform','Modified','# Data Sources','# Dashboards','# Worksheets']
40
+ csvHeader = ['Rec #', 'Workbook','Directory','Type','Version','Build','Platform','Modified','# Data Sources','# Dashboards','# Worksheets']
41
41
  if @recordDir
42
42
  csvHeader.push('Workbook Directory')
43
43
  end
@@ -65,6 +65,7 @@ module Analysis
65
65
  @twb = twb
66
66
  emit " -- #{@twbName}"
67
67
  recordCSV [ @twb.name,
68
+ @twb.dir,
68
69
  @twb.type,
69
70
  @twb.version,
70
71
  @twb.build,
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2014, 2015, 2017 Chris Gerrard
1
+ # Copyright (C) 2014, 2015, 2020 Chris Gerrard
2
2
  #
3
3
  # This program is free software: you can redistribute it and/or modify
4
4
  # it under the terms of the GNU General Public License as published by
@@ -26,7 +26,7 @@ module Twb
26
26
  attr_reader :node, :properties
27
27
  attr_reader :caption, :name, :uiname
28
28
  attr_reader :datatype, :role, :propType
29
- attr_reader :calculation, :calcFields
29
+ attr_reader :calculation, :referencedFields
30
30
  attr_reader :isGroup, :groupMembers
31
31
  attr_reader :hidden
32
32
 
@@ -52,8 +52,8 @@ module Twb
52
52
  @properties ||= loadProperties
53
53
  end
54
54
 
55
- def calcFields
56
- @calculation.calcFields
55
+ def referencedFields
56
+ @calculation.referencedFields
57
57
  end
58
58
 
59
59
  def formulaLines
@@ -14,6 +14,7 @@
14
14
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
15
 
16
16
  require 'nokogiri'
17
+ require 'pry'
17
18
 
18
19
  module Twb
19
20
 
@@ -85,7 +86,6 @@ module Twb
85
86
  @aggregation = load 'aggregation'
86
87
  @autoColumn = load 'auto-column'
87
88
  @datatypeCustomized = load 'datatype-customized'
88
- # @calcField = loadCalcField
89
89
  end
90
90
 
91
91
  def id
@@ -94,7 +94,7 @@ module Twb
94
94
 
95
95
  def load nodeName
96
96
  attr = @node.attribute(nodeName)
97
- val = attr.nil? ? nil : attr.text.strip.gsub(/^\[|\]$/,'')
97
+ val = attr.nil? ? nil : attr.text.strip
98
98
  return val
99
99
  end
100
100
 
@@ -56,7 +56,7 @@ module Twb
56
56
  attr_reader :tableFieldsMap
57
57
  attr_reader :fieldUINames
58
58
  attr_reader :aliases
59
- attr_reader :calculatedFields, :calculatedFieldNamesMap, :calculatedFieldNames, :calculatedField
59
+ attr_reader :calculatedFields, :calculatedFieldsMap, :calculatedFieldNames, :calculatedField
60
60
  attr_reader :allFields
61
61
  attr_reader :groups
62
62
  attr_reader :filters
@@ -391,40 +391,6 @@ module Twb
391
391
  return @fieldUINames
392
392
  end
393
393
 
394
- def loadFieldUINames
395
- # puts 'loadFieldUINames'
396
- @fieldUINames = {}
397
- # puts 'metadataFields'
398
- metadataFields.each do |fld|
399
- # puts " - name:%-45s | uiname:%-45s | localName:%-45s " % [ "'#{fld.name}'", "'#{fld.uiname}'", "'#{fld.localName}'"]
400
- @fieldUINames[fld.uiname] = fld.uiname
401
- @fieldUINames[fld.localName] = fld.uiname unless fld.localName.nil?
402
- @fieldUINames[fld.name] = fld.uiname unless fld.name.nil?
403
- end
404
- # puts 'calculatedFields'
405
- calculatedFields.each do |fld|
406
- # puts " - name:%-45s | uiname:%-45s " % [ "'#{fld.name}'", "'#{fld.uiname}'"]
407
- @fieldUINames[fld.name] = fld.uiname
408
- @fieldUINames[fld.uiname] = fld.uiname
409
- end
410
- # puts 'localFields'
411
- localFields.each do |fld|
412
- # puts " - name:%-45s | uiname:%-45s " % [ "'#{fld.name}'", "'#{fld.uiname}'"]
413
- @fieldUINames[fld.name] = fld.uiname
414
- @fieldUINames[fld.uiname] = fld.uiname
415
- end
416
- # puts "columnFields: #{columnFields.length}"
417
- columnFields.each do |fld|
418
- @fieldUINames[fld.name] = fld.uiname
419
- @fieldUINames[fld.uiname] = fld.uiname
420
- end
421
- groups.each do |fld|
422
- @fieldUINames[fld.name] = fld.uiname
423
- @fieldUINames[fld.uiname] = fld.uiname
424
- end
425
- return @fieldUINames
426
- end
427
-
428
394
  def tableFields
429
395
  @tableFieldsMap.values
430
396
  end
@@ -482,8 +448,7 @@ module Twb
482
448
 
483
449
  def loadAllFields
484
450
  @allFields = SortedSet.new
485
- dbf = dbFields
486
- @allFields << dbf
451
+ @allFields << dbFields
487
452
  @allFields << calculatedFieldNames
488
453
  end
489
454
 
@@ -532,6 +497,53 @@ module Twb
532
497
 
533
498
  private
534
499
 
500
+ def loadFieldUINames
501
+ @fieldUINames = {}
502
+ # puts"metadataFields: #{metadataFields.length}"
503
+ metadataFields.each do |fld|
504
+ # puts" - name:%-45s | caption:%-45s | uiname:%-45s " % [ "'#{fld.name}'", "'#{fld.caption}'", "'#{fld.uiname}'"]
505
+ unless fld.name.nil?
506
+ @fieldUINames[fld.uiname] = fld.uiname
507
+ @fieldUINames[fld.localName] = fld.uiname unless fld.localName.nil?
508
+ @fieldUINames[fld.name] = fld.uiname
509
+ end
510
+ end
511
+ # puts"localFields: #{localFields.length}"
512
+ localFields.each do |fld|
513
+ # puts" - name:%-45s | caption:%-45s | uiname:%-45s " % [ "'#{fld.name}'", "'#{fld.caption}'", "'#{fld.uiname}'"]
514
+ unless fld.caption.nil?
515
+ @fieldUINames[fld.name] = fld.caption
516
+ @fieldUINames[fld.uiname] = fld.caption
517
+ end
518
+ end
519
+ # puts"groups: #{groups.length}"
520
+ groups.each do |fld|
521
+ # puts" - name:%-45s | caption:%-45s | uiname:%-45s " % [ "'#{fld.name}'", "'#{fld.caption}'", "'#{fld.uiname}'"]
522
+ unless fld.caption.nil?
523
+ @fieldUINames[fld.name] = fld.caption
524
+ @fieldUINames[fld.uiname] = fld.caption
525
+ end
526
+ end
527
+ # puts"calculatedFields: #{calculatedFields.length}"
528
+ calculatedFields.each do |fld|
529
+ # puts" - name:%-45s | caption:%-45s | uiname:%-45s " % [ "'#{fld.name}'", "'#{fld.caption}'", "'#{fld.uiname}'"]
530
+ unless fld.caption.nil?
531
+ @fieldUINames[fld.name] = fld.caption
532
+ @fieldUINames[fld.uiname] = fld.caption
533
+ end
534
+ end
535
+ # puts"columnFields: #{columnFields.length}"
536
+ columnFields.each do |fld|
537
+ # puts" - name:%-45s | caption:%-45s | uiname:%-45s " % [ "'#{fld.name}'", "'#{fld.caption}'", "'#{fld.uiname}'"]
538
+ unless fld.caption.nil?
539
+ # @fieldUINames[fld.name] = fld.caption
540
+ @fieldUINames[fld.name.gsub(/^[\[]|[\]]$/,'')] = fld.caption
541
+ @fieldUINames[fld.uiname] = fld.caption
542
+ end
543
+ end
544
+ return @fieldUINames
545
+ end
546
+
535
547
  def loadGroups
536
548
  @groups = []
537
549
  groupNodes = @node.xpath('.//group')
@@ -16,6 +16,7 @@
16
16
  require 'nokogiri'
17
17
  require 'digest/md5'
18
18
  require 'csv'
19
+ require 'pry'
19
20
 
20
21
  module Twb
21
22
 
@@ -35,7 +36,7 @@ module Twb
35
36
  attr_reader :is_tableCalc
36
37
  attr_reader :is_lod, :lodCodePos
37
38
  attr_reader :class, :scopeIsolation
38
- attr_reader :fields, :remoteFields, :calcFields
39
+ attr_reader :fields, :remoteFields, :referencedFields
39
40
  attr_reader :comments, :uuid
40
41
 
41
42
  # attr_accessor :ttlogfile
@@ -120,59 +121,29 @@ module Twb
120
121
  end
121
122
 
122
123
  def formulaResolved
123
- @formulaResolved ||= @formulaResolved = resolveFormula
124
+ @formulaResolved ||= resolveFormula
124
125
  end
125
126
 
126
127
  def resolveFormula
127
128
  # puts "\ndef resolveFormula:\n--\n#{@formula}"
128
129
  formula = @formula
129
- parseFormFields # - extracts the fields from the formula; as persisted they're the internal names
130
- @calcFields.each do |calcField|
131
- if calcField.techUIdiff
132
- # puts ":::: #{calcField.techCode} // #{calcField.uiCode}"
133
- formula = formula.gsub(calcField.techCode,calcField.uiCode)
134
- # puts ":--: #{formula}"
135
- end
130
+ # parseFormFields # - extracts the fields from the formula; as persisted they're the internal names
131
+ referencedFields.each do |calcField|
132
+ # if calcField.techUIdiff
133
+ # # puts ":::: #{calcField.techCode} // #{calcField.uiCode}"
134
+ # formula = formula.gsub(calcField.techCode,calcField.uiCode)
135
+ # # puts ":--: #{formula}"
136
+ # end
136
137
  end
137
138
  return formula
138
139
  end
139
140
 
140
- def calcFields
141
- @calcFields ||= parseFormFields
141
+ def referencedFields
142
+ @referencedFields ||= parseFormFields
142
143
  end
143
144
 
144
- def parseFormFields
145
- # puts "--parseFormFields"
146
- @fields = Set.new
147
- @calcFields = Set.new
148
- formula = @formulaFlat
149
- if !formula.nil? && formula.include?('[') && formula.include?(']')
150
- fields = Set.new
151
- # noSqLits = formula.gsub( /'[\[\.\]]+'/, ' ')
152
- quotes = formula.gsub('"',"'")
153
- noSqLits = quotes.gsub( /'[\[\.\]]+'/, ' ')
154
- flatForm = noSqLits.gsub( /\n/, ' ')
155
- stripFrt = flatForm.gsub( /^[^\[]*[\[]/ , '[' )
156
- stripBck = stripFrt.gsub( /\][^\]]+$/ , ']' )
157
- stripMid = stripBck.gsub( /\][^\]]{2,}\[/ , ']]..[[' )
158
- stripCom = stripMid.gsub( /\][ ]*,[ ]*\[/ , ']]..[[' )
159
- stripFns = stripMid.gsub( /\][ ]*[\*\/+\-><,=][ ]*\[/ , ']]..[[' )
160
- fields = stripFns.split(']..[')
161
- emit "::self::: #{self} :: #{__LINE__} :: fields:'#{fields.inspect}'"
162
- fields.each do |field|
163
- emit "::self::: #{self} :: #{__LINE__} :: field:'#{field}'"
164
- cf = CalculationField.new( field.gsub(/^\[|\]$/, ''), @dataSource )
165
- @calcFields.add cf
166
- @fields.add field.gsub(/^\[|\]$/, '')
167
- end
168
- end
169
- return @calcFields
170
- end
171
145
 
172
146
  def formulaResolvedLines
173
- # puts "\ndef formulaResolvedLines\n--\n#{formulaResolved}"
174
- # puts "--\n#{formulaResolved.split(/\n|\r\n/)}"
175
- # puts "--\n#{formulaResolved.split(/\n|\r\n/)}"
176
147
  formulaResolved.split(/\n|\r\n/)
177
148
  end
178
149
 
@@ -212,11 +183,149 @@ module Twb
212
183
  return comments.strip
213
184
  end
214
185
 
215
- end # class FieldCalculation
186
+ private
216
187
 
217
188
 
189
+ def pullString chars
190
+ delim1 = chars.shift
191
+ delim2 = delim1+delim1
192
+ field = delim1
193
+ done = false
194
+ until done | chars.empty?
195
+ s01 = chars[0..1].join
196
+ if !delim1.eql? chars[0]
197
+ field += chars.shift
198
+ else
199
+ case s01
200
+ when delim2
201
+ field += delim2
202
+ chars.shift(2)
203
+ when delim1
204
+ field += delim1
205
+ chars.shift
206
+ done = true
207
+ else
208
+ field += delim1
209
+ chars.shift
210
+ done = true
211
+ end
212
+ end
213
+ end
214
+ end
215
+
216
+ def pullField chars
217
+ # chars = str.split ''
218
+ done = false
219
+ ds = ''
220
+ field = ''
221
+ until done
222
+ s01 = chars[0..1].join
223
+ s02 = chars[0..2].join
224
+ if ']'.eql? chars[0]
225
+ case s01
226
+ when ']]'
227
+ field += ']]'
228
+ chars.shift(2)
229
+ when ']'
230
+ field += chars.shift
231
+ done = true
232
+ else
233
+ if '].['.eql?(s02)
234
+ ds = field + ']'
235
+ chars.shift(2)
236
+ # fldstr = chars.join
237
+ field = pullField(chars)[:field]
238
+ done = true
239
+ else
240
+ field += ']'
241
+ chars.shift
242
+ done = true
243
+ end
244
+ end
245
+ else
246
+ field += chars[0]
247
+ chars.shift
248
+ end
249
+ end
250
+ # puts "field: '#{field}' \t\t ds: #{ds}"
251
+ return {:field => field.sub(/\[/,'').sub(/\]$/,''), :ds => ds.sub(/\[/,'').sub(/\]$/,'') }
252
+ end
218
253
 
219
- class CalculationField
254
+ def parseFormFields # formula
255
+ @referencedFields = Array.new
256
+ rawFields = Array.new
257
+ if !@formula.nil? && @formula.include?('[') && @formula.include?(']')
258
+ chars = formula.split('')
259
+ until chars.empty?
260
+ char0 = chars[0]
261
+ case char0
262
+ when '"', "'"
263
+ pullString(chars)
264
+ when '['
265
+ rawFields << pullField(chars)
266
+ else
267
+ unless chars.nil? | chars.empty?
268
+ chars.shift
269
+ end
270
+ end
271
+ end
272
+ rawFields.each do |rf|
273
+ ds = rf[:ds]
274
+ dataSource = if ''.eql? ds
275
+ @dataSource
276
+ else
277
+ @dataSource.workbook.datasource(ds)
278
+ end
279
+ # fieldUIName = dataSource.fieldUIName(rf[:field])
280
+ refField = ReferencedField.new(rf[:field], dataSource)
281
+ @referencedFields << refField
282
+ end
283
+ end
284
+ return @referencedFields
285
+ end
286
+
287
+ def parseFormFieldsx # formula
288
+ rawFields = Set.new
289
+ if !@formula.nil? && @formula.include?('[') && @formula.include?(']')
290
+ noComms = @formula.gsub(/\/\/.*\r\n/,' ')
291
+ formBase = noComms.gsub(/\r\n/,' ')
292
+ formLen = formBase.length
293
+ formChars = formBase.split ''
294
+ until formChars.empty?
295
+ c = formChars.shift
296
+ case c
297
+ when '"', "'"
298
+ pullString(formChars, c)
299
+ when '['
300
+ rawFields << pullField(formChars, ']', @referencedFields)
301
+ end
302
+ end
303
+ end
304
+ @referencedFields = Set.new
305
+ rawFields.each do |rf|
306
+ # @referencedFields << rf
307
+ dataSource = if ''.eql? rf[:ds]
308
+ @dataSource
309
+ else
310
+ @dataSource.workbook.datasource(rf[:ds])
311
+ end
312
+ # if dataSource.nil?
313
+ # binding.pry
314
+ # end
315
+ fieldUIName = dataSource.fieldUIName(rf[:field])
316
+ # binding.pry
317
+ refField = ReferencedField.new(rf[:field], dataSource)
318
+ # binding.pry
319
+ @referencedFields << refField
320
+ end
321
+ return @referencedFields
322
+ end
323
+
324
+ end # class FieldCalculation
325
+
326
+
327
+ # class CalculationField
328
+ class ReferencedField
220
329
  # is a field used in a calculation, resolved into its human-meaningful form
221
330
 
222
331
  include Comparable
@@ -228,65 +337,29 @@ module Twb
228
337
  attr_reader :fqName, :type
229
338
  attr_reader :techUIdiff
230
339
 
231
- def initialize code, datasource
232
- # puts "\n\nCalculationField :: %-25s | %s " % [datasource.uiname, code]
340
+ def initialize name, datasource
341
+ # puts "\n\nReferencedField :: ds: %-25s | n: %s " % [datasource, name]
342
+ @name = name
233
343
  @dataSource = datasource
234
- @dataSourceName = datasource.uiname
344
+ @dataSourceName = datasource.nil? ? nil : datasource.uiname
235
345
  @dataSourceRef = :local
236
346
  @dataSourceExists = true
347
+ @techCode = "[#{name}]"
237
348
  @techUIdiff = false
238
- @uiname = ''
239
- rawCode = code.gsub(/^\[|\]$/,'')
240
- parts = rawCode.split('].[')
241
- #puts "Field: #{code} \t parts: #{parts.length} - #{parts.inspect}"
242
- if parts.length == 1
243
- @name = parts[0]
244
- @techCode = "[#{parts[0]}]"
245
- #puts "@name: #{@name}"
246
- if datasource.nil?
247
- # puts 'a'
248
- @uiname = @name
249
- @uiCode = @techCode
250
- @techUIdiff = false
251
- else # !datasource.nil?
252
- # puts 'b'
253
- #puts "b - found uiname for '#{@name}'?: #{!datasource.fieldUIName(@name).nil?} \t is:#{datasource.fieldUIName(@name)} "
254
- @uiname = datasource.fieldUIName(@name).nil? ? @name : datasource.fieldUIName(@name)
255
- @uiCode = @uiname.nil? ? @techCode : "[#{@uiname}]"
256
- @techUIdiff = !@techCode.eql?(@uiCode)
257
- # puts ":b #{datasource.fieldUIName(@name).nil?} ... #{@name} ... #{@uiname}"
258
- # puts "CalculationField :: uin: %-25s | @name:%-s" % [@uiname,@name]
259
- end
260
- else # parts.length <> 1
261
- # puts 'c'
262
- rdstech = parts[0]
263
- calcField = parts[1]
264
- @uiname = calcField
265
- @dataSourceName = rdstech
266
- @dataSourceRef = :remote
267
- @techCode = "[#{rdstech}].[#{calcField}]"
268
- workbook = datasource.workbook
269
- @dataSource = workbook.nil? ? nil : workbook.datasource(rdstech)
270
- # puts "\t twb: #{workbook.class} / remoteds: #{remoteds.class} : #{remoteds.nil? ? "<<NOT FOUND:#{rdstech}:>>" : remoteds.uiname} "
271
- #--
272
- if @dataSource.nil? || @dataSource.fieldUIName(calcField).nil?
273
- # puts 'd'
274
- @uiname = calcField
275
- @uiCode = "[<<NOT FOUND>>#{rdstech}].[#{calcField}]"
276
- @techUIdiff = true
277
- @dataSourceExists = false
278
- else # !remoteds.nil?
279
- # puts 'e'
280
- @dataSourceName = @dataSource.uiname
281
- @uiname = @dataSource.fieldUIName(calcField)
282
- @uiCode = "[#{@dataSourceName}].[#{@uiname}]"
283
- @techUIdiff = !@techCode.eql?(@uiCode)
284
- @dataSourceExists = true
285
- end
349
+ if dataSource.nil?
350
+ # puts 'a'
351
+ @uiname = @name
352
+ @uiCode = @techCode
353
+ @techUIdiff = false
354
+ else # !datasource.nil?
355
+ # puts 'b'
356
+ # puts "b - found uiname for '#{@name}'?: #{!datasource.fieldUIName(@name).nil?} \t is:#{datasource.fieldUIName(@name)} "
357
+ @uiname = datasource.fieldUIName(@name).nil? ? @name : datasource.fieldUIName(@name)
358
+ @uiCode = @uiname.nil? ? @techCode : "[#{@uiname}]"
359
+ @techUIdiff = !@techCode.eql?(@uiCode)
360
+ # puts ":b #{datasource.fieldUIName(@name).nil?} ... #{@name} ... #{@uiname}"
361
+ # puts "CalculationField :: uin: %-25s | @name:%-s" % [@uiname,@name]
286
362
  end
287
- # puts "\t dsName: #{@dataSourceName}"
288
- # puts "\t @name: #{@name}"
289
- # puts "\t uiname: #{@uiname}"
290
363
  @fqName = "#{@dataSourceName}::#{@uiname}"
291
364
  @type = if @dataSource.nil?
292
365
  :CalculatedField
@@ -308,6 +381,6 @@ module Twb
308
381
  @fqName <=> other.fqName
309
382
  end
310
383
 
311
- end # class CalculationField
384
+ end # class ReferencedField
312
385
 
313
386
  end # module Twb
@@ -26,18 +26,20 @@ module Util
26
26
 
27
27
  #====================================================================
28
28
 
29
- attr_accessor :nodes, :edges, :fileName, :fileName
29
+ attr_accessor :nodes, :edges, :fileName, :mode
30
30
  attr_accessor :cleanup
31
31
 
32
32
  def initialize
33
33
  emit "Cypher.initialize"
34
34
  @fileName = 'Neo4jCommands'
35
+ @mode = :create
35
36
  @nodes = []
36
37
  @edges = []
37
38
  @cleanup = false
38
39
  end
39
40
 
40
41
  def render
42
+ puts "Cypher.render"
41
43
  @file = File.open(docFile("#{@fileName}.cypher"),'w')
42
44
  renderNodes
43
45
  renderEdges
@@ -46,6 +48,7 @@ module Util
46
48
  end
47
49
 
48
50
  def renderNodes
51
+ puts "Cypher def renderNodes @nodes:#{@nodes.to_s}"
49
52
  csv = CSV.open(docFile("#{@fileName}.nodes.csv"),'w')
50
53
  csv << ['Type','Name','UUID']
51
54
  nodesCSV = Set.new
@@ -53,7 +56,7 @@ module Util
53
56
  nodesByType = Hash.new { |type,nodes| type[nodes] = [] }
54
57
  @nodes.each do |node|
55
58
  nodesCSV << [node.type, node.name, node.uuid]
56
- nodeCmds << encode('MERGE','node',node,';')
59
+ nodeCmds << encode(node,';')
57
60
  nodesByType[node.type] << node
58
61
  end
59
62
  if @cleanup
@@ -93,8 +96,12 @@ module Util
93
96
  csv.close
94
97
  end
95
98
 
96
- def encode command, varName, node, terminator=''
97
- "%-8s (%s:%s { name:'%s', uuid: '%s' } ) %s" % [command, varName, node.type, node.name, node.uuid, terminator ]
99
+ def encode node, terminator=''
100
+ puts "def encode node: #{node} "
101
+ case @mode
102
+ when :merge then "MERGE (%s:%s { name:'%s', uuid: '%s' } ) %s" % [node.uuid, varName, node.type, node.name, terminator ]
103
+ when :create then "CREATE (#{node.uuid}:#{node.type} {} )"
104
+ end
98
105
  end
99
106
 
100
107
  def encodeEdge command, varName, node, terminator=''
@@ -77,10 +77,16 @@ GMLHEADER
77
77
 
78
78
  def renderNodes file
79
79
  nodes = Set.new
80
+ # puts 'def renderNodes'
80
81
  @nodes.each do |node|
81
82
  gmlID = Digest::MD5.hexdigest(node.id)
82
83
  gmlName = node.name.gsub('&','&amp;').gsub('"','&quot;')
83
- nodes << "node [\n id \"#{gmlID}\" \n label \"#{gmlName}\" \n ]"
84
+ nodestr = "node [\n id \"#{gmlID}\" \n label \"#{gmlName}\" "
85
+ unless node.colour.nil?
86
+ nodestr += "\n graphics \n [ \n fill \"#{node.colour}\" \n ] "
87
+ end
88
+ nodestr += "\n ]"
89
+ nodes << nodestr
84
90
  end
85
91
  nodes.each do |node|
86
92
  file.puts node
@@ -46,7 +46,7 @@ module Util
46
46
  }
47
47
 
48
48
  attr_reader :id, :uuid, :type, :name, :dotid
49
- attr_accessor :properties
49
+ attr_accessor :properties, :colour
50
50
 
51
51
  def initialize (name:, id:, type:, properties: {})
52
52
  # puts "graphNode : t: %-20s tc: %s " % [type, type.class]
@@ -56,6 +56,7 @@ module Util
56
56
  @type = resolveType type
57
57
  # puts " : t: %-20s tc: %s \n " % [@type, @type.class]
58
58
  @name = name
59
+ @colour = nil
59
60
  @properties = properties
60
61
  @properties['name'] = name unless @properties.key? 'name'
61
62
  if @name.nil?
@@ -117,7 +118,7 @@ module Util
117
118
  end
118
119
 
119
120
  def <=>(other)
120
- uuid <=> other.uuid
121
+ hash <=> other.hash
121
122
  end
122
123
 
123
124
  end # class Graphnode
@@ -16,7 +16,9 @@
16
16
  require 'nokogiri'
17
17
 
18
18
  #require 'twb'
19
- require 'C:\tech\Tableau\tools\Ruby\gems\twb\lib\twb.rb'
19
+ # require 'C:\tech\Tableau\tools\Ruby\gems\twb\lib\twb.rb'
20
+ # require 'C:\tech\Tableau\Tableau Tools\Ruby\gems\twb\lib\twb.rb'
21
+ require 'twb'
20
22
  require "test/unit"
21
23
 
22
24
  system "cls"
@@ -25,10 +27,11 @@ class TestFieldCalculation < Test::Unit::TestCase
25
27
 
26
28
  def test_fragment1
27
29
  doc = Nokogiri::XML::Document.parse <<-EOHTML
28
- <calculation class='tableau' formula='abc' />
30
+ <calculation class='tableau' name='I am a formular field' formula='abc' datatype='datatype' role='' type='' class='calcfield' />
29
31
  EOHTML
30
- calcNode = doc.at_xpath('./calculation')
31
- calc = Twb::FieldCalculation.new(calcNode)
32
+ calcNode = doc.at_xpath('./calculation')
33
+ calcField = Twb::CalculatedField.new calcNode
34
+ calc = Twb::FieldCalculation.new(calcField)
32
35
  assert(!calc.nil?)
33
36
  #puts "node: #{calcNode}"
34
37
  #puts "formula: #{calc.formula}"
@@ -41,10 +44,11 @@ EOHTML
41
44
 
42
45
  def test_fragment2
43
46
  doc = Nokogiri::XML::Document.parse <<-EOHTML
44
- <calculation class='tableau' formula='// this is the number of days between the order and shipment&#13;&#10;&#13;&#10;datediff(&apos;day&apos;,[Order Date] , [other].[Ship Date])' />
47
+ <calculation class='tableau' name='Another formula fied' formula='// this is the number of days between the order and shipment&#13;&#10;&#13;&#10;datediff(&apos;day&apos;,[Order Date] , [other].[Ship Date])' />
45
48
  EOHTML
46
49
  calcNode = doc.at_xpath('./calculation')
47
- calc = Twb::FieldCalculation.new(calcNode)
50
+ calcField = Twb::CalculatedField calcNode
51
+ calc = Twb::FieldCalculation.new(calcField)
48
52
  assert(!calc.nil?)
49
53
  #puts "node: #{calcNode}"
50
54
  #puts "formula: #{calc.formula}"
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: 4.9.2
4
+ version: 5.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Gerrard
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-07 00:00:00.000000000 Z
11
+ date: 2020-06-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: creek
@@ -58,6 +58,7 @@ extensions: []
58
58
  extra_rdoc_files: []
59
59
  files:
60
60
  - lib/t.rb
61
+ - lib/tfl/Flow.rb
61
62
  - lib/twb.rb
62
63
  - lib/twb/action.rb
63
64
  - lib/twb/analysis/annotatedfieldscsvemitter.rb
@@ -66,6 +67,7 @@ files:
66
67
  - lib/twb/analysis/calculatedfields/fieldsaliasesanalyzer.rb
67
68
  - lib/twb/analysis/calculatedfields/groupfieldsanalyzer.rb
68
69
  - lib/twb/analysis/calculatedfields/markdownemitter.rb
70
+ - lib/twb/analysis/calculatedfields/t.rb
69
71
  - lib/twb/analysis/datasources/categoricalcolorcodinganalyzer.rb
70
72
  - lib/twb/analysis/datasources/datasourcefieldsanalyzer.rb
71
73
  - lib/twb/analysis/datasources/datasourcefieldscsvemitter.rb
@@ -82,6 +84,7 @@ files:
82
84
  - lib/twb/analysis/documentedfieldscsvemitter.rb
83
85
  - lib/twb/analysis/documentedfieldsmarkdownemitter.rb
84
86
  - lib/twb/analysis/sheets/analyzedashboardsheets.rb
87
+ - lib/twb/analysis/sheets/dashboardsummarizer.rb
85
88
  - lib/twb/analysis/sheets/dashsheetsanalyzer.rb
86
89
  - lib/twb/analysis/sheets/sheetfieldsanalyzer.rb
87
90
  - lib/twb/analysis/sheets/sheetfiltersanalyzer.rb