diff --git a/Gemfile.lock b/Gemfile.lock index c0283d9c2..0e8efd8fa 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -125,6 +125,7 @@ GEM rack (~> 2) aws-sigv4 (1.4.0) aws-eventstream (~> 1, >= 1.0.2) + base64 (0.2.0) bcrypt (3.1.18) bindata (2.4.10) bindex (0.8.1) @@ -138,7 +139,7 @@ GEM bootstrap-sass (3.4.1) autoprefixer-rails (>= 5.2.1) sassc (>= 2.0.0) - builder (3.2.4) + builder (3.3.0) byebug (11.1.3) capybara (3.35.3) addressable @@ -185,7 +186,7 @@ GEM compass (~> 1.0.0) sass-rails (< 5.1) sprockets (< 4.0) - concurrent-ruby (1.2.3) + concurrent-ruby (1.3.3) crack (0.4.5) rexml crass (1.0.6) @@ -251,7 +252,7 @@ GEM http_parser.rb (~> 0.6.0) errbase (0.2.2) error_page_assets (0.4) - erubi (1.12.0) + erubi (1.13.0) eventmachine (1.2.0.1) exception_notification (4.4.3) actionmailer (>= 4.0, < 7) @@ -337,7 +338,8 @@ GEM activesupport (>= 4.2) aes_key_wrap bindata - jwt (2.7.1) + jwt (2.8.2) + base64 keyword_search (1.5.0) knockoutjs-rails (3.5.0) railties (>= 3.1, < 6) @@ -384,31 +386,31 @@ GEM nokogiri (~> 1) rake mini_mime (1.1.5) - mini_portile2 (2.8.5) + mini_portile2 (2.8.7) mini_racer (0.6.2) libv8-node (~> 16.10.0.0) - minitest (5.21.2) + minitest (5.24.0) msgpack (1.5.2) multi_json (1.15.0) multi_xml (0.6.0) - multipart-post (2.3.0) + multipart-post (2.4.1) nenv (0.3.0) - net-imap (0.4.9.1) + net-imap (0.4.14) date net-protocol net-pop (0.1.2) net-protocol net-protocol (0.2.2) timeout - net-smtp (0.4.0.1) + net-smtp (0.5.0) net-protocol - nio4r (2.7.0) - nokogiri (1.15.5) + nio4r (2.7.3) + nokogiri (1.15.6) mini_portile2 (~> 2.8.2) racc (~> 1.4) - nokogiri (1.15.5-x86_64-darwin) + nokogiri (1.15.6-x86_64-darwin) racc (~> 1.4) - nokogiri (1.15.5-x86_64-linux) + nokogiri (1.15.6-x86_64-linux) racc (~> 1.4) notiffany (0.1.0) nenv (~> 0.1) @@ -463,7 +465,7 @@ GEM rails (>= 3.0) openstax_rescue_from (4.2.0) rails (>= 3.1, < 7.0) - openstax_salesforce (7.6.2) + openstax_salesforce (7.7.0) openstax_active_force rails (>= 5.0, < 7.0) restforce @@ -499,8 +501,8 @@ GEM puma_worker_killer (0.3.1) get_process_mem (~> 0.2) puma (>= 2.7) - racc (1.7.3) - rack (2.2.8) + racc (1.8.0) + rack (2.2.9) rack-contrib (2.3.0) rack (~> 2.0) rack-cors (1.1.1) @@ -553,7 +555,7 @@ GEM rake (>= 0.8.7) thor (>= 0.19.0, < 2.0) rainbow (3.0.0) - rake (13.1.0) + rake (13.2.1) rb-fsevent (0.11.0) rb-inotify (0.10.1) ffi (~> 1.0) @@ -682,7 +684,8 @@ GEM spring (1.7.2) spring-commands-rspec (1.0.4) spring (>= 0.9.1) - sprockets (3.7.2) + sprockets (3.7.3) + base64 concurrent-ruby (~> 1.0) rack (> 1, < 3) sprockets-rails (3.4.2) @@ -694,7 +697,7 @@ GEM test_xml (0.1.8) diffy (~> 3.0) nokogiri (>= 1.3.2) - thor (1.3.0) + thor (1.3.1) thread_safe (0.3.6) tilt (2.0.10) timecop (0.8.1) diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 53a7d1306..ebecdc12b 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -93,8 +93,8 @@ def change_salesforce_contact return true end - if new_id.downcase == "remove" - flash[:notice] = "Removed the Salesforce Contact ID" + if new_id.downcase == 'remove' + flash[:notice] = 'Removed the Salesforce Contact ID' @user.salesforce_contact_id = nil return @user.save end @@ -104,16 +104,19 @@ def change_salesforce_contact if contact.present? # The contact really exists, so save its ID to the User - flash[:notice] = "Updated Salesforce Contact" + flash[:notice] = 'Updated Salesforce Contact' @user.salesforce_contact_id = new_id return @user.save + else + flash[:alert] = "Can't find a Salesforce contact with ID #{new_id}" end rescue # exploded, probably due to badly formed SF ID + flash[:alert] = 'Failed to update Salesforce Contact ID' end # if haven't returned yet, either exploded or contact was `nil` (not found) - flash[:alert] = "Can't find a Salesforce contact with ID #{new_id}" + return false end def update_user diff --git a/app/routines/update_salesforce_assignable_fields.rb b/app/routines/update_salesforce_assignable_fields.rb new file mode 100644 index 000000000..99a78faef --- /dev/null +++ b/app/routines/update_salesforce_assignable_fields.rb @@ -0,0 +1,25 @@ +class UpdateSalesforceAssignableFields + def self.call(created_after = nil) + new.call(created_after) + end + + def call(created_after) + created_after ||= 1.month.ago + + # Currently ExternalIds are only used by Assignable + # If this will change at some point, migrate ExternalIds first to add a field to distinguish them + ExternalId.select(:user_id, ExternalId.arel_table[:created_at].minimum.as('min_created_at')) + .group(:user_id) + .having(ExternalId.arel_table[:created_at].minimum.gt(created_after)) + .preload(:user) + .each do |external_id| + contact_id = external_id.user.salesforce_contact_id + next if contact_id.nil? + + contact = OpenStax::Salesforce::Remote::Contact.find(contact_id) + contact.assignable_interest = 'Fully Integrated' + contact.assignable_adoption_date = external_id.min_created_at + contact.save! + end + end +end diff --git a/spec/cassettes/Change_Salesforce_contact_manually/can_be_set_if_the_Contact_exists_in_SF.yml b/spec/cassettes/Change_Salesforce_contact_manually/can_be_set_if_the_Contact_exists_in_SF.yml index f2374d52b..a074606c0 100644 --- a/spec/cassettes/Change_Salesforce_contact_manually/can_be_set_if_the_Contact_exists_in_SF.yml +++ b/spec/cassettes/Change_Salesforce_contact_manually/can_be_set_if_the_Contact_exists_in_SF.yml @@ -122,7 +122,7 @@ http_interactions: recorded_at: Mon, 22 Jan 2024 19:56:09 GMT - request: method: get - uri: "/services/data/v51.0/query?q=SELECT%20Id,%20Name,%20FirstName,%20LastName,%20Email,%20Email_alt__c,%20Faculty_Confirmed_Date__c,%20FV_Status__c,%20LastModifiedDate,%20AccountId,%20School_Type__c,%20SendFacultyVerificationTo__c,%20All_Emails__c,%20Adoption_Status__c,%20Grant_Tutor_Access__c,%20Title_1_school__c,%20Accounts_UUID__c,%20LeadSource,%20Signup_Date__c,%20Renewal_Eligible__c%20FROM%20Contact%20WHERE%20(Id%20=%20%27003VZ000002wESuYAM%27)%20LIMIT%201" + uri: "/services/data/v51.0/query?q=SELECT%20Id,%20Name,%20FirstName,%20LastName,%20Email,%20Email_alt__c,%20Faculty_Confirmed_Date__c,%20FV_Status__c,%20LastModifiedDate,%20AccountId,%20School_Type__c,%20SendFacultyVerificationTo__c,%20All_Emails__c,%20Adoption_Status__c,%20Grant_Tutor_Access__c,%20Title_1_school__c,%20Accounts_UUID__c,%20LeadSource,%20Signup_Date__c,%20Renewal_Eligible__c,%20Assignable_Interest__c,%20Assignable_Adoption_Date__c%20FROM%20Contact%20WHERE%20(Id%20=%20%27003VZ000002wESuYAM%27)%20LIMIT%201" body: encoding: US-ASCII string: '' diff --git a/spec/cassettes/Change_Salesforce_contact_manually/cannot_be_set_if_the_Contact_does_not_exist_in_SF.yml b/spec/cassettes/Change_Salesforce_contact_manually/cannot_be_set_if_the_Contact_does_not_exist_in_SF.yml index 83bfdbe91..ab4162d29 100644 --- a/spec/cassettes/Change_Salesforce_contact_manually/cannot_be_set_if_the_Contact_does_not_exist_in_SF.yml +++ b/spec/cassettes/Change_Salesforce_contact_manually/cannot_be_set_if_the_Contact_does_not_exist_in_SF.yml @@ -2,7 +2,7 @@ http_interactions: - request: method: get - uri: "/services/data/v51.0/query?q=SELECT%20Id,%20Name,%20FirstName,%20LastName,%20Email,%20Email_alt__c,%20Faculty_Confirmed_Date__c,%20FV_Status__c,%20LastModifiedDate,%20AccountId,%20School_Type__c,%20SendFacultyVerificationTo__c,%20All_Emails__c,%20Adoption_Status__c,%20Grant_Tutor_Access__c,%20Title_1_school__c,%20Accounts_UUID__c,%20LeadSource,%20Signup_Date__c,%20Renewal_Eligible__c%20FROM%20Contact%20WHERE%20(Id%20=%20%270010v000002Wo0qAAC%27)%20LIMIT%201" + uri: "/services/data/v51.0/query?q=SELECT%20Id,%20Name,%20FirstName,%20LastName,%20Email,%20Email_alt__c,%20Faculty_Confirmed_Date__c,%20FV_Status__c,%20LastModifiedDate,%20AccountId,%20School_Type__c,%20SendFacultyVerificationTo__c,%20All_Emails__c,%20Adoption_Status__c,%20Grant_Tutor_Access__c,%20Title_1_school__c,%20Accounts_UUID__c,%20LeadSource,%20Signup_Date__c,%20Renewal_Eligible__c,%20Assignable_Interest__c,%20Assignable_Adoption_Date__c%20FROM%20Contact%20WHERE%20(Id%20=%20%270010v000002Wo0qAAC%27)%20LIMIT%201" body: encoding: US-ASCII string: '' diff --git a/spec/cassettes/Change_Salesforce_contact_manually/cannot_be_set_if_the_ID_is_of_malformed.yml b/spec/cassettes/Change_Salesforce_contact_manually/cannot_be_set_if_the_ID_is_of_malformed.yml index 3215fa89d..41427e037 100644 --- a/spec/cassettes/Change_Salesforce_contact_manually/cannot_be_set_if_the_ID_is_of_malformed.yml +++ b/spec/cassettes/Change_Salesforce_contact_manually/cannot_be_set_if_the_ID_is_of_malformed.yml @@ -2,7 +2,7 @@ http_interactions: - request: method: get - uri: "/services/data/v51.0/query?q=SELECT%20Id,%20Name,%20FirstName,%20LastName,%20Email,%20Email_alt__c,%20Faculty_Confirmed_Date__c,%20FV_Status__c,%20LastModifiedDate,%20AccountId,%20School_Type__c,%20SendFacultyVerificationTo__c,%20All_Emails__c,%20Adoption_Status__c,%20Grant_Tutor_Access__c,%20Title_1_school__c,%20Accounts_UUID__c,%20LeadSource,%20Signup_Date__c,%20Renewal_Eligible__c%20FROM%20Contact%20WHERE%20(Id%20=%20%27somethingwonky%27)%20LIMIT%201" + uri: "/services/data/v51.0/query?q=SELECT%20Id,%20Name,%20FirstName,%20LastName,%20Email,%20Email_alt__c,%20Faculty_Confirmed_Date__c,%20FV_Status__c,%20LastModifiedDate,%20AccountId,%20School_Type__c,%20SendFacultyVerificationTo__c,%20All_Emails__c,%20Adoption_Status__c,%20Grant_Tutor_Access__c,%20Title_1_school__c,%20Accounts_UUID__c,%20LeadSource,%20Signup_Date__c,%20Renewal_Eligible__c%20Assignable_Interest__c,%20Assignable_Adoption_Date__c%20FROM%20Contact%20WHERE%20(Id%20=%20%27somethingwonky%27)%20LIMIT%201" body: encoding: US-ASCII string: '' diff --git a/spec/features/admin/change_salesforce_contact_manually_spec.rb b/spec/features/admin/change_salesforce_contact_manually_spec.rb index fc24ed7d9..123cff307 100644 --- a/spec/features/admin/change_salesforce_contact_manually_spec.rb +++ b/spec/features/admin/change_salesforce_contact_manually_spec.rb @@ -24,6 +24,7 @@ @target_user.update_attribute(:salesforce_contact_id, 'something') fill_in 'user_salesforce_contact_id', with: 'remove' click_button 'Save' + expect(page).to have_content('successfully updated') @target_user.reload expect(@target_user.salesforce_contact_id).to be_nil end @@ -32,6 +33,7 @@ contact = @proxy.new_contact fill_in 'user_salesforce_contact_id', with: contact.id click_button 'Save' + expect(page).to have_content('successfully updated') @target_user.reload expect(@target_user.salesforce_contact_id).to eq contact.id end @@ -40,14 +42,16 @@ @target_user.update_attribute(:salesforce_contact_id, 'original') fill_in 'user_salesforce_contact_id', with: '0010v000002Wo0qAAC' click_button 'Save' + expect(page).to have_content("Can't find") @target_user.reload expect(@target_user.salesforce_contact_id).to eq "original" end - it 'cannot be set if the ID is of malformed' do + it 'cannot be set if the ID is malformed' do @target_user.update_attribute(:salesforce_contact_id, 'original') fill_in 'user_salesforce_contact_id', with: 'somethingwonky' click_button 'Save' + expect(page).to have_content('Failed') @target_user.reload expect(@target_user.salesforce_contact_id).to eq "original" end diff --git a/spec/routines/update_salesforce_assignable_fields_spec.rb b/spec/routines/update_salesforce_assignable_fields_spec.rb new file mode 100644 index 000000000..45783177f --- /dev/null +++ b/spec/routines/update_salesforce_assignable_fields_spec.rb @@ -0,0 +1,48 @@ +require 'rails_helper' + +RSpec.describe UpdateSalesforceAssignableFields, type: :routine do + let!(:non_assignable_student) { FactoryBot.create :user } + + let!(:non_assignable_instructor) { FactoryBot.create :user, salesforce_contact_id: 'TESTCONTACT1' } + + let!(:assignable_student) do + FactoryBot.create(:user).tap do |user| + FactoryBot.create :external_id, user: user + FactoryBot.create :external_id, user: user + end + end + + let!(:assignable_instructor) do + FactoryBot.create(:user, salesforce_contact_id: 'TESTCONTACT2').tap do |user| + FactoryBot.create :external_id, user: user + FactoryBot.create :external_id, user: user + end + end + + context 'new School' do + it "updates Salesforce Contact with Assignable user's info" do + stub_contacts [ non_assignable_instructor, assignable_instructor ] + + expect_any_instance_of(OpenStax::Salesforce::Remote::Contact).to( + receive(:assignable_interest=).with('Fully Integrated').and_call_original + ) + expect_any_instance_of(OpenStax::Salesforce::Remote::Contact).to( + receive(:assignable_adoption_date=).with( + assignable_instructor.external_ids.map(&:created_at).min + ).and_call_original + ) + expect_any_instance_of(OpenStax::Salesforce::Remote::Contact).to receive(:save!) + + described_class.call + end + end + + def stub_contacts(users) + sf_contacts = [users].flatten.map do |user| + id = user.salesforce_contact_id + [ id, OpenStax::Salesforce::Remote::Contact.new(id: id) ] + end.to_h + + expect(OpenStax::Salesforce::Remote::Contact).to receive(:find) { |id| sf_contacts[id] } + end +end