From db6857668e2672caea5f9d0671b07d9374d25c8b Mon Sep 17 00:00:00 2001 From: Mohamed Hamda <mhamda@gitlab.com> Date: Tue, 27 Feb 2024 20:35:46 +0000 Subject: [PATCH] Change lib to a service Change the bulk assign lib to a service --- doc/raketasks/user_management.md | 2 +- .../duo_pro/bulk_user_assignment.rb | 97 +++++++++++++++++++ ee/lib/duo_pro/bulk_user_assignment.rb | 96 ------------------ .../tasks/duo_pro/bulk_user_assignment.rake | 2 +- .../duo_pro/bulk_user_assignment_spec.rb | 2 +- .../duo_pro/bulk_user_assignment_spec.rb | 3 +- 6 files changed, 102 insertions(+), 100 deletions(-) create mode 100644 ee/app/services/gitlab_subscriptions/duo_pro/bulk_user_assignment.rb delete mode 100644 ee/lib/duo_pro/bulk_user_assignment.rb rename ee/spec/{lib => services/gitlab_subscriptions}/duo_pro/bulk_user_assignment_spec.rb (98%) diff --git a/doc/raketasks/user_management.md b/doc/raketasks/user_management.md index c5a5febc0cb52..5c42a65c8e777 100644 --- a/doc/raketasks/user_management.md +++ b/doc/raketasks/user_management.md @@ -190,7 +190,7 @@ DETAILS: > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/142189) in GitLab 16.9. -The Rake task for bulk user assignment is available in GitLab 16.9 and later. For GitLab 16.8, use the script [`bulk_user_assignment.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/duo_pro/bulk_user_assignment.rb) instead. +The Rake task for bulk user assignment is available in GitLab 16.9 and later. For GitLab 16.8, use the script [`bulk_user_assignment.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/services/gitlab_subscriptions/duo_pro/bulk_user_assignment.rb) instead. To perform bulk user assignment for GitLab Duo Pro, you can use the following Rake task: diff --git a/ee/app/services/gitlab_subscriptions/duo_pro/bulk_user_assignment.rb b/ee/app/services/gitlab_subscriptions/duo_pro/bulk_user_assignment.rb new file mode 100644 index 0000000000000..030a828d0da80 --- /dev/null +++ b/ee/app/services/gitlab_subscriptions/duo_pro/bulk_user_assignment.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +# GitLab Duo Pro Bulk User Assignment +# 1. Set the `add_on_purchase` variable to point to your AddOnPurchase record +# add_on_purchase = GitlabSubscriptions::AddOnPurchase.find_by(add_on: GitlabSubscriptions::AddOn.code_suggestions.last) +# 2. Set the `usernames` variable to point to an array of usernames: +# usernames = ["user1", "user2", "user3", "user4", "user5"] +# If reading from a CSV file +# usernames = CSV.read(FILE_PATH, headers: true).pluck('username') +# 3. Execute the bulk assignment: +# GitlabSubscriptions::DuoPro::BulkUserAssignment.new(usernames, add_on_purchase).execute + +# Error Messages: +# - `User is not found` +# - `ERROR_NO_SEATS_AVAILABLE`: No more seats are available. +# - `ERROR_INVALID_USER_MEMBERSHIP`: User is not eligible for assignment due to being inactive, a bot, or a ghost. +module GitlabSubscriptions + module DuoPro + class BulkUserAssignment + include ::GitlabSubscriptions::SubscriptionHelper + attr_reader :usernames, :add_on_purchase, :successful_assignments, :failed_assignments + + THROTTLE_BATCH_SIZE = 50 + THROTTLE_SLEEP_DELAY = 0.5.seconds + + def initialize(usernames, add_on_purchase) + @usernames = usernames + @add_on_purchase = add_on_purchase + @successful_assignments = [] + @failed_assignments = [] + end + + def execute + return 'AddOn not purchased' unless add_on_purchase + + process_users(usernames) + + { successful_assignments: successful_assignments, failed_assignments: failed_assignments } + end + + private + + def process_users(usernames) + usernames.each.with_index(1) do |username, index| + user_to_be_assigned = User.find_by_username(username) + + unless user_to_be_assigned + log_failed_assignment("User is not found: #{username}") + next + end + + result = assign(user_to_be_assigned) + + if result.errors.include?("NO_SEATS_AVAILABLE") + log_no_seats_available(result, username) + break + end + + log_result(result, username) + + sleep(THROTTLE_SLEEP_DELAY) if index % THROTTLE_BATCH_SIZE == 0 + end + end + + def assign(user) + service_class = if gitlab_com_subscription? + ::GitlabSubscriptions::UserAddOnAssignments::Saas::CreateService + else + ::GitlabSubscriptions::UserAddOnAssignments::SelfManaged::CreateService + end + + service_class.new(add_on_purchase: add_on_purchase, user: user).execute + end + + def log_no_seats_available(result, username) + log_failed_assignment("Failed to assign seat to user: #{username}, Errors: #{result.errors}") + log_failed_assignment("##No seats are left; users starting from @#{username} onwards were not assigned.##") + end + + def log_successful_assignment(username) + successful_assignments << "User assigned: #{username}" + end + + def log_failed_assignment(message) + failed_assignments << message + end + + def log_result(result, username) + if result.errors.empty? + log_successful_assignment(username) + else + log_failed_assignment("Failed to assign seat to user: #{username}, Errors: #{result.errors}") + end + end + end + end +end diff --git a/ee/lib/duo_pro/bulk_user_assignment.rb b/ee/lib/duo_pro/bulk_user_assignment.rb deleted file mode 100644 index 1ee20d6cea27c..0000000000000 --- a/ee/lib/duo_pro/bulk_user_assignment.rb +++ /dev/null @@ -1,96 +0,0 @@ -# frozen_string_literal: true - -# Duo Pro Bulk User Assignment -# 1. Set the `add_on_purchase` variable to point to your AddOnPurchase record -# add_on_purchase = GitlabSubscriptions::AddOnPurchase.find_by(add_on: GitlabSubscriptions::AddOn.code_suggestions.last) -# 2. Set the `usernames` variable to point to an array of usernames: -# usernames = ["user1", "user2", "user3", "user4", "user5"] -# If reading from a CSV file -# usernames = CSV.read(FILE_PATH, headers: true).pluck('username') -# 3. Execute the bulk assignment: -# DuoPro::BulkUserAssignment.new(usernames, add_on_purchase).execute - -# Error Messages: -# - `User is not found` -# - `ERROR_NO_SEATS_AVAILABLE`: No more seats are available. -# - `ERROR_INVALID_USER_MEMBERSHIP`: User is not eligible for assignment due to being inactive, a bot, or a ghost. - -module DuoPro - class BulkUserAssignment - include ::GitlabSubscriptions::SubscriptionHelper - attr_reader :usernames, :add_on_purchase, :successful_assignments, :failed_assignments - - THROTTLE_BATCH_SIZE = 50 - THROTTLE_SLEEP_DELAY = 0.5.seconds - - def initialize(usernames, add_on_purchase) - @usernames = usernames - @add_on_purchase = add_on_purchase - @successful_assignments = [] - @failed_assignments = [] - end - - def execute - return 'AddOn not purchased' unless add_on_purchase - - process_users(usernames) - - { successful_assignments: successful_assignments, failed_assignments: failed_assignments } - end - - private - - def process_users(usernames) - usernames.each.with_index(1) do |username, index| - user_to_be_assigned = User.find_by_username(username) - - unless user_to_be_assigned - log_failed_assignment("User is not found: #{username}") - next - end - - result = assign(user_to_be_assigned) - - if result.errors.include?("NO_SEATS_AVAILABLE") - log_no_seats_available(result, username) - break - end - - log_result(result, username) - - sleep(THROTTLE_SLEEP_DELAY) if index % THROTTLE_BATCH_SIZE == 0 - end - end - - def assign(user) - service_class = if gitlab_com_subscription? - ::GitlabSubscriptions::UserAddOnAssignments::Saas::CreateService - else - ::GitlabSubscriptions::UserAddOnAssignments::SelfManaged::CreateService - end - - service_class.new(add_on_purchase: add_on_purchase, user: user).execute - end - - def log_no_seats_available(result, username) - log_failed_assignment("Failed to assign seat to user: #{username}, Errors: #{result.errors}") - log_failed_assignment("##No seats are left; users starting from @#{username} onwards were not assigned.##") - end - - def log_successful_assignment(username) - successful_assignments << "User assigned: #{username}" - end - - def log_failed_assignment(message) - failed_assignments << message - end - - def log_result(result, username) - if result.errors.empty? - log_successful_assignment(username) - else - log_failed_assignment("Failed to assign seat to user: #{username}, Errors: #{result.errors}") - end - end - end -end diff --git a/ee/lib/tasks/duo_pro/bulk_user_assignment.rake b/ee/lib/tasks/duo_pro/bulk_user_assignment.rake index bab70f9ec3d64..a474da6a84714 100644 --- a/ee/lib/tasks/duo_pro/bulk_user_assignment.rake +++ b/ee/lib/tasks/duo_pro/bulk_user_assignment.rake @@ -29,7 +29,7 @@ namespace :duo_pro do ERROR_MESSAGE end - result = DuoPro::BulkUserAssignment.new(user_names, add_on_purchase).execute + result = GitlabSubscriptions::DuoPro::BulkUserAssignment.new(user_names, add_on_purchase).execute display_results(result) end diff --git a/ee/spec/lib/duo_pro/bulk_user_assignment_spec.rb b/ee/spec/services/gitlab_subscriptions/duo_pro/bulk_user_assignment_spec.rb similarity index 98% rename from ee/spec/lib/duo_pro/bulk_user_assignment_spec.rb rename to ee/spec/services/gitlab_subscriptions/duo_pro/bulk_user_assignment_spec.rb index a853f8002e7ca..6e920001bf9bb 100644 --- a/ee/spec/lib/duo_pro/bulk_user_assignment_spec.rb +++ b/ee/spec/services/gitlab_subscriptions/duo_pro/bulk_user_assignment_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe DuoPro::BulkUserAssignment, feature_category: :purchase do +RSpec.describe GitlabSubscriptions::DuoPro::BulkUserAssignment, feature_category: :seat_cost_management do describe '#initialize' do subject(:bulk_assignment) { described_class.new([], nil) } diff --git a/ee/spec/tasks/duo_pro/bulk_user_assignment_spec.rb b/ee/spec/tasks/duo_pro/bulk_user_assignment_spec.rb index 2caee50034f9d..f3e101065b6bf 100644 --- a/ee/spec/tasks/duo_pro/bulk_user_assignment_spec.rb +++ b/ee/spec/tasks/duo_pro/bulk_user_assignment_spec.rb @@ -33,7 +33,8 @@ before do add_on_purchase = create(:gitlab_subscription_add_on_purchase, :self_managed, quantity: 10, add_on: add_on) - allow_next_instance_of(DuoPro::BulkUserAssignment, %w[user1 user2 user3], add_on_purchase) do |instance| + allow_next_instance_of(GitlabSubscriptions::DuoPro::BulkUserAssignment, %w[user1 user2 user3], + add_on_purchase) do |instance| response = { successful_assignments: ['success'], failed_assignments: ['Failed'] } allow(instance).to receive(:execute).and_return(response) end -- GitLab