Repository navigation
Replace the Auditor write path with MemberActivityRecorder to create one funnel for activity rows #3068
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Replace the Auditor write path with MemberActivityRecorder to create one funnel for activity rows #3068
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -42,10 +42,16 @@ class MemberActivityRecorder | |
| # historical activity is ever backfilled. | ||
| TRACKING_STARTED_ON = Date.new(2026, 9, 8).freeze | ||
|
|
||
| def self.record(actor:, key:, trackable: nil, recipient: nil) | ||
| def self.record(actor:, key:, trackable: nil, recipient: nil, contact_email: nil) | ||
| Rails.logger.warn("MemberActivityRecorder: unregistered key '#{key}'") unless KEYS.include?(key) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both migrated call sites pass keys that Adding the four keys follows the comment on |
||
| trackable ||= actor | ||
| PublicActivity::Activity.create!(owner: actor, key:, trackable:, recipient:) | ||
| PublicActivity::Activity.create!( | ||
| owner: actor, | ||
| key:, | ||
| trackable:, | ||
| recipient:, | ||
| parameters: contact_email ? { note: contact_email } : {} | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nothing in |
||
| ) | ||
| rescue StandardError => e | ||
| Rails.logger.warn("MemberActivityRecorder failed (#{key}): #{e.class}: #{e.message}") | ||
| end | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| require 'rails_helper' | ||
|
|
||
| RSpec.describe ContactPreferencesController do | ||
| let(:contact) { Fabricate(:contact, mailing_list_consent: false) } | ||
|
|
||
| before do | ||
| allow(ContactMailingListService).to receive(:sync) | ||
| end | ||
|
|
||
| describe 'GET #show' do | ||
| it 'sets the contact for a valid token' do | ||
| get :show, params: { token: contact.token } | ||
|
|
||
| expect(response).to have_http_status(:success) | ||
| expect(controller.instance_variable_get(:@contact)).to eq(contact) | ||
| expect(flash[:notice]).to be_nil | ||
| end | ||
|
|
||
| it 'shows an invalid-session notice for an unknown token' do | ||
| get :show, params: { token: 'unknown-token' } | ||
|
|
||
| expect(response).to have_http_status(:success) | ||
| expect(flash[:notice]).to eq(I18n.t('messages.invalid_session')) | ||
| end | ||
| end | ||
|
|
||
| describe 'PATCH #update' do | ||
| it 'passes the contact and email to the activity recorder' do | ||
| expect(MemberActivityRecorder).to receive(:record).with( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two problems with this example.
Dropping it is enough. If the intent is to pin that no |
||
| actor: contact, | ||
| key: 'sponsor.contact_subscribe', | ||
| trackable: contact.sponsor, | ||
| contact_email: contact.email | ||
| ) | ||
|
|
||
| patch :update, params: { | ||
| contact: { token: contact.token, mailing_list_consent: '1' } | ||
| } | ||
| end | ||
|
|
||
| it 'subscribes the contact and records the consent activity' do | ||
| patch :update, params: { | ||
| contact: { token: contact.token, mailing_list_consent: '1' } | ||
| } | ||
|
|
||
| expect(contact.reload.mailing_list_consent).to be(true) | ||
| expect(ContactMailingListService).to have_received(:sync).with(contact) | ||
| expect(response).to redirect_to( | ||
| contact_preferences_path(token: contact.token, notice: I18n.t('messages.contact_preferences.updated')) | ||
| ) | ||
|
|
||
| activity = PublicActivity::Activity.find_by!(key: 'sponsor.contact_subscribe') | ||
| expect(activity.owner).to eq(contact) | ||
| expect(activity.trackable).to eq(contact.sponsor) | ||
| expect(activity.parameters).to include(note: contact.email) | ||
| end | ||
|
|
||
| it 'unsubscribes the contact and records the consent activity' do | ||
| contact.update!(mailing_list_consent: true) | ||
|
|
||
| patch :update, params: { | ||
| contact: { token: contact.token, mailing_list_consent: '0' } | ||
| } | ||
|
|
||
| expect(contact.reload.mailing_list_consent).to be(false) | ||
| expect(ContactMailingListService).to have_received(:sync).with(contact) | ||
|
|
||
| activity = PublicActivity::Activity.find_by!(key: 'sponsor.contact_unsubscribe') | ||
| expect(activity.owner).to eq(contact) | ||
| expect(activity.trackable).to eq(contact.sponsor) | ||
| expect(activity.parameters).to include(note: contact.email) | ||
| end | ||
|
|
||
| it 'returns not found when the submitted token is unknown' do | ||
| patch :update, params: { | ||
| contact: { token: 'unknown-token', mailing_list_consent: '1' } | ||
| } | ||
|
|
||
| expect(response).to have_http_status(:not_found) | ||
| end | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -161,8 +161,15 @@ | |
| context 'with activities' do | ||
| scenario 'when there are activities' do | ||
| contact = sponsor.contacts.first | ||
| audit = Auditor::Audit.new(sponsor, 'sponsor.admin_contact_subscribe', manager, contact) | ||
| audit.log_with_note(contact.email) | ||
| contact_audit_key = contact.mailing_list_consent ? 'subscribe' : 'unsubscribe' | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This mirrors the controller's own ternary, so a key-derivation regression there would still satisfy the rendering assertions below. Hardcode it here ( |
||
|
|
||
| MemberActivityRecorder.record( | ||
| actor: manager, | ||
| key: "sponsor.admin_contact_#{contact_audit_key}", | ||
| trackable: sponsor, | ||
| recipient: contact, | ||
| contact_email: contact.email | ||
| ) | ||
|
|
||
| visit admin_sponsor_path(sponsor) | ||
|
|
||
|
|
@@ -173,8 +180,15 @@ | |
|
|
||
| scenario 'when an activity is associated with a deleted contact' do | ||
| contact = sponsor.contacts.first | ||
| audit = Auditor::Audit.new(sponsor, 'sponsor.admin_contact_subscribe', manager, contact) | ||
| audit.log_with_note(contact.email) | ||
| contact_audit_key = contact.mailing_list_consent ? 'subscribe' : 'unsubscribe' | ||
|
|
||
| MemberActivityRecorder.record( | ||
| actor: manager, | ||
| key: "sponsor.admin_contact_#{contact_audit_key}", | ||
| trackable: sponsor, | ||
| recipient: contact, | ||
| contact_email: contact.email | ||
| ) | ||
|
|
||
| contact.delete | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is now 11 lines against
Metrics/MethodLengthMax 10, so the lint job fails. Pulling the hash into a local keeps thecreate!call on one line: