From 0c960fea677d796df03c3535ab5887ba3d315fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergei=20Ts=C3=B5ganov?= Date: Thu, 25 Aug 2022 10:57:36 +0300 Subject: [PATCH 1/8] Created market share chart data endpoint --- app/controllers/repp/v1/stats_controller.rb | 38 +++++++++++++++++++++ config/routes.rb | 5 +++ 2 files changed, 43 insertions(+) create mode 100644 app/controllers/repp/v1/stats_controller.rb diff --git a/app/controllers/repp/v1/stats_controller.rb b/app/controllers/repp/v1/stats_controller.rb new file mode 100644 index 000000000..f53c24a16 --- /dev/null +++ b/app/controllers/repp/v1/stats_controller.rb @@ -0,0 +1,38 @@ +module Repp + module V1 + class StatsController < BaseController + api :get, '/repp/v1/stats/market_share' + desc 'Get market share and distribution of registrars' + def market_share + registrars = ::Registrar.where(test_registrar: false).joins(:domains) + registrars = registrars.where(from_condition).where(to_condition) + grouped = registrars.group(:name).count + + result = grouped.map do |key, value| + hash = { name: key.strip, y: value } + hash.merge!({ sliced: true, selected: true }) if current_user.registrar.name == key + hash + end + render_success(data: result) + end + + private + + def search_params + params.permit(:q, q: %i[start_date end_date]).fetch(:q, {}) || {} + end + + def from_condition + return unless search_params[:start_date] + + "domains.created_at >= '#{Date.strptime(search_params[:start_date], '%m.%y')}'" + end + + def to_condition + return unless search_params[:end_date] + + "domains.created_at <= '#{Date.strptime(search_params[:end_date], '%m.%y').end_of_month}'" + end + end + end +end \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 180a4687d..22a4dc9c6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -99,6 +99,11 @@ Rails.application.routes.draw do end resources :auctions, only: %i[index] resources :retained_domains, only: %i[index] + resources :stats do + collection do + get '/market_share', to: 'stats#market_share' + end + end namespace :registrar do resources :notifications, only: [:index, :show, :update] do collection do From 3e3688358b97b49956c6b34f01c1a1d34ebee7f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergei=20Ts=C3=B5ganov?= Date: Thu, 25 Aug 2022 12:24:39 +0300 Subject: [PATCH 2/8] Fixed codeclimate issue --- app/controllers/repp/v1/stats_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/repp/v1/stats_controller.rb b/app/controllers/repp/v1/stats_controller.rb index f53c24a16..a8fa1d077 100644 --- a/app/controllers/repp/v1/stats_controller.rb +++ b/app/controllers/repp/v1/stats_controller.rb @@ -35,4 +35,4 @@ module Repp end end end -end \ No newline at end of file +end From 261e2d31626b3e489dc281b3bdd4868eed359376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergei=20Ts=C3=B5ganov?= Date: Thu, 25 Aug 2022 13:01:54 +0300 Subject: [PATCH 3/8] Add test --- .../repp/v1/stats/market_share_test.rb | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 test/integration/repp/v1/stats/market_share_test.rb diff --git a/test/integration/repp/v1/stats/market_share_test.rb b/test/integration/repp/v1/stats/market_share_test.rb new file mode 100644 index 000000000..12f6c2ab8 --- /dev/null +++ b/test/integration/repp/v1/stats/market_share_test.rb @@ -0,0 +1,25 @@ +require 'test_helper' + +class ReppV1StatsMarketShareTest < ActionDispatch::IntegrationTest + def setup + @user = users(:api_bestnames) + token = Base64.encode64("#{@user.username}:#{@user.plain_text_password}") + token = "Basic #{token}" + + @auth_headers = { 'Authorization' => token } + end + + def test_shows_market_share_data + get '/repp/v1/stats/market_share', headers: @auth_headers + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :ok + assert_equal 1000, json[:code] + assert_equal 'Command completed successfully', json[:message] + + assert json[:data].is_a? Array + assert json[:data][0].is_a? Hash + assert_equal json[:data][0][:name], 'Best Names' + assert json[:data][0][:selected] + end +end \ No newline at end of file From dc4c3cf6162c079424fa70e75f87d25813a1f062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergei=20Ts=C3=B5ganov?= Date: Mon, 5 Sep 2022 14:44:24 +0300 Subject: [PATCH 4/8] Added market share growth rate data endpoint --- app/controllers/repp/v1/stats_controller.rb | 60 +++++++++++++++++-- config/locales/en.yml | 3 + config/locales/et.yml | 7 ++- config/routes.rb | 3 +- test/fixtures/domains.yml | 4 ++ .../repp/v1/stats/market_share_test.rb | 24 +++++++- 6 files changed, 90 insertions(+), 11 deletions(-) diff --git a/app/controllers/repp/v1/stats_controller.rb b/app/controllers/repp/v1/stats_controller.rb index a8fa1d077..2894de252 100644 --- a/app/controllers/repp/v1/stats_controller.rb +++ b/app/controllers/repp/v1/stats_controller.rb @@ -1,11 +1,14 @@ module Repp module V1 class StatsController < BaseController - api :get, '/repp/v1/stats/market_share' + api :get, '/repp/v1/stats/market_share_distribution' desc 'Get market share and distribution of registrars' - def market_share + param :q, Hash, required: true, desc: 'Period parameters for data' do + param :end_date, String, required: true, desc: 'Period end date' + end + def market_share_distribution registrars = ::Registrar.where(test_registrar: false).joins(:domains) - registrars = registrars.where(from_condition).where(to_condition) + .where(from_condition).where(to_condition) grouped = registrars.group(:name).count result = grouped.map do |key, value| @@ -16,22 +19,67 @@ module Repp render_success(data: result) end + api :get, '/repp/v1/stats/market_share_growth_rate' + desc 'Get market share and growth rate of registrars' + param :q, Hash, required: true, desc: 'Period parameters for data' do + param :end_date, String, required: true, desc: 'Period end date' + param :compare_to_date, String, required: true, desc: 'Comparison date' + end + def market_share_growth_rate + registrars = ::Registrar.where(test_registrar: false).joins(:domains) + .where(from_condition) + + domains_by_registrar = registrars.where(to_condition).group(:name).count + prev_domains_by_registrar = registrars.where(compare_to_condition).group(:name).count + + set_zero_values!(domains_by_registrar, prev_domains_by_registrar) + + result = { prev_data: { name: search_params[:compare_to_date], + domains: serialize(prev_domains_by_registrar) }, + data: { name: search_params[:end_date], + domains: serialize(domains_by_registrar) } } + render_success(data: result) + end + private def search_params - params.permit(:q, q: %i[start_date end_date]).fetch(:q, {}) || {} + params.permit(:q, q: %i[start_date end_date compare_to_date]) + .fetch(:q, {}) || {} end def from_condition return unless search_params[:start_date] - "domains.created_at >= '#{Date.strptime(search_params[:start_date], '%m.%y')}'" + "domains.created_at >= '#{to_date(search_params[:start_date])}'" end def to_condition return unless search_params[:end_date] - "domains.created_at <= '#{Date.strptime(search_params[:end_date], '%m.%y').end_of_month}'" + "domains.created_at <= '#{to_date(search_params[:end_date]).end_of_month}'" + end + + def compare_to_condition + return unless search_params[:compare_to_date] + + "domains.created_at <= '#{to_date(search_params[:compare_to_date]).end_of_month}'" + end + + def to_date(date_param) + Date.strptime(date_param, '%m.%y') + end + + def serialize(rars) + rars.map { |key, value| [key, value] } + end + + def set_zero_values!(cur, prev) + cur_dup = cur.dup + cur_dup.each_key do |k| + cur_dup[k] = prev[k] || 0 + end + prev.clear.merge!(cur_dup) end end end diff --git a/config/locales/en.yml b/config/locales/en.yml index 9021eff60..f15a8a55d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -8,8 +8,11 @@ en: date_long: "%d. %B %Y" filename: "%Y-%m-%d_%H.%M" date: + month_names: + [~, January, February, March, April, May, June, July, August, September, October, November, December] formats: default: "%Y-%m-%d" + month_year: "%B, %Y" activerecord: errors: diff --git a/config/locales/et.yml b/config/locales/et.yml index 84672ed3c..6da5860ff 100644 --- a/config/locales/et.yml +++ b/config/locales/et.yml @@ -3,8 +3,11 @@ et: password: 'Parool' date: - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, Jaanuar, Veebruar, Märts, Aprill, Mai, Juuni, Juuli, August, September, Oktoober, November, Detsember] + month_names: + [~, Jaanuar, Veebruar, Märts, April, Mai, Juuni, Juuli, August, September, Oktoober, November, Detsember] + formats: + default: "%Y-%m-%d" + month_year: "%B, %Y" emails: "Meillaadressid" invoice: title: 'Arve' diff --git a/config/routes.rb b/config/routes.rb index 22a4dc9c6..7ef8474f0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -101,7 +101,8 @@ Rails.application.routes.draw do resources :retained_domains, only: %i[index] resources :stats do collection do - get '/market_share', to: 'stats#market_share' + get '/market_share_distribution', to: 'stats#market_share_distribution' + get '/market_share_growth_rate', to: 'stats#market_share_growth_rate' end end namespace :registrar do diff --git a/test/fixtures/domains.yml b/test/fixtures/domains.yml index c8ea1bfce..01c43d644 100644 --- a/test/fixtures/domains.yml +++ b/test/fixtures/domains.yml @@ -12,6 +12,7 @@ shop: period: 1 period_unit: m uuid: 1b3ee442-e8fe-4922-9492-8fcb9dccc69c + created_at: <%= 2.days.ago.to_s :db %> airport: name: airport.test @@ -24,6 +25,7 @@ airport: period: 1 period_unit: m uuid: 2df2c1a1-8f6a-490a-81be-8bdf29866880 + created_at: <%= 2.days.ago.to_s :db %> library: name: library.test @@ -36,6 +38,7 @@ library: period: 1 period_unit: m uuid: 647bcc48-8d5e-4a04-8ce5-2a3cd17b6eab + created_at: <%= 2.days.ago.to_s :db %> metro: name: metro.test @@ -48,6 +51,7 @@ metro: period: 1 period_unit: m uuid: ef97cb80-333b-4893-b9df-163f2b452798 + created_at: <%= 2.days.ago.to_s :db %> hospital: name: hospital.test diff --git a/test/integration/repp/v1/stats/market_share_test.rb b/test/integration/repp/v1/stats/market_share_test.rb index 12f6c2ab8..614b5868d 100644 --- a/test/integration/repp/v1/stats/market_share_test.rb +++ b/test/integration/repp/v1/stats/market_share_test.rb @@ -7,10 +7,12 @@ class ReppV1StatsMarketShareTest < ActionDispatch::IntegrationTest token = "Basic #{token}" @auth_headers = { 'Authorization' => token } + @today = Time.zone.today.strftime('%m.%y') end - def test_shows_market_share_data - get '/repp/v1/stats/market_share', headers: @auth_headers + def test_shows_market_share_distribution_data + get '/repp/v1/stats/market_share_distribution', headers: @auth_headers, + params: { q: { end_date: @today } } json = JSON.parse(response.body, symbolize_names: true) assert_response :ok @@ -22,4 +24,22 @@ class ReppV1StatsMarketShareTest < ActionDispatch::IntegrationTest assert_equal json[:data][0][:name], 'Best Names' assert json[:data][0][:selected] end + + def test_shows_market_share_growth_rate_data + prev_date = Time.zone.today.last_month.strftime('%m.%y') + get '/repp/v1/stats/market_share_growth_rate', headers: @auth_headers, + params: { q: { end_date: @today, + compare_to_date: prev_date } } + json = JSON.parse(response.body, symbolize_names: true) + + assert_response :ok + assert_equal 1000, json[:code] + assert_equal 'Command completed successfully', json[:message] + + data = json[:data] + assert data[:data].is_a? Hash + assert data[:prev_data].is_a? Hash + assert_equal data[:data][:name], @today + assert data[:data][:domains].is_a? Array + end end \ No newline at end of file From 74a6add05421d5f84cb3f0edcd840d9e0d05002b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergei=20Ts=C3=B5ganov?= Date: Wed, 7 Sep 2022 11:16:22 +0300 Subject: [PATCH 5/8] Added market share % data to growth rate endpoint --- app/controllers/repp/v1/stats_controller.rb | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/app/controllers/repp/v1/stats_controller.rb b/app/controllers/repp/v1/stats_controller.rb index 2894de252..69aad2912 100644 --- a/app/controllers/repp/v1/stats_controller.rb +++ b/app/controllers/repp/v1/stats_controller.rb @@ -29,15 +29,20 @@ module Repp registrars = ::Registrar.where(test_registrar: false).joins(:domains) .where(from_condition) - domains_by_registrar = registrars.where(to_condition).group(:name).count - prev_domains_by_registrar = registrars.where(compare_to_condition).group(:name).count + domains_by_rar = registrars.where(to_condition).group(:name).count + prev_domains_by_rar = registrars.where(compare_to_condition).group(:name).count - set_zero_values!(domains_by_registrar, prev_domains_by_registrar) + set_zero_values!(domains_by_rar, prev_domains_by_rar) + + market_share_by_rar = calculate_market_share(domains_by_rar) + prev_market_share_by_rar = calculate_market_share(prev_domains_by_rar) result = { prev_data: { name: search_params[:compare_to_date], - domains: serialize(prev_domains_by_registrar) }, + domains: serialize(prev_domains_by_rar), + market_share: serialize(prev_market_share_by_rar) }, data: { name: search_params[:end_date], - domains: serialize(domains_by_registrar) } } + domains: serialize(domains_by_rar), + market_share: serialize(market_share_by_rar) } } render_success(data: result) end @@ -81,6 +86,11 @@ module Repp end prev.clear.merge!(cur_dup) end + + def calculate_market_share(domains_by_rar) + sum = domains_by_rar.values.sum + domains_by_rar.transform_values { |v| (v.to_f / sum * 100.0).round(1) } + end end end end From 85c0c28bcf1fab13248c9f8607c45f994e5c715c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergei=20Ts=C3=B5ganov?= Date: Wed, 7 Sep 2022 11:21:24 +0300 Subject: [PATCH 6/8] Fixed codeclimate issue --- app/controllers/repp/v1/stats_controller.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/controllers/repp/v1/stats_controller.rb b/app/controllers/repp/v1/stats_controller.rb index 69aad2912..62d8943f8 100644 --- a/app/controllers/repp/v1/stats_controller.rb +++ b/app/controllers/repp/v1/stats_controller.rb @@ -19,6 +19,7 @@ module Repp render_success(data: result) end + # rubocop:disable Metrics/MethodLength api :get, '/repp/v1/stats/market_share_growth_rate' desc 'Get market share and growth rate of registrars' param :q, Hash, required: true, desc: 'Period parameters for data' do @@ -45,6 +46,7 @@ module Repp market_share: serialize(market_share_by_rar) } } render_success(data: result) end + # rubocop:enable Metrics/MethodLength private From 440513f4cef592cb48dac8ca5158b42b4e45769b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergei=20Ts=C3=B5ganov?= Date: Wed, 7 Sep 2022 12:19:37 +0300 Subject: [PATCH 7/8] Fixed from condition for domains by registrar data --- app/controllers/repp/v1/stats_controller.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/controllers/repp/v1/stats_controller.rb b/app/controllers/repp/v1/stats_controller.rb index 62d8943f8..b7be0df88 100644 --- a/app/controllers/repp/v1/stats_controller.rb +++ b/app/controllers/repp/v1/stats_controller.rb @@ -28,9 +28,8 @@ module Repp end def market_share_growth_rate registrars = ::Registrar.where(test_registrar: false).joins(:domains) - .where(from_condition) - domains_by_rar = registrars.where(to_condition).group(:name).count + domains_by_rar = registrars.where(from_condition).where(to_condition).group(:name).count prev_domains_by_rar = registrars.where(compare_to_condition).group(:name).count set_zero_values!(domains_by_rar, prev_domains_by_rar) From 9b3d1ebf34ce644b883a93e915d7dab3a4061b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergei=20Ts=C3=B5ganov?= Date: Fri, 16 Sep 2022 21:34:39 +0300 Subject: [PATCH 8/8] Changed rounding of market share values --- app/controllers/repp/v1/stats_controller.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/controllers/repp/v1/stats_controller.rb b/app/controllers/repp/v1/stats_controller.rb index b7be0df88..480c81a6b 100644 --- a/app/controllers/repp/v1/stats_controller.rb +++ b/app/controllers/repp/v1/stats_controller.rb @@ -90,7 +90,10 @@ module Repp def calculate_market_share(domains_by_rar) sum = domains_by_rar.values.sum - domains_by_rar.transform_values { |v| (v.to_f / sum * 100.0).round(1) } + domains_by_rar.transform_values do |v| + value = v.to_f / sum * 100.0 + value < 0.1 ? value.round(3) : value.round(1) + end end end end