twb 3.7.5 → 3.9.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|