mdash 0.1.0 → 0.2.0
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/README.md +293 -1
- data/app/controllers/mdash/announce_controller.rb +9 -5
- data/app/controllers/mdash/stats_controller.rb +4 -2
- data/app/models/mdash/metric.rb +7 -5
- data/lib/generators/mdash/templates/config/initializers/mdash.rb.tt +4 -0
- data/lib/mdash/configuration.rb +23 -9
- data/lib/mdash/version.rb +1 -1
- data/lib/mdash.rb +6 -3
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 99e02fcb481a90b671b153894b496e402c72e5507ec3c5905de47290395e5161
|
4
|
+
data.tar.gz: 63a4ea369081f75e2515677df81f35261637da50195586dc9bdf4c0c39cd90e0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 67a995b78d3f381fa6c54333ed805fc16ebd8ffa6ed9e3e39522a9bd552dde5f77e9f9985e5bfe9c96c1e15f2b5ae33b3ff149f059cd03d69e16d9e04e892567
|
7
|
+
data.tar.gz: 54fcde0b2ef3cb0fc2784e632efb9e66a9b409c456bf24a58284f73f4a5f6e156ec9f42ac7f27645c6354c424d10a9d00bcc838ea1ac7ab32ea9d067e35198d4
|
data/README.md
CHANGED
@@ -35,9 +35,301 @@ Mount the Mdash engine in your routes file
|
|
35
35
|
mount Mdash::Engine => "/mdash"
|
36
36
|
```
|
37
37
|
|
38
|
+
#### Configuration
|
39
|
+
|
40
|
+
There are three main sections to be configured to properly setup Mdash in your Rails app.
|
41
|
+
|
42
|
+
##### Site Name
|
43
|
+
|
44
|
+
This is the name of your site that will be displayed in the Mdash dashboard when setting up widgets.
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
config.site_name = "Your Site Name"
|
48
|
+
```
|
49
|
+
|
50
|
+
##### Secret
|
51
|
+
|
52
|
+
This is the secret key that will be used to authenticate requests from Mdash to your app.
|
53
|
+
In the future we plan to support authing against Mdash and storing the secret on our service but for now direct connections are the only way
|
54
|
+
|
55
|
+
Requirements:
|
56
|
+
- Must be a string
|
57
|
+
- Must be at least 10 characters long but really use a longer one, please
|
58
|
+
- Ideally store this in your Rails credentials file or a secret manager
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
config.secret = "a-really-long-secret-key"
|
62
|
+
```
|
63
|
+
|
64
|
+
##### Exported Metrics
|
65
|
+
|
66
|
+
This is where you define the metrics that you want to expose to Mdash.
|
67
|
+
Mdash will have predefined templates for common metrics but you can also define your own custom metrics.
|
68
|
+
|
69
|
+
A metric is defined by the following attributes:
|
70
|
+
- `model` - The name of the model that the metric is based on
|
71
|
+
- `metrics` - A hash of metrics that you want to expose
|
72
|
+
- `aggregation` - The type of aggregation to perform on the model
|
73
|
+
- `aggregation_field` - The field to aggregate on, defaults to `id`
|
74
|
+
- `period` - The period over which to aggregate the metric
|
75
|
+
- `periods` - The number of periods to aggregate over
|
76
|
+
- `modifier` - A modifier to apply to the aggregation
|
77
|
+
|
78
|
+
Valid aggregations are:
|
79
|
+
- `:count` - Count the number of records
|
80
|
+
- `:sum` - Sum the values of a column
|
81
|
+
- `:average` - Average the values of a column
|
82
|
+
|
83
|
+
If you define an aggregation of sum or average you should also define the field to aggregate on with the `aggregation_field` attribute.
|
84
|
+
- String
|
85
|
+
- Default is `id`
|
86
|
+
|
87
|
+
Valid period values are:
|
88
|
+
- `nil` - No period, aggregate over all time
|
89
|
+
- `:hour` - Aggregate over an hour
|
90
|
+
- `:day` - Aggregate over a day
|
91
|
+
- `:week` - Aggregate over a week
|
92
|
+
- `:month` - Aggregate over a month
|
93
|
+
- `:year` - Aggregate over a year
|
94
|
+
|
95
|
+
If you define a period you can also define the number of periods to aggregate over with the `periods` attribute.
|
96
|
+
- Integer > 1
|
97
|
+
- Default is 1
|
98
|
+
- If periods is set then we will return an object of values even if periods is 1
|
99
|
+
|
100
|
+
If you define a modifier you can also define the modifier with the `modifier` attribute.
|
101
|
+
- String
|
102
|
+
- Default is nil
|
103
|
+
- Must be a method that exists on the model
|
104
|
+
|
105
|
+
##### Example Config
|
106
|
+
|
107
|
+
Here's a real world example from https://timewith.xyz
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
Mdash.configure do |config|
|
111
|
+
config.site_name = "Timewith"
|
112
|
+
config.secret = Rails.application.credentials.dig(:mdash, :secret)
|
113
|
+
|
114
|
+
config.exported_metrics = {
|
115
|
+
users: {
|
116
|
+
model: "Account",
|
117
|
+
metrics: {
|
118
|
+
total: {
|
119
|
+
aggregation: :count,
|
120
|
+
},
|
121
|
+
recent_signups: {
|
122
|
+
aggregation: :count,
|
123
|
+
period: :week,
|
124
|
+
},
|
125
|
+
weekly_signups: {
|
126
|
+
aggregation: :count,
|
127
|
+
period: :week,
|
128
|
+
periods: 12,
|
129
|
+
}
|
130
|
+
}
|
131
|
+
},
|
132
|
+
profiles: {
|
133
|
+
model: "Profile",
|
134
|
+
metrics: {
|
135
|
+
total: {
|
136
|
+
aggregation: :count,
|
137
|
+
}
|
138
|
+
}
|
139
|
+
},
|
140
|
+
events: {
|
141
|
+
model: "Event",
|
142
|
+
metrics: {
|
143
|
+
total: {
|
144
|
+
aggregation: :count,
|
145
|
+
}
|
146
|
+
}
|
147
|
+
},
|
148
|
+
bookings: {
|
149
|
+
model: "Booking",
|
150
|
+
metrics: {
|
151
|
+
total: {
|
152
|
+
aggregation: :count,
|
153
|
+
},
|
154
|
+
recent: {
|
155
|
+
aggregation: :count,
|
156
|
+
period: :week,
|
157
|
+
},
|
158
|
+
weekly: {
|
159
|
+
aggregation: :count,
|
160
|
+
period: :week,
|
161
|
+
periods: 12,
|
162
|
+
},
|
163
|
+
cancelled_total: {
|
164
|
+
aggregation: :count,
|
165
|
+
modifier: "cancelled",
|
166
|
+
},
|
167
|
+
cancelled_recent: {
|
168
|
+
aggregation: :count,
|
169
|
+
period: :week,
|
170
|
+
modifier: "cancelled",
|
171
|
+
},
|
172
|
+
}
|
173
|
+
}
|
174
|
+
}
|
175
|
+
end
|
176
|
+
```
|
177
|
+
|
178
|
+
## Consuming
|
179
|
+
|
180
|
+
The simplest way to consume the metrics from Mdash is to use the Mdash App (coming soon).
|
181
|
+
|
182
|
+
If you want to consume the metrics in your own app you can use the Mdash API.
|
183
|
+
|
184
|
+
#### API
|
185
|
+
|
186
|
+
The Mdash API is a simple RESTful API that allows you to fetch the metrics that you have defined in your Rails app.
|
187
|
+
|
188
|
+
##### Authentication
|
189
|
+
|
190
|
+
To authenticate with the Mdash API you need to include the secret key that you defined in your Rails app as a header "X-Mdash-Token"
|
191
|
+
|
192
|
+
##### Announce
|
193
|
+
|
194
|
+
The announce endpoint contains a list of all the valid metrics that you have defined in your Rails app.
|
195
|
+
|
196
|
+
```http
|
197
|
+
GET /mdash/announce
|
198
|
+
```
|
199
|
+
|
200
|
+
```json
|
201
|
+
{
|
202
|
+
"site_name": "Timewith",
|
203
|
+
"metrics": [
|
204
|
+
{
|
205
|
+
"name": "users_total",
|
206
|
+
"model": "Account",
|
207
|
+
"aggregation": "count",
|
208
|
+
"aggregation_field": "id",
|
209
|
+
"period": null,
|
210
|
+
"periods": null,
|
211
|
+
"modifier": null
|
212
|
+
},
|
213
|
+
{
|
214
|
+
"name": "users_recent_signups",
|
215
|
+
"model": "Account",
|
216
|
+
"aggregation": "count",
|
217
|
+
"aggregation_field": "id",
|
218
|
+
"period": "week",
|
219
|
+
"periods": null,
|
220
|
+
"modifier": null
|
221
|
+
},
|
222
|
+
{
|
223
|
+
"name": "users_weekly_signups",
|
224
|
+
"model": "Account",
|
225
|
+
"aggregation": "count",
|
226
|
+
"aggregation_field": "id",
|
227
|
+
"period": "week",
|
228
|
+
"periods": 12,
|
229
|
+
"modifier": null
|
230
|
+
},
|
231
|
+
{
|
232
|
+
"name": "profiles_total",
|
233
|
+
"model": "Profile",
|
234
|
+
"aggregation": "count",
|
235
|
+
"aggregation_field": "id",
|
236
|
+
"period": null,
|
237
|
+
"periods": null,
|
238
|
+
"modifier": null
|
239
|
+
},
|
240
|
+
{
|
241
|
+
"name": "events_total",
|
242
|
+
"model": "Event",
|
243
|
+
"aggregation": "count",
|
244
|
+
"aggregation_field": "id",
|
245
|
+
"period": null,
|
246
|
+
"periods": null,
|
247
|
+
"modifier": null
|
248
|
+
},
|
249
|
+
{
|
250
|
+
"name": "bookings_total",
|
251
|
+
"model": "Booking",
|
252
|
+
"aggregation": "count",
|
253
|
+
"aggregation_field": "id",
|
254
|
+
"period": null,
|
255
|
+
"periods": null,
|
256
|
+
"modifier": null
|
257
|
+
},
|
258
|
+
{
|
259
|
+
"name": "bookings_recent",
|
260
|
+
"model": "Booking",
|
261
|
+
"aggregation": "count",
|
262
|
+
"aggregation_field": "id",
|
263
|
+
"period": "week",
|
264
|
+
"periods": null,
|
265
|
+
"modifier": null
|
266
|
+
},
|
267
|
+
{
|
268
|
+
"name": "bookings_weekly",
|
269
|
+
"model": "Booking",
|
270
|
+
"aggregation": "count",
|
271
|
+
"aggregation_field": "id",
|
272
|
+
"period": "week",
|
273
|
+
"periods": 12,
|
274
|
+
"modifier": null
|
275
|
+
},
|
276
|
+
{
|
277
|
+
"name": "bookings_cancelled_total",
|
278
|
+
"model": "Booking",
|
279
|
+
"aggregation": "count",
|
280
|
+
"aggregation_field": "id",
|
281
|
+
"period": null,
|
282
|
+
"periods": null,
|
283
|
+
"modifier": "cancelled"
|
284
|
+
},
|
285
|
+
{
|
286
|
+
"name": "bookings_cancelled_recent",
|
287
|
+
"model": "Booking",
|
288
|
+
"aggregation": "count",
|
289
|
+
"aggregation_field": "id",
|
290
|
+
"period": "week",
|
291
|
+
"periods": null,
|
292
|
+
"modifier": "cancelled"
|
293
|
+
}
|
294
|
+
]
|
295
|
+
}
|
296
|
+
```
|
297
|
+
|
298
|
+
##### Stats
|
299
|
+
|
300
|
+
The stats endpoint allows you to fetch the values of the metrics that you have defined in your Rails app.
|
301
|
+
Stats contains the rollup values for each metric.
|
302
|
+
Last updated is the time that the stats were last updated (ie. if they're stale or cached)
|
303
|
+
|
304
|
+
```http
|
305
|
+
GET /mdash/stats
|
306
|
+
```
|
307
|
+
|
308
|
+
```json
|
309
|
+
{
|
310
|
+
"stats": {
|
311
|
+
"users_total": 1,
|
312
|
+
"users_recent_signups": 0,
|
313
|
+
"users_weekly_signups": {
|
314
|
+
"2025-01-05": 1
|
315
|
+
},
|
316
|
+
"profiles_total": 2,
|
317
|
+
"events_total": 2,
|
318
|
+
"bookings_total": 4,
|
319
|
+
"bookings_recent": 0,
|
320
|
+
"bookings_weekly": {
|
321
|
+
"2025-01-05": 4
|
322
|
+
},
|
323
|
+
"bookings_cancelled_total": 0,
|
324
|
+
"bookings_cancelled_recent": 0
|
325
|
+
},
|
326
|
+
"last_updated": "2025-01-05T00:00:00Z"
|
327
|
+
}
|
328
|
+
```
|
329
|
+
|
38
330
|
## Contributing
|
39
331
|
|
40
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/imothee/mdash. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/imothee/policygen/blob/main/CODE_OF_CONDUCT.md).
|
332
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/imothee/mdash-rails. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/imothee/policygen/blob/main/CODE_OF_CONDUCT.md).
|
41
333
|
|
42
334
|
## License
|
43
335
|
|
@@ -1,18 +1,22 @@
|
|
1
1
|
module Mdash
|
2
2
|
class AnnounceController < ApplicationController
|
3
3
|
def index
|
4
|
-
|
5
|
-
|
4
|
+
metrics = Mdash.config.metrics.map { |metric|
|
5
|
+
{
|
6
|
+
name: metric.id,
|
6
7
|
model: metric.model,
|
7
8
|
aggregation: metric.aggregation,
|
8
|
-
|
9
|
+
aggregation_field: metric.aggregation_field,
|
9
10
|
period: metric.period,
|
10
11
|
periods: metric.periods,
|
11
12
|
modifier: metric.modifier
|
12
13
|
}
|
13
|
-
|
14
|
+
}
|
14
15
|
|
15
|
-
render json:
|
16
|
+
render json: {
|
17
|
+
site_name: Mdash.config.site_name,
|
18
|
+
metrics: metrics
|
19
|
+
}.as_json
|
16
20
|
end
|
17
21
|
end
|
18
22
|
end
|
data/app/models/mdash/metric.rb
CHANGED
@@ -7,7 +7,7 @@ module Mdash
|
|
7
7
|
# Valid periods
|
8
8
|
PERIODS = %i[hour day week month year].freeze
|
9
9
|
|
10
|
-
attr_reader :id, :model, :aggregation, :
|
10
|
+
attr_reader :id, :model, :aggregation, :aggregation_field, :period, :periods, :modifier
|
11
11
|
|
12
12
|
def self.all(configuration: nil)
|
13
13
|
configuration ||= Mdash.config
|
@@ -20,11 +20,11 @@ module Mdash
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
def initialize(id:, model:, aggregation:,
|
23
|
+
def initialize(id:, model:, aggregation:, aggregation_field: :id, period: nil, periods: nil, modifier: nil)
|
24
24
|
@id = id.to_sym
|
25
25
|
@model = model.to_sym
|
26
26
|
@aggregation = aggregation.to_sym
|
27
|
-
@
|
27
|
+
@aggregation_field = aggregation_field.to_sym
|
28
28
|
@period = period&.to_sym
|
29
29
|
@periods = periods
|
30
30
|
@modifier = modifier
|
@@ -34,6 +34,8 @@ module Mdash
|
|
34
34
|
return false unless AGGREGATIONS.include?(@aggregation)
|
35
35
|
return false unless PERIODS.include?(@period) if @period.present?
|
36
36
|
|
37
|
+
return false if @periods.present? && !@periods.positive?
|
38
|
+
|
37
39
|
true
|
38
40
|
end
|
39
41
|
|
@@ -82,9 +84,9 @@ module Mdash
|
|
82
84
|
def aggregation_query(query)
|
83
85
|
case @aggregation
|
84
86
|
when :sum
|
85
|
-
query.sum(@
|
87
|
+
query.sum(@aggregation_field)
|
86
88
|
when :avg
|
87
|
-
query.average(@
|
89
|
+
query.average(@aggregation_field)
|
88
90
|
when :count
|
89
91
|
query.count
|
90
92
|
end
|
@@ -1,6 +1,10 @@
|
|
1
1
|
# Use this setup block to configure all options available in Mdash
|
2
2
|
Mdash.configure do |config|
|
3
|
+
config.site_name = "<%= Rails.application.class.module_parent.name %>"
|
3
4
|
config.secret = "<%= SecureRandom.hex(32) %>"
|
4
5
|
|
6
|
+
# How long should we cache metrics for so we don't hit the database too often?
|
7
|
+
# config.cache_expiry = 5.minutes
|
8
|
+
|
5
9
|
config.exported_metrics = {}
|
6
10
|
end
|
data/lib/mdash/configuration.rb
CHANGED
@@ -1,19 +1,33 @@
|
|
1
1
|
module Mdash
|
2
2
|
class Configuration
|
3
|
+
attr_accessor :site_name
|
3
4
|
attr_accessor :secret
|
5
|
+
attr_accessor :cache_expiry
|
4
6
|
attr_accessor :exported_metrics
|
5
7
|
|
8
|
+
def initialize
|
9
|
+
@cache_expiry = 5.minutes
|
10
|
+
end
|
11
|
+
|
6
12
|
def metrics
|
7
|
-
@metrics ||= exported_metrics.
|
8
|
-
model
|
9
|
-
params[:
|
10
|
-
|
13
|
+
@metrics ||= exported_metrics.each_with_object([]) do |(prefix, params), arr|
|
14
|
+
# Check if the model exists, if not return
|
15
|
+
next Rails.logger.warn("Invalid model: #{params[:model]}") unless params[:model].to_s.classify.safe_constantize
|
16
|
+
|
17
|
+
params[:metrics].each do |k, metric_params|
|
18
|
+
id = "#{prefix}_#{k}".to_sym
|
19
|
+
# Check if the metric is a hash
|
20
|
+
next Rails.logger.warn("Metric is not a hash: #{id}") unless metric_params.is_a?(Hash)
|
21
|
+
# Check if metric id is unique
|
22
|
+
next Rails.logger.warn("Duplicate metric: #{id}") if arr.any? { |m| m.id == id }
|
23
|
+
|
24
|
+
# Create the metric
|
25
|
+
metric = Metric.new(id:, model: params[:model], **metric_params)
|
26
|
+
|
11
27
|
# Check if the metric is valid
|
12
|
-
unless metric.valid?
|
13
|
-
|
14
|
-
|
15
|
-
end
|
16
|
-
metric
|
28
|
+
next Rails.logger.warn("Invalid metric: #{id}") unless metric.valid?
|
29
|
+
|
30
|
+
arr << metric
|
17
31
|
end
|
18
32
|
end
|
19
33
|
end
|
data/lib/mdash/version.rb
CHANGED
data/lib/mdash.rb
CHANGED
@@ -18,15 +18,18 @@ module Mdash
|
|
18
18
|
def stats
|
19
19
|
# Check the last time stats were updated
|
20
20
|
# If it's been more than 5 minutes, update the stats
|
21
|
-
if @last_updated.nil? || @last_updated <
|
22
|
-
@stats = config.metrics.
|
21
|
+
if @last_updated.nil? || @last_updated < config.cache_expiry.ago
|
22
|
+
@stats = config.metrics.each_with_object({}) do |metric, stats|
|
23
23
|
stats[metric.id] = metric.data
|
24
|
-
stats
|
25
24
|
end
|
26
25
|
@last_updated = Time.now
|
27
26
|
end
|
28
27
|
@stats
|
29
28
|
end
|
29
|
+
|
30
|
+
def last_updated
|
31
|
+
@last_updated
|
32
|
+
end
|
30
33
|
end
|
31
34
|
end
|
32
35
|
|