Skip to content
Open
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
9cb2327
deps: promote restforce/activeforce as direct deps, remove openstax_s…
mwvolo May 21, 2026
efba7d8
deps: use openstax_active_force, not the unrelated activeforce gem
mwvolo May 21, 2026
4f43b45
salesforce: remove stale openstax_salesforce references (replaced in …
mwvolo May 21, 2026
2066d8b
salesforce: add Salesforce module + Configuration
mwvolo May 21, 2026
055f781
salesforce: add Client (Restforce wrapper)
mwvolo May 21, 2026
7d3f34d
salesforce: add Records::Base with ActiveForce client lazy-init
mwvolo May 22, 2026
44fa7ec
salesforce: add Records (Lead, Contact, School)
mwvolo May 22, 2026
73c274b
salesforce: add initializer + OpenStax inflection acronym
mwvolo May 22, 2026
1f5ba8a
salesforce: repoint OpenStax::Salesforce::Remote::* to Salesforce::Re…
mwvolo May 22, 2026
ad5fe4d
salesforce: append new SecurityLog event types for sync redesign
mwvolo May 22, 2026
6c3c767
salesforce: add Audit wrapper with stable event taxonomy
mwvolo May 22, 2026
9e6579a
salesforce: add Settings fields + wrappers for sync, reconcile, alerts
mwvolo May 22, 2026
16fbbd7
salesforce: add Metrics (counters + Sentry check-in + tagged alerts)
mwvolo May 22, 2026
4956a43
salesforce: add Verify (lead/contact ownership + replacement checks)
mwvolo May 22, 2026
5028307
salesforce: add Lookup waterfall (stored_id -> uuid -> email)
mwvolo May 22, 2026
d02df5a
salesforce: add ResolveFacultyStatus (signup + contact paths)
mwvolo May 22, 2026
10dd0f0
salesforce: extract BuildLead (pure User -> Lead mapping)
mwvolo May 22, 2026
83a32d7
salesforce: add UpsertLead orchestrator with persist retry
mwvolo May 22, 2026
b0b98e4
salesforce: Newflow::CreateOrUpdateSalesforceLead becomes a shim
mwvolo May 22, 2026
92732a4
salesforce: add SyncContacts (cursor-driven, verify-before-swap)
mwvolo May 22, 2026
fe7b82e
salesforce: UpdateUserContactInfo becomes a shim
mwvolo May 22, 2026
16161d9
salesforce: extract SyncSchools, keep UpdateSchoolSalesforceInfo shim
mwvolo May 22, 2026
5d2b00e
salesforce: add salesforce_drift_findings table + index on users.sale…
mwvolo May 22, 2026
6b16e2a
salesforce: SalesforceDriftFinding model + factory + spec
mwvolo May 22, 2026
7c76385
salesforce: add Reconcile (3 passes + SF-orphan sweep + finalize)
mwvolo May 22, 2026
b3cba30
salesforce: schedule Reconcile daily, delete dead UpdateUserLeadInfo
mwvolo May 22, 2026
b7251bb
salesforce: /admin/salesforce_drift_findings (list + mark resolved)
mwvolo May 22, 2026
96c16cd
salesforce: Salesforce timeline section on admin user edit page
mwvolo May 22, 2026
7ef3cec
salesforce: spec helpers absorb gem helpers + add records-stub
mwvolo May 22, 2026
6aed4ec
salesforce: fix CI regressions surfaced after Phase 8
mwvolo May 22, 2026
eb43c84
salesforce: ensure_school_or_fallback creates a stub local School whe…
mwvolo May 22, 2026
c086b37
salesforce: drop VCR cassettes for Salesforce-touching specs in favor…
mwvolo May 22, 2026
c2eb648
salesforce: bump SF API version to v66.0 (Spring '26), drop dead VCR …
mwvolo May 22, 2026
6b72179
js: remove setTimeout race from NewflowUi.enableOnChecked
mwvolo May 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ gem 'will_paginate'
gem 'chronic'

# Salesforce
gem 'openstax_salesforce'
gem 'restforce'
gem 'openstax_active_force'

# Allows 'ap' alternative to 'pp', used in a mailer
gem 'awesome_print'
Expand Down
7 changes: 2 additions & 5 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -518,10 +518,6 @@ GEM
rails (>= 3.0)
openstax_rescue_from (4.3.0)
rails (>= 3.1, < 7.0)
openstax_salesforce (8.3.0)
openstax_active_force
rails (>= 5.0, < 7.0)
restforce
openstax_transaction_isolation (2.0.0)
activerecord (>= 6)
openstax_transaction_retry (2.0.0)
Expand Down Expand Up @@ -876,11 +872,11 @@ DEPENDENCIES
omniauth-google-oauth2
omniauth-identity
omniauth-twitter
openstax_active_force
openstax_api
openstax_healthcheck
openstax_path_prefixer!
openstax_rescue_from
openstax_salesforce
openstax_transaction_isolation
openstax_transaction_retry
openstax_utilities
Expand All @@ -903,6 +899,7 @@ DEPENDENCIES
recaptcha
redis-rails
representable
restforce
rexml
rspec-instafail
rspec-rails
Expand Down
17 changes: 17 additions & 0 deletions app/controllers/admin/salesforce_drift_findings_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module Admin
class SalesforceDriftFindingsController < BaseController
def index
scope = SalesforceDriftFinding.open.includes(:user).order(last_seen_at: :desc)
scope = scope.where(category: params[:category]) if params[:category].present?
scope = scope.where(user_id: params[:user_id]) if params[:user_id].present?
@findings = scope.limit(500)
@categories = SalesforceDriftFinding.open.distinct.pluck(:category).sort
end

def update
finding = SalesforceDriftFinding.find(params[:id])
finding.resolve!
redirect_to admin_salesforce_drift_findings_path, notice: 'Finding marked resolved.'
end
end
end
10 changes: 9 additions & 1 deletion app/controllers/admin/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ def js_search

def index; end

def edit
@salesforce_timeline = SecurityLog
.where(user_id: @user.id)
.where("event_type LIKE 'salesforce_%'")
.order(created_at: :asc)
.limit(500)
Comment thread
mwvolo marked this conversation as resolved.
end

# Used by full console page
def search
security_log :users_searched_by_admin, search: params[:search]
Expand Down Expand Up @@ -108,7 +116,7 @@ def change_salesforce_contact
end

begin
contact = OpenStax::Salesforce::Remote::Contact.find(new_id)
contact = Salesforce::Records::Contact.find(new_id)

if contact.present?
# The contact really exists, so save its ID to the User
Expand Down
43 changes: 43 additions & 0 deletions app/models/salesforce_drift_finding.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
class SalesforceDriftFinding < ApplicationRecord
belongs_to :user, optional: true

validates :category, presence: true
validates :first_seen_at, presence: true
validates :last_seen_at, presence: true

scope :open, -> { where(resolved_at: nil) }
scope :resolved, -> { where.not(resolved_at: nil) }
scope :for_category, ->(c) { where(category: c) }

# Upsert an open finding: if a matching open finding already exists, bump
# its last_seen_at (and details, when provided); otherwise create one.
def self.upsert_finding!(category:, user: nil, record_type: nil, record_id: nil, details: {})
existing = open.find_by(
user_id: user&.id,
category: category,
salesforce_record_type: record_type,
salesforce_record_id: record_id
)

if existing
attrs = { last_seen_at: Time.current }
attrs[:details] = details if details.present?
existing.update!(attrs)
existing
else
create!(
user: user,
category: category,
salesforce_record_type: record_type,
salesforce_record_id: record_id,
details: details,
first_seen_at: Time.current,
last_seen_at: Time.current
)
end
end

def resolve!
update!(resolved_at: Time.current)
end
end
23 changes: 23 additions & 0 deletions app/models/security_log.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,29 @@ class SecurityLog < ApplicationRecord
user_already_has_contact_not_creating_lead
creating_new_salesforce_lead
salesforce_lead_save_failed
salesforce_lookup_started
salesforce_lookup_matched_by_stored_id
salesforce_lookup_matched_by_uuid
salesforce_lookup_matched_by_email
salesforce_lookup_stored_id_disowned
salesforce_lookup_email_collision
salesforce_upsert_lead_begin
salesforce_upsert_lead_saved
salesforce_upsert_lead_save_failed
salesforce_upsert_lead_skipped_user_has_contact
salesforce_lead_id_persist_retry
salesforce_lead_id_persist_failed
salesforce_stale_contact_id_cleared
salesforce_contact_skipped_merged_or_deleted
salesforce_contact_id_swapped
salesforce_contact_id_conflict
salesforce_user_school_not_cached
salesforce_reconcile_user_ok
salesforce_reconcile_contact_id_cleared
salesforce_reconcile_followed_merge
salesforce_reconcile_attached_from_conversion
salesforce_link_restored_by_reconcile
salesforce_contact_id_orphaned
]

json_serialize :event_data, Hash
Expand Down
4 changes: 2 additions & 2 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -162,11 +162,11 @@ class User < ApplicationRecord
attribute :is_not_gdpr_location, :boolean, default: nil

def lead
OpenStax::Salesforce::Remote::Lead.find_by(email: best_email_address_for_salesforce)
Salesforce::Records::Lead.find_by(email: best_email_address_for_salesforce)
end

def contact
OpenStax::Salesforce::Remote::Contact.find(salesforce_contact_id)
Salesforce::Records::Contact.find(salesforce_contact_id)
end

def most_accurate_school_name
Expand Down
Loading
Loading