twb 3.7.5 → 3.9.3
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 +4 -1
- data/lib/twb/analysis/CalculatedFields/CalculatedFieldsAnalyzer.rb +355 -337
- data/lib/twb/analysis/CalculatedFields/MarkdownEmitter.rb +6 -4
- data/lib/twb/analysis/CalculatedFields/groupfieldsanalyzer.rb +93 -0
- data/lib/twb/analysis/DataSources/googlesheetdatasourcesanalyzer.rb +18 -9
- data/lib/twb/analysis/Sheets/analyzeDashboardSheets.rb +50 -0
- data/lib/twb/analysis/Sheets/dashsheetsanalyzer.rb +75 -0
- data/lib/twb/analysis/Sheets/sheetfieldsanalyzer.rb +20 -6
- data/lib/twb/analysis/Sheets/sheetfiltersanalyzer.rb +210 -52
- data/lib/twb/calculatedfield.rb +42 -4
- data/lib/twb/codedfield.rb +14 -13
- data/lib/twb/datasource.rb +4 -0
- data/lib/twb/tabtool.rb +60 -44
- data/lib/twb/util/fielddomainloader.rb +3 -1
- data/lib/twb/worksheet.rb +16 -1
- metadata +5 -2
@@ -20,14 +20,17 @@ module Analysis
|
|
20
20
|
module CalculatedFields
|
21
21
|
|
22
22
|
class MarkdownEmitter
|
23
|
+
include TabTool
|
23
24
|
|
24
25
|
attr_reader :docFileName
|
25
26
|
|
26
27
|
def initialize
|
27
|
-
|
28
|
+
init
|
29
|
+
@funcdoc = {:class=>self.class, :blurb=>'Generate Markdown doc for Calculated Fields.', :description=>'Creates a Markdown file documenting the Calculated Fields for an individual Workbook.',}
|
30
|
+
@metrics = {}
|
28
31
|
end
|
29
32
|
|
30
|
-
def
|
33
|
+
def processTWB twb
|
31
34
|
# twb = File.basename(twb)
|
32
35
|
@twb = twb #Twb::Workbook.new twb
|
33
36
|
@docFileName = './ttdoc/' + @twb.name + '.CalculatedFields.md'
|
@@ -35,8 +38,6 @@ module CalculatedFields
|
|
35
38
|
@docFile.puts "# #{twb.name}"
|
36
39
|
dsNames = @twb.datasourceUINames
|
37
40
|
@docFile.puts "#{dsNames.length} Data Sources"
|
38
|
-
|
39
|
-
|
40
41
|
@twb.datasources.each do |ds|
|
41
42
|
@docFile.puts "## #{ds.uiname}"
|
42
43
|
@docFile.puts "__has #{ds.calculatedFields.length} calculated fields__\n "
|
@@ -77,6 +78,7 @@ module CalculatedFields
|
|
77
78
|
end
|
78
79
|
@docFile.puts "counted #{cnt} calculated fields\n "
|
79
80
|
end # twb.datasources.each
|
81
|
+
finis
|
80
82
|
end # def processTwb twb
|
81
83
|
|
82
84
|
end # class MarkdownEmitter
|
@@ -0,0 +1,93 @@
|
|
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 GroupFieldsAnalyzer
|
23
|
+
|
24
|
+
include TabTool
|
25
|
+
|
26
|
+
attr_accessor :localEmit
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
init
|
30
|
+
@funcdoc = {:class=>self.class, :blurb=>'Analyze Group Fields.', :description=>'Identifies the Groups and their Members for grouped fields.',}
|
31
|
+
#--
|
32
|
+
docFileName = docFile('TwbGroupFields.csv')
|
33
|
+
@csv = CSV.open(docFileName,'w')
|
34
|
+
@csv << ['Workbook','Data Source','Field','Group','Member']
|
35
|
+
addDocFile docFileName, "Workbooks, Data Sources, and their Grouped Fields"
|
36
|
+
#--
|
37
|
+
@twbCount = 0
|
38
|
+
@dsCount = 0
|
39
|
+
@gfCount = 0
|
40
|
+
@gfmCount = 0
|
41
|
+
end
|
42
|
+
|
43
|
+
def metrics
|
44
|
+
{
|
45
|
+
'# of Workbooks' => @twbCount,
|
46
|
+
'# of Data Sources' => @dsCount,
|
47
|
+
'# of Group Fields' => @gfCount,
|
48
|
+
'# of Group Field Members' => @gfmCount
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
def processTWB twb
|
53
|
+
@twb = twb
|
54
|
+
@twbName = @twb.name
|
55
|
+
emit " -- #{@twbName}"
|
56
|
+
@twbCount += 1
|
57
|
+
initMarkdown
|
58
|
+
parseDataSources
|
59
|
+
finis
|
60
|
+
$mdFile.close unless $mdFile.nil?
|
61
|
+
end
|
62
|
+
|
63
|
+
def initMarkdown
|
64
|
+
$mdFile = File.open(docFile("#{@twbName}.GroupFields.md"), 'w')
|
65
|
+
$mdFile << "# #{@twbName}\n "
|
66
|
+
end
|
67
|
+
|
68
|
+
def parseDataSources
|
69
|
+
@twb.datasources.each do |ds|
|
70
|
+
$mdFile.puts "\n## #{ds.uiname}"
|
71
|
+
@dsCount += 1
|
72
|
+
cfs = ds.calculatedFields
|
73
|
+
cfs.each do |cf|
|
74
|
+
if cf.isGroup
|
75
|
+
@gfCount += 1
|
76
|
+
$mdFile.puts "### #{cf.name}"
|
77
|
+
cf.groupMembers.each do |lead,values|
|
78
|
+
$mdFile.puts "* #{lead}"
|
79
|
+
values.each do |v|
|
80
|
+
@gfmCount += 1
|
81
|
+
$mdFile.puts " > #{v}"
|
82
|
+
@csv << [@twbName, ds.uiname,cf.uiname,lead,v]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end #class SheetFieldsAnalyzer
|
91
|
+
|
92
|
+
end # module Analysis
|
93
|
+
end # module Twb
|
@@ -31,12 +31,8 @@ module Analysis
|
|
31
31
|
#-- set up metrics
|
32
32
|
@twbcount = 0
|
33
33
|
@dscount = 0
|
34
|
+
@filecount = 0
|
34
35
|
@sheetcount = 0
|
35
|
-
@metrics = {
|
36
|
-
'# of Workbooks' => @twbcount ,
|
37
|
-
'# of Worksheets' => @dscount ,
|
38
|
-
'# of Worksheets' => @sheetcount
|
39
|
-
}
|
40
36
|
#--
|
41
37
|
@funcdoc = {:class=>self.class, :blurb=>'Analyzing Google Sheet Data Sources from Tableau Workbooks.', :description=>nil,}
|
42
38
|
docFileName = docFile('TWBGoogleSheetDataSources.csv')
|
@@ -57,16 +53,19 @@ module Analysis
|
|
57
53
|
dss = twb.datasources
|
58
54
|
dss.each do |ds|
|
59
55
|
emit ds.uiname
|
56
|
+
@dscount += 1
|
60
57
|
conns = ds.node.xpath(".//connection[@class='google-sheets']")
|
61
58
|
if conns.length > 0
|
62
|
-
@relation
|
63
|
-
@relName
|
64
|
-
@relType
|
65
|
-
@fileName
|
59
|
+
@relation = ds.node.at_xpath('./connection/relation')
|
60
|
+
@relName = @relation.attribute('name').text
|
61
|
+
@relType = @relation.attribute('type').text
|
62
|
+
@fileName = ds.node.at_xpath('.//named-connection/connection').attribute('filename')
|
63
|
+
@filecount += 1
|
66
64
|
emit "FILENAME: #{@fileName}"
|
67
65
|
tables = ds.node.xpath(".//relation[@type='table']")
|
68
66
|
# emit "# Tables: #{tables.length}"
|
69
67
|
tables.each do |table|
|
68
|
+
@sheetcount += 1
|
70
69
|
tableName = table.attribute('name')
|
71
70
|
columns = table.xpath('.//column')
|
72
71
|
columns.each do |column|
|
@@ -78,6 +77,16 @@ module Analysis
|
|
78
77
|
end
|
79
78
|
emit " "
|
80
79
|
end
|
80
|
+
finis
|
81
|
+
end
|
82
|
+
|
83
|
+
def metrics
|
84
|
+
{
|
85
|
+
'# of Workbooks' => @twbcount,
|
86
|
+
'# of Data Sources' => @dscount,
|
87
|
+
'# of Google Docs' => @filecount,
|
88
|
+
'# of Worksheets' => @sheetcount
|
89
|
+
}
|
81
90
|
end
|
82
91
|
|
83
92
|
end #class SheetFieldsAnalyzer
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# analyzeSheetFields.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
|
+
system 'cls'
|
17
|
+
|
18
|
+
require 'twb'
|
19
|
+
|
20
|
+
$twbCount = 0
|
21
|
+
|
22
|
+
$analyzer = Twb::Analysis::DashSheetsAnalyzer.new
|
23
|
+
|
24
|
+
def processTwb twbName
|
25
|
+
twb = Twb::Workbook.new twbName
|
26
|
+
puts "\t - #{twb.name}"
|
27
|
+
# --
|
28
|
+
$analyzer.processTWB twb
|
29
|
+
# --
|
30
|
+
$twbCount += 1
|
31
|
+
end
|
32
|
+
|
33
|
+
puts "\n\n "
|
34
|
+
puts " #{$analyzer.funcdoc[:class]}"
|
35
|
+
puts " #{$analyzer.funcdoc[:blurb]}"
|
36
|
+
puts "\n "
|
37
|
+
|
38
|
+
path = if ARGV.empty? then ['*.twb','*.twbx'] else ARGV[0] end
|
39
|
+
puts " Processing Workbooks matching: '#{path}'\n "
|
40
|
+
Dir.glob(path) { |twb| processTwb twb if twb =~ /twb[x]?$/ && twb !~ /dot[.]twb[x]?$/}
|
41
|
+
|
42
|
+
puts "\n "
|
43
|
+
puts " Analysis complete, found: #{$twbCount} Workbooks"
|
44
|
+
puts "\n"
|
45
|
+
|
46
|
+
$analyzer.docfilesdoc.each do |l|
|
47
|
+
puts ' ' + l
|
48
|
+
end
|
49
|
+
|
50
|
+
puts "\n\n That's all, folks.\n "
|
@@ -0,0 +1,75 @@
|
|
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 DashSheetsAnalyzer
|
23
|
+
|
24
|
+
include TabTool
|
25
|
+
|
26
|
+
attr_accessor :localEmit
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
init
|
30
|
+
@funcdoc = {:class=>self.class, :blurb=>'Analyze Dashboard Sheets from Tableau Workbooks.', :description=>'Identifies the Worksheets present in Dashboards.',}
|
31
|
+
#--
|
32
|
+
docFileName = docFile('TwbDashboardSheets.csv')
|
33
|
+
@dashSheetsCSV = CSV.open(docFileName,'w')
|
34
|
+
@dashSheetsCSV << ['Workbook','Dashboard','Worksheet','Hidden','Visible']
|
35
|
+
addDocFile docFileName, "Workbooks, Dashboards, and Worksheets in the Dashboards"
|
36
|
+
#--
|
37
|
+
@twbCount = 0
|
38
|
+
@dashCount = 0
|
39
|
+
@sheetCount = 0
|
40
|
+
end
|
41
|
+
|
42
|
+
def metrics
|
43
|
+
{
|
44
|
+
'# of Workbooks' => @twbCount,
|
45
|
+
'# of Dashboards' => @dashCount,
|
46
|
+
'# of Worksheets' => @sheetCount
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def processTWB twb
|
51
|
+
@twb = twb
|
52
|
+
@twbName = @twb.name
|
53
|
+
emit " -- #{@twbName}"
|
54
|
+
@twbCount += 1
|
55
|
+
parseDashes
|
56
|
+
finis
|
57
|
+
end
|
58
|
+
|
59
|
+
def parseDashes
|
60
|
+
@dashboards = @twb.dashboards
|
61
|
+
@dashboards.each do |dash|
|
62
|
+
emit "DASH:: #{dash.name}"
|
63
|
+
@dashCount += 1
|
64
|
+
dash.worksheets.each do |sheet|
|
65
|
+
@sheetCount += 1
|
66
|
+
emit "SHEET: #{sheet.name}"
|
67
|
+
@dashSheetsCSV << [@twbName, dash.name, sheet.name, sheet.hidden, sheet.visible ]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end #class SheetFieldsAnalyzer
|
73
|
+
|
74
|
+
end # module Analysis
|
75
|
+
end # module Twb
|
@@ -26,14 +26,25 @@ module Analysis
|
|
26
26
|
attr_accessor :localEmit
|
27
27
|
|
28
28
|
def initialize
|
29
|
-
|
30
|
-
@
|
31
|
-
|
32
|
-
|
33
|
-
docFileName = docFile('TWBWorksheetFields.csv')
|
29
|
+
init
|
30
|
+
@funcdoc = {:class=>self.class, :blurb=>'Analyze Sheet Fields from Tableau Workbooks.', :description=>nil,}
|
31
|
+
#--
|
32
|
+
docFileName = docFile('TwbWorksheetFields.csv')
|
34
33
|
@sheetFieldsCSV = CSV.open(docFileName,'w')
|
35
34
|
@sheetFieldsCSV << ['Workbook','Worksheet','Data Source','Data Source (tech)','Field','Field (tech)']
|
36
|
-
addDocFile docFileName, "
|
35
|
+
addDocFile docFileName, "Workbooks, Worksheets, and the Sheets' Data Sources and Fields"
|
36
|
+
#--
|
37
|
+
@twbCnt = 0
|
38
|
+
@sheetCnt = 0
|
39
|
+
@fieldsCnt = 0
|
40
|
+
end
|
41
|
+
|
42
|
+
def metrics
|
43
|
+
{
|
44
|
+
'# of Workbooks' => @twbcount,
|
45
|
+
'# of Worksheets' => @sheetCnt,
|
46
|
+
'# of Worksheet Fields' => @fieldsCnt
|
47
|
+
}
|
37
48
|
end
|
38
49
|
|
39
50
|
def processTWB twb
|
@@ -42,11 +53,13 @@ module Analysis
|
|
42
53
|
@twbCnt += 1
|
43
54
|
@twbDomainsLoaded = false
|
44
55
|
parseSheets
|
56
|
+
finis
|
45
57
|
end
|
46
58
|
|
47
59
|
def parseSheets
|
48
60
|
@worksheets = @twb.worksheets
|
49
61
|
@worksheets.each do |sheet|
|
62
|
+
@sheetCnt += 1
|
50
63
|
emit "SHEET: #{sheet.name}"
|
51
64
|
showFields sheet unless sheet.datasourceFields.nil?
|
52
65
|
end
|
@@ -64,6 +77,7 @@ module Analysis
|
|
64
77
|
emit " - #{ds.uiname}"
|
65
78
|
emit " : #{ds.class}"
|
66
79
|
dsfields.each do |sheetField|
|
80
|
+
@fieldsCnt += 1
|
67
81
|
emit " f: #{sheetField}"
|
68
82
|
emit " c: #{sheetField.class}"
|
69
83
|
fuiName = ds.fieldUIName sheetField #Fields[sheetField]
|
@@ -15,7 +15,6 @@
|
|
15
15
|
|
16
16
|
require 'twb'
|
17
17
|
require 'csv'
|
18
|
-
require 'set'
|
19
18
|
|
20
19
|
module Twb
|
21
20
|
module Analysis
|
@@ -27,20 +26,34 @@ module Analysis
|
|
27
26
|
attr_accessor :localEmit
|
28
27
|
|
29
28
|
def initialize
|
30
|
-
|
31
|
-
@
|
32
|
-
|
33
|
-
|
34
|
-
$sheetFieldsCSV = CSV.open(
|
35
|
-
$sheetFieldsCSV << ['Workbook','Worksheet','Filter Type','Data Source','Field','Value','Alias', 'Alias?']
|
29
|
+
init
|
30
|
+
@funcdoc = {:class=>self.class, :blurb=>'Analyze Worksheet filters.', :description=>'Documents Quick Filters and the values they employ, if any. Work in progess.',}
|
31
|
+
#--
|
32
|
+
docFileName = docFile('TWBWorksheetFilters.csv')
|
33
|
+
$sheetFieldsCSV = CSV.open( docFileName ,'w')
|
34
|
+
$sheetFieldsCSV << ['Workbook','Worksheet','Filter Type','Operation','Data Source','Field','Value','Alias', 'Alias?']
|
35
|
+
addDocFile docFileName, "Workbooks, Worksheets and the sheets' Quick Filters"
|
36
|
+
#--
|
37
|
+
@twbCount = 0
|
38
|
+
@sheetCount = 0
|
39
|
+
@filterCount = 0
|
40
|
+
end
|
41
|
+
|
42
|
+
def metrics
|
43
|
+
{
|
44
|
+
'# of Workbooks' => @twbcount,
|
45
|
+
'# of Worksheets' => @sheetCount,
|
46
|
+
'# of Worksheet Filters' => @filterCount
|
47
|
+
}
|
36
48
|
end
|
37
49
|
|
38
50
|
def processTWB twb
|
39
51
|
@twb = twb
|
40
52
|
emit " -- #{@twb.name}"
|
41
|
-
@
|
53
|
+
@twbCount += 1
|
42
54
|
@twbDomainsLoaded = false
|
43
55
|
parseFilters
|
56
|
+
finis
|
44
57
|
end
|
45
58
|
|
46
59
|
|
@@ -48,72 +61,230 @@ module Analysis
|
|
48
61
|
@worksheets = @twb.worksheets
|
49
62
|
@worksheets.each do |sheet|
|
50
63
|
emit "\n\nSHEET: #{sheet.name}"
|
51
|
-
@
|
64
|
+
@sheetCount += 1
|
52
65
|
filters = sheet.node.xpath('.//filter[@column]')
|
53
66
|
filters.each do |filter|
|
67
|
+
@filterCount += 1
|
54
68
|
emit "\nFILTER:\n#{filter}\n====="
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
69
|
+
filterClass = filter.attribute('class').text
|
70
|
+
fieldCode = filter.attribute('column').text
|
71
|
+
codedField = Twb::CodedField.new(fieldCode)
|
72
|
+
fieldTech = codedField.name
|
73
|
+
inexclude = fieldTech =~ /^(Ex|In)clusions/
|
74
|
+
measureNames = ':Measure Names' == fieldTech
|
75
|
+
srcTech = codedField.dataSource
|
76
|
+
dataSource = @twb.datasource(srcTech)
|
77
|
+
@dsAliases = dataSource.aliases
|
78
|
+
field = measureNames || inexclude ? fieldTech : dataSource.field(fieldTech)
|
79
|
+
fieldName = measureNames || inexclude ? fieldTech : dataSource.fieldUIName(fieldTech)
|
80
|
+
emit "\n:: FIELD :: #{field} == #{fieldName} -- #{fieldTech} -- #{codedField.rawCode}"
|
81
|
+
emit " filter class: #{filterClass}"
|
82
|
+
emit " field code: #{fieldCode}"
|
83
|
+
emit " coded field: #{codedField}"
|
84
|
+
emit " field tech: #{fieldTech}"
|
85
|
+
emit " field name: nil? #{fieldName.nil?} #{fieldName} "
|
86
|
+
emit " src tech: #{srcTech}"
|
87
|
+
emit " measureNames: #{measureNames}"
|
88
|
+
emit " sheet: #{sheet.name}"
|
89
|
+
emit " node: #{filter}"
|
90
|
+
emit " ds: #{dataSource.uiname}"
|
91
|
+
emit " field: #{field}"
|
92
|
+
emit " "
|
93
|
+
aaa = case filterClass
|
94
|
+
when 'relative-date' then resolveRelativeDate(sheet, dataSource, fieldName, filter)
|
95
|
+
when 'quantitative' then resolveQuantitative(sheet, dataSource, fieldName, filter)
|
96
|
+
when 'categorical' then resolveCategoricalValues(sheet, dataSource, fieldName, filter, measureNames)
|
97
|
+
end
|
64
98
|
end
|
65
99
|
end
|
66
100
|
end
|
67
101
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
102
|
+
# filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='-1' include-future='true' include-null='false' last-period='-1' period-type='day' />
|
103
|
+
# filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='-1' include-future='true' include-null='false' last-period='-1' period-type='year' />
|
104
|
+
# filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='-1' include-future='true' include-null='false' last-period='0' period-type='year' />
|
105
|
+
# filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='-2' include-future='true' include-null='false' last-period='0' period-type='day' />
|
106
|
+
# filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='-2' include-future='true' include-null='false' last-period='0' period-type='year' />
|
107
|
+
# filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='-6' include-future='true' include-null='false' last-period='0' period-type='day' />
|
108
|
+
# filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='0' include-future='false' include-null='false' last-period='0' period-type='year' />
|
109
|
+
# filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='0' include-future='true' include-null='false' last-period='0' period-type='day' />
|
110
|
+
# filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='0' include-future='true' include-null='false' last-period='0' period-type='year' />
|
111
|
+
# filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='0' include-future='true' include-null='false' last-period='2' period-type='day' />
|
112
|
+
# filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='0' include-future='true' include-null='false' last-period='2' period-type='year' />
|
113
|
+
# filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='0' include-future='true' include-null='false' last-period='4' period-type='day' />
|
114
|
+
# filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='1' include-future='true' include-null='false' last-period='1' period-type='day' />
|
115
|
+
# filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='1' include-future='true' include-null='false' last-period='1' period-type='year' />
|
116
|
+
def resolveRelativeDate sheet, dataSource, field, node
|
117
|
+
emit "resolveRelativeDate"
|
118
|
+
operation = 'Inclusive'
|
119
|
+
periodType = node['period-type']
|
120
|
+
inclFuture = node['include-future'] == 'true'
|
121
|
+
firstPeriod = node['first-period'].to_i
|
122
|
+
lastPeriod = node['last-period'].to_i
|
123
|
+
sum = firstPeriod + lastPeriod
|
124
|
+
prod = firstPeriod * lastPeriod
|
125
|
+
periodTech = "#{periodType} : #{firstPeriod} -> #{lastPeriod}"
|
126
|
+
period = periodTech
|
127
|
+
if sum == 0 && prod == 0
|
128
|
+
period = case periodType
|
129
|
+
when 'day' then "Today"
|
130
|
+
else inclFuture ? "This #{periodType.capitalize}" : "#{periodType.capitalize} to date"
|
131
|
+
end
|
132
|
+
elsif firstPeriod == lastPeriod
|
133
|
+
future = firstPeriod > 0
|
134
|
+
reln = future ? 'Next' : 'Previous'
|
135
|
+
period = case periodType
|
136
|
+
when 'day' then future ? 'Tomorrow' : 'Yesterday'
|
137
|
+
else "#{reln} #{periodType.capitalize}"
|
138
|
+
end
|
139
|
+
else
|
140
|
+
span = lastPeriod - firstPeriod + 1
|
141
|
+
future = firstPeriod == 0
|
142
|
+
reln = future ? "Next" : "Last"
|
143
|
+
period = "#{reln} #{span} #{periodType.capitalize}s"
|
144
|
+
end
|
145
|
+
$sheetFieldsCSV << [ @twb.name, sheet.name, 'Relative Date',operation, dataSource.uiname, field, period, periodTech ]
|
146
|
+
end
|
147
|
+
|
148
|
+
# <filter class='quantitative' column='[Sample - Superstore].[none:Profit:qk]' included-values='in-range'>
|
149
|
+
# <filter class='quantitative' column='[Sample - Superstore].[none:Profit:qk]' included-values='in-range'>
|
150
|
+
# <filter class='quantitative' column='[Sample - Superstore].[none:Profit:qk]' included-values='in-range'>
|
151
|
+
# <filter class='quantitative' column='[Sample - Superstore].[none:Profit:qk]' included-values='all' />
|
152
|
+
# <filter class='quantitative' column='[Sample - Superstore].[none:Profit:qk]' included-values='non-null' />
|
153
|
+
# <filter class='quantitative' column='[Sample - Superstore].[none:Profit:qk]' included-values='null' />
|
154
|
+
def resolveQuantitative sheet, dataSource, field, node
|
155
|
+
emit "resolveQuantitative"
|
156
|
+
operation = 'Inclusive'
|
157
|
+
inclValues = node['included-values']
|
158
|
+
values = if 'in-range' == inclValues
|
159
|
+
quantRangeValues node
|
160
|
+
else
|
161
|
+
"#{inclValues.capitalize} Values"
|
162
|
+
end
|
163
|
+
$sheetFieldsCSV << [ @twb.name, sheet.name, 'Range (row)',operation.capitalize, dataSource.uiname, field, values, inclValues ]
|
164
|
+
end
|
165
|
+
|
166
|
+
def quantRangeValues node
|
167
|
+
min = node.at_xpath('min')
|
168
|
+
max = node.at_xpath('max')
|
169
|
+
emit "min: nil? %-6s val: %-s " % [min.nil?,min]
|
170
|
+
emit "max: nil? %-6s val: %-s " % [max.nil?,max]
|
171
|
+
minrv = parseRangeVal min unless min.nil?
|
172
|
+
maxrv = parseRangeVal max unless max.nil?
|
173
|
+
mintxt = min.nil? ? '' : max.nil? ? "At least: #{minrv}" : "Range #{minrv}"
|
174
|
+
maxtxt = max.nil? ? '' : min.nil? ? "At most: #{maxrv}" : " ... #{maxrv}"
|
175
|
+
emit "#{mintxt} #{maxtxt}"
|
176
|
+
return "#{mintxt} #{maxtxt}"
|
177
|
+
end
|
178
|
+
|
179
|
+
def parseRangeVal node
|
180
|
+
return 'nil' if node.nil?
|
181
|
+
text = node.text
|
182
|
+
if text.start_with?('#')
|
183
|
+
return text.gsub(/^[#]|[#]$/,'')
|
184
|
+
end
|
185
|
+
num = text.to_f
|
186
|
+
# num.negative? ? num.floor : num.ceil
|
187
|
+
result = if num < 0
|
188
|
+
num.floor
|
189
|
+
else
|
190
|
+
num.ceil
|
191
|
+
end
|
192
|
+
return result
|
193
|
+
end
|
194
|
+
|
195
|
+
def resolveCategoricalValues sheet, dataSource, field, node, measureNames
|
196
|
+
emit "resolveCategoricalValues"
|
197
|
+
emit "measureNames: #{measureNames}"
|
75
198
|
firstChild = node.at_xpath('./groupfilter')
|
76
199
|
results = {}
|
200
|
+
#-- handling Inclusions & Exclusions, in-sheet pick-data filters
|
201
|
+
if field =~ /^(Ex|In)clusions/
|
202
|
+
enumerate = firstChild['user:ui-enumeration']
|
203
|
+
operation = enumerate.nil? ? 'Inclusive' : enumerate.capitalize
|
204
|
+
$sheetFieldsCSV << [ @twb.name, sheet.name, 'In-Sheet', operation, dataSource.uiname, field, '+++', '+++' ]
|
205
|
+
return results
|
206
|
+
end
|
207
|
+
#--
|
77
208
|
unless firstChild.nil?
|
78
209
|
function = firstChild.attribute('function').text
|
79
|
-
|
210
|
+
enumerate = firstChild['user:ui-enumeration']
|
211
|
+
operation = enumerate.nil? ? 'Inclusive' : enumerate.capitalize
|
212
|
+
emit "function : #{function}"
|
213
|
+
emit "enumerate: #{enumerate}"
|
214
|
+
emit "operation: #{operation}"
|
80
215
|
emit node.to_s
|
81
216
|
#-- single element filter
|
82
217
|
if 'member'.eql? function
|
83
218
|
emit "HANDLING SINGLE MEMBER FILTER"
|
84
|
-
|
219
|
+
member = firstChild['member']
|
220
|
+
value = measureNames ? dataSource.fieldUIName(Twb::CodedField.new(member).name) : member.gsub(/^"|"$/,'')
|
85
221
|
alia = dataSource.deAlias(field,value)
|
86
222
|
emit "value :%-25s => alias: %-s" % [value, alia]
|
87
|
-
$sheetFieldsCSV << [ @twb.name, sheet.name, 'single', dataSource.uiname, field, value, alia ]
|
223
|
+
$sheetFieldsCSV << [ @twb.name, sheet.name, 'single', operation, dataSource.uiname, field, value, alia ]
|
88
224
|
return {value => alia}
|
89
225
|
end
|
226
|
+
#-- another single element filter
|
227
|
+
# <groupfilter function="level-members" level="[none:Business Line:nk]" user:ui-enumeration="all" user:ui-marker="enumerate"/>
|
228
|
+
# <filter class='categorical' column='[federated.1astm0q1hl2ydc1dyqhqq0igvxkp].[none:Business Line:nk]' context='true'>
|
229
|
+
# <groupfilter function='level-members'
|
230
|
+
# level='[none:Business Line:nk]'
|
231
|
+
# user:ui-enumeration='all'
|
232
|
+
# user:ui-exclude='true'
|
233
|
+
# user:ui-marker='enumerate' />
|
234
|
+
# </filter>
|
235
|
+
# <filter class='categorical' column='[federated.1astm0q1hl2ydc1dyqhqq0igvxkp].[none:Business Line:nk]' context='true'>
|
236
|
+
# <groupfilter function='level-members'
|
237
|
+
# level='[none:Business Line:nk]'
|
238
|
+
# user:ui-enumeration='all'
|
239
|
+
# user:ui-marker='enumerate' />
|
240
|
+
# </filter>
|
241
|
+
if 'level-members'.eql? function
|
242
|
+
emit "HANDLING level-members FILTER"
|
243
|
+
inclorexcl = firstChild.has_attribute?('user:ui-exclude') ? 'Exclusive' : 'Inclusive'
|
244
|
+
$sheetFieldsCSV << [ @twb.name, sheet.name, 'single', inclorexcl, dataSource.uiname, field, operation, enumerate ]
|
245
|
+
end
|
90
246
|
#-- otherwise filter contains multiple elements
|
91
247
|
#-- handle individual member elements
|
92
248
|
elements = firstChild.xpath('.//groupfilter')
|
93
249
|
elements.each do |element|
|
94
250
|
function = element.attribute('function').text
|
95
|
-
emit "
|
251
|
+
emit "element function: #{function}\n node:\n#{element}"
|
96
252
|
if 'member'.eql? function
|
97
|
-
|
98
|
-
|
253
|
+
member = element.attribute('member').text
|
254
|
+
name = measureNames ? dataSource.fieldUIName(Twb::CodedField.new(member).name) : member.gsub(/^"|"$/,'')
|
255
|
+
emit "%%%% member NAME:: #{name}"
|
256
|
+
if '%null%' == name then name = 'Null' end
|
99
257
|
alia = dataSource.fieldAlias(field,name) # $TableNameAliases[name]
|
100
258
|
results[name] = alia
|
101
259
|
end
|
102
260
|
if 'range'.eql? function
|
261
|
+
emit "%%%% range element:: #{element}"
|
103
262
|
t = filtersFromRangeNode(dataSource, field, element)
|
263
|
+
|
264
|
+
if t.empty?
|
265
|
+
from = element['from']
|
266
|
+
to = element['to']
|
267
|
+
range = "#{from} ... #{to}"
|
268
|
+
results[range] = range
|
269
|
+
end
|
270
|
+
|
104
271
|
t.each do |name,alia|
|
105
|
-
emit "
|
272
|
+
emit "%%%% range Name: %-20s ALIAS: %-s " % [name, alia]
|
106
273
|
results[name] = dataSource.fieldAlias(field,name)
|
107
274
|
end
|
108
275
|
end
|
109
276
|
end
|
110
277
|
results.each do |name,alia|
|
111
|
-
$sheetFieldsCSV << [ @twb.name, sheet.name, 'single', dataSource.uiname, field, name, alia, !name.eql?(alia) ]
|
278
|
+
$sheetFieldsCSV << [ @twb.name, sheet.name, 'single',operation, dataSource.uiname, field, name, alia, !name.eql?(alia) ]
|
112
279
|
end
|
113
280
|
end
|
114
281
|
return results
|
115
282
|
end
|
116
283
|
|
284
|
+
def processMeasureNames sheet, dataSource, field, node
|
285
|
+
emit 'processMeasureNames'
|
286
|
+
end
|
287
|
+
|
117
288
|
def filtersFromRangeNode dataSource, field, node
|
118
289
|
unless @twbDomainsLoaded
|
119
290
|
loadDomains
|
@@ -134,11 +305,11 @@ module Analysis
|
|
134
305
|
dsFields = @twbFielddomains[dataSource.uiname]
|
135
306
|
emit "dsFields : #{dsFields}"
|
136
307
|
if dsFields.nil? || dsFields.empty?
|
137
|
-
|
138
|
-
emit
|
139
|
-
emit
|
140
|
-
emit
|
141
|
-
emit
|
308
|
+
alert "#### ALERT #### - '#{field}' FIELD DOMAIN VALUES FOR '#{@twb.name} DATASOURCE #{dataSource.uiname} NOT LOADED ####"
|
309
|
+
emit @twbFieldDomains
|
310
|
+
emit "==========="
|
311
|
+
emit dsFields
|
312
|
+
emit "==========="
|
142
313
|
else
|
143
314
|
fieldVals = dsFields[field].to_a
|
144
315
|
if dataSource.fieldHasAliases field
|
@@ -191,23 +362,10 @@ module Analysis
|
|
191
362
|
emit "def loadDomains\n=="
|
192
363
|
loader = Twb::Util::FieldDomainLoader.new
|
193
364
|
@twbFielddomains = loader.loadWorkbook @twb
|
194
|
-
emit "#{@twbFielddomains}\n=="
|
365
|
+
emit "FIELD DOMAINS:: #{@twbFielddomains}\n=="
|
195
366
|
@twbDomainsLoaded = true
|
196
367
|
end
|
197
368
|
|
198
|
-
# def emit(local=@localEmit, stuff)
|
199
|
-
# if stuff.is_a? String then
|
200
|
-
# lines = stuff.split(/\n/)
|
201
|
-
# lines.each do |line|
|
202
|
-
# @ttlogfile.puts "#{$emitPrefix}#{line}"
|
203
|
-
# puts "#{$emitPrefix}#{line}" if local
|
204
|
-
# end
|
205
|
-
# else
|
206
|
-
# @ttlogfile.puts "#{$emitPrefix}#{stuff}"
|
207
|
-
# puts "#{$emitPrefix}#{stuff}" if local
|
208
|
-
# end
|
209
|
-
# end
|
210
|
-
|
211
369
|
end # class
|
212
370
|
|
213
371
|
end # module Twb
|