diff --git a/.github/workflows/brakeman.yml b/.github/workflows/brakeman.yml index e94cc3c..88b43bf 100644 --- a/.github/workflows/brakeman.yml +++ b/.github/workflows/brakeman.yml @@ -11,7 +11,7 @@ runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 # Will run Brakeman checks on dependencies # https://github.com/marketplace/actions/brakeman-linter diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index 9a087ce..8f043ff 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -8,7 +8,7 @@ runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 # Will run ES Lint checks on javascript files # https://github.com/marketplace/actions/run-eslint diff --git a/.github/workflows/mysql.yml b/.github/workflows/mysql.yml index a4839f6..dbacba0 100644 --- a/.github/workflows/mysql.yml +++ b/.github/workflows/mysql.yml @@ -13,7 +13,7 @@ steps: # Checkout the repo - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 with: fetch-depth: 1 @@ -77,6 +77,12 @@ - name: 'Determine wkhtmltopdf location' run: echo ::set-env name=WICKED_PDF_PATH::$(echo `bundle exec which wkhtmltopdf`) + # Startup MySQL + - name: 'Start MySQL' + run: sudo systemctl start mysql + + + # Install the JS dependencies - name: 'Yarn Install' run: | diff --git a/.github/workflows/postgres.yml b/.github/workflows/postgres.yml index c6f5ce1..9fa69ff 100644 --- a/.github/workflows/postgres.yml +++ b/.github/workflows/postgres.yml @@ -29,7 +29,7 @@ steps: # Checkout the repo - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 with: fetch-depth: 1 diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml index 1bb1d76..1d3d2a7 100644 --- a/.github/workflows/rubocop.yml +++ b/.github/workflows/rubocop.yml @@ -10,7 +10,7 @@ runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 # Extract the Ruby version from the Gemfile.lock # - name: 'Determine Ruby Version' @@ -31,4 +31,4 @@ # additional-gems: 'rubocop-dmp_roadmap' - name: 'Placeholder for Rubocop' - run: echo "Rubocop has been temporarily disabled" \ No newline at end of file + run: echo "Rubocop has been temporarily disabled" diff --git a/Gemfile b/Gemfile index 67e418c..9d269a2 100644 --- a/Gemfile +++ b/Gemfile @@ -185,6 +185,9 @@ gem 'activerecord-session_store' +# ------------------------------------------------- +# UTILITIES +gem 'parallel' # ------------------------------------------------ # ENVIRONMENT SPECIFIC DEPENDENCIES diff --git a/Gemfile.lock b/Gemfile.lock index 99f84ce..8528d85 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -513,6 +513,7 @@ omniauth-orcid omniauth-rails_csrf_protection omniauth-shibboleth + parallel pg (~> 0.19.0) progress_bar puma diff --git a/app/controllers/usage_controller.rb b/app/controllers/usage_controller.rb index 15d0cd3..a469ee9 100644 --- a/app/controllers/usage_controller.rb +++ b/app/controllers/usage_controller.rb @@ -43,6 +43,17 @@ send_data(data_csvified, filename: "totals.csv") end + # GET + def org_statistics + authorize :usage + + data = Org::MonthlyUsageService.call + sep = sep_param + data_csvified = Csvable.from_array_of_hashes(data, true, sep) + + send_data(data_csvified, filename: "totals.csv") + end + # POST /usage_filter # rubocop:disable Metrics/MethodLength def filter diff --git a/app/models/stat_exported_plan.rb b/app/models/stat_exported_plan.rb new file mode 100644 index 0000000..0f11074 --- /dev/null +++ b/app/models/stat_exported_plan.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: stats +# +# id :integer not null, primary key +# count :integer default(0) +# date :date not null +# details :text +# type :string not null +# created_at :datetime not null +# updated_at :datetime not null +# org_id :integer +# + +class StatExportedPlan < Stat + + class << self + + def to_csv(exported_plans) + Stat.to_csv(exported_plans) + end + + end + +end diff --git a/app/models/stat_exported_plan/create_or_update.rb b/app/models/stat_exported_plan/create_or_update.rb new file mode 100644 index 0000000..a88b123 --- /dev/null +++ b/app/models/stat_exported_plan/create_or_update.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +class StatExportedPlan + + class CreateOrUpdate + + class << self + + def do(start_date:, end_date:, org:) + count = exported_plans(start_date: start_date, end_date: end_date, org_id: org.id) + attrs = { date: end_date.to_date, count: count, org_id: org.id } + + stat_exported_plan = StatExportedPlan.find_by( + date: attrs[:date], + org_id: attrs[:org_id] + ) + + if stat_exported_plan.present? + stat_exported_plan.update(attrs) + else + StatExportedPlan.create(attrs) + end + end + + private + + def users(org_id) + User.where(users: {org_id: org_id }) + end + + def org_plan_ids(org_id) + Role.joins(:user) + .creator + .merge(users(org_id)) + .pluck(:plan_id) + .uniq + end + + def exported_plans(start_date:, end_date:, org_id:) + ExportedPlan.where(plan_id: org_plan_ids(org_id)) + .where(created_at: start_date..end_date) + .count + end + + end + + end + +end diff --git a/app/models/stat_shared_plan.rb b/app/models/stat_shared_plan.rb new file mode 100644 index 0000000..b0ffe10 --- /dev/null +++ b/app/models/stat_shared_plan.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: stats +# +# id :integer not null, primary key +# count :integer default(0) +# date :date not null +# details :text +# type :string not null +# created_at :datetime not null +# updated_at :datetime not null +# org_id :integer +# + +class StatSharedPlan < Stat + + class << self + + def to_csv(shared_plans) + Stat.to_csv(shared_plans) + end + + end + +end diff --git a/app/models/stat_shared_plan/create_or_update.rb b/app/models/stat_shared_plan/create_or_update.rb new file mode 100644 index 0000000..3022d1e --- /dev/null +++ b/app/models/stat_shared_plan/create_or_update.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +class StatSharedPlan + + class CreateOrUpdate + + class << self + + def do(start_date:, end_date:, org:) + count = shared_plans(start_date: start_date, end_date: end_date, org_id: org.id) + attrs = { date: end_date.to_date, count: count, org_id: org.id } + + stat_shared_plan = StatSharedPlan.find_by( + date: attrs[:date], + org_id: attrs[:org_id] + ) + + if stat_shared_plan.present? + stat_shared_plan.update(attrs) + else + StatSharedPlan.create(attrs) + end + end + + private + + def users(org_id) + User.where(users: {org_id: org_id }) + end + + def org_plan_ids(org_id) + Role.joins(:user) + .creator + .merge(users(org_id)) + .pluck(:plan_id) + .uniq + end + + def shared_plans(start_date:, end_date:, org_id:) + Role.not_creator + .where(plan_id: org_plan_ids(org_id)) + .where(created_at: start_date..end_date) + .count + end + + end + + end + +end diff --git a/app/policies/usage_policy.rb b/app/policies/usage_policy.rb index 6c26e3c..88d1d92 100644 --- a/app/policies/usage_policy.rb +++ b/app/policies/usage_policy.rb @@ -21,6 +21,10 @@ end def global_statistics? + @user.can_super_admin? + end + + def org_statistics? @user.can_org_admin? end diff --git a/app/services/org/create_created_plan_service.rb b/app/services/org/create_created_plan_service.rb index c668a77..c1cd442 100644 --- a/app/services/org/create_created_plan_service.rb +++ b/app/services/org/create_created_plan_service.rb @@ -1,15 +1,25 @@ # frozen_string_literal: true +#import statements fix Circular dependancy errors due to threading +import OrgDateRangeable +import StatCreatedPlan +import StatCreatedPlan::CreateOrUpdate +import Role +import User +import Plan +import Perm +import Template + class Org class CreateCreatedPlanService class << self - def call(org = nil) + def call(org = nil, threads: 0) orgs = org.nil? ? Org.all : [org] - orgs.each do |org| + Parallel.each(orgs, in_threads: threads) do |org| OrgDateRangeable.split_months_from_creation(org) do |start_date, end_date| StatCreatedPlan::CreateOrUpdate.do( start_date: start_date, diff --git a/app/services/org/create_exported_plan_service.rb b/app/services/org/create_exported_plan_service.rb new file mode 100644 index 0000000..0d1de4c --- /dev/null +++ b/app/services/org/create_exported_plan_service.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +#import statements fix Circular dependancy errors +import OrgDateRangeable +import StatExportedPlan +import StatExportedPlan::CreateOrUpdate +import Role +import User +import ExportedPlan + +class Org + + class CreateExportedPlanService + + class << self + + def call(org = nil, threads: 0) + orgs = org.nil? ? Org.all : [org] + + Parallel.each(orgs, in_threads: threads) do |org| + OrgDateRangeable.split_months_from_creation(org) do |start_date, end_date| + StatExportedPlan::CreateOrUpdate.do( + start_date: start_date, + end_date: end_date, + org: org + ) + end + end + end + + end + + end + +end diff --git a/app/services/org/create_joined_user_service.rb b/app/services/org/create_joined_user_service.rb index e4842eb..44f8ed3 100644 --- a/app/services/org/create_joined_user_service.rb +++ b/app/services/org/create_joined_user_service.rb @@ -1,14 +1,21 @@ # frozen_string_literal: true +#import statements fix Circular dependancy errors due to threading +import OrgDateRangeable +import StatJoinedUser +import StatJoinedUser::CreateOrUpdate +import User + class Org class CreateJoinedUserService class << self - def call(org = nil) + def call(org = nil, threads: 0) orgs = org.nil? ? Org.all : [org] - orgs.each do |org| + + Parallel.each(orgs, in_threads: threads) do |org| OrgDateRangeable.split_months_from_creation(org) do |start_date, end_date| StatJoinedUser::CreateOrUpdate.do( start_date: start_date, @@ -17,6 +24,7 @@ ) end end + # pp StatJoinedUser.where.not(count: 0) end end diff --git a/app/services/org/create_last_month_created_plan_service.rb b/app/services/org/create_last_month_created_plan_service.rb index 8093862..4acea4a 100644 --- a/app/services/org/create_last_month_created_plan_service.rb +++ b/app/services/org/create_last_month_created_plan_service.rb @@ -1,15 +1,26 @@ # frozen_string_literal: true +#import statements fix Circular dependancy errors due to threading +import OrgDateRangeable +import StatCreatedPlan +import StatCreatedPlan::CreateOrUpdate +import Role +import User +import Plan +import Perm +import Template + + class Org class CreateLastMonthCreatedPlanService class << self - def call(org = nil) + def call(org = nil, threads: 0) orgs = org.nil? ? Org.all : [org] - orgs.each do |org| + Parallel.each(orgs, in_threads: threads) do |org| months = OrgDateRangeable.split_months_from_creation(org) last = months.last if last.present? diff --git a/app/services/org/create_last_month_exported_plan_service.rb b/app/services/org/create_last_month_exported_plan_service.rb new file mode 100644 index 0000000..50ff09f --- /dev/null +++ b/app/services/org/create_last_month_exported_plan_service.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +#import statements fix Circular dependancy errors +import OrgDateRangeable +import StatExportedPlan +import StatExportedPlan::CreateOrUpdate +import Role +import User +import ExportedPlan + +class Org + + class CreateLastMonthExportedPlanService + + class << self + + def call(org = nil, threads: 0) + orgs = org.nil? ? Org.all : [org] + + Parallel.each(orgs, in_threads: threads) do |org| + months = OrgDateRangeable.split_months_from_creation(org) + last = months.last + if last.present? + StatExportedPlan::CreateOrUpdate.do( + start_date: last[:start_date], + end_date: last[:end_date], + org: org + ) + end + end + end + + end + + end + +end diff --git a/app/services/org/create_last_month_joined_user_service.rb b/app/services/org/create_last_month_joined_user_service.rb index 026dfd8..7b53534 100644 --- a/app/services/org/create_last_month_joined_user_service.rb +++ b/app/services/org/create_last_month_joined_user_service.rb @@ -1,14 +1,21 @@ # frozen_string_literal: true +#import statements fix Circular dependancy errors due to threading +import OrgDateRangeable +import StatJoinedUser +import StatJoinedUser::CreateOrUpdate +import User + class Org class CreateLastMonthJoinedUserService class << self - def call(org = nil) + def call(org = nil, threads: 0) orgs = org.nil? ? ::Org.all : [org] - orgs.each do |org| + + Parallel.each(orgs, in_threads: threads) do |org| months = OrgDateRangeable.split_months_from_creation(org) last = months.last if last.present? diff --git a/app/services/org/create_last_month_shared_plan_service.rb b/app/services/org/create_last_month_shared_plan_service.rb new file mode 100644 index 0000000..9eba8d6 --- /dev/null +++ b/app/services/org/create_last_month_shared_plan_service.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +#import statements fix Circular dependancy errors due to threading +import OrgDateRangeable +import StatSharedPlan +import StatSharedPlan::CreateOrUpdate +import User +import Role + +class Org + + class CreateLastMonthSharedPlanService + + class << self + + def call(org = nil, threads: 0) + orgs = org.nil? ? Org.all : [org] + + Parallel.each(orgs, in_threads: threads) do |org| + months = OrgDateRangeable.split_months_from_creation(org) + last = months.last + if last.present? + StatSharedPlan::CreateOrUpdate.do( + start_date: last[:start_date], + end_date: last[:end_date], + org: org + ) + end + end + end + + end + + end + +end diff --git a/app/services/org/create_shared_plan_service.rb b/app/services/org/create_shared_plan_service.rb new file mode 100644 index 0000000..b784235 --- /dev/null +++ b/app/services/org/create_shared_plan_service.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +#import statements fix Circular dependancy errors due to threading +import OrgDateRangeable +import StatSharedPlan +import StatSharedPlan::CreateOrUpdate +import User +import Role + +class Org + + class CreateSharedPlanService + + class << self + + def call(org = nil, threads: 0) + orgs = org.nil? ? Org.all : [org] + + Parallel.each(orgs, in_threads: threads) do |org| + OrgDateRangeable.split_months_from_creation(org) do |start_date, end_date| + StatSharedPlan::CreateOrUpdate.do( + start_date: start_date, + end_date: end_date, + org: org + ) + end + end + end + + end + + end + +end diff --git a/app/services/org/monthly_usage_service.rb b/app/services/org/monthly_usage_service.rb new file mode 100644 index 0000000..fa406c9 --- /dev/null +++ b/app/services/org/monthly_usage_service.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +class Org + + class MonthlyUsageService + + class << self + + def call + total = build_from_joined_user + build_from_created_plan(total) + build_from_shared_plan(total) + build_from_exported_plan(total) + total.values + end + + private + + def current_user + User.find_by_email 'xsrust@gmail.com' + end + + def build_model(month:, new_plans: 0, new_users: 0, downloads: 0, plans_shared: 0) + { + month: month, + new_plans: new_plans, + new_users: new_users, + downloads: downloads, + plans_shared: plans_shared + } + end + + def reducer_body(acc, rec, key_target) + month = rec.date.strftime("%b-%y") + count = rec.count + + if acc[month].present? + acc[month][key_target] = count + else + args = { month: month } + args[key_target] = count + acc[month] = build_model(args) + end + + acc + end + + def build_from_joined_user(total = {}) + joined_users = Stat::StatJoinedUser.monthly_range(org: current_user.org).order(:date) + joined_users.reduce(total) do |acc, rec| + reducer_body(acc, rec, :new_users) + end + end + + def build_from_created_plan(total = {}) + created_plans = Stat::StatCreatedPlan.monthly_range(org: current_user.org).order(:date) + created_plans.reduce(total) do |acc, rec| + reducer_body(acc, rec, :new_plans) + end + end + + def build_from_shared_plan(total = {}) + shared_plans = Stat::StatSharedPlan.monthly_range(org: current_user.org).order(:date) + shared_plans.reduce(total) do |acc, rec| + reducer_body(acc, rec, :plans_shared) + end + end + + def build_from_exported_plan(total = {}) + exported_plans = Stat::StatExportedPlan.monthly_range(org: current_user.org).order(:date) + exported_plans.reduce(total) do |acc, rec| + reducer_body(acc, rec, :downloads) + end + end + + end + + end + +end diff --git a/app/views/usage/_total_usage.html.erb b/app/views/usage/_total_usage.html.erb index 854bc85..c7814b7 100644 --- a/app/views/usage/_total_usage.html.erb +++ b/app/views/usage/_total_usage.html.erb @@ -2,10 +2,10 @@