Skip to content

Commit

Permalink
creates operational process for force syncing for crm (#4156)
Browse files Browse the repository at this point in the history
  • Loading branch information
saikumar9 authored Aug 1, 2024
1 parent 72156a2 commit 4e40d42
Show file tree
Hide file tree
Showing 5 changed files with 364 additions and 0 deletions.
112 changes: 112 additions & 0 deletions app/domain/operations/crm/family/publish.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# frozen_string_literal: true

module Operations
module Crm
module Family
# This operation is responsible for publishing an event for a a primary person.
class Publish
include Dry::Monads[:do, :result]
include EventSource::Command

# Publishes an event for a given person identified by hbx_id.
#
# @param hbx_id [String] The HBX ID of the person.
# @return [Dry::Monads::Result] The result of the publish operation.
def call(hbx_id:)
person = yield find_person(hbx_id)
family = yield find_primary_family(person)
headers = yield headers(family, person)
transformed_cv = yield construct_cv_transform(family, hbx_id)
family_entity = yield create_family_entity(transformed_cv)
es_event = yield create_es_event(family_entity, headers)
published_result = yield publish_es_event(es_event, hbx_id)

Success(published_result)
end

private

# Finds a person by their HBX ID.
#
# @param hbx_id [String] The HBX ID of the person.
# @return [Dry::Monads::Result] The result containing the person or an error message.
def find_person(hbx_id)
result = ::Operations::People::Find.new.call({ person_hbx_id: hbx_id })

if result.success?
Success(result.value!)
else
Failure("Provide a valid person_hbx_id to fetch person. Invalid input hbx_id: #{hbx_id}")
end
end

# Finds the primary family of a given person.
#
# @param person [Person] The person object.
# @return [Dry::Monads::Result] The result containing the family or an error message.
def find_primary_family(person)
family = person.primary_family

if family
Success(family)
else
Failure("Primary Family does not exist with given hbx_id: #{person.hbx_id}")
end
end

# Constructs headers for the event.
#
# @param family [Family] The family object.
# @param person [Person] The person object.
# @return [Dry::Monads::Result] The result containing the headers.
def headers(family, person)
eligible_dates = [person.created_at, person.updated_at, family.created_at, family.updated_at].compact

Success({ after_updated_at: eligible_dates.max, before_updated_at: eligible_dates.min })
end

# Constructs a CV transform for the family.
#
# @param family [Family] The family object.
# @param hbx_id [String] The HBX ID of the person.
# @return [Dry::Monads::Result] The result containing the transformed CV or an error message.
def construct_cv_transform(family, hbx_id)
::Operations::Transformers::FamilyTo::Cv3Family.new.call(family)
rescue StandardError => e
Failure("Failed to transform family with primary person_hbx_id: #{hbx_id} to CV: #{e.message}")
end

# Creates a family entity from the transformed CV.
#
# @param transformed_cv [Hash] The transformed CV.
# @return [Dry::Monads::Result] The result containing the family entity.
def create_family_entity(transformed_cv)
::AcaEntities::Operations::CreateFamily.new.call(transformed_cv)
end

# Creates an event source event for the family entity.
#
# @param family_entity [FamilyEntity] The family entity object.
# @param headers [Hash] The headers for the event.
# @return [Dry::Monads::Result] The result containing the event.
def create_es_event(family_entity, headers)
event(
'events.families.created_or_updated',
attributes: { before_save_cv_family: {}, after_save_cv_family: family_entity.to_h },
headers: headers
)
end

# Publishes the event source event.
#
# @param es_event [EventSource::Event] The event source event.
# @param hbx_id [String] The HBX ID of the person.
# @return [Dry::Monads::Result] The result of the publish operation.
def publish_es_event(es_event, hbx_id)
es_event.publish
Success("Successfully published event: #{es_event.name} for family with primary person hbx_id: #{hbx_id}")
end
end
end
end
end
66 changes: 66 additions & 0 deletions app/domain/operations/crm/force_sync.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# frozen_string_literal: true

module Operations
module Crm
# This operation is responsible for initiating the force sync operation.
class ForceSync
include Dry::Monads[:do, :result]
include EventSource::Command

# Initiates the force sync operation.
#
# @param params [Hash] the parameters for the operation
# @option params [Array<String>] :primary_hbx_ids an array of primary HBX IDs
# @return [Dry::Monads::Result] the result of the operation
def call(params)
primary_person_hbx_ids = yield validate(params)
csv_file_name = yield publish_families(primary_person_hbx_ids)
message = yield generate_success_message(csv_file_name)

Success(message)
end

private

# Validates the input parameters.
#
# @param params [Hash] the parameters to validate
# @return [Dry::Monads::Result::Success, Dry::Monads::Result::Failure] the validation result
def validate(params)
if params[:primary_hbx_ids].is_a?(Array) && params[:primary_hbx_ids].present? && params[:primary_hbx_ids].all? { |id| id.is_a?(String) }
Success(params[:primary_hbx_ids])
else
Failure("Invalid input for primary_hbx_ids: #{params[:primary_hbx_ids]}. Provide an array of HBX IDs.")
end
end

# Publishes each family and logs o/p into a CSV file.
#
# @param primary_person_hbx_ids [Array<String>] an array of primary HBX IDs
# @return [Dry::Monads::Result::Success, Dry::Monads::Result::Failure] the result of the publishing operation
def publish_families(primary_person_hbx_ids)
csv_file_name = "#{Rails.root}/crm_force_sync_#{DateTime.now.strftime('%Y_%m_%d_%H_%M_%S')}.csv"

CSV.open(csv_file_name, 'w', force_quotes: true) do |csv|
csv << ['Hbx ID', 'Result', 'Message']

primary_person_hbx_ids.each do |hbx_id|
result = ::Operations::Crm::Family::Publish.new.call(hbx_id: hbx_id)

csv << [
hbx_id,
result.success? ? 'Success' : 'Failed',
result.success? ? result.success : result.failure
]
end
end

Success(csv_file_name)
end

def generate_success_message(csv_file_name)
Success("Successfully published events for all families. Review the CSV file with results: #{csv_file_name}")
end
end
end
end
38 changes: 38 additions & 0 deletions script/crm/trigger_force_sync.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

# This script takes a string of comma separated hbx_ids and initiates the force sync operation.

# Command to trigger the script:
# CLIENT=me bundle exec rails runner script/crm/trigger_force_sync.rb 'hbx_id1, hbx_id2, hbx_id3'

# Checks if the :async_publish_updated_families feature is enabled in the EnrollRegistry.
# If the feature is not enabled, it prints a message and exits the script.
#
# @return [void]
unless EnrollRegistry.feature_enabled?(:async_publish_updated_families)
puts 'The CRM Sync Refactor feature is not enabled. Please enable the feature :async_publish_updated_families to run this script. EXITING.'
exit
end

# Creates an array of hbx_ids from the input string and removes any nil values, spaces or empty strings.
# @example
# bundle exec rails runner script/crm/trigger_force_sync.rb 'hbx_id1, hbx_id2, hbx_id3'
# @param [String] ARGV[0] a comma separated list of HBX IDs
# @return [void]
primary_person_hbx_ids = begin
# The below line splits the input string by comma, removes any leading or trailing spaces, converts the values to string, and rejects any empty strings.
ARGV[0].split(',').map { |hbx_id| hbx_id.strip.to_s }.reject(&:empty?)
rescue StandardError => e
puts "Error: #{e.message}. Invalid input for primary_hbx_ids: #{ARGV[0]}. Provide a comma separated list of HBX IDs."
exit
end

# Initiates the force sync operation with the provided HBX IDs.
# @param [Array<String>] primary_hbx_ids an array of HBX IDs
# @return [void]
result = ::Operations::Crm::ForceSync.new.call({ primary_hbx_ids: primary_person_hbx_ids })
if result.success?
puts result.success
else
puts result.failure
end
49 changes: 49 additions & 0 deletions spec/domain/operations/crm/family/publish_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Operations::Crm::Family::Publish do
before :all do
DatabaseCleaner.clean
end

after :all do
DatabaseCleaner.clean
end

let(:primary) { FactoryBot.create(:person, :with_consumer_role, :with_active_consumer_role) }
let(:family) { FactoryBot.create(:family, :with_primary_family_member, person: primary) }

describe '#call' do
context 'success' do
let(:hbx_id) { family.primary_person.hbx_id }

it 'publishes the event successfully' do
result = subject.call(hbx_id: hbx_id)
expect(result.success).to eq(
"Successfully published event: events.families.created_or_updated for family with primary person hbx_id: #{hbx_id}"
)
end
end

context 'failure' do
context 'when a person does not exist with the given hbx_id' do
it 'returns failure' do
result = subject.call(hbx_id: 'primary.hbx_id')
expect(result.failure).to eq(
"Provide a valid person_hbx_id to fetch person. Invalid input hbx_id: primary.hbx_id"
)
end
end

context 'when person does not have primary family' do
it 'returns failure' do
result = subject.call(hbx_id: primary.hbx_id)
expect(result.failure).to eq(
"Primary Family does not exist with given hbx_id: #{primary.hbx_id}"
)
end
end
end
end
end
99 changes: 99 additions & 0 deletions spec/domain/operations/crm/force_sync_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Operations::Crm::ForceSync do
include Dry::Monads[:result]

before :all do
DatabaseCleaner.clean
end

after :all do
DatabaseCleaner.clean
end

describe '#call' do
context 'success' do
let(:primary1_hbx_id) { 'primary1' }
let(:primary2_hbx_id) { 'primary2' }
let(:primary3_hbx_id) { 'primary3' }
let(:params) { { primary_hbx_ids: [primary1_hbx_id, primary2_hbx_id, primary3_hbx_id] } }

let(:publish_instance) { instance_double(Operations::Crm::Family::Publish) }

let(:message1) { "Successfully published event: events.families.created_or_updated for family with primary person hbx_id: #{primary1_hbx_id}" }
let(:message2) { "Provide a valid person_hbx_id to fetch person. Invalid input hbx_id: primary2_hbx_id" }
let(:message3) { "Primary Family does not exist with given hbx_id: #{primary3_hbx_id}" }

before do
allow(::Operations::Crm::Family::Publish).to receive(:new).and_return(publish_instance)
allow(publish_instance).to receive(:call).with(hbx_id: primary1_hbx_id).and_return(
Success(message1)
)

allow(publish_instance).to receive(:call).with(hbx_id: primary2_hbx_id).and_return(
Failure(message2)
)

allow(publish_instance).to receive(:call).with(hbx_id: primary3_hbx_id).and_return(
Failure(message3)
)
end

it 'returns a success monad' do
expect(subject.call(params).success?).to be_truthy
end

it 'creates a CSV ' do
message = subject.call(params).success
csv_file_name = message.split(': ').last
expect(
File.exist?(csv_file_name)
).to be_truthy
end

it 'logs the results of the operation in CSV' do
message = subject.call(params).success
csv_file_name = message.split(': ').last
csv = CSV.read(csv_file_name)
expect(csv[1]).to eq([primary1_hbx_id, 'Success', message1])
expect(csv[2]).to eq([primary2_hbx_id, 'Failed', message2])
expect(csv[3]).to eq([primary3_hbx_id, 'Failed', message3])
end
end

context 'failure' do
context 'when primary_hbx_ids is not an array' do
let(:params) { { primary_hbx_ids: 'primary1' } }

it 'returns a failure monad' do
expect(subject.call(params).failure).to eq('Invalid input for primary_hbx_ids: primary1. Provide an array of HBX IDs.')
end
end

context 'when primary_hbx_ids is an empty array' do
let(:params) { { primary_hbx_ids: [] } }

it 'returns a failure monad' do
expect(subject.call(params).failure).to eq('Invalid input for primary_hbx_ids: []. Provide an array of HBX IDs.')
end
end

context 'when primary_hbx_ids array has non-string elements' do
let(:params) { { primary_hbx_ids: [100] } }

it 'returns a failure monad' do
expect(subject.call(params).failure).to eq('Invalid input for primary_hbx_ids: [100]. Provide an array of HBX IDs.')
end
end
end
end

after :all do
# Clean up the CSV files created during the test
Dir.glob("#{Rails.root}/crm_force_sync_*.csv").each do |file|
FileUtils.rm(file)
end
end
end

0 comments on commit 4e40d42

Please sign in to comment.