twb 3.9.3 → 3.9.7
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 +6 -2
- data/lib/twb/analysis/DataSources/parametersanalyzer.rb +95 -0
- data/lib/twb/analysis/Sheets/sheetfiltersanalyzer.rb +18 -309
- data/lib/twb/analysis/Sheets/sheetfiltersanalyzerA.rb +81 -0
- data/lib/twb/calculatedfield.rb +3 -1
- data/lib/twb/datasource.rb +26 -3
- data/lib/twb/groupfield.rb +55 -0
- data/lib/twb/parameter.rb +119 -0
- data/lib/twb/quickfilter.rb +442 -0
- data/lib/twb/tabtool.rb +13 -4
- data/lib/twb/workbook.rb +26 -5
- data/lib/twb/worksheet.rb +28 -4
- metadata +7 -6
- data/LICENSE.md +0 -1
- data/LICENSE.txt +0 -619
- data/README.md +0 -32
- data/lib/twb/dashboard.txt +0 -57
@@ -0,0 +1,81 @@
|
|
1
|
+
# WorksheetFields.rb Copyright (C) 2017, 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 SheetFiltersAnalyzerA
|
23
|
+
|
24
|
+
include TabTool
|
25
|
+
|
26
|
+
attr_accessor :localEmit
|
27
|
+
|
28
|
+
def initialize
|
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('TWBWorksheetFiltersA.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
|
+
@sheetCount = 0
|
38
|
+
@filterCount = 0
|
39
|
+
end
|
40
|
+
|
41
|
+
def metrics
|
42
|
+
{
|
43
|
+
'# of Worksheets' => @sheetCount,
|
44
|
+
'# of Worksheet Filters' => @filterCount
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def processTWB twb
|
49
|
+
@twb = twb
|
50
|
+
@twbName = @twb.name
|
51
|
+
emit " -- #{@twbName}"
|
52
|
+
@twbDomainsLoaded = false
|
53
|
+
parseFilters
|
54
|
+
end
|
55
|
+
|
56
|
+
def parseFilters
|
57
|
+
@worksheets = @twb.worksheets
|
58
|
+
@worksheets.each do |sheet|
|
59
|
+
emit "\n\nSHEET: #{sheet.name}"
|
60
|
+
filters = sheet.filters
|
61
|
+
filters.each do |filter|
|
62
|
+
filter.emit "-----------------------------\nWORKSHEET:: #{sheet.name}\n-----------------------------"
|
63
|
+
if filter.values.empty?
|
64
|
+
# $sheetFieldsCSV << ['Workbook','Worksheet','Filter Type','Operation' ,'Data Source','Field','Value','Alias', 'Alias?']
|
65
|
+
$sheetFieldsCSV << [@twbName ,sheet.name, filter.type ,filter.inexclude, filter.dataSource.uiname, filter.uiname,nil,nil,nil]
|
66
|
+
end
|
67
|
+
filter.values.each do |valueMap|
|
68
|
+
value = valueMap[:value]
|
69
|
+
valias = valueMap[:alias]
|
70
|
+
same = value.eql? valias
|
71
|
+
# puts "RECORDING FILTER VALUES: %-25s -- %-25s same? %s " % [value,valias,same]
|
72
|
+
$sheetFieldsCSV << [@twbName ,sheet.name, filter.type ,filter.inexclude, filter.dataSource.uiname, filter.uiname, value, valias, same]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end # class SheetFiltersAnalyzerA
|
79
|
+
|
80
|
+
end # module Twb
|
81
|
+
end # module Analysis
|
data/lib/twb/calculatedfield.rb
CHANGED
data/lib/twb/datasource.rb
CHANGED
@@ -13,13 +13,15 @@
|
|
13
13
|
# You should have received a copy of the GNU General Public License
|
14
14
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
15
|
|
16
|
-
require 'nokogiri'
|
17
|
-
require 'digest/md5'
|
16
|
+
# require 'nokogiri'
|
17
|
+
# require 'digest/md5'
|
18
18
|
require 'json'
|
19
19
|
|
20
20
|
module Twb
|
21
21
|
|
22
|
-
class DataSource
|
22
|
+
class DataSource
|
23
|
+
|
24
|
+
include TabTool
|
23
25
|
|
24
26
|
@@hasher = Digest::SHA256.new
|
25
27
|
|
@@ -53,6 +55,7 @@ module Twb
|
|
53
55
|
attr_reader :aliases
|
54
56
|
attr_reader :calculatedFields, :calculatedFieldNamesMap, :calculatedFieldNames, :calculatedField
|
55
57
|
attr_reader :allFields
|
58
|
+
attr_reader :groups
|
56
59
|
attr_reader :filters
|
57
60
|
attr_reader :node
|
58
61
|
|
@@ -222,6 +225,7 @@ module Twb
|
|
222
225
|
def loadAliases
|
223
226
|
@aliases = {}
|
224
227
|
# puts $node.xpath('.//column/aliases/..').length
|
228
|
+
cnt = 0
|
225
229
|
@node.xpath('./column//aliases/..').each do |anode|
|
226
230
|
# puts "anode:: #{anode}"
|
227
231
|
# puts " path:: #{anode.path}"
|
@@ -252,6 +256,7 @@ module Twb
|
|
252
256
|
end
|
253
257
|
@aliases[name] = aliasMap
|
254
258
|
end
|
259
|
+
emit "FIELD ALIASES: @aliases"
|
255
260
|
return @aliases
|
256
261
|
end
|
257
262
|
|
@@ -366,6 +371,10 @@ module Twb
|
|
366
371
|
@fieldUINames[fld.name] = fld.uiname
|
367
372
|
@fieldUINames[fld.uiname] = fld.uiname
|
368
373
|
end
|
374
|
+
groups.each do |fld|
|
375
|
+
@fieldUINames[fld.name] = fld.uiname
|
376
|
+
@fieldUINames[fld.uiname] = fld.uiname
|
377
|
+
end
|
369
378
|
return @fieldUINames
|
370
379
|
end
|
371
380
|
|
@@ -463,6 +472,20 @@ module Twb
|
|
463
472
|
end
|
464
473
|
end
|
465
474
|
|
475
|
+
def groups
|
476
|
+
@groups ||= loadGroups
|
477
|
+
end
|
478
|
+
|
479
|
+
def loadGroups
|
480
|
+
@groups = []
|
481
|
+
groupNodes = @node.xpath('.//group')
|
482
|
+
groupNodes.each do |groupNode|
|
483
|
+
groupField = Twb::GroupField.new(groupNode)
|
484
|
+
@groups << groupField
|
485
|
+
end
|
486
|
+
return @groups
|
487
|
+
end
|
488
|
+
|
466
489
|
def processFilters
|
467
490
|
if @filters.nil?
|
468
491
|
@filters = {}
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# 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 'nokogiri'
|
17
|
+
|
18
|
+
module Twb
|
19
|
+
# module Data
|
20
|
+
|
21
|
+
class GroupField
|
22
|
+
|
23
|
+
include TabTool
|
24
|
+
|
25
|
+
attr_reader :node
|
26
|
+
attr_reader :name, :uiname, :caption
|
27
|
+
|
28
|
+
def initialize node
|
29
|
+
@node = node
|
30
|
+
@caption = @node['caption']
|
31
|
+
@name = @node['name'].gsub(/^\[|\]$/,'')
|
32
|
+
@uiname = @caption.nil? ? @name : @caption
|
33
|
+
end
|
34
|
+
|
35
|
+
def id
|
36
|
+
@id ||= @id = @name.hash
|
37
|
+
end
|
38
|
+
|
39
|
+
def properties
|
40
|
+
@properties ||= loadProperties
|
41
|
+
end
|
42
|
+
|
43
|
+
def loadProperties
|
44
|
+
@properties= {}
|
45
|
+
@node.attributes.each do |name,attr|
|
46
|
+
@properties[name] = attr.value
|
47
|
+
end
|
48
|
+
@properties[:uiname] = @name
|
49
|
+
return @properties
|
50
|
+
end
|
51
|
+
|
52
|
+
end # class GroupField
|
53
|
+
|
54
|
+
# end # module Data
|
55
|
+
end # module Twb
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# 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 'nokogiri'
|
17
|
+
require 'digest/md5'
|
18
|
+
|
19
|
+
module Twb
|
20
|
+
|
21
|
+
class Parameter
|
22
|
+
|
23
|
+
include TabTool
|
24
|
+
|
25
|
+
attr_reader :caption, :name, :uiname
|
26
|
+
attr_reader :dataType, :format, :dataTypeCustom
|
27
|
+
attr_reader :domainType, :role, :type
|
28
|
+
attr_reader :currentValue, :values
|
29
|
+
attr_reader :node
|
30
|
+
|
31
|
+
def initialize(paramNode)
|
32
|
+
@node = paramNode
|
33
|
+
# --
|
34
|
+
@name = @node['name'].gsub(/^\[|\]$/,'')
|
35
|
+
@caption = @node['caption']
|
36
|
+
@uiname = caption.nil? ? nameTech : caption
|
37
|
+
@dataType = @node['datatype']
|
38
|
+
@format = @node['default-format'].nil? ? '<<default>>' : @node['default-format']
|
39
|
+
@dataTypeCustom = @node['datatype-customized'].nil? ? false : true
|
40
|
+
@domainType = @node['param-domain-type']
|
41
|
+
@role = @node['role']
|
42
|
+
@type = @node['type']
|
43
|
+
@currentValue = paramNode.at_xpath('./calculation[@class="tableau"]')['formula'].gsub(/^[#"]|[#"]$/,'')
|
44
|
+
@values = []
|
45
|
+
processNode
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
"%s => %s" % [uiname, values]
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def processNode
|
55
|
+
processMembers
|
56
|
+
processRange
|
57
|
+
processAll
|
58
|
+
end
|
59
|
+
|
60
|
+
def processMembers
|
61
|
+
#-- for when there are members, i.e. Lists
|
62
|
+
# <column caption='Date - List' datatype='date' datatype-customized='true' name='[Date - All 9/5/2018 Std Short Date (copy)]' param-domain-type='list' role='measure' type='quantitative' value='#2002-01-02#'>
|
63
|
+
# <calculation class='tableau' formula='#2002-01-02#' />
|
64
|
+
# <members>
|
65
|
+
# <member value='#2002-01-02#' />
|
66
|
+
# <member value='#2006-12-05#' />
|
67
|
+
# <member value='#1999-11-01#' />
|
68
|
+
# </members>
|
69
|
+
members = @node.xpath('.//member')
|
70
|
+
@hasMembers = !members.empty?
|
71
|
+
emit "# members: #{members.length}"
|
72
|
+
members.each do |m|
|
73
|
+
mvalue = m['value'].gsub(/^[#"]|[#"]$/,'')
|
74
|
+
malias = m['alias']
|
75
|
+
mvalui = malias.nil? ? mvalue : malias
|
76
|
+
@values << {:valueTech => mvalue, :alias => malias, :value => mvalui}
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def processRange
|
82
|
+
# <column caption='Date - Range CV 3/3/2003 : ...12/5/2006' datatype='date' datatype-customized='true' name='[Date - Range CV 8/29/2001 3/23/2000...12/5/2006 (copy)]' param-domain-type='range' role='measure' type='quantitative' value='#2003-03-03#'>
|
83
|
+
# <calculation class='tableau' formula='#2003-03-03#' />
|
84
|
+
# <range max='#2006-12-05#' />
|
85
|
+
#--
|
86
|
+
# <column caption='Integer List Range -73...2,109 SS1' datatype='integer' datatype-customized='true' name='[Integer List Primes (copy)]' param-domain-type='range' role='measure' type='quantitative' value='1'>
|
87
|
+
# <calculation class='tableau' formula='1' />
|
88
|
+
# <range max='2109' min='1' />
|
89
|
+
# </column>
|
90
|
+
#--
|
91
|
+
# <column caption='Date - Range CV 8/29/2001 3/23/2000...' datatype='date' datatype-customized='true' name='[Date - Range CV 8/29/2001 3/23/2000...12/5/2006 (copy 2)]' param-domain-type='range' role='measure' type='quantitative' value='#2001-09-29#'>
|
92
|
+
# <calculation class='tableau' formula='#2001-09-29#' />
|
93
|
+
# <range min='#2000-03-23#' />
|
94
|
+
# </column>
|
95
|
+
#--
|
96
|
+
# <column caption='Integer List Range -73...2,109 SS3' datatype='integer' datatype-customized='true' name='[Integer List Range -73...2,109 SS1 (copy)]' param-domain-type='range' role='measure' type='quantitative' value='1'>
|
97
|
+
# <calculation class='tableau' formula='1' />
|
98
|
+
# <range granularity='3' max='2109' min='1' />
|
99
|
+
# </column>
|
100
|
+
if 'range' == domainType
|
101
|
+
@hasRange = true
|
102
|
+
rangeNode = @node.at_xpath('./range')
|
103
|
+
min = rangeNode['min'].nil? ? nil : rangeNode['min'].gsub(/^[#"]|[#"]$/,'')
|
104
|
+
max = rangeNode['max'].nil? ? nil : rangeNode['max'].gsub(/^[#"]|[#"]$/,'')
|
105
|
+
range = "#{min}...#{max}"
|
106
|
+
@values << {:value => range}
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def processAll
|
111
|
+
unless @hasMembers || @hasRange
|
112
|
+
@values << {:value => '<<All>>'}
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
end # class Parameter
|
117
|
+
|
118
|
+
end # module Twb
|
119
|
+
|
@@ -0,0 +1,442 @@
|
|
1
|
+
# 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 'nokogiri'
|
17
|
+
require 'digest/md5'
|
18
|
+
|
19
|
+
module Twb
|
20
|
+
|
21
|
+
class QuickFilter
|
22
|
+
|
23
|
+
include TabTool
|
24
|
+
|
25
|
+
attr_reader :field, :name, :uiname
|
26
|
+
attr_reader :type
|
27
|
+
attr_reader :dataSource
|
28
|
+
attr_reader :node, :values
|
29
|
+
attr_reader :inexclude, :inexMode, :includeNull
|
30
|
+
|
31
|
+
def initialize(node,twb)
|
32
|
+
init
|
33
|
+
emit "\nFILTER:\n#{node}\n====="
|
34
|
+
@node = node
|
35
|
+
filterClass = node['class']
|
36
|
+
@type = filterClass.gsub('-',' ').capitalize
|
37
|
+
fieldCode = node['column']
|
38
|
+
codedField = Twb::CodedField.new(fieldCode)
|
39
|
+
fieldTech = codedField.name
|
40
|
+
inexclusions = fieldTech =~ /^(Ex|In)clusions/
|
41
|
+
@measureNames = ':Measure Names' == fieldTech
|
42
|
+
srcTech = codedField.dataSource
|
43
|
+
@dataSource = twb.datasource(srcTech)
|
44
|
+
@dsAliases = @dataSource.aliases
|
45
|
+
field = @measureNames || inexclusions ? fieldTech : @dataSource.field(fieldTech)
|
46
|
+
uiname = @dataSource.fieldUIName(fieldTech)
|
47
|
+
@uiname = @measureNames || inexclusions ? fieldTech : uiname.nil? ? fieldTech : uiname
|
48
|
+
@field = @uiname
|
49
|
+
@includeNull = if @node['include-null'].nil?
|
50
|
+
true
|
51
|
+
elsif 'false' == @node['include-null']
|
52
|
+
false
|
53
|
+
else
|
54
|
+
true
|
55
|
+
end
|
56
|
+
@values = []
|
57
|
+
enode = @node.at_xpath('.//*[@user:ui-enumeration]')
|
58
|
+
@inexclude = if enode.nil?
|
59
|
+
'Include'
|
60
|
+
else case enode['user:ui-enumeration']
|
61
|
+
when 'inclusive' then 'Include'
|
62
|
+
when 'exclusive' then 'Exclude'
|
63
|
+
when nil then 'Include'
|
64
|
+
when 'all' then 'Include'
|
65
|
+
else 'undefined'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
@inexMode = enode.nil? ? 'Default' : 'Specified'
|
69
|
+
emit "\n:: FIELD :: #{field} == #{@uiname} -- #{fieldTech} -- #{codedField.rawCode}"
|
70
|
+
emit " filter class: #{filterClass}"
|
71
|
+
emit " field code: #{fieldCode}"
|
72
|
+
emit " coded field: #{codedField}"
|
73
|
+
emit " field tech: #{fieldTech}"
|
74
|
+
emit " field name: nil? #{@uiname.nil?} #{@uiname} "
|
75
|
+
emit " src tech: #{srcTech}"
|
76
|
+
emit " @measureNames: #{@measureNames}"
|
77
|
+
emit " ds: #{@dataSource.uiname}"
|
78
|
+
emit " field: #{@uiname}"
|
79
|
+
emit " @inexclude: #{@inexclude}"
|
80
|
+
emit " "
|
81
|
+
aaa = case filterClass
|
82
|
+
when 'relative-date' then resolveRelativeDate
|
83
|
+
when 'quantitative' then resolveQuantitative
|
84
|
+
when 'categorical' then resolveCategoricalValues
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def to_s
|
89
|
+
"%s => %s" % [uiname, values]
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def recordValue value, valias=nil
|
95
|
+
# puts "recordValue--: #{value} :: @'#{valias}'"
|
96
|
+
valias = value if valias.nil?
|
97
|
+
@values << {:value => value, :alias => valias}
|
98
|
+
# puts "recordValue--: done"
|
99
|
+
end
|
100
|
+
|
101
|
+
|
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
|
117
|
+
emit "resolveRelativeDate"
|
118
|
+
periodType = @node['period-type']
|
119
|
+
inclFuture = @node['include-future'] == 'true'
|
120
|
+
firstPeriod = @node['first-period'].to_i
|
121
|
+
lastPeriod = @node['last-period'].to_i
|
122
|
+
sum = firstPeriod + lastPeriod
|
123
|
+
prod = firstPeriod * lastPeriod
|
124
|
+
periodTech = "#{periodType} : #{firstPeriod} -> #{lastPeriod}"
|
125
|
+
period = periodTech
|
126
|
+
if sum == 0 && prod == 0
|
127
|
+
period = case periodType
|
128
|
+
when 'day' then "Today"
|
129
|
+
else inclFuture ? "This #{periodType.capitalize}" : "#{periodType.capitalize} to date"
|
130
|
+
end
|
131
|
+
elsif firstPeriod == lastPeriod
|
132
|
+
future = firstPeriod > 0
|
133
|
+
reln = future ? 'Next' : 'Previous'
|
134
|
+
period = case periodType
|
135
|
+
when 'day' then future ? 'Tomorrow' : 'Yesterday'
|
136
|
+
else "#{reln} #{periodType.capitalize}"
|
137
|
+
end
|
138
|
+
else
|
139
|
+
span = lastPeriod - firstPeriod + 1
|
140
|
+
future = firstPeriod == 0
|
141
|
+
reln = future ? "Next" : "Last"
|
142
|
+
period = "#{reln} #{span} #{periodType.capitalize}s"
|
143
|
+
end
|
144
|
+
recordValue period, periodTech
|
145
|
+
end
|
146
|
+
|
147
|
+
# <filter class='quantitative' column='[Sample - Superstore].[none:Profit:qk]' included-values='in-range'>
|
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='all' />
|
151
|
+
# <filter class='quantitative' column='[Sample - Superstore].[none:Profit:qk]' included-values='non-null' />
|
152
|
+
# <filter class='quantitative' column='[Sample - Superstore].[none:Profit:qk]' included-values='null' />
|
153
|
+
def resolveQuantitative
|
154
|
+
emit "resolveQuantitative"
|
155
|
+
inclValues = @node['included-values']
|
156
|
+
qvalues = if 'in-range' == inclValues
|
157
|
+
quantRangeValues
|
158
|
+
else
|
159
|
+
"#{inclValues.capitalize} Values"
|
160
|
+
end
|
161
|
+
# recordValue qvalues, inclValues
|
162
|
+
end
|
163
|
+
|
164
|
+
def quantRangeValues
|
165
|
+
min = @node.at_xpath('min')
|
166
|
+
max = @node.at_xpath('max')
|
167
|
+
emit "min: nil? %-6s val: %-s " % [min.nil?,min]
|
168
|
+
emit "max: nil? %-6s val: %-s " % [max.nil?,max]
|
169
|
+
minrv = parseRangeVal min unless min.nil?
|
170
|
+
maxrv = parseRangeVal max unless max.nil?
|
171
|
+
mintxt = min.nil? ? '' : max.nil? ? "At least: #{minrv}" : "#{minrv}"
|
172
|
+
maxtxt = max.nil? ? '' : min.nil? ? "At most: #{maxrv}" : ",...,#{maxrv}"
|
173
|
+
recordValue "#{mintxt}#{maxtxt}"
|
174
|
+
# emit "#{mintxt}#{maxtxt}"
|
175
|
+
# return "#{mintxt} #{maxtxt}"
|
176
|
+
end
|
177
|
+
|
178
|
+
def parseRangeVal node
|
179
|
+
return 'nil' if node.nil?
|
180
|
+
text = node.text
|
181
|
+
if text.start_with?('#')
|
182
|
+
return text.gsub(/^[#]|[#]$/,'')
|
183
|
+
end
|
184
|
+
num = text.to_f
|
185
|
+
# num.negative? ? num.floor : num.ceil
|
186
|
+
result = if num < 0
|
187
|
+
num.floor
|
188
|
+
else
|
189
|
+
num.ceil
|
190
|
+
end
|
191
|
+
return result
|
192
|
+
end
|
193
|
+
|
194
|
+
# <filter class='categorical' column='[Sample - Superstore - English (Extract)].[none:Region:nk]'>
|
195
|
+
# <groupfilter from='"East"' function='range' level='[none:Region:nk]' to='"West"' user:ui-domain='relevant' user:ui-enumeration='inclusive' user:ui-marker='enumerate' />
|
196
|
+
# </filter>
|
197
|
+
#--
|
198
|
+
def resolveCategoricalValues
|
199
|
+
emit "resolveCategoricalValues"
|
200
|
+
emit "@measureNames: #{@measureNames}"
|
201
|
+
if @node.element_children.empty?
|
202
|
+
# <filter class='categorical' column='[Sample - Superstore].[Top Customers by Profit (copy)]' />
|
203
|
+
recordValue 'All'
|
204
|
+
return
|
205
|
+
end
|
206
|
+
firstChild = @node.at_xpath('./groupfilter')
|
207
|
+
unless firstChild.nil? # should not happen per 'if @node.element_children.empty?' above
|
208
|
+
function = firstChild['function']
|
209
|
+
emit "function : #{function}"
|
210
|
+
emit node.to_s
|
211
|
+
#-- single element filter
|
212
|
+
case function
|
213
|
+
when 'member'
|
214
|
+
member = firstChild['member']
|
215
|
+
value = @measureNames ? @dataSource.fieldUIName(Twb::CodedField.new(member).name) : member.gsub(/^"|"$/,'')
|
216
|
+
recordValue value, @dataSource.deAlias(@uiname,value)
|
217
|
+
when 'empty-level'
|
218
|
+
recordValue 'None'
|
219
|
+
when 'range'
|
220
|
+
filtersFromRangeNode firstChild
|
221
|
+
when 'union'
|
222
|
+
emit "No resolving to do here. 'union' function accommodated with collection of members"
|
223
|
+
when 'except'
|
224
|
+
emit "UNRESOLVED function: #{function}"
|
225
|
+
when 'reorder-dimensionality'
|
226
|
+
emit "UNRESOLVED function: #{function}, involved with Inclusions & Exclusions - complicated to handle"
|
227
|
+
when 'level-members'
|
228
|
+
uiEnum = firstChild['user:ui-enumeration']
|
229
|
+
case uiEnum
|
230
|
+
when 'all'
|
231
|
+
recordValue 'All'
|
232
|
+
else
|
233
|
+
recordValue 'N/A'
|
234
|
+
emit "###### ALERT - Unresolved Quick filter ######"
|
235
|
+
end
|
236
|
+
else
|
237
|
+
recordValue "UNRESOLVED fn: #{function}"
|
238
|
+
end
|
239
|
+
# if 'member'.eql? function
|
240
|
+
# emit "HANDLING SINGLE MEMBER FILTER"
|
241
|
+
# member = firstChild['member']
|
242
|
+
# value = @measureNames ? @dataSource.fieldUIName(Twb::CodedField.new(member).name) : member.gsub(/^"|"$/,'')
|
243
|
+
# alia = @dataSource.deAlias(@uiname,value)
|
244
|
+
# emit "value :%-25s => alias: %-s" % [value, alia]
|
245
|
+
# recordValue value, alia
|
246
|
+
# return
|
247
|
+
# end
|
248
|
+
# if 'empty-level' == function
|
249
|
+
# recordValue 'None', 'None'
|
250
|
+
# return
|
251
|
+
# end
|
252
|
+
# if 'range'.eql? function
|
253
|
+
# emit "HANDLING RANGE 1st Child FILTER"
|
254
|
+
# # values = filtersFromRangeNode firstChild
|
255
|
+
# filtersFromRangeNode firstChild
|
256
|
+
# # values.each do |valMap|
|
257
|
+
# # recordValue valMap[:value], valMap[:alias]
|
258
|
+
# # end
|
259
|
+
# end
|
260
|
+
#-- another single element filter
|
261
|
+
# <groupfilter function="level-members" level="[none:Business Line:nk]" user:ui-enumeration="all" user:ui-marker="enumerate"/>
|
262
|
+
# <filter class='categorical' column='[federated.1astm0q1hl2ydc1dyqhqq0igvxkp].[none:Business Line:nk]' context='true'>
|
263
|
+
# <groupfilter function='level-members'
|
264
|
+
# level='[none:Business Line:nk]'
|
265
|
+
# user:ui-enumeration='all'
|
266
|
+
# user:ui-exclude='true'
|
267
|
+
# user:ui-marker='enumerate' />
|
268
|
+
# </filter>
|
269
|
+
# <filter class='categorical' column='[federated.1astm0q1hl2ydc1dyqhqq0igvxkp].[none:Business Line:nk]' context='true'>
|
270
|
+
# <groupfilter function='level-members'
|
271
|
+
# level='[none:Business Line:nk]'
|
272
|
+
# user:ui-enumeration='all'
|
273
|
+
# user:ui-marker='enumerate' />
|
274
|
+
# </filter>
|
275
|
+
if 'level-members'.eql? function
|
276
|
+
emit "HANDLING level-members FILTER"
|
277
|
+
#--
|
278
|
+
# <filter class='categorical' column='[Sample - Superstore].[Top Customers by Profit (copy)]'>
|
279
|
+
# <groupfilter function='level-members' level='[Customer Name]' user:ui-enumeration='all' user:ui-marker='enumerate' />
|
280
|
+
# </filter>
|
281
|
+
#--
|
282
|
+
# <filter class='categorical' column='[Sample - Superstore].[none:Customer Name:nk]'>
|
283
|
+
# <groupfilter function='level-members' level='[none:Customer Name:nk]' user:ui-enumeration='all' user:ui-marker='enumerate' />
|
284
|
+
# </filter>
|
285
|
+
#--
|
286
|
+
# <filter class='categorical' column='[Sample - Superstore].[Top Customers by Profit (copy)]'>
|
287
|
+
# <groupfilter function='level-members' level='[Customer Name]' user:ui-enumeration='all' user:ui-exclude='true' user:ui-marker='enumerate' />
|
288
|
+
# </filter>
|
289
|
+
#--
|
290
|
+
# <filter class='categorical' column='[Sample - Superstore].[none:Customer Name:nk]'>
|
291
|
+
# <groupfilter function='level-members' level='[none:Customer Name:nk]' user:ui-enumeration='all' user:ui-marker='enumerate' />
|
292
|
+
# </filter>
|
293
|
+
#--
|
294
|
+
# <filter class='categorical' column='[Sample - Superstore].[Top Customers by Profit (copy)]'>
|
295
|
+
# <groupfilter function='except' user:ui-domain='relevant' user:ui-enumeration='exclusive' user:ui-marker='enumerate'>
|
296
|
+
# <groupfilter function='level-members' level='[Customer Name]' />
|
297
|
+
# <groupfilter function='union'>
|
298
|
+
# <groupfilter function='member' level='[Customer Name]' member='"Adrian Barton"' />
|
299
|
+
# <groupfilter function='member' level='[Customer Name]' member='"Raymond Buch"' />
|
300
|
+
# </groupfilter>
|
301
|
+
# </groupfilter>
|
302
|
+
# </filter>
|
303
|
+
#--
|
304
|
+
uiEnum = firstChild['user:ui-enumeration']
|
305
|
+
case uiEnum
|
306
|
+
when 'all'
|
307
|
+
recordValue 'All', 'All'
|
308
|
+
when 'inclusive'
|
309
|
+
@inexclude = 'Include'
|
310
|
+
when 'exclusive'
|
311
|
+
@inexclude = 'Exclude'
|
312
|
+
end
|
313
|
+
end
|
314
|
+
#-- otherwise filter contains multiple elements
|
315
|
+
#-- handle individual member elements
|
316
|
+
elements = firstChild.xpath('.//groupfilter')
|
317
|
+
elements.each do |element|
|
318
|
+
function = element.attribute('function').text
|
319
|
+
emit "element function: #{function}\n node:\n#{element}"
|
320
|
+
if 'member'.eql? function
|
321
|
+
member = element.attribute('member').text
|
322
|
+
name = @measureNames ? @dataSource.fieldUIName(Twb::CodedField.new(member).name) : member.gsub(/^"|"$/,'')
|
323
|
+
emit "%%%% member NAME:: #{name}"
|
324
|
+
if '%null%' == name then name = 'Null' end
|
325
|
+
alia = @dataSource.fieldAlias(@uiname,name) # $TableNameAliases[name]
|
326
|
+
recordValue name, alia
|
327
|
+
end
|
328
|
+
if 'range'.eql? function
|
329
|
+
emit "%%%% range element:: #{element}"
|
330
|
+
t = filtersFromRangeNode element
|
331
|
+
if t.empty?
|
332
|
+
fromAttr = element['from']
|
333
|
+
from = fromAttr.nil? ? '' : fromAttr.gsub(/^[#"']|[#"']$/,'')
|
334
|
+
toAttr = element['to']
|
335
|
+
to = toAttr.nil? ? '' : toAttr.gsub(/^[#"']|[#"']$/,'')
|
336
|
+
range = "#{from},...,#{to}"
|
337
|
+
recordValue range, range
|
338
|
+
end
|
339
|
+
t.each do |name,alia|
|
340
|
+
# emit "%%%% range Name: %-20s ALIAS: %-s " % [name, alia]
|
341
|
+
recordValue name, @dataSource.fieldAlias(@uiname,name)
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
def filtersFromRangeNode node
|
349
|
+
unless @twbDomainsLoaded
|
350
|
+
loadDomains
|
351
|
+
end
|
352
|
+
# results = []
|
353
|
+
emit "filtersFromRangeNode"
|
354
|
+
emit " from: #{node.attribute('from')}"
|
355
|
+
emit " to. : #{node.attribute('to')}"
|
356
|
+
from = node.attribute('from').text.gsub(/^"|"$/,'')
|
357
|
+
to = node.attribute( 'to').text.gsub(/^"|"$/,'')
|
358
|
+
emit " from: #{from}"
|
359
|
+
emit " to. : #{to}"
|
360
|
+
if @twbDomainsLoaded
|
361
|
+
# results = filtersInRange from, to
|
362
|
+
filtersInRange from, to
|
363
|
+
else
|
364
|
+
range = "#{from},...,#{to}"
|
365
|
+
recordValue range, range
|
366
|
+
end
|
367
|
+
# return results
|
368
|
+
end
|
369
|
+
|
370
|
+
def filtersInRange from, to
|
371
|
+
emit "filtersInRange"
|
372
|
+
# results = {}
|
373
|
+
dsFields = @twbFielddomains[@dataSource.uiname]
|
374
|
+
emit "dsFields : #{dsFields}"
|
375
|
+
if dsFields.nil? || dsFields.empty?
|
376
|
+
alert "#### ALERT #### - '#{@uiname}' FIELD DOMAIN VALUES FOR '#{@twb.name} DATASOURCE #{@dataSource.uiname} NOT LOADED ####"
|
377
|
+
emit @twbFieldDomains
|
378
|
+
emit "==========="
|
379
|
+
emit dsFields
|
380
|
+
emit "==========="
|
381
|
+
else
|
382
|
+
fieldVals = dsFields[@uiname].to_a
|
383
|
+
if @dataSource.fieldHasAliases @uiname
|
384
|
+
#-- resolve aliases
|
385
|
+
dbValues = SortedSet.new
|
386
|
+
aliases = @dataSource.fieldAliases @uiname
|
387
|
+
fieldVals.each do |fv|
|
388
|
+
fvAliased = aliases.has_value? fv
|
389
|
+
if fvAliased
|
390
|
+
dbValues << aliases.key(fv)
|
391
|
+
else
|
392
|
+
dbValues << fv
|
393
|
+
end
|
394
|
+
end
|
395
|
+
$fieldVals = dbValues.to_a
|
396
|
+
else
|
397
|
+
#-- use domain values as returned
|
398
|
+
$fieldVals = dsFields[@uiname].to_a
|
399
|
+
end
|
400
|
+
# domainVals = dsFields[field].to_a
|
401
|
+
# #-- need to dealias (unalias?) the field domain values
|
402
|
+
# dbVals = SortedSet.new
|
403
|
+
# domainVals.each do |dv|
|
404
|
+
emit "field values: #{$fieldVals}"
|
405
|
+
unless $fieldVals.nil? || $fieldVals.empty?
|
406
|
+
values = $fieldVals
|
407
|
+
f_i = values.index from
|
408
|
+
t_i = values.index to
|
409
|
+
emit " from: #{f_i} :: '#{from}'"
|
410
|
+
emit " to: #{t_i} :: '#{to}'"
|
411
|
+
badRange = f_i.nil? || t_i.nil?
|
412
|
+
emit "badRange: #{badRange}"
|
413
|
+
if badRange
|
414
|
+
emit "BAD RANGE"
|
415
|
+
else
|
416
|
+
tnames = values[f_i..t_i]
|
417
|
+
tnames.each do |tname|
|
418
|
+
# results[tname] = @dataSource.deAlias(@uiname,tname) # $TableNameAliases[tname]
|
419
|
+
recordValue tname, @dataSource.deAlias(@uiname,tname)
|
420
|
+
end
|
421
|
+
# emit " filtersInRange f:%-35s t:%-s " % ["'#{from}:#{f_i}'", "'#{to}:#{t_i}'"]
|
422
|
+
# emit " names: #{results.keys}"
|
423
|
+
# emit " aliases: #{results.values}"
|
424
|
+
end
|
425
|
+
end
|
426
|
+
end
|
427
|
+
# return results
|
428
|
+
end
|
429
|
+
|
430
|
+
def loadDomains
|
431
|
+
emit "def loadDomains\n=="
|
432
|
+
unless @twb.nil?
|
433
|
+
loader = Twb::Util::FieldDomainLoader.new
|
434
|
+
@twbFielddomains = loader.loadWorkbook @twb
|
435
|
+
emit "FIELD DOMAINS:: #{@twbFielddomains}\n=="
|
436
|
+
@twbDomainsLoaded = true
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
end # class QuickFilter
|
441
|
+
|
442
|
+
end # module Twb
|