twb 4.9.3 → 5.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/tfl/Flow.rb +311 -0
- data/lib/twb.rb +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 +171 -98
- data/lib/twb/util/cypher.rb +11 -4
- data/lib/twb/util/gml.rb +1 -1
- data/lib/twb/util/graphnode.rb +1 -1
- data/test/testFieldCalculation.rb +10 -6
- metadata +6 -2
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
data/lib/twb/util/graphnode.rb
CHANGED
@@ -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.2.0
|
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-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: creek
|
@@ -58,14 +58,17 @@ 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
|
64
65
|
- lib/twb/analysis/calculatedfields/calculatedfieldsanalyzer.rb
|
65
66
|
- lib/twb/analysis/calculatedfields/csvemitter.rb
|
67
|
+
- lib/twb/analysis/calculatedfields/dotanalyzer.rb
|
66
68
|
- lib/twb/analysis/calculatedfields/fieldsaliasesanalyzer.rb
|
67
69
|
- lib/twb/analysis/calculatedfields/groupfieldsanalyzer.rb
|
68
70
|
- lib/twb/analysis/calculatedfields/markdownemitter.rb
|
71
|
+
- lib/twb/analysis/calculatedfields/t.rb
|
69
72
|
- lib/twb/analysis/datasources/categoricalcolorcodinganalyzer.rb
|
70
73
|
- lib/twb/analysis/datasources/datasourcefieldsanalyzer.rb
|
71
74
|
- lib/twb/analysis/datasources/datasourcefieldscsvemitter.rb
|
@@ -82,6 +85,7 @@ files:
|
|
82
85
|
- lib/twb/analysis/documentedfieldscsvemitter.rb
|
83
86
|
- lib/twb/analysis/documentedfieldsmarkdownemitter.rb
|
84
87
|
- lib/twb/analysis/sheets/analyzedashboardsheets.rb
|
88
|
+
- lib/twb/analysis/sheets/dashboardsummarizer.rb
|
85
89
|
- lib/twb/analysis/sheets/dashsheetsanalyzer.rb
|
86
90
|
- lib/twb/analysis/sheets/sheetfieldsanalyzer.rb
|
87
91
|
- lib/twb/analysis/sheets/sheetfiltersanalyzer.rb
|