twb 4.9.3 → 5.2.0
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 +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
|