twb 4.9.2 → 5.1.4

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.
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