plausible_api 0.3 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3164447cd3eac3c7a7cbf0c7e7cccd36eaf2bd195966283fc0da4ad023b7b21e
4
- data.tar.gz: 34acbb3e21f80bd3537d4273b6555dea0da7e4fdb5cf3dcbf8bede5d477e8ce0
3
+ metadata.gz: 45537f5b6e81b09eb72e08831934672798f3cdbd9af9d003ac38680a2116cdd7
4
+ data.tar.gz: eaaa12cf820d0c2462670881cac678c2d58be6e763205291b271357d74eb78ac
5
5
  SHA512:
6
- metadata.gz: 03273d140652347b6b282d94ff0c6294746fc7bd410dcaa1feab54a627d80ed64fa8eb1c9c56c0a3a0e3e2a95cb9cda755501987f46a3b795cb9dc2862aa1e01
7
- data.tar.gz: b6e84673b3763918a28488c717e3be089107571ecb1b357b4d7e2b1ded589b68eade9d65f615e70a6655b4571a73c7bf767a0af3363b6ca90631cd0810ea91e8
6
+ metadata.gz: e4c268ef6298bef18548b52d6484193d065dcdee251f4a5c741cb77abafb0aecc4d86249c13a1ce0982491701c3d1a7d6d98c6b20f7456a091c197eb494b23e5
7
+ data.tar.gz: f8aaac22771ab3e1ec2a542414402d2eeb67e5b59aa5363dd4b78bb8bb51f2ecf5d6d4f46b4a01ecae173a3f24bb347bbc98e2cbd20a7146c1ad38b7d3360f8a
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- plausible_api (0.3)
4
+ plausible_api (0.4.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -7,15 +7,38 @@ Add this gem to your Gemfile:
7
7
  ```rb
8
8
  gem 'plausible_api'
9
9
  ```
10
- Then you need to initialize a Client with your `site_id` (the domain) and your `token`.
10
+ Then you need to initialize a Client with your `site_id` (the domain) and your `api_key`.
11
+ Optionally, you can pass a third parameter in case you are using a self-hosted instance of Plausible (You don't need to add this third parameter if your are using the comercial version of Plausible).
11
12
  ```rb
12
- c = PlausibleApi::Client.new('dailytics.com', '123123')
13
+ # Using the comercial version:
14
+ c = PlausibleApi::Client.new("mysite.com", "MYAPIKEY")
15
+
16
+ # Using a self hosted instance
17
+ c = PlausibleApi::Client.new("mysite.com", "MYAPIKEY", "https://my-hosted-plausible.com")
13
18
 
14
19
  # Test if the site and token are valid
15
20
  c.valid?
16
21
  => true
17
22
  ```
18
23
 
24
+ If you will always work with the same site, you can set some (or all) of these 3 parameters
25
+ before initializing the client. On a Ruby on Rails app, you can add this to an initializer like
26
+ `config/initializers/plausible.rb`
27
+
28
+ ```rb
29
+ # Do not include a trailing slash
30
+ PlausibleApi.configure do |config|
31
+ config.base_url = "https://your-plausible-instance.com"
32
+ config.site_id = "dailytics.com"
33
+ config.api_key = "123123"
34
+ end
35
+ ```
36
+
37
+ And then, initializing the client simply like this:
38
+ ```rb
39
+ c = PlausibleApi::Client.new
40
+ ```
41
+
19
42
  ### Stats > Aggregate
20
43
 
21
44
  You have all these options to get the aggregate stats
@@ -87,18 +110,6 @@ c.event({
87
110
  })
88
111
  ```
89
112
 
90
-
91
- ### Self-hosted Plausible instances
92
-
93
- If you are using a self-hosted Plausible instance, you can set the `base_url` before initializing the client. On a Ruby on Rails app, you can add this to an initializer like `config/initializers/plausible.rb`
94
-
95
- ```rb
96
- # Do not include a trailing slash
97
- PlausibleApi.configure do |config|
98
- config.base_url = "https://your-plausible-instance.com"
99
- end
100
- ```
101
-
102
113
  ## Development
103
114
 
104
115
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -0,0 +1,41 @@
1
+ module PlausibleApi
2
+ class ApiBase < Utils
3
+ def request_class
4
+ # Net::HTTP::Post
5
+ raise NotImplementedError
6
+ end
7
+
8
+ def request_path
9
+ # "/api/event"
10
+ raise NotImplementedError
11
+ end
12
+
13
+ def request_auth?
14
+ true
15
+ end
16
+
17
+ def request_body
18
+ nil
19
+ end
20
+
21
+ def request_body?
22
+ present?(request_body)
23
+ end
24
+
25
+ def request_headers
26
+ {"content-type" => "application/json"}
27
+ end
28
+
29
+ def parse_response(body)
30
+ raise NotImplementedError
31
+ end
32
+
33
+ def errors
34
+ raise NotImplementedError
35
+ end
36
+
37
+ def valid?
38
+ errors.empty?
39
+ end
40
+ end
41
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "plausible_api/utils"
4
+ require "plausible_api/api_base"
3
5
  require "plausible_api/stats/base"
4
6
  require "plausible_api/stats/realtime/visitors"
5
7
  require "plausible_api/stats/aggregate"
@@ -14,10 +16,15 @@ require "uri"
14
16
  require "cgi"
15
17
 
16
18
  module PlausibleApi
17
- class Client
18
- def initialize(site_id, token)
19
- @site_id = site_id.to_s
20
- @token = token.to_s
19
+ class Client < Utils
20
+
21
+ attr_accessor :configuration
22
+
23
+ def initialize(site_id = nil, api_key = nil, base_url = nil)
24
+ @configuration = Configuration.new
25
+ @configuration.api_key = presence(api_key) || PlausibleApi.configuration.api_key
26
+ @configuration.site_id = presence(site_id) || PlausibleApi.configuration.site_id
27
+ @configuration.base_url = presence(base_url) || PlausibleApi.configuration.base_url
21
28
  end
22
29
 
23
30
  def aggregate(options = {})
@@ -44,7 +51,7 @@ module PlausibleApi
44
51
  end
45
52
 
46
53
  def event(options = {})
47
- call PlausibleApi::Event::Post.new(options.merge(domain: @site_id))
54
+ call PlausibleApi::Event::Post.new(options.merge(domain: configuration.site_id))
48
55
  end
49
56
 
50
57
  private
@@ -53,14 +60,14 @@ module PlausibleApi
53
60
 
54
61
  def call(api)
55
62
  raise Error, api.errors unless api.valid?
56
- raise ConfigurationError, PlausibleApi.configuration.errors unless PlausibleApi.configuration.valid?
63
+ raise ConfigurationError, configuration.errors unless configuration.valid?
57
64
 
58
- url = "#{PlausibleApi.configuration.base_url}#{api.request_url.gsub("$SITE_ID", @site_id)}"
65
+ url = "#{configuration.base_url}#{api.request_path.gsub("$SITE_ID", configuration.site_id)}"
59
66
  uri = URI.parse(url)
60
67
 
61
68
  req = api.request_class.new(uri.request_uri)
62
69
  req.initialize_http_header(api.request_headers)
63
- req.add_field("authorization", "Bearer #{@token}") if api.request_auth?
70
+ req.add_field("authorization", "Bearer #{configuration.api_key}") if api.request_auth?
64
71
  req.body = api.request_body if api.request_body?
65
72
 
66
73
  http = Net::HTTP.new(uri.host, uri.port)
@@ -71,7 +78,7 @@ module PlausibleApi
71
78
  if SUCCESS_CODES.include?(response.code)
72
79
  api.parse_response response.body
73
80
  else
74
- raise StandardError.new response.body
81
+ raise Error.new response
75
82
  end
76
83
  end
77
84
  end
@@ -1,10 +1,13 @@
1
1
  module PlausibleApi
2
2
  class Configuration
3
- attr_accessor :base_url
3
+ attr_accessor :base_url, :default_user_agent, :api_key, :site_id
4
4
 
5
5
  # Setting up default values
6
6
  def initialize
7
7
  @base_url = "https://plausible.io"
8
+ @default_user_agent = "plausible_api_ruby/#{PlausibleApi::VERSION}"
9
+ @api_key = nil
10
+ @site_id = nil
8
11
  end
9
12
 
10
13
  def valid?
@@ -20,6 +23,12 @@ module PlausibleApi
20
23
  elsif base_url.end_with?("/")
21
24
  errors.push(base_url: "base_url should not end with a trailing slash")
22
25
  end
26
+ if api_key.nil? || api_key.empty?
27
+ errors.push(api_key: "api_key is required")
28
+ end
29
+ if site_id.nil? || site_id.empty?
30
+ errors.push(site_id: "site_id is required")
31
+ end
23
32
  errors
24
33
  end
25
34
  end
@@ -1,31 +1,17 @@
1
1
  module PlausibleApi
2
2
  module Event
3
- class Base
4
- DEFAULT_USER_AGENT = "plausible_api_ruby/#{PlausibleApi::VERSION}"
5
-
3
+ class Base < ApiBase
6
4
  def request_class
7
5
  Net::HTTP::Post
8
6
  end
9
7
 
10
- def request_url_base
8
+ def request_path
11
9
  "/api/event"
12
10
  end
13
11
 
14
- def request_url
15
- request_url_base
16
- end
17
-
18
12
  def request_auth?
19
13
  false
20
14
  end
21
-
22
- def request_body?
23
- true
24
- end
25
-
26
- def valid?
27
- errors.empty?
28
- end
29
15
  end
30
16
  end
31
17
  end
@@ -3,7 +3,6 @@
3
3
  module PlausibleApi
4
4
  module Event
5
5
  class Post < Base
6
- DEFAULT_USER_AGENT = "plausible_api_ruby/#{PlausibleApi::VERSION}"
7
6
  VALID_REVENUE_KEYS = %i[amount currency].freeze
8
7
  OPTIONS_IN_HEADERS = %i[ip user_agent].freeze
9
8
 
@@ -16,7 +15,7 @@ module PlausibleApi
16
15
 
17
16
  @domain = @options[:domain]
18
17
  @ip = @options[:ip]
19
- @user_agent = presence(@options[:user_agent]) || DEFAULT_USER_AGENT
18
+ @user_agent = presence(@options[:user_agent]) || PlausibleApi.configuration.default_user_agent
20
19
  @name = presence(@options[:name]) || "pageview"
21
20
  @url = presence(@options[:url]) || "app://localhost/#{@name}"
22
21
  @referrer = @options[:referrer]
@@ -60,10 +59,10 @@ module PlausibleApi
60
59
 
61
60
  if present?(@revenue)
62
61
  if @revenue.is_a?(Hash)
63
- unless valid_revenue_keys?(@revenue)
62
+ unless @revenue.keys.map(&:to_sym).all? { |key| VALID_REVENUE_KEYS.include?(key) }
64
63
  errors.push(
65
64
  revenue: "revenue must have keys #{VALID_REVENUE_KEYS.join(", ")} " \
66
- "but was #{@revenue.inspect}"
65
+ "but was #{@revenue.inspect}"
67
66
  )
68
67
  end
69
68
  else
@@ -83,19 +82,6 @@ module PlausibleApi
83
82
  def valid_revenue_keys?(revenue)
84
83
  revenue.keys.sort.map(&:to_sym) == VALID_REVENUE_KEYS.sort
85
84
  end
86
-
87
- def present?(value)
88
- !value.nil? && !value.empty?
89
- end
90
-
91
- def blank?(value)
92
- !present?(value)
93
- end
94
-
95
- def presence(value)
96
- return nil if blank?(value)
97
- value
98
- end
99
85
  end
100
86
  end
101
87
  end
@@ -3,20 +3,15 @@
3
3
  module PlausibleApi
4
4
  module Stats
5
5
  class Aggregate < Base
6
-
7
6
  def initialize(options = {})
8
- super({ period: '30d',
9
- metrics: 'visitors,visits,pageviews,views_per_visit,bounce_rate,visit_duration,events' }
10
- .merge(options))
7
+ super({period: "30d",
8
+ metrics: "visitors,visits,pageviews,views_per_visit,bounce_rate,visit_duration,events"}
9
+ .merge(options))
11
10
  end
12
11
 
13
- def request_url_base
12
+ def request_path_base
14
13
  "/api/v1/stats/aggregate?site_id=$SITE_ID"
15
14
  end
16
-
17
- def parse_response(body)
18
- JSON.parse(body)['results']
19
- end
20
15
  end
21
16
  end
22
17
  end
@@ -2,100 +2,79 @@
2
2
 
3
3
  module PlausibleApi
4
4
  module Stats
5
- class Base
5
+ class Base < ApiBase
6
+ ALLOWED_PERIODS = %w[12mo 6mo month 30d 7d day custom]
7
+ ALLOWED_METRICS = %w[visitors visits pageviews views_per_visit bounce_rate visit_duration events]
8
+ ALLOWED_COMPARE = %w[previous_period]
9
+ ALLOWED_INTERVALS = %w[date month]
10
+ ALLOWED_PROPERTIES = %w[event:goal event:page visit:entry_page visit:exit_page visit:source
11
+ visit:referrer visit:utm_medium visit:utm_source visit:utm_campaign
12
+ visit:utm_content visit:utm_term visit:device visit:browser
13
+ visit:browser_version visit:os visit:os_version visit:country visit:region visit:city
14
+ event:props:.+]
15
+ ALLOWED_FILTER_OPERATORS = %w[== != \|]
6
16
 
7
17
  def initialize(options = {})
8
- @options = { compare: nil, date: nil, filters: nil, interval: nil,
9
- limit: nil, metrics: nil, page: nil, period: nil,
10
- property: nil }.merge(options)
11
- @options[:period] = 'custom' if @options[:date]
12
- end
13
-
14
- def request_url_base
15
- raise NotImplementedError
18
+ @options = {compare: nil, date: nil, filters: nil, interval: nil,
19
+ limit: nil, metrics: nil, page: nil, period: nil,
20
+ property: nil}.merge(options)
21
+ @options[:period] = "custom" if @options[:date]
16
22
  end
17
23
 
18
24
  def request_class
19
25
  Net::HTTP::Get
20
26
  end
21
27
 
22
- def request_body?
23
- false
24
- end
25
-
26
- def request_body
27
- nil
28
- end
29
-
30
- def request_url
31
- params = @options.select{ |_,v| !v.to_s.empty? }
32
- [request_url_base, URI.encode_www_form(params)].reject{|e| e.empty?}.join('&')
33
- end
34
-
35
- def request_headers
36
- {}
37
- end
38
-
39
- def request_auth?
40
- true
28
+ def request_path
29
+ params = @options.select { |_, v| !v.to_s.empty? }
30
+ [request_path_base, URI.encode_www_form(params)].reject { |e| e.empty? }.join("&")
41
31
  end
42
32
 
43
33
  def parse_response(body)
44
- raise NotImplementedError
45
- end
46
-
47
- def valid?
48
- errors.empty?
34
+ JSON.parse(body)["results"]
49
35
  end
50
36
 
51
37
  def errors
52
- allowed_period = %w(12mo 6mo month 30d 7d day custom)
53
- allowed_metrics = %w(visitors visits pageviews views_per_visit bounce_rate visit_duration events)
54
- allowed_compare = %w(previous_period)
55
- allowed_interval = %w(date month)
56
- allowed_property = %w(event:page visit:entry_page visit:exit_page visit:source visit:referrer
57
- visit:utm_medium visit:utm_source visit:utm_campaign visit:device visit:browser
58
- visit:browser_version visit:os visit:os_version visit:country)
59
- e = 'Not a valid parameter. Allowed parameters are: '
38
+ e = "Not a valid parameter. Allowed parameters are: "
60
39
 
61
40
  errors = []
62
41
  if @options[:period]
63
- errors.push({ period: "#{e}#{allowed_period.join(', ')}" }) unless allowed_period.include? @options[:period]
42
+ errors.push({period: "#{e}#{ALLOWED_PERIODS.join(", ")}"}) unless ALLOWED_PERIODS.include? @options[:period]
64
43
  end
65
44
  if @options[:metrics]
66
- metrics_array = @options[:metrics].split(',')
67
- errors.push({ metrics: "#{e}#{allowed_metrics.join(', ')}" }) unless metrics_array & allowed_metrics == metrics_array
45
+ metrics_array = @options[:metrics].split(",")
46
+ errors.push({metrics: "#{e}#{ALLOWED_METRICS.join(", ")}"}) unless metrics_array & ALLOWED_METRICS == metrics_array
68
47
  end
69
48
  if @options[:compare]
70
- errors.push({ compare: "#{e}#{allowed_compare.join(', ')}" }) unless allowed_compare.include? @options[:compare]
49
+ errors.push({compare: "#{e}#{ALLOWED_COMPARE.join(", ")}"}) unless ALLOWED_COMPARE.include? @options[:compare]
71
50
  end
72
51
  if @options[:interval]
73
- errors.push({ interval: "#{e}#{allowed_interval.join(', ')}" }) unless allowed_interval.include? @options[:interval]
52
+ errors.push({interval: "#{e}#{ALLOWED_INTERVALS.join(", ")}"}) unless ALLOWED_INTERVALS.include? @options[:interval]
74
53
  end
75
54
  if @options[:property]
76
- errors.push({ property: "#{e}#{allowed_property.join(', ')}" }) unless allowed_property.include? @options[:property]
55
+ unless @options[:property].match?(/^(#{ALLOWED_PROPERTIES.join('|')})$/)
56
+ errors.push({property: "#{e}#{ALLOWED_PROPERTIES.join(", ")}"}) unless ALLOWED_PROPERTIES.include? @options[:property]
57
+ end
77
58
  end
78
- if @options[:filters]
79
- filters_array = @options[:filters].to_s.split(';')
80
- filters_array.each do |f|
81
- parts = f.split("==")
82
- errors.push({ filters: "Unrecognized filter: #{f}" }) unless parts.length == 2
83
- errors.push({ filters: "Unknown metric for filter: #{parts[0]}" }) unless allowed_property.include? parts[0]
59
+ @options[:filters]&.split(";")&.each do |filter|
60
+ unless filter.match?(/^(#{ALLOWED_PROPERTIES.join('|')})(#{ALLOWED_FILTER_OPERATORS.join('|')})(.+)$/)
61
+ errors.push({filters: "Filter #{filter} is not valid"})
84
62
  end
85
63
  end
64
+
86
65
  if @options[:limit]
87
- errors.push({ limit: "Limit param must be a positive number" }) unless @options[:limit].is_a? Integer and @options[:limit] > 0
66
+ errors.push({limit: "Limit param must be a positive number"}) unless @options[:limit].to_i > 0
88
67
  end
89
68
  if @options[:page]
90
- errors.push({ page: "Page param must be a positive number" }) unless @options[:page].is_a? Integer and @options[:page] > 0
69
+ errors.push({page: "Page param must be a positive number"}) unless @options[:page].to_i > 0
91
70
  end
92
71
  if @options[:date]
93
- errors.push({ date: 'You must define the period parameter as custom' }) unless @options[:period] == 'custom'
72
+ errors.push({date: "You must define the period parameter as custom"}) unless @options[:period] == "custom"
94
73
  date_array = @options[:date].split(",")
95
- errors.push({ date: 'You must define start and end dates divided by comma' }) unless date_array.length == 2
96
- regex = /\d{4}\-\d{2}\-\d{2}/
97
- errors.push({ date: 'Wrong format for the start date' }) unless date_array[0] =~ regex
98
- errors.push({ date: 'Wrong format for the end date' }) unless date_array[1] =~ regex
74
+ errors.push({date: "You must define start and end dates divided by comma"}) unless date_array.length == 2
75
+ regex = /\d{4}-\d{2}-\d{2}/
76
+ errors.push({date: "Wrong format for the start date"}) unless date_array[0]&.match?(regex)
77
+ errors.push({date: "Wrong format for the end date"}) unless date_array[1]&.match?(regex)
99
78
  end
100
79
  errors
101
80
  end
@@ -3,18 +3,13 @@
3
3
  module PlausibleApi
4
4
  module Stats
5
5
  class Breakdown < Base
6
-
7
6
  def initialize(options = {})
8
- super({ period: '30d', property: 'event:page' }.merge(options))
7
+ super({period: "30d", property: "event:page"}.merge(options))
9
8
  end
10
9
 
11
- def request_url_base
10
+ def request_path_base
12
11
  "/api/v1/stats/breakdown?site_id=$SITE_ID"
13
12
  end
14
-
15
- def parse_response(body)
16
- JSON.parse(body)['results']
17
- end
18
13
  end
19
14
  end
20
15
  end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module PlausibleApi
3
+ module PlausibleApi
4
4
  module Stats
5
5
  module Realtime
6
6
  class Visitors < PlausibleApi::Stats::Base
7
- def request_url_base
7
+ def request_path_base
8
8
  "/api/v1/stats/realtime/visitors?site_id=$SITE_ID"
9
9
  end
10
10
 
@@ -14,4 +14,4 @@ module PlausibleApi
14
14
  end
15
15
  end
16
16
  end
17
- end
17
+ end
@@ -3,18 +3,13 @@
3
3
  module PlausibleApi
4
4
  module Stats
5
5
  class Timeseries < Base
6
-
7
6
  def initialize(options = {})
8
- super({ period: '30d' }.merge(options))
7
+ super({period: "30d"}.merge(options))
9
8
  end
10
9
 
11
- def request_url_base
10
+ def request_path_base
12
11
  "/api/v1/stats/timeseries?site_id=$SITE_ID"
13
12
  end
14
-
15
- def parse_response(body)
16
- JSON.parse(body)['results']
17
- end
18
13
  end
19
14
  end
20
15
  end
@@ -0,0 +1,16 @@
1
+ module PlausibleApi
2
+ class Utils
3
+ def present?(value)
4
+ !value.nil? && !value.empty?
5
+ end
6
+
7
+ def blank?(value)
8
+ !present?(value)
9
+ end
10
+
11
+ def presence(value)
12
+ return false if blank?(value)
13
+ value
14
+ end
15
+ end
16
+ end
@@ -1,3 +1,3 @@
1
1
  module PlausibleApi
2
- VERSION = "0.3"
2
+ VERSION = "0.4.1"
3
3
  end
data/lib/plausible_api.rb CHANGED
@@ -7,7 +7,6 @@ module PlausibleApi
7
7
 
8
8
  class ConfigurationError < StandardError; end
9
9
 
10
- # Your code goes here...
11
10
  class << self
12
11
  def configuration
13
12
  @configuration ||= Configuration.new
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plausible_api
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.3'
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gustavo Garcia
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-02-02 00:00:00.000000000 Z
11
+ date: 2024-03-10 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A very humble wrapper for the new API by Plausible
14
14
  email:
@@ -29,6 +29,7 @@ files:
29
29
  - examples/basic.rb
30
30
  - examples/event.rb
31
31
  - lib/plausible_api.rb
32
+ - lib/plausible_api/api_base.rb
32
33
  - lib/plausible_api/client.rb
33
34
  - lib/plausible_api/configuration.rb
34
35
  - lib/plausible_api/event/base.rb
@@ -38,6 +39,7 @@ files:
38
39
  - lib/plausible_api/stats/breakdown.rb
39
40
  - lib/plausible_api/stats/realtime/visitors.rb
40
41
  - lib/plausible_api/stats/timeseries.rb
42
+ - lib/plausible_api/utils.rb
41
43
  - lib/plausible_api/version.rb
42
44
  - plausible_api.gemspec
43
45
  homepage: https://github.com/dailytics/plausible_api