twb 3.9.3 → 3.9.7
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 +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
|