sleek 0.0.1 → 0.0.2
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.
- data/README.md +95 -20
- data/lib/sleek.rb +4 -2
- data/lib/sleek/core_ext/range.rb +6 -7
- data/lib/sleek/event.rb +4 -1
- data/lib/sleek/filter.rb +11 -0
- data/lib/sleek/group_by_criteria.rb +144 -0
- data/lib/sleek/interval.rb +21 -20
- data/lib/sleek/{base.rb → namespace.rb} +13 -8
- data/lib/sleek/queries.rb +0 -1
- data/lib/sleek/queries/average.rb +1 -1
- data/lib/sleek/queries/count_unique.rb +1 -1
- data/lib/sleek/queries/maximum.rb +1 -1
- data/lib/sleek/queries/minimum.rb +1 -1
- data/lib/sleek/queries/query.rb +70 -37
- data/lib/sleek/queries/sum.rb +1 -1
- data/lib/sleek/query_collection.rb +3 -3
- data/lib/sleek/query_command.rb +71 -0
- data/lib/sleek/timeframe.rb +45 -52
- data/lib/sleek/version.rb +1 -1
- data/spec/lib/sleek/event_spec.rb +3 -3
- data/spec/lib/sleek/filter_spec.rb +3 -3
- data/spec/lib/sleek/group_by_criteria_spec.rb +139 -0
- data/spec/lib/sleek/interval_spec.rb +6 -5
- data/spec/lib/sleek/{base_spec.rb → namespace_spec.rb} +15 -8
- data/spec/lib/sleek/queries/average_spec.rb +1 -1
- data/spec/lib/sleek/queries/count_spec.rb +1 -1
- data/spec/lib/sleek/queries/count_unique_spec.rb +1 -1
- data/spec/lib/sleek/queries/maximum_spec.rb +1 -1
- data/spec/lib/sleek/queries/minimum_spec.rb +1 -1
- data/spec/lib/sleek/queries/query_spec.rb +58 -84
- data/spec/lib/sleek/queries/sum_spec.rb +1 -1
- data/spec/lib/sleek/query_collection_spec.rb +11 -9
- data/spec/lib/sleek/query_command_spec.rb +171 -0
- data/spec/lib/sleek/timeframe_spec.rb +70 -36
- data/spec/lib/sleek_spec.rb +2 -2
- metadata +11 -8
- data/lib/sleek/queries/targetable.rb +0 -13
- data/spec/lib/sleek/queries/targetable_spec.rb +0 -29
@@ -1,12 +1,12 @@
|
|
1
1
|
module Sleek
|
2
|
-
class
|
3
|
-
attr_reader :
|
2
|
+
class Namespace
|
3
|
+
attr_reader :name
|
4
4
|
|
5
5
|
# Internal: Initialize Sleek with namespace.
|
6
6
|
#
|
7
7
|
# namespace - the Symbol namespace name.
|
8
|
-
def initialize(
|
9
|
-
@
|
8
|
+
def initialize(name)
|
9
|
+
@name = name
|
10
10
|
end
|
11
11
|
|
12
12
|
# Public: Record an event.
|
@@ -14,12 +14,17 @@ module Sleek
|
|
14
14
|
# bucket - the String name of bucket.
|
15
15
|
# payload - the Hash of event data.
|
16
16
|
def record(bucket, payload)
|
17
|
-
Event.create_with_namespace(
|
17
|
+
Event.create_with_namespace(name, bucket, payload)
|
18
18
|
end
|
19
19
|
|
20
20
|
# Public: Get `QueriesCollection` for the namespace.
|
21
21
|
def queries
|
22
|
-
@queries ||= QueryCollection.new(
|
22
|
+
@queries ||= QueryCollection.new(self)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Public: Delete the namespace.
|
26
|
+
def delete!
|
27
|
+
events.delete_all
|
23
28
|
end
|
24
29
|
|
25
30
|
# Public: Delete event bucket.
|
@@ -40,13 +45,13 @@ module Sleek
|
|
40
45
|
# Internal: Get events associated with current namespace and,
|
41
46
|
# optionally, specified bucket.
|
42
47
|
def events(bucket = nil)
|
43
|
-
evts = Event.where(namespace:
|
48
|
+
evts = Event.where(namespace: name)
|
44
49
|
evts = evts.where(bucket: bucket) if bucket.present?
|
45
50
|
evts
|
46
51
|
end
|
47
52
|
|
48
53
|
def inspect
|
49
|
-
"#<Sleek::
|
54
|
+
"#<Sleek::Namespace #{name}>"
|
50
55
|
end
|
51
56
|
end
|
52
57
|
end
|
data/lib/sleek/queries.rb
CHANGED
data/lib/sleek/queries/query.rb
CHANGED
@@ -1,11 +1,30 @@
|
|
1
1
|
module Sleek
|
2
2
|
module Queries
|
3
|
+
# Public: The query.
|
4
|
+
#
|
5
|
+
# Queries are performed on a set of events and usually return
|
6
|
+
# numeric values. You shouldn't be using Sleek::Queries::Query
|
7
|
+
# directly, instead, you should subclass it and define `#perform` on
|
8
|
+
# it, which takes an events criteria and does its job.
|
9
|
+
#
|
10
|
+
# Sleek::Queries::Query would take care of processing options,
|
11
|
+
# filtering events, handling series and groups.
|
12
|
+
#
|
13
|
+
# Examples
|
14
|
+
#
|
15
|
+
# class SomeQuery < Query
|
16
|
+
# def perform(events)
|
17
|
+
# ...
|
18
|
+
# end
|
19
|
+
# end
|
3
20
|
class Query
|
4
|
-
attr_reader :namespace, :bucket, :options
|
21
|
+
attr_reader :namespace, :bucket, :options, :timeframe
|
22
|
+
|
23
|
+
delegate :require_target_property?, to: 'self.class'
|
5
24
|
|
6
25
|
# Internal: Initialize the query.
|
7
26
|
#
|
8
|
-
# namespace - the
|
27
|
+
# namespace - the Sleek::Namespace object.
|
9
28
|
# bucket - the String bucket name.
|
10
29
|
# options - the optional Hash of options.
|
11
30
|
# :timeframe - the optional timeframe description.
|
@@ -16,29 +35,35 @@ module Sleek
|
|
16
35
|
@namespace = namespace
|
17
36
|
@bucket = bucket
|
18
37
|
@options = options
|
38
|
+
@timeframe = options[:timeframe]
|
19
39
|
|
20
|
-
raise ArgumentError,
|
40
|
+
raise ArgumentError, 'options are invalid' unless valid_options?
|
21
41
|
end
|
22
42
|
|
23
43
|
# Internal: Get Mongoid::Criteria for events to perform the query.
|
24
44
|
#
|
25
45
|
# time_range - the optional range of Time objects.
|
26
|
-
def events
|
27
|
-
evts =
|
28
|
-
evts = evts.between(
|
46
|
+
def events
|
47
|
+
evts = namespace.events(bucket)
|
48
|
+
evts = evts.between('s.t' => timeframe) if timeframe?
|
29
49
|
evts = apply_filters(evts) if filter?
|
50
|
+
|
51
|
+
if group_by.present?
|
52
|
+
evts = Sleek::GroupByCriteria.new(evts, "d.#{group_by}")
|
53
|
+
end
|
54
|
+
|
30
55
|
evts
|
31
56
|
end
|
32
57
|
|
33
58
|
# Internal: Apply all the filters to the criteria.
|
34
59
|
def apply_filters(criteria)
|
35
|
-
filters.
|
60
|
+
filters.reduce(criteria) { |crit, filter| filter.apply(crit) }
|
36
61
|
end
|
37
62
|
|
38
63
|
# Internal: Get filters.
|
39
64
|
def filters
|
40
65
|
filters = options[:filter]
|
41
|
-
|
66
|
+
|
42
67
|
if filters.is_a?(Array) && filters.size == 3 && filters.none? { |f| f.is_a?(Array) }
|
43
68
|
filters = [filters]
|
44
69
|
elsif !filters.is_a?(Array) || !filters.all? { |f| f.is_a?(Array) && f.size == 3 }
|
@@ -48,22 +73,6 @@ module Sleek
|
|
48
73
|
filters.map { |f| Sleek::Filter.new(*f) }
|
49
74
|
end
|
50
75
|
|
51
|
-
# Internal: Get timeframe for the query.
|
52
|
-
#
|
53
|
-
# Returns nil if no timeframe was passed to initializer.
|
54
|
-
def timeframe
|
55
|
-
Sleek::Timeframe.new(options[:timeframe]) if timeframe?
|
56
|
-
end
|
57
|
-
|
58
|
-
# Internal: Get timeframe range.
|
59
|
-
def time_range
|
60
|
-
timeframe.try(:to_time_range)
|
61
|
-
end
|
62
|
-
|
63
|
-
def series
|
64
|
-
Sleek::Interval.new(options[:interval], timeframe).timeframes if series? && timeframe?
|
65
|
-
end
|
66
|
-
|
67
76
|
# Internal: Check if options include filter.
|
68
77
|
def filter?
|
69
78
|
options[:filter].present?
|
@@ -71,23 +80,24 @@ module Sleek
|
|
71
80
|
|
72
81
|
# Internal: Check if options include timeframe.
|
73
82
|
def timeframe?
|
74
|
-
|
83
|
+
timeframe.present?
|
75
84
|
end
|
76
85
|
|
77
|
-
# Internal:
|
78
|
-
def
|
79
|
-
options[:
|
86
|
+
# Internal: Get group_by property.
|
87
|
+
def group_by
|
88
|
+
options[:group_by]
|
89
|
+
end
|
90
|
+
|
91
|
+
# Internal: Get the target property.
|
92
|
+
def target_property
|
93
|
+
if options[:target_property].present?
|
94
|
+
"d.#{options[:target_property]}"
|
95
|
+
end
|
80
96
|
end
|
81
97
|
|
82
98
|
# Internal: Run the query.
|
83
99
|
def run
|
84
|
-
|
85
|
-
series.map do |tf|
|
86
|
-
{ timeframe: tf, value: perform(events(tf.to_time_range)) }
|
87
|
-
end
|
88
|
-
else
|
89
|
-
perform(events)
|
90
|
-
end
|
100
|
+
perform(events)
|
91
101
|
end
|
92
102
|
|
93
103
|
# Internal: Perform the query on a set of events.
|
@@ -97,8 +107,31 @@ module Sleek
|
|
97
107
|
|
98
108
|
# Internal: Validate the options.
|
99
109
|
def valid_options?
|
100
|
-
options.is_a?(Hash) &&
|
101
|
-
(
|
110
|
+
options.is_a?(Hash) &&
|
111
|
+
(filter? ? options[:filter].is_a?(Array) : true) &&
|
112
|
+
(require_target_property? ? options[:target_property].present? : true)
|
113
|
+
end
|
114
|
+
|
115
|
+
class << self
|
116
|
+
# Public: Indicate that the query requires target property.
|
117
|
+
#
|
118
|
+
# Examples
|
119
|
+
#
|
120
|
+
# class SomeQuery < Query
|
121
|
+
# require_target_property!
|
122
|
+
#
|
123
|
+
# def perform(events)
|
124
|
+
# ...
|
125
|
+
# end
|
126
|
+
# end
|
127
|
+
def require_target_property!
|
128
|
+
@require_target_property = true
|
129
|
+
end
|
130
|
+
|
131
|
+
# Public: Check if the query requires target property.
|
132
|
+
def require_target_property?
|
133
|
+
!!@require_target_property
|
134
|
+
end
|
102
135
|
end
|
103
136
|
end
|
104
137
|
end
|
data/lib/sleek/queries/sum.rb
CHANGED
@@ -4,7 +4,7 @@ module Sleek
|
|
4
4
|
|
5
5
|
# Inernal: Initialize query collection.
|
6
6
|
#
|
7
|
-
# namespace - the
|
7
|
+
# namespace - the Sleek::Namespace object.
|
8
8
|
def initialize(namespace)
|
9
9
|
@namespace = namespace
|
10
10
|
end
|
@@ -14,12 +14,12 @@ module Sleek
|
|
14
14
|
klass = "Sleek::Queries::#{name.to_s.camelize}".constantize
|
15
15
|
|
16
16
|
define_method(name) do |bucket, options = {}|
|
17
|
-
|
17
|
+
QueryCommand.new(klass, namespace, bucket, options).run
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
21
|
def inspect
|
22
|
-
"#<Sleek::QueryCollection ns=#{namespace}>"
|
22
|
+
"#<Sleek::QueryCollection ns=#{namespace.name}>"
|
23
23
|
end
|
24
24
|
end
|
25
25
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Sleek
|
2
|
+
# Internal: A query command. It's primarily responsible for breaking a
|
3
|
+
# timeframe into intervals (if applicable), running the query on each
|
4
|
+
# sub-timeframe, and wrapping up a result.
|
5
|
+
class QueryCommand
|
6
|
+
attr_reader :klass, :namespace, :bucket, :options
|
7
|
+
|
8
|
+
# Internal: Initialize the query command.
|
9
|
+
#
|
10
|
+
# klass - the Sleek::Queries::Query subclass.
|
11
|
+
# namespace - the Sleek::Namespace object.
|
12
|
+
# bucket - the String bucket name.
|
13
|
+
# options - the optional Hash of options. Everything but
|
14
|
+
# :timeframe and :interval will be passed on to the
|
15
|
+
# query class.
|
16
|
+
# :timeframe - the optional timeframe description.
|
17
|
+
# :timezone - the optional TZ identifier.
|
18
|
+
# :interval - the optional interval description. If
|
19
|
+
# passed, requires that :timeframe is passed
|
20
|
+
# as well.
|
21
|
+
#
|
22
|
+
# Raises ArgumentError if :interval is passed but :timeframe is not.
|
23
|
+
def initialize(klass, namespace, bucket, options = {})
|
24
|
+
@klass = klass
|
25
|
+
@namespace = namespace
|
26
|
+
@bucket = bucket
|
27
|
+
@timeframe = options.delete(:timeframe)
|
28
|
+
@timezone = options.delete(:timezone)
|
29
|
+
@interval = options.delete(:interval)
|
30
|
+
@options = options
|
31
|
+
|
32
|
+
if @interval.present? && @timeframe.blank?
|
33
|
+
raise ArgumentError, 'interval requires timeframe'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Internal: Check if options include interval.
|
38
|
+
def series?
|
39
|
+
@interval.present?
|
40
|
+
end
|
41
|
+
|
42
|
+
# Internal: Parse a time range from the timeframe description.
|
43
|
+
# description.
|
44
|
+
def timeframe
|
45
|
+
Sleek::Timeframe.to_range(@timeframe, @timezone) if @timeframe
|
46
|
+
end
|
47
|
+
|
48
|
+
# Internal: Split timeframe into sub-timeframes of interval.
|
49
|
+
def series
|
50
|
+
Sleek::Interval.new(@interval, timeframe).timeframes
|
51
|
+
end
|
52
|
+
|
53
|
+
# Internal: Instantiate a query object.
|
54
|
+
#
|
55
|
+
# timeframe - the time range.
|
56
|
+
def new_query(timeframe)
|
57
|
+
klass.new(namespace, bucket, options.merge(timeframe: timeframe))
|
58
|
+
end
|
59
|
+
|
60
|
+
# Internal: Run the query on each timeframe.
|
61
|
+
def run
|
62
|
+
if series?
|
63
|
+
series.map do |tf|
|
64
|
+
{ timeframe: tf, value: new_query(tf).run }
|
65
|
+
end
|
66
|
+
else
|
67
|
+
new_query(timeframe).run
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/sleek/timeframe.rb
CHANGED
@@ -1,40 +1,6 @@
|
|
1
1
|
module Sleek
|
2
2
|
class Timeframe
|
3
|
-
|
4
|
-
#
|
5
|
-
# timeframe - either a range of Time objects, an array of two Time
|
6
|
-
# objects, or a special string.
|
7
|
-
#
|
8
|
-
# Possible special string format:
|
9
|
-
# (this|previous)_((\d+)_)?(minute|hour|day|week|month)s?
|
10
|
-
#
|
11
|
-
# Examples
|
12
|
-
#
|
13
|
-
# Timeframe.new "this_2_days"
|
14
|
-
#
|
15
|
-
# Timeframe.new "previous_hour"
|
16
|
-
def initialize(timeframe)
|
17
|
-
@timeframe = timeframe
|
18
|
-
end
|
19
|
-
|
20
|
-
# Public: Get timeframe start.
|
21
|
-
def start
|
22
|
-
to_time_range.begin
|
23
|
-
end
|
24
|
-
|
25
|
-
# Public: Get timeframe end.
|
26
|
-
def end
|
27
|
-
to_time_range.end
|
28
|
-
end
|
29
|
-
|
30
|
-
# Internal: Convert Timeframe instance to a range of Time objects.
|
31
|
-
def to_time_range
|
32
|
-
@range ||= self.class.to_range(@timeframe)
|
33
|
-
end
|
34
|
-
|
35
|
-
def inspect
|
36
|
-
"#<Sleek::Timeframe #{to_time_range}>"
|
37
|
-
end
|
3
|
+
REGEXP = /(this|previous)_((\d+)_)?(minute|hour|day|week|month)s?/
|
38
4
|
|
39
5
|
class << self
|
40
6
|
# Internal: Transform the object passed to Timeframe initializer
|
@@ -42,44 +8,71 @@ module Sleek
|
|
42
8
|
#
|
43
9
|
# timeframe - either a Range of Time objects, a two-element array
|
44
10
|
# of Time Objects, or a special string.
|
11
|
+
# timezone - the optional String TZ identifier. See
|
12
|
+
# `ActiveSupport::TimeZone`.
|
13
|
+
#
|
14
|
+
# Examples
|
15
|
+
#
|
16
|
+
# Timeframe.to_range :this_2_days
|
17
|
+
#
|
18
|
+
# Timeframe.to_range :previous_hour
|
19
|
+
#
|
20
|
+
# Timeframe.to_range :this_day, timezone: 'US/Pacific'
|
45
21
|
#
|
46
22
|
# Raises ArgumentError if passed object can't be processed.
|
47
|
-
def to_range(timeframe)
|
23
|
+
def to_range(timeframe, timezone = nil)
|
48
24
|
case timeframe
|
49
|
-
when Range
|
50
|
-
t = timeframe
|
51
|
-
when Array
|
52
|
-
t = timeframe.first..timeframe.last
|
25
|
+
when proc { |tf| tf.is_a?(Range) && tf.time_range? }
|
26
|
+
t = timeframe
|
27
|
+
when proc { |tf| tf.is_a?(Array) && tf.size == 2 && tf.count { |_tf| _tf.is_a?(Time) } == 2 }
|
28
|
+
t = timeframe.first..timeframe.last
|
53
29
|
when String, Symbol
|
54
|
-
t = parse(timeframe.to_s)
|
30
|
+
t = parse(timeframe.to_s, timezone)
|
31
|
+
else
|
32
|
+
raise ArgumentError, "wrong timeframe - #{timeframe}"
|
55
33
|
end
|
56
|
-
|
57
|
-
raise ArgumentError, "wrong timeframe" unless t
|
58
|
-
t
|
59
34
|
end
|
60
35
|
|
61
36
|
# Internal: Process timeframe string to make up a range.
|
62
37
|
#
|
63
38
|
# timeframe - the String matching
|
64
39
|
# (this|previous)_((\d+)_)?(minute|hour|day|week|month)s?
|
65
|
-
|
66
|
-
|
67
|
-
|
40
|
+
# timezone - the optional String TZ identifier. See
|
41
|
+
# `ActiveSupport::TimeZone`.
|
42
|
+
def parse(timeframe, timezone = nil)
|
43
|
+
_, category, _, number, interval = *timeframe.match(REGEXP)
|
68
44
|
|
69
45
|
unless category && interval
|
70
|
-
raise ArgumentError,
|
46
|
+
raise ArgumentError, 'special timeframe string is malformed'
|
71
47
|
end
|
72
48
|
|
73
49
|
number ||= 1
|
74
50
|
number = number.to_i
|
75
51
|
|
76
|
-
|
77
|
-
start_point = end_point - number.send(interval)
|
78
|
-
|
79
|
-
range = start_point..end_point
|
52
|
+
range = range_from_interval(interval, number, timezone)
|
80
53
|
range = range - 1.send(interval) if category == 'previous'
|
81
54
|
range
|
82
55
|
end
|
56
|
+
|
57
|
+
# Internal: Create a time range from interval type & number of
|
58
|
+
# intervals.
|
59
|
+
#
|
60
|
+
# interval - the String interval type name. Valid values are
|
61
|
+
# minute, hour, day, week, and month.
|
62
|
+
# number - the Integer number of periods.
|
63
|
+
# timezone - the optional String TZ identifier. See
|
64
|
+
# `ActiveSupport::TimeZone`.
|
65
|
+
#
|
66
|
+
# Returns the range of TimeWithZone objects.
|
67
|
+
def range_from_interval(interval, number = 1, timezone = nil)
|
68
|
+
timezone ||= 'UTC'
|
69
|
+
now = ActiveSupport::TimeZone.new(timezone).now
|
70
|
+
|
71
|
+
end_point = now.send("end_of_#{interval}").round
|
72
|
+
start_point = end_point - number.send(interval)
|
73
|
+
|
74
|
+
start_point..end_point
|
75
|
+
end
|
83
76
|
end
|
84
77
|
end
|
85
78
|
end
|