twb 4.9.4 → 5.2.1
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 +3 -2
- data/lib/twb/analysis/calculatedfields/calculatedfieldsanalyzer.rb +101 -122
- data/lib/twb/analysis/calculatedfields/dotanalyzer.rb +139 -0
- data/lib/twb/analysis/calculatedfields/markdownemitter.rb +2 -2
- 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 +178 -101
- data/lib/twb/util/cypher.rb +1 -1
- data/lib/twb/util/gml.rb +1 -1
- data/test/testFieldCalculation.rb +10 -6
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 786d3cb3bde8d6d6db0ee5c4063e998df008b5ca1354ba8fb4fc77c06aa7e10d
|
4
|
+
data.tar.gz: f9e73b1494d4d3946749f03e045caf6db221d4750ac12d99a5af037a77ca955a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1ed3f0c9dfd61b8accd948ac10f29382f16c0fc8b15c415c8f5de55ab5a2739b6e7991e82c02ec953fd1507cb27079b59a5a15785da4a44b2fbe57f5e08eb083
|
7
|
+
data.tar.gz: 6171c5e6615b4d426690fca14bc92fea5e7325ee143985bd2fd4fd178833c41085a9e0b9f75a219884fdc900ab4d04a56ccd12d69580636e85b30855dd5f7792
|
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
@@ -54,6 +54,7 @@ require_relative 'twb/analysis/documentedfieldsmarkdownemitter'
|
|
54
54
|
require_relative 'twb/analysis/annotatedfieldscsvemitter'
|
55
55
|
require_relative 'twb/analysis/workbooksummaryanalyzer'
|
56
56
|
require_relative 'twb/analysis/calculatedfields/calculatedfieldsanalyzer'
|
57
|
+
require_relative 'twb/analysis/calculatedfields/dotanalyzer'
|
57
58
|
require_relative 'twb/analysis/calculatedfields/groupfieldsanalyzer'
|
58
59
|
require_relative 'twb/analysis/calculatedfields/markdownemitter'
|
59
60
|
require_relative 'twb/analysis/calculatedfields/csvemitter'
|
@@ -73,12 +74,12 @@ require_relative 'twb/analysis/sheets/worksheetsummarizer'
|
|
73
74
|
require_relative 'twb/analysis/sheets/worksheetdatastructurecsvemitter'
|
74
75
|
require_relative 'twb/analysis/sheets/sheetfiltersanalyzer'
|
75
76
|
require_relative 'twb/analysis/sheets/sheetfieldsanalyzer'
|
77
|
+
require_relative 'twb/analysis/sheets/dashboardsummarizer'
|
76
78
|
require_relative 'twb/analysis/sheets/dashsheetsanalyzer'
|
77
79
|
require_relative 'twb/analysis/sheets/sheetsintooltipanalyzer'
|
78
80
|
|
79
|
-
|
80
81
|
# Represents Tableau Workbooks, their contents, and classes that analyze and manipulate them.
|
81
82
|
#
|
82
83
|
module Twb
|
83
|
-
VERSION = '
|
84
|
+
VERSION = '5.2.1'
|
84
85
|
end
|
@@ -78,12 +78,12 @@ module Analysis
|
|
78
78
|
@fieldTables = {}
|
79
79
|
|
80
80
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
81
|
+
# @@dotHeader = <<DOTHEADER
|
82
|
+
# digraph g {
|
83
|
+
# graph [rankdir="LR" splines=line];
|
84
|
+
# node [shape="box" width="2"];
|
85
85
|
|
86
|
-
DOTHEADER
|
86
|
+
# DOTHEADER
|
87
87
|
|
88
88
|
def initialize(**args)
|
89
89
|
emit "initialize CalculatedFieldsAnalyzer args #{args}"
|
@@ -141,7 +141,7 @@ DOTHEADER
|
|
141
141
|
# end
|
142
142
|
processDataSource ds
|
143
143
|
end
|
144
|
-
mapTwb
|
144
|
+
# mapTwb
|
145
145
|
emitGml
|
146
146
|
@twbCount += 1
|
147
147
|
finis
|
@@ -195,10 +195,10 @@ DOTHEADER
|
|
195
195
|
calculatedFields.add calcField.id
|
196
196
|
dsFields[calcField.uiname] = calcField
|
197
197
|
# if @doGraph
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
198
|
+
calcFieldNode = Twb::Util::Graphnode.new(name: calcField.uiname, id: calcField.id, type: calcField, properties: {:DataSource => ds.uiname})
|
199
|
+
@nodes.add calcFieldNode
|
200
|
+
dsFieldEdge = Twb::Util::Graphedge.new(from: dataSourceNode, to: calcFieldNode, relationship: 'contains')
|
201
|
+
@edges.add dsFieldEdge
|
202
202
|
# end
|
203
203
|
calculation = calcField.calculation
|
204
204
|
if calculation.has_formula
|
@@ -249,47 +249,26 @@ 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
|
+
refFieldNode = Twb::Util::Graphnode.new(name: rf.uiname, id: rf.id, type: rf, properties: {:DataSource => ds.uiname})
|
269
|
+
@nodes.add refFieldNode
|
270
|
+
refFieldEdge = Twb::Util::Graphedge.new(from: calcFieldNode, to: refFieldNode , relationship: 'references')
|
271
|
+
@edges.add refFieldEdge
|
293
272
|
end # resolvedFields.each do
|
294
273
|
end # if calculation.has_formula
|
295
274
|
end # ds.calculatedFields.each
|
@@ -298,8 +277,8 @@ DOTHEADER
|
|
298
277
|
@referencedFields.merge referencedFields
|
299
278
|
@twbRootFields.merge dsRootFields
|
300
279
|
if @doGraph
|
301
|
-
cypher @twb.name
|
302
|
-
cypherPy @twb.name
|
280
|
+
# cypher @twb.name
|
281
|
+
# cypherPy @twb.name
|
303
282
|
end
|
304
283
|
emit "#######################"
|
305
284
|
#--
|
@@ -330,49 +309,49 @@ DOTHEADER
|
|
330
309
|
emit "\t formula:: #{calculation.formulaFlat}"
|
331
310
|
end
|
332
311
|
|
333
|
-
def mapTwb
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
end
|
312
|
+
# def mapTwb
|
313
|
+
# twb = @twb.name
|
314
|
+
# rootFields = @twbRootFields
|
315
|
+
# dotStuff = initDot twb
|
316
|
+
# dotFile = dotStuff[:file]
|
317
|
+
# dotFileName = dotStuff[:name]
|
318
|
+
# dotFile.puts "\n // subgraph cluster_1 {"
|
319
|
+
# dotFile.puts " // color= grey;"
|
320
|
+
# dotFile.puts ""
|
321
|
+
# edgesAsStrings = SortedSet.new
|
322
|
+
# # this two step process coalesces the edges into a unique set, avoiding duplicating the dot
|
323
|
+
# # file entries, and can be shrunk when graph edges expose the bits necessary for management by Set
|
324
|
+
# emit "\n========================\nLoading Edges\n========================\n From DC? Referenced? Edge \n %s %s %s" % ['--------', '-----------', '-'*45]
|
325
|
+
# @edges.each do |e|
|
326
|
+
# # don't want to emit edge which is from a Data Connection to a
|
327
|
+
# # Calculated Field which is also referenced by another calculated field
|
328
|
+
# isFromDC = e.from.type == :TwbDataConnection
|
329
|
+
# isRefField = @referencedFields.include?(e.to.id)
|
330
|
+
# edgesAsStrings.add(e.dot) unless isFromDC && isRefField
|
331
|
+
# # emit " ES #{e.dot}"
|
332
|
+
# # emit " ES from #{e.from}"
|
333
|
+
# # emit " ES to #{e.to}"
|
334
|
+
# end
|
335
|
+
# emit "------------------------\n "
|
336
|
+
# edgesAsStrings.each do |es|
|
337
|
+
# dotFile.puts " #{es}"
|
338
|
+
# end
|
339
|
+
# emit "========================\n "
|
340
|
+
# dotFile.puts ""
|
341
|
+
# dotFile.puts " // }"
|
342
|
+
# dotFile.puts "\n\n // 4 NODES --------------------------------------------------------------------"
|
343
|
+
# @nodes.each do |n|
|
344
|
+
# dotFile.puts n.dotLabel
|
345
|
+
# end
|
346
|
+
# dotFile.puts "\n\n // 5--------------------------------------------------------------------"
|
347
|
+
# emitTypes( dotFile )
|
348
|
+
# closeDot( dotFile, twb )
|
349
|
+
# emit "Rendering DOT file - #{twb}"
|
350
|
+
# renderDot(twb,dotFileName,'pdf')
|
351
|
+
# renderDot(twb,dotFileName,'png')
|
352
|
+
# renderDot(twb,dotFileName,'svg')
|
353
|
+
# # emitEdges
|
354
|
+
# end
|
376
355
|
|
377
356
|
def cypher twbName
|
378
357
|
if @doGraph
|
@@ -488,44 +467,44 @@ DOTHEADER
|
|
488
467
|
dotFile.puts ' }'
|
489
468
|
end
|
490
469
|
|
491
|
-
def initDot twb
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
end
|
470
|
+
# def initDot twb
|
471
|
+
# dotFileName = docFile("#{twb}#{@@processName}.dot")
|
472
|
+
# dotFile = File.open(dotFileName,'w')
|
473
|
+
# dotFile.puts @@dotHeader
|
474
|
+
# return {:file => dotFile, :name => dotFileName}
|
475
|
+
# end
|
497
476
|
|
498
|
-
def closeDot dotFile, twb
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
end
|
477
|
+
# def closeDot dotFile, twb
|
478
|
+
# dotFile.puts ' '
|
479
|
+
# dotFile.puts '// -------------------------------------------------------------'
|
480
|
+
# dotFile.puts ' '
|
481
|
+
# dotFile.puts ' subgraph cluster_1 {'
|
482
|
+
# # dotFile.puts ' color=white;'
|
483
|
+
# dotFile.puts ' style=invis;'
|
484
|
+
# # dotFile.puts ' border=0;'
|
485
|
+
# dotFile.puts ' node [border=blue];'
|
486
|
+
# dotFile.puts ' '
|
487
|
+
# dotFile.puts ' "" [style=invis]'
|
488
|
+
# dotFile.puts " \"Tableau Tools\\nCalculated Fields Map\\nWorkbook '#{twb}'\\n#{Time.new.ctime}\" [penwidth=0]"
|
489
|
+
# # dotFile.puts " \"Tableau Tools Workbook Calculated Fields Map\\n#{Time.new.ctime}\" -> \"\" [style=invis]"
|
490
|
+
# dotFile.puts ' '
|
491
|
+
# dotFile.puts ' }'
|
492
|
+
# dotFile.puts ' '
|
493
|
+
# dotFile.puts '}'
|
494
|
+
# dotFile.close
|
495
|
+
# end
|
517
496
|
|
518
497
|
|
519
|
-
def renderDot twb, dot, format
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
end
|
498
|
+
# def renderDot twb, dot, format
|
499
|
+
# imageType = '-T' + format
|
500
|
+
# imageFile = './ttdoc/' + twb + @@processName + 'Graph.' + format
|
501
|
+
# imageParam = '-o"' + imageFile + '"'
|
502
|
+
# emit "system #{@@gvDotLocation} #{imageType} #{imageParam} \"#{dot}\""
|
503
|
+
# system "#{@@gvDotLocation} #{imageType} #{imageParam} \"#{dot}\""
|
504
|
+
# emit " - #{imageFile}"
|
505
|
+
# @imageFiles << imageFile
|
506
|
+
# return imageFile
|
507
|
+
# end
|
529
508
|
|
530
509
|
end # class
|
531
510
|
|