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