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 +4 -4
- data/lib/tfl/Flow.rb +311 -0
- data/lib/twb.rb +2 -2
- data/lib/twb/analysis/calculatedfields/calculatedfieldsanalyzer.rb +49 -34
- data/lib/twb/analysis/calculatedfields/t.rb +6 -0
- data/lib/twb/analysis/datasources/datasourcefilesanalyzer.rb +7 -4
- data/lib/twb/analysis/sheets/dashboardsummarizer.rb +94 -0
- data/lib/twb/analysis/workbooksummaryanalyzer.rb +2 -1
- data/lib/twb/calculatedfield.rb +4 -4
- data/lib/twb/columnfield.rb +2 -2
- data/lib/twb/datasource.rb +49 -37
- data/lib/twb/fieldcalculation.rb +171 -98
- data/lib/twb/util/cypher.rb +11 -4
- data/lib/twb/util/gml.rb +7 -1
- data/lib/twb/util/graphnode.rb +3 -2
- data/test/testFieldCalculation.rb +10 -6
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f839625abecd6c9b4ad520856fd216678790a2dace5307e62458db4e07b970cd
|
4
|
+
data.tar.gz: f95701f60e3f4f99525113e723392b1d039b361db9ac63eb2689c58ef4a3a490
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d59954bb21cc6b232402d0792219df4e0e48ddcf8a664088b58409c7e28b30175f3f3304910459304f2d87639bf1ce5a6f7d9b533b43ed0088bfbb1c4f131c8f
|
7
|
+
data.tar.gz: c8fa036df0e3e8808f507404f5930a8db20ca19fb7a04251eda1deee7dc1ea09f1e06a811f79539275d2eed9cb473f009239dca5ae1c56c7179f4826ed0201c0
|
data/lib/tfl/Flow.rb
ADDED
@@ -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 = '
|
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.
|
253
|
-
calculation.
|
254
|
-
emit " referenced field ::
|
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
|
#--
|
@@ -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
|
-
@
|
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
|
-
|
73
|
-
|
74
|
-
|
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,
|
data/lib/twb/calculatedfield.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (C) 2014, 2015,
|
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, :
|
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
|
56
|
-
@calculation.
|
55
|
+
def referencedFields
|
56
|
+
@calculation.referencedFields
|
57
57
|
end
|
58
58
|
|
59
59
|
def formulaLines
|
data/lib/twb/columnfield.rb
CHANGED
@@ -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
|
97
|
+
val = attr.nil? ? nil : attr.text.strip
|
98
98
|
return val
|
99
99
|
end
|
100
100
|
|
data/lib/twb/datasource.rb
CHANGED
@@ -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, :
|
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
|
-
|
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')
|
data/lib/twb/fieldcalculation.rb
CHANGED
@@ -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,
|
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 ||=
|
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
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
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
|
141
|
-
@
|
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
|
-
|
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
|
-
|
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
|
232
|
-
# puts "\n\
|
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
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
#puts "@name
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
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
|
384
|
+
end # class ReferencedField
|
312
385
|
|
313
386
|
end # module Twb
|
data/lib/twb/util/cypher.rb
CHANGED
@@ -26,18 +26,20 @@ module Util
|
|
26
26
|
|
27
27
|
#====================================================================
|
28
28
|
|
29
|
-
attr_accessor :nodes, :edges, :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(
|
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
|
97
|
-
"
|
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=''
|
data/lib/twb/util/gml.rb
CHANGED
@@ -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('&','&').gsub('"','"')
|
83
|
-
|
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
|
data/lib/twb/util/graphnode.rb
CHANGED
@@ -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
|
-
|
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
|
31
|
-
|
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 datediff('day',[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 datediff('day',[Order Date] , [other].[Ship Date])' />
|
45
48
|
EOHTML
|
46
49
|
calcNode = doc.at_xpath('./calculation')
|
47
|
-
|
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
|
+
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:
|
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
|