twb 2.2.1 → 3.7.2
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/twb.rb +13 -1
- data/lib/twb/action.rb +5 -1
- data/lib/twb/analysis/AnnotatedFieldsCSVEmitter.rb +3 -0
- data/lib/twb/analysis/CalculatedFields/CalculatedFieldsAnalyzer.rb +276 -287
- data/lib/twb/analysis/CalculatedFields/MarkdownEmitter.rb +48 -34
- data/lib/twb/analysis/DataSources/DataSourceFieldsCSVEmitter.rb +103 -103
- data/lib/twb/analysis/DataSources/googlesheetdatasourcesanalyzer.rb +79 -0
- data/lib/twb/analysis/DocumentedFieldsMarkdownEmitter.rb +1 -1
- data/lib/twb/analysis/Sheets/sheetfieldsanalyzer.rb +82 -0
- data/lib/twb/analysis/Sheets/sheetfiltersanalyzer.rb +214 -0
- data/lib/twb/calculatedfield.rb +20 -5
- data/lib/twb/codedfield.rb +87 -0
- data/lib/twb/columnfield.rb +21 -2
- data/lib/twb/connection.rb +33 -0
- data/lib/twb/dashboard.rb +5 -1
- data/lib/twb/datasource.rb +131 -20
- data/lib/twb/dbfield.rb +4 -0
- data/lib/twb/field.rb +5 -1
- data/lib/twb/fieldcalculation.rb +134 -78
- data/lib/twb/localfield.rb +5 -1
- data/lib/twb/mappedfield.rb +5 -1
- data/lib/twb/metadatafield.rb +5 -1
- data/lib/twb/storyboard.rb +5 -1
- data/lib/twb/tabclass.rb +71 -0
- data/lib/twb/tabtest.rb +31 -0
- data/lib/twb/tabtool.rb +63 -0
- data/lib/twb/twbcodedfield.rb +87 -0
- data/lib/twb/util/cypher.rb +112 -0
- data/lib/twb/util/cypherpython.rb +128 -0
- data/lib/twb/util/docprep.rb +46 -0
- data/lib/twb/util/fielddomainloader.rb +108 -0
- data/lib/twb/util/gml.rb +144 -0
- data/lib/twb/util/gmledge.rb +73 -0
- data/lib/twb/util/graph.rb +30 -0
- data/lib/twb/util/graphedge.rb +8 -9
- data/lib/twb/util/graphnode.rb +46 -29
- data/lib/twb/util/tabgraph.rb +30 -0
- data/lib/twb/window.rb +5 -1
- data/lib/twb/workbook.rb +18 -5
- data/lib/twb/worksheet.rb +5 -1
- data/test/fieldAliases.rb +10 -0
- data/test/testFieldAliases.rb +65 -0
- data/test/testFieldDomainLoaded.rb +14 -0
- data/test/testFieldDomainLoader.rb +131 -0
- metadata +22 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fd921c477a3ab3ef1de872935a8057bd4fd5ea27
|
4
|
+
data.tar.gz: de2bc297a944a544bb0f251ac5cc9f972e1f763c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 46becdab0ca9837b76ddd7ffc46e26433ab130bda120c5150ddce3575da04e80309874dfef9ca69531556b6185b46131889b62fe3f0a05e58b341be83ef04d56
|
7
|
+
data.tar.gz: 41f15a08b3b9971c4ca237ea5a166c212488c6e13a5ee3ca59613b8894d7b23d619e9d54fbc3c41280b4fc543c9109af1985befb92a7b42dc2bea00dc14f865a
|
data/lib/twb.rb
CHANGED
@@ -13,6 +13,8 @@
|
|
13
13
|
# You should have received a copy of the GNU General Public License
|
14
14
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
15
|
|
16
|
+
require_relative 'twb/tabclass'
|
17
|
+
require_relative 'twb/tabtool'
|
16
18
|
require_relative 'twb/dashboard'
|
17
19
|
require_relative 'twb/datasource'
|
18
20
|
require_relative 'twb/docdashboard'
|
@@ -26,6 +28,7 @@ require_relative 'twb/worksheet'
|
|
26
28
|
require_relative 'twb/action'
|
27
29
|
require_relative 'twb/columnfield.rb'
|
28
30
|
require_relative 'twb/calculatedfield'
|
31
|
+
require_relative 'twb/codedfield'
|
29
32
|
require_relative 'twb/fieldcalculation'
|
30
33
|
require_relative 'twb/docdashboardimagevert'
|
31
34
|
require_relative 'twb/docdashboardwebvert'
|
@@ -33,9 +36,15 @@ require_relative 'twb/util/twbDashSheetDataDotBuilder'
|
|
33
36
|
require_relative 'twb/util/dotFileRenderer'
|
34
37
|
require_relative 'twb/util/htmllistcollapsible'
|
35
38
|
require_relative 'twb/util/xraydashboards'
|
39
|
+
require_relative 'twb/util/graph'
|
36
40
|
require_relative 'twb/util/graphnode'
|
37
41
|
require_relative 'twb/util/graphedge'
|
38
42
|
require_relative 'twb/util/graphedges'
|
43
|
+
require_relative 'twb/util/gml'
|
44
|
+
require_relative 'twb/util/cypher'
|
45
|
+
require_relative 'twb/util/cypherpython'
|
46
|
+
require_relative 'twb/util/fielddomainloader'
|
47
|
+
require_relative 'twb/util/docprep'
|
39
48
|
require_relative 'twb/analysis/documentedfieldsmarkdownemitter'
|
40
49
|
require_relative 'twb/analysis/annotatedfieldsCSVEmitter'
|
41
50
|
require_relative 'twb/analysis/calculatedfields/calculatedfieldsanalyzer'
|
@@ -43,10 +52,13 @@ require_relative 'twb/analysis/calculatedfields/markdownemitter'
|
|
43
52
|
require_relative 'twb/analysis/calculatedfields/csvemitter'
|
44
53
|
require_relative 'twb/analysis/datasources/DataSourceFieldsCSVEmitter'
|
45
54
|
require_relative 'twb/analysis/datasources/DataSourceTableFieldsCSVEmitter'
|
55
|
+
require_relative 'twb/analysis/datasources/googlesheetdatasourcesanalyzer'
|
46
56
|
require_relative 'twb/analysis/Sheets/WorksheetDataStructureCSVEmitter'
|
57
|
+
require_relative 'twb/analysis/Sheets/sheetfiltersanalyzer'
|
58
|
+
require_relative 'twb/analysis/Sheets/sheetfieldsanalyzer'
|
47
59
|
|
48
60
|
# Represents Tableau Workbooks and their contents.
|
49
61
|
#
|
50
62
|
module Twb
|
51
|
-
VERSION = '
|
63
|
+
VERSION = '3.7.2'
|
52
64
|
end
|
data/lib/twb/action.rb
CHANGED
@@ -18,7 +18,7 @@ require 'digest/md5'
|
|
18
18
|
|
19
19
|
module Twb
|
20
20
|
|
21
|
-
class Action
|
21
|
+
class Action < TabClass
|
22
22
|
|
23
23
|
# Notes
|
24
24
|
# Sources:
|
@@ -68,6 +68,10 @@ module Twb
|
|
68
68
|
return self
|
69
69
|
end
|
70
70
|
|
71
|
+
def id
|
72
|
+
@id ||= @id = @name.hash
|
73
|
+
end
|
74
|
+
|
71
75
|
def process
|
72
76
|
|
73
77
|
end
|
@@ -37,6 +37,7 @@ module Analysis
|
|
37
37
|
@@csvFileName = @@csvFileType + '.csv'
|
38
38
|
|
39
39
|
def initialize
|
40
|
+
# puts "AnnotatedFieldsCSVEmitter - initializing"
|
40
41
|
@csvFile = CSV.open(@@csvFileName,'w')
|
41
42
|
@csvFile << @@csvHeader
|
42
43
|
@csvRecords = Set.new
|
@@ -63,6 +64,7 @@ module Analysis
|
|
63
64
|
@twb = twb if twb.instance_of? Twb::Workbook
|
64
65
|
@twb = Twb::Workbook.new(twb) if twb.instance_of? String
|
65
66
|
raise ArgumentError.new("ERROR in Workbok processing: '#{twb}' must be a Workbook (class) or the name of a Workbook (String), is a #{twb.class} \n ") unless @twb.is_a? Twb::Workbook
|
67
|
+
# puts "Processing #{@twb.name}"
|
66
68
|
# --
|
67
69
|
dss = @twb.datasources
|
68
70
|
dss.each do |ds|
|
@@ -78,6 +80,7 @@ module Analysis
|
|
78
80
|
end # def processTwb twb
|
79
81
|
|
80
82
|
def recordField field
|
83
|
+
# puts "- #{field.uiname} :: #{field} ::: #{field.node}"
|
81
84
|
@fieldNum += 1
|
82
85
|
@lineNum = 0
|
83
86
|
field.comment.each do |line|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# calculatedfieldsanalyzer.rb - this Ruby script Copyright 2017 Christopher Gerrard
|
1
|
+
# calculatedfieldsanalyzer.rb - this Ruby script Copyright 2017, 2018 Christopher 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
|
@@ -17,14 +17,16 @@ require 'nokogiri'
|
|
17
17
|
require 'twb'
|
18
18
|
require 'set'
|
19
19
|
require 'csv'
|
20
|
-
require 'logger'
|
21
20
|
|
22
21
|
module Twb
|
23
22
|
module Analysis
|
24
23
|
|
25
|
-
class CalculatedFieldsAnalyzer
|
24
|
+
class CalculatedFieldsAnalyzer < Twb::Util::Graph
|
26
25
|
|
27
|
-
|
26
|
+
include TabTool
|
27
|
+
|
28
|
+
attr_reader :calculatedFieldsCount, :formulaFieldsCount, :dataFiles
|
29
|
+
attr_accessor :ttdocdir
|
28
30
|
|
29
31
|
@@ttlogfile = 'CalculatedFieldsAnalyzer.ttlog'
|
30
32
|
@@gvDotLocation = 'C:\\tech\\graphviz\\Graphviz2.38\\bin\\dot.exe'
|
@@ -40,12 +42,20 @@ module Analysis
|
|
40
42
|
'Class',
|
41
43
|
'Scope Isolation',
|
42
44
|
'Formula Length',
|
43
|
-
'Formula Code',
|
44
45
|
'Formula',
|
46
|
+
'Formula (tech)',
|
45
47
|
'Formula Comments',
|
46
48
|
'Formula LOD?'
|
47
49
|
]
|
48
50
|
|
51
|
+
@@calcLinesCSVFileName = 'TwbCalculatedFieldFormulaLines.csv'
|
52
|
+
@@calcLinesCSVFileHeader = ['Calc Field #',
|
53
|
+
'Workbook', 'Workbook Dir',
|
54
|
+
'Data Source', 'Data Source Caption', 'Data Source Name (tech)',
|
55
|
+
'Field Name', 'Field Caption', 'Field Name (tech)',
|
56
|
+
'Formula', 'Formula Line #', 'Formula Line'
|
57
|
+
]
|
58
|
+
|
49
59
|
@@formFieldsCSVFileName = 'TwbFormulaFields.csv'
|
50
60
|
@@formFieldsCSVFileHeader = ['Rec #',
|
51
61
|
'Workbook', 'Workbook Dir',
|
@@ -71,258 +81,234 @@ module Analysis
|
|
71
81
|
DOTHEADER
|
72
82
|
|
73
83
|
def initialize
|
84
|
+
#-- Logging setup --
|
85
|
+
@ttdocdir = 'ttdoc'
|
86
|
+
# logfile = docFile(@@ttlogfile)
|
87
|
+
# @logger = Logger.new(logfile)
|
88
|
+
# @logger.level = Logger::DEBUG
|
89
|
+
emit ' Initializing CalculatedFieldsAnalyzer'
|
90
|
+
#-- CSV records collectors
|
74
91
|
@csvCalculatedFields = []
|
75
92
|
@csvFormulaFields = []
|
76
|
-
|
77
|
-
# @testFile.puts @@calcFieldsCSVFileHeader.inspect
|
78
|
-
#-- Logging setup --
|
79
|
-
@logger = Logger.new(@@ttlogfile)
|
80
|
-
@logger.level = Logger::DEBUG
|
93
|
+
@csvFormulaLines = []
|
81
94
|
#-- Counters setup --
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
95
|
+
@twbCount = 0
|
96
|
+
@calculatedFieldsCount = 0
|
97
|
+
@formulaFieldsCount = 0
|
98
|
+
#--
|
99
|
+
@referencedFields = SortedSet.new
|
100
|
+
#--
|
101
|
+
emit "init CSV #{@@calcFieldsCSVFileName}"
|
102
|
+
@csvCF = File.open(docFile(@@calcFieldsCSVFileName), 'w')
|
103
|
+
@csvCF.puts @@calcFieldsCSVFileHeader.to_csv
|
104
|
+
# @csvCF.close
|
105
|
+
#--
|
106
|
+
emit "init CSV #{@@calcLinesCSVFileName}"
|
107
|
+
@csvCFLs = File.open(docFile(@@calcLinesCSVFileName), 'w')
|
108
|
+
@csvCFLs.puts @@calcLinesCSVFileHeader.to_csv
|
109
|
+
# @csvCFLs.close
|
110
|
+
#--
|
111
|
+
emit "init CSV #{@@formFieldsCSVFileName}"
|
112
|
+
@csvFF = File.open(docFile(@@formFieldsCSVFileName), 'w')
|
113
|
+
@csvFF.puts @@formFieldsCSVFileHeader.to_csv
|
114
|
+
# @csvFF.close
|
115
|
+
#--
|
116
|
+
@dataFiles = { @@calcFieldsCSVFileName => 'Calculated Fields & their Formulas',
|
117
|
+
@@calcLinesCSVFileName => 'Calculated Fields & individual Formula Lines',
|
118
|
+
@@formFieldsCSVFileName => 'Fields referenced in Formulas'
|
119
|
+
}
|
120
|
+
#--
|
121
|
+
@localEmit = false
|
122
|
+
@imageFiles = []
|
123
|
+
end
|
124
|
+
|
125
|
+
def initDocDir
|
126
|
+
return if $ttdocdir.nil?
|
127
|
+
return if ''.eql? $ttdocdir
|
128
|
+
return if Dir.exists?($ttdocdir)
|
129
|
+
if File.exists? $ttdocdir
|
130
|
+
$ttdocdir = nil
|
131
|
+
return
|
132
|
+
end
|
133
|
+
Dir.mkdir $ttdocdir
|
93
134
|
end
|
94
135
|
|
95
|
-
def
|
96
|
-
|
97
|
-
|
98
|
-
|
136
|
+
def docFile name
|
137
|
+
@ttdocdir.nil? ? name : "#{@ttdocdir}\\#{name}"
|
138
|
+
end
|
139
|
+
|
140
|
+
def processTWB workbook
|
141
|
+
@twb = workbook.is_a?(String) ? Twb::Workbook.new(workbook) : workbook
|
142
|
+
throw Exception unless @twb.is_a? Twb::Workbook
|
143
|
+
emit "- Workbook: #{workbook}"
|
99
144
|
emit " version: #{@twb.version}"
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
# -- processing
|
145
|
+
@twbDir = @twb.dir #File.dirname(File.expand_path(workbook))
|
146
|
+
@edges = Set.new
|
147
|
+
#-- processing
|
104
148
|
dss = @twb.datasources
|
105
|
-
puts " # data sources: #{dss.length}"
|
106
|
-
twbRootFields = Set.new
|
149
|
+
# puts " # data sources: #{dss.length}"
|
150
|
+
@twbRootFields = Set.new
|
107
151
|
@twbFields = {}
|
152
|
+
@nodes = Set.new
|
108
153
|
dss.each do |ds|
|
109
|
-
puts "\t\t - #{ds.uiname} \t\t #{ds.calculatedFields.length}"
|
110
|
-
next if ds.Parameters? # don't process the Parameters data source
|
111
|
-
ds.
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
dsTechName = ds.name
|
116
|
-
dsCaption = ds.caption
|
117
|
-
dsName = ds.uiname
|
118
|
-
dsID = dsTechName + ':::' + dsName
|
119
|
-
emit "\n\n "
|
120
|
-
emit "======================================================"
|
121
|
-
emit "======================================================"
|
122
|
-
emit "======= DATA SOURCE: #{ds.uiname} ====== "
|
123
|
-
emit "======================================================"
|
124
|
-
emit "======================================================\n\n "
|
125
|
-
dsGraphNode = Twb::Util::Graphnode.new(name: dsName, id: dsID, type: :TwbDataConnection, properties: {workbook: twbWithDir})
|
126
|
-
emit "\t dsgnode: #{dsGraphNode}"
|
127
|
-
fieldUINames = ds.fieldUINames
|
128
|
-
emit "ds.calculatedFields :: nil? #{ds.calculatedFields.nil?}"
|
129
|
-
end
|
154
|
+
# puts "\t\t - #{ds.uiname} \t\t #{ds.calculatedFields.length}"
|
155
|
+
next if ds.Parameters? # don't process the Parameters data source - Parameters' fields aren't Calculated fields for our purposes
|
156
|
+
# dataSourceNode = Twb::Util::Graphnode.new(name: ds.uiname, id: ds.id, type: ds, properties: {workbook: workbook})
|
157
|
+
# @nodes.add dataSourceNode
|
158
|
+
# ds.calculatedFields.each do |calcField|
|
159
|
+
# end
|
130
160
|
processDataSource ds
|
131
161
|
end
|
162
|
+
mapTwb
|
163
|
+
emitGml
|
164
|
+
@twbCount += 1
|
165
|
+
end
|
166
|
+
|
167
|
+
def emitGml
|
168
|
+
gml = Twb::Util::GML.new
|
169
|
+
gml.fileName = @twb.name
|
170
|
+
gml.nodes = @nodes
|
171
|
+
gml.edges = @edges
|
172
|
+
gml.render
|
132
173
|
end
|
133
174
|
|
134
175
|
def processDataSource ds
|
135
|
-
emit "Datasource: '#{ds.uiname}' -> #{ds.Parameters?}"
|
136
|
-
dsFields = {}
|
137
|
-
@twbFields[ds.uiname] = dsFields
|
138
|
-
# next if ds.Parameters? # don't process the Parameters data source
|
139
|
-
# it requires special handling, has different XML structure
|
140
|
-
#-- For tracking unreferenced (root) calculated fields = calculatedFields - referencedFields
|
141
|
-
calculatedFields = SortedSet.new
|
142
|
-
referencedFields = SortedSet.new
|
143
|
-
#--
|
144
|
-
dsTechName = ds.name
|
145
|
-
dsCaption = ds.caption
|
146
|
-
dsName = ds.uiname
|
147
|
-
dsID = dsTechName + ':::' + dsName
|
148
|
-
emit "\n\n "
|
149
|
-
emit "======================================================"
|
150
|
-
emit "======================================================"
|
151
176
|
emit "======= DATA SOURCE: #{ds.uiname} ====== "
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
177
|
+
dsNodes = Set.new
|
178
|
+
dsEdges = Set.new
|
179
|
+
dsFields = {}
|
180
|
+
@twbFields[ds.uiname] = dsFields
|
181
|
+
calculatedFields = SortedSet.new
|
182
|
+
fieldFormulaLines = []
|
183
|
+
referencedFields = SortedSet.new
|
184
|
+
dataSourceNode = Twb::Util::Graphnode.new(name: ds.uiname, id: ds.id, type: ds, properties: {workbook: @twb.name})
|
185
|
+
@nodes.add dataSourceNode
|
186
|
+
#-- process Calculatred Fields
|
158
187
|
ds.calculatedFields.each do |calcField|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
role = calcField.role
|
166
|
-
type = calcField.type
|
167
|
-
fieldID = fldTechName+'::'+dsName
|
168
|
-
calculatedFields.add fieldID
|
169
|
-
#--
|
170
|
-
dsFields[fldName] = calcField
|
171
|
-
srcGraphNode = Twb::Util::Graphnode.new(name: fldName, id: fieldID, type: :CalculatedField, properties: {:DataSource => dsName})
|
172
|
-
dsFieldEdge = Twb::Util::Graphedge.new(from: dsGraphNode, to: srcGraphNode, relationship: 'contains')
|
173
|
-
#--
|
174
|
-
emit "\t srfnode: #{srcGraphNode} "
|
175
|
-
emit "\t dsfedge: #{dsFieldEdge} "
|
176
|
-
edges.add dsFieldEdge
|
188
|
+
calculatedFields.add calcField.id
|
189
|
+
dsFields[calcField.uiname] = calcField
|
190
|
+
calcFieldNode = Twb::Util::Graphnode.new(name: calcField.uiname, id: calcField.id, type: calcField, properties: {:DataSource => ds.uiname})
|
191
|
+
@nodes.add calcFieldNode
|
192
|
+
dsFieldEdge = Twb::Util::Graphedge.new(from: dataSourceNode, to: calcFieldNode, relationship: 'contains')
|
193
|
+
@edges.add dsFieldEdge
|
177
194
|
calculation = calcField.calculation
|
178
|
-
emit "calculation: f? %8s -> %s " % [calculation.has_formula, calculation.formula ]
|
179
195
|
if calculation.has_formula
|
180
|
-
|
181
|
-
uiFormula = formulaFlat.gsub(' XX ',' ')
|
182
|
-
formulaLOD = formulaFlat.upcase =~ /^[ ]*{[ ]*(INCLUDE|FIXED|EXCLUDE)/
|
183
|
-
resolvedFields = calculation.resolvedFields
|
184
|
-
resolvedFields.each do |rf|
|
185
|
-
emit "\tRESOLVED FLD: #{rf.inspect}"
|
186
|
-
calcFieldName = rf[:field]
|
187
|
-
if rf[:source].nil?
|
188
|
-
calcFieldRef = "[%s]" % [ calcFieldName ]
|
189
|
-
dispFieldRef = "[%s]" % [ ds.fieldUIName(calcFieldName) ]
|
190
|
-
else
|
191
|
-
remoteDS = @twb.datasource(rf[:source])
|
192
|
-
if remoteDS.nil?
|
193
|
-
calcFieldRef = "[DS_NOT_FOUND].[%s]" % [ calcFieldName ]
|
194
|
-
dispFieldRef = calcFieldRef
|
195
|
-
else
|
196
|
-
remoteDSName = remoteDS.uiname
|
197
|
-
remoteDSFld = remoteDS.fieldUIName(calcFieldName)
|
198
|
-
calcFieldRef = "[%s].[%s]" % [ rf[:source], calcFieldName ]
|
199
|
-
dispFieldRef = "[%s].[%s]" % [ remoteDSName, remoteDSFld ]
|
200
|
-
end # remoteDS.nil?
|
201
|
-
end # rf[:source].nil?
|
202
|
-
emit "\tcalcFieldRef: #{calcFieldRef}"
|
203
|
-
emit "\tdispFieldRef: #{dispFieldRef}"
|
204
|
-
uiFormula = uiFormula.gsub(calcFieldRef, dispFieldRef)
|
205
|
-
end # resolvedFields.each
|
196
|
+
#-- collect field formulas as single lines
|
206
197
|
@csvCalculatedFields.push [
|
207
198
|
@calculatedFieldsCount += 1,
|
208
|
-
twb,
|
209
|
-
twbDir,
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
role,
|
219
|
-
type,
|
199
|
+
@twb.name,
|
200
|
+
@twbDir,
|
201
|
+
ds.uiname,
|
202
|
+
ds.caption,
|
203
|
+
ds.name,
|
204
|
+
calcField.uiname,
|
205
|
+
calcField.caption,
|
206
|
+
calcField.name,
|
207
|
+
ds.name + '::' + calcField.name,
|
208
|
+
calcField.datatype,
|
209
|
+
calcField.role,
|
210
|
+
calcField.type,
|
220
211
|
calculation.class,
|
221
212
|
calculation.scopeIsolation,
|
222
213
|
calculation.formulaFlat.length,
|
223
|
-
calculation.
|
224
|
-
|
214
|
+
calculation.formulaResolved,
|
215
|
+
calculation.formulaFlat,
|
225
216
|
calculation.comments,
|
226
|
-
|
217
|
+
calculation.is_lod
|
227
218
|
]
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
if !calcFieldTable.nil?
|
268
|
-
tableID = calcFieldTable + ':::' + ds.uiname
|
269
|
-
tableName = "-[#{calcFieldTable}]-"
|
270
|
-
tableNode = Twb::Util::Graphnode.new(name: tableName, id: tableID, type: :DBTable, properties: properties)
|
271
|
-
fieldFieldEdge = Twb::Util::Graphedge.new(from: calcFieldNode, to: tableNode, relationship: 'is a field in')
|
272
|
-
edges.add fieldFieldEdge
|
273
|
-
fldToDsNode = tableNode
|
219
|
+
#-- collect individual formula lines
|
220
|
+
flnum = 0
|
221
|
+
emit "@@ FL: #{calcField.uiname}"
|
222
|
+
calculation.formulaResolvedLines.each do |fl|
|
223
|
+
emit "@@ FL: => '#{fl}'"
|
224
|
+
fieldFormulaLines.push [ @calculatedFieldsCount, # 'Calc Field #',
|
225
|
+
@twb.name, # 'Workbook',
|
226
|
+
@twbDir, # 'Workbook Dir',
|
227
|
+
ds.uiname, # 'Data Source',
|
228
|
+
ds.caption, # 'Data Source Caption',
|
229
|
+
ds.name, # 'Data Source Name (tech)',
|
230
|
+
calcField.uiname, # 'Field Name',
|
231
|
+
calcField.caption, # 'Field Caption',
|
232
|
+
calcField.name, # 'Field Name (tech)',
|
233
|
+
calcField.calculation.formulaFlatResolved, # 'Formula'
|
234
|
+
flnum += 1, # 'Formula Line #',
|
235
|
+
fl.start_with?(" ") ? "'#{fl}" : fl # 'Formula Line' - THIS IS A STUPID HACK NEEDED BECAUSE TABLEAU STRIPS LEADING BLANKS FROM CSV VALUES
|
236
|
+
]
|
237
|
+
end
|
238
|
+
#-- collect fields referenced in formula
|
239
|
+
calculation.calcFields.each do |rf|
|
240
|
+
emit " rf.name :'#{rf.name}'"
|
241
|
+
emit " rf.uiname:'#{rf.uiname}'"
|
242
|
+
properties = {'DataSource' => ds.uiname, 'DataSourceReference' => 'local', :source => rf}
|
243
|
+
refFieldNode = Twb::Util::Graphnode.new(name: rf.uiname, id: rf.id, type: rf.type, properties: properties)
|
244
|
+
@nodes.add refFieldNode
|
245
|
+
fieldFieldEdge = Twb::Util::Graphedge.new(from: calcFieldNode, to: refFieldNode, relationship: 'references')
|
246
|
+
@edges.add fieldFieldEdge
|
247
|
+
referencedFields.add rf.id
|
248
|
+
refFieldTable = ds.fieldTable(rf.name)
|
249
|
+
emit "refFieldTable.nil? : #{refFieldTable.nil?}"
|
250
|
+
unless refFieldTable.nil?
|
251
|
+
tableID = refFieldTable + ':::' + ds.uiname
|
252
|
+
tableName = "||#{refFieldTable}||"
|
253
|
+
tableNode = Twb::Util::Graphnode.new(name: tableName, id: tableID, type: :DBTable, properties: properties)
|
254
|
+
@nodes.add tableNode
|
255
|
+
fieldFieldEdge = Twb::Util::Graphedge.new(from: refFieldNode, to: tableNode, relationship: 'is a field in')
|
256
|
+
@edges.add fieldFieldEdge
|
257
|
+
# fldToDsNode = tableNode
|
274
258
|
end
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
259
|
+
@csvFormulaFields.push [
|
260
|
+
@formulaFieldsCount += 1,
|
261
|
+
@twb.name,
|
262
|
+
@twbDir,
|
263
|
+
ds.uiname,
|
264
|
+
calcField.uiname,
|
265
|
+
calculation.formulaFlat,
|
266
|
+
calculation.formulaResolved,
|
267
|
+
rf.name,
|
268
|
+
rf.uiname,
|
269
|
+
rf.id,
|
270
|
+
refFieldTable
|
271
|
+
]
|
288
272
|
end # resolvedFields.each do
|
289
273
|
end # if calculation.has_formula
|
290
274
|
end # ds.calculatedFields.each
|
291
275
|
|
292
276
|
dsRootFields = calculatedFields - referencedFields
|
293
277
|
@referencedFields.merge referencedFields
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
emit "--\nReferenced Fields\n-----------------"
|
298
|
-
referencedFields.each { |f| emit f }
|
299
|
-
emit "--\nDS Root Fields\n-----------------"
|
300
|
-
dsRootFields.each { |f| emit f }
|
301
|
-
emit "--"
|
302
|
-
# --
|
303
|
-
twbRootFields.merge dsRootFields
|
304
|
-
@twbCount += 1
|
305
|
-
mapTwb twb, edges, twbRootFields
|
306
|
-
graphEdges twb, edges
|
278
|
+
@twbRootFields.merge dsRootFields
|
279
|
+
cypher @twb.name
|
280
|
+
cypherPy @twb.name
|
307
281
|
emit "#######################"
|
308
|
-
#--
|
309
|
-
|
310
|
-
|
282
|
+
#-- record calculated fields
|
283
|
+
# @csvCF = File.open(docFile(@@calcFieldsCSVFileName), 'a')
|
284
|
+
# @csvCF.puts @@calcFieldsCSVFileHeader.to_csv
|
285
|
+
emit "@@ record calculated fields ds: #{ds.uiname}"
|
286
|
+
# @csvCF.open('a')
|
311
287
|
@csvCalculatedFields.each do |r|
|
312
|
-
csvCF.puts r.to_csv
|
288
|
+
@csvCF.puts r.to_csv
|
313
289
|
end
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
290
|
+
#-- record individual formula lines
|
291
|
+
# @csvCFLs = File.open(docFile(@@calcLinesCSVFileName), 'a')
|
292
|
+
# @csvCFLs.puts @@calcLinesCSVFileHeader.to_csv
|
293
|
+
emit "@@ individual formula lines ds: #{ds.uiname}"
|
294
|
+
fieldFormulaLines.each do |ffl|
|
295
|
+
emit "@@@@ FFLA: #{ffl}"
|
296
|
+
emit "@@@@ FFLB: #{ffl.to_csv}"
|
297
|
+
emit "@@@@ FFLc: "
|
298
|
+
@csvCFLs.puts ffl.to_csv
|
299
|
+
end
|
300
|
+
#-- record formula-referenced fields
|
301
|
+
# @csvFF = File.open(docFile(@@formFieldsCSVFileName), 'a')
|
302
|
+
# @csvFF.puts @@formFieldsCSVFileHeader.to_csv
|
303
|
+
emit "@@ formula-referenced fields ds: #{ds.uiname}"
|
318
304
|
@csvFormulaFields.each do |r|
|
319
|
-
csvFF.puts r.to_csv
|
305
|
+
@csvFF.puts r.to_csv
|
320
306
|
end
|
321
|
-
csvFF.close
|
322
307
|
#--
|
323
308
|
return @imageFiles
|
324
309
|
end # def processDataSource
|
325
310
|
|
311
|
+
|
326
312
|
def emitCalcfield calcField
|
327
313
|
emit "\t FIELD cap :: #{calcField.caption} "
|
328
314
|
emit "\t tname:: #{calcField.name}"
|
@@ -330,9 +316,12 @@ DOTHEADER
|
|
330
316
|
emit "\t formula:: #{calculation.formulaFlat}"
|
331
317
|
end
|
332
318
|
|
333
|
-
def mapTwb
|
334
|
-
|
335
|
-
|
319
|
+
def mapTwb
|
320
|
+
twb = @twb.name
|
321
|
+
rootFields = @twbRootFields
|
322
|
+
dotStuff = initDot twb
|
323
|
+
dotFile = dotStuff[:file]
|
324
|
+
dotFileName = dotStuff[:name]
|
336
325
|
dotFile.puts "\n // subgraph cluster_1 {"
|
337
326
|
dotFile.puts " // color= grey;"
|
338
327
|
dotFile.puts ""
|
@@ -340,85 +329,94 @@ DOTHEADER
|
|
340
329
|
# this two step process coalesces the edges into a unique set, avoiding duplicating the dot
|
341
330
|
# file entries, and can be shrunk when graph edges expose the bits necessary for management by Set
|
342
331
|
emit "\n========================\nLoading Edges\n========================\n From DC? Referenced? Edge \n %s %s %s" % ['--------', '-----------', '-'*45]
|
343
|
-
edges.each do |e|
|
332
|
+
@edges.each do |e|
|
344
333
|
# don't want to emit edge which is from a Data Connection to a
|
345
334
|
# Calculated Field which is also referenced by another calculated field
|
346
335
|
isFromDC = e.from.type == :TwbDataConnection
|
347
336
|
isRefField = @referencedFields.include?(e.to.id)
|
348
337
|
edgesAsStrings.add(e.dot) unless isFromDC && isRefField
|
338
|
+
# emit " ES #{e.dot}"
|
339
|
+
# emit " ES from #{e.from}"
|
340
|
+
# emit " ES to #{e.to}"
|
349
341
|
end
|
350
342
|
emit "------------------------\n "
|
351
343
|
edgesAsStrings.each do |es|
|
352
344
|
dotFile.puts " #{es}"
|
353
|
-
emit " #{es}"
|
354
345
|
end
|
355
346
|
emit "========================\n "
|
356
347
|
dotFile.puts ""
|
357
348
|
dotFile.puts " // }"
|
358
|
-
dotFile.puts "\n\n // 4--------------------------------------------------------------------"
|
359
|
-
|
360
|
-
|
361
|
-
edges.each do |e|
|
362
|
-
nodes.add e.from.dotLabel
|
363
|
-
nodes.add e.to.dotLabel
|
364
|
-
end
|
365
|
-
nodes.each do |n|
|
366
|
-
dotFile.puts n
|
349
|
+
dotFile.puts "\n\n // 4 NODES --------------------------------------------------------------------"
|
350
|
+
@nodes.each do |n|
|
351
|
+
dotFile.puts n.dotLabel
|
367
352
|
end
|
368
353
|
dotFile.puts "\n\n // 5--------------------------------------------------------------------"
|
369
|
-
emitTypes(
|
370
|
-
rankRootFields( dotFile, rootFields )
|
354
|
+
emitTypes( dotFile )
|
371
355
|
closeDot( dotFile, twb )
|
372
|
-
#
|
373
|
-
# renderPdf(twb.name,dotFileName)
|
356
|
+
emit "Rendering DOT file - #{twb}"
|
374
357
|
renderDot(twb,dotFileName,'pdf')
|
375
358
|
renderDot(twb,dotFileName,'png')
|
376
359
|
renderDot(twb,dotFileName,'svg')
|
377
|
-
emitEdges
|
360
|
+
# emitEdges
|
378
361
|
end
|
379
362
|
|
363
|
+
def cypher twbName
|
364
|
+
cypher = Twb::Util::Cypher.new
|
365
|
+
cypher.fileName = "#{twbName}.calcFields"
|
366
|
+
cypher.nodes = @nodes
|
367
|
+
cypher.edges = @edges
|
368
|
+
cypher.render
|
369
|
+
end
|
380
370
|
|
381
|
-
def
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
edges
|
386
|
-
|
387
|
-
cypherCode.add edge.to.cypherCreate
|
388
|
-
cypherCode.add edge.cypherCreate
|
389
|
-
end
|
390
|
-
cypherCode.each do |cc|
|
391
|
-
graphFile.puts cc
|
392
|
-
end
|
393
|
-
graphFile.puts "\nreturn *"
|
394
|
-
graphFile.close unless graphFile.nil?
|
395
|
-
@imageFiles << File.basename(graphFile)
|
371
|
+
def cypherPy twbName
|
372
|
+
cypher = Twb::Util::CypherPython.new
|
373
|
+
cypher.fileName = "#{twbName}.calcFields"
|
374
|
+
cypher.nodes = @nodes
|
375
|
+
cypher.edges = @edges
|
376
|
+
cypher.render
|
396
377
|
end
|
397
378
|
|
398
|
-
def
|
379
|
+
# def graphEdges twb
|
380
|
+
# # graphFile = File.new(twb + '.cypher', 'w')
|
381
|
+
# # # graphFile.puts "OKEY DOKE, graphing away"
|
382
|
+
# # cypherCode = Set.new
|
383
|
+
# # @edges.each do |edge|
|
384
|
+
# # cypherCode.add edge.from.cypherCreate
|
385
|
+
# # cypherCode.add edge.to.cypherCreate
|
386
|
+
# # cypherCode.add edge.cypherCreate
|
387
|
+
# # end
|
388
|
+
# # cypherCode.each do |cc|
|
389
|
+
# # graphFile.puts cc
|
390
|
+
# # end
|
391
|
+
# # graphFile.puts "\nreturn *"
|
392
|
+
# # graphFile.close unless graphFile.nil?
|
393
|
+
# # @imageFiles << File.basename(graphFile)
|
394
|
+
# end
|
395
|
+
|
396
|
+
def emitEdges
|
399
397
|
emit " %-15s %s" % ['type', 'Edge']
|
400
398
|
emit " %-15s %s" % ['-'*15, '-'*35]
|
401
|
-
edges.each do |edge|
|
399
|
+
@edges.each do |edge|
|
402
400
|
emit " %-15s %s" % [edge.from.type, edge.from]
|
403
401
|
emit " %-15s %s" % [edge.to.type, edge.to]
|
404
402
|
emit "\n "
|
405
403
|
end
|
406
404
|
end
|
407
405
|
|
408
|
-
def emitTypes
|
406
|
+
def emitTypes dotFile
|
409
407
|
typedNodes = {}
|
410
408
|
dotFile.puts "\n\n // 2--------------------------------------------------------------------"
|
411
|
-
edges.each do |edge|
|
409
|
+
@edges.each do |edge|
|
412
410
|
emit " EDGE :: #{edge}"
|
413
411
|
loadNodeType typedNodes, edge.from
|
414
412
|
loadNodeType typedNodes, edge.to
|
415
413
|
end
|
416
414
|
typedNodes.each do |type, nodes|
|
417
|
-
emit "+++++++++ typedNodes of '#{type}'' "
|
418
|
-
nodes.each do |node|
|
419
|
-
|
420
|
-
end
|
421
|
-
rankSame(dotFile, type, nodes) unless type == :CalculatedField
|
415
|
+
# emit "+++++++++ typedNodes of '#{type}'' "
|
416
|
+
# nodes.each do |node|
|
417
|
+
# emit " -n- #{node}"
|
418
|
+
# end
|
419
|
+
rankSame(dotFile, type, nodes) unless type.eql? 'CalculatedField' # == :CalculatedField
|
422
420
|
end
|
423
421
|
# labelTypes dotFile, edges
|
424
422
|
end
|
@@ -429,12 +427,17 @@ DOTHEADER
|
|
429
427
|
set[type].add node
|
430
428
|
end
|
431
429
|
|
430
|
+
@@unrankedTypes = ['CalculationField']
|
432
431
|
def rankSame dotFile, type, nodes
|
432
|
+
return if @@unrankedTypes.include? type.to_s
|
433
|
+
@lines = SortedSet.new
|
434
|
+
nodes.each do |node|
|
435
|
+
@lines << node.id
|
436
|
+
end
|
433
437
|
dotFile.puts "\n // '#{type}' --------------------------------------------------------------------"
|
434
438
|
dotFile.puts "\n {rank=same "
|
435
|
-
|
436
|
-
|
437
|
-
dotFile.puts " \"#{node.id}\""
|
439
|
+
@lines.each do |line|
|
440
|
+
dotFile.puts " \"#{line}\""
|
438
441
|
end
|
439
442
|
dotFile.puts " }"
|
440
443
|
end
|
@@ -443,15 +446,16 @@ DOTHEADER
|
|
443
446
|
dotFile.puts "\n // Unreferenced (root) Calculated Fields -----------------------------------------"
|
444
447
|
dotFile.puts "\n {rank=same "
|
445
448
|
dsRootFields.each do |rf|
|
449
|
+
emit "ROOT FIELD: #{rf.class} :: #{rf}"
|
446
450
|
dotFile.puts " \"#{rf}\""
|
447
451
|
end
|
448
452
|
dotFile.puts " }"
|
449
453
|
end
|
450
454
|
|
451
455
|
|
452
|
-
def labelTypes dotFile
|
456
|
+
def labelTypes dotFile
|
453
457
|
fromTos = Set.new
|
454
|
-
edges.each do |edge|
|
458
|
+
@edges.each do |edge|
|
455
459
|
# fromTos.add "\"Alien Data Source\" -> \"Alien Data Source\""
|
456
460
|
fromTos.add "\"#{edge.from.type}\""
|
457
461
|
fromTos.add "\"#{edge.to.type}\""
|
@@ -467,26 +471,11 @@ DOTHEADER
|
|
467
471
|
dotFile.puts ' }'
|
468
472
|
end
|
469
473
|
|
470
|
-
|
471
|
-
def emit(local=@localEmit, stuff)
|
472
|
-
# puts "\nstuff.class #{stuff.class} :: #{stuff}" if local
|
473
|
-
if stuff.is_a? String then
|
474
|
-
lines = stuff.split(/\n/)
|
475
|
-
lines.each do |line|
|
476
|
-
@logger.debug "#{@emitPrefix}#{line}"
|
477
|
-
puts "#{@emitPrefix}#{line}" if local
|
478
|
-
end
|
479
|
-
else
|
480
|
-
@logger.debug "#{@emitPrefix}#{stuff}"
|
481
|
-
puts "#{@emitPrefix}#{stuff}" if local
|
482
|
-
end
|
483
|
-
end
|
484
|
-
|
485
|
-
|
486
474
|
def initDot twb
|
487
|
-
|
475
|
+
dotFileName = docFile("#{twb}#{@@processName}.dot")
|
476
|
+
dotFile = File.open(dotFileName,'w')
|
488
477
|
dotFile.puts @@dotHeader
|
489
|
-
return dotFile
|
478
|
+
return {:file => dotFile, :name => dotFileName}
|
490
479
|
end
|
491
480
|
|
492
481
|
def closeDot dotFile, twb
|
@@ -511,12 +500,12 @@ DOTHEADER
|
|
511
500
|
|
512
501
|
|
513
502
|
def renderDot twb, dot, format
|
514
|
-
emit "Rendering DOT file\n - #{twb}\n - #{dot}\n - #{format}"
|
515
503
|
imageType = '-T' + format
|
516
|
-
imageFile = twb + @@processName + 'Graph.' + format
|
517
|
-
imageParam = '-o' + imageFile
|
518
|
-
emit "system
|
519
|
-
#
|
504
|
+
imageFile = './ttdoc/' + twb + @@processName + 'Graph.' + format
|
505
|
+
imageParam = '-o"' + imageFile + '"'
|
506
|
+
emit "system #{@@gvDotLocation} #{imageType} #{imageParam} \"#{dot}\""
|
507
|
+
system "#{@@gvDotLocation} #{imageType} #{imageParam} \"#{dot}\""
|
508
|
+
emit " - #{imageFile}"
|
520
509
|
@imageFiles << imageFile
|
521
510
|
return imageFile
|
522
511
|
end
|