twb 4.9.4 → 4.9.9
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 +2 -2
- data/lib/twb/analysis/datasources/datasourcefilesanalyzer.rb +1 -1
- data/lib/twb/analysis/sheets/dashboardsummarizer.rb +94 -0
- data/lib/twb/analysis/workbooksummaryanalyzer.rb +2 -1
- data/lib/twb/util/cypher.rb +1 -1
- data/lib/twb/util/gml.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b285e90d1a99aa8de5a3613e00f03e634611643cac0ede6360a67a6f13f66f50
|
4
|
+
data.tar.gz: 32ef66741caea4210c0ca8959c001046a6884799a2bd544974fd155bc86fe625
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2ba95c4825210b53050e39cd172672456c46ff38e22dfabd0b1dcfe5d63a4e1ba0761295a92eedae92e744e34197c2a70625fdd36cddc284b874b66fe07bb18f
|
7
|
+
data.tar.gz: d78f2d12dd031a66ce686ad9bc2ca2cd5546d509ef049a7e129f58f7a0da00deddaa1860bba8946cf8ff7cbe5ef5d255d9b2dbda48a25acd77fd75f387803882
|
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 = '4.9.
|
83
|
+
VERSION = '4.9.9'
|
84
84
|
end
|
@@ -298,8 +298,8 @@ DOTHEADER
|
|
298
298
|
@referencedFields.merge referencedFields
|
299
299
|
@twbRootFields.merge dsRootFields
|
300
300
|
if @doGraph
|
301
|
-
cypher @twb.name
|
302
|
-
cypherPy @twb.name
|
301
|
+
# cypher @twb.name
|
302
|
+
# cypherPy @twb.name
|
303
303
|
end
|
304
304
|
emit "#######################"
|
305
305
|
#--
|
@@ -71,7 +71,7 @@ module DataSources
|
|
71
71
|
fileDir = File.dirname fqFileName
|
72
72
|
modtime = File.mtime fqFileName
|
73
73
|
size = File.size fqFileName
|
74
|
-
data = [
|
74
|
+
data = [@recNum+=1, twbName, twb.dir, ds.uiname, fileName, fileDir, modtime, size]
|
75
75
|
@csvFile << data
|
76
76
|
end
|
77
77
|
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/util/cypher.rb
CHANGED
data/lib/twb/util/gml.rb
CHANGED
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.9.
|
4
|
+
version: 4.9.9
|
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-05-04 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
|
@@ -82,6 +83,7 @@ files:
|
|
82
83
|
- lib/twb/analysis/documentedfieldscsvemitter.rb
|
83
84
|
- lib/twb/analysis/documentedfieldsmarkdownemitter.rb
|
84
85
|
- lib/twb/analysis/sheets/analyzedashboardsheets.rb
|
86
|
+
- lib/twb/analysis/sheets/dashboardsummarizer.rb
|
85
87
|
- lib/twb/analysis/sheets/dashsheetsanalyzer.rb
|
86
88
|
- lib/twb/analysis/sheets/sheetfieldsanalyzer.rb
|
87
89
|
- lib/twb/analysis/sheets/sheetfiltersanalyzer.rb
|