Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,14 @@ export const SUBSCRIPTION_EVENTS: SubscriptionEventModel[] = [
labelKey: 'settings.notifications.notificationPreferences.items.preprints',
},
];

export const FORM_EVENT_TO_API_EVENT: Record<string, string> = {
new_pending_submissions: 'new_pending_submissions',
files_updated: 'files_updated',
global_file_updated: 'global_file_updated',
};

export const API_EVENT_TO_FORM_EVENT: Record<string, string> = {
new_pending_submissions: 'new_pending_submissions',
files_updated: 'global_file_updated',
};
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,19 @@ describe('NotificationsComponent', () => {
subscribeOsfHelpEmail: false,
};

// new_pending_submissions → global_reviews
// files_updated → global_file_updated
const mockNotificationSubscriptions = [
{ id: 'id1', event: SubscriptionEvent.GlobalFileUpdated, frequency: SubscriptionFrequency.Daily },
{
id: 'id2',
event: SubscriptionEvent.GlobalFileUpdated,
id: 'osf_new_pending_submissions',
event: 'new_pending_submissions',
frequency: SubscriptionFrequency.Instant,
},
{
id: 'cuzg4_global_file_updated',
event: 'files_updated',
frequency: SubscriptionFrequency.Daily,
},
];

beforeEach(async () => {
Expand Down Expand Up @@ -77,7 +83,7 @@ describe('NotificationsComponent', () => {
return signal(null);
});

MOCK_STORE.dispatch.mockImplementation(() => of());
MOCK_STORE.dispatch.mockReturnValue(of({}));

await TestBed.configureTestingModule({
imports: [
Expand Down Expand Up @@ -116,9 +122,9 @@ describe('NotificationsComponent', () => {

return signal(null);
});
component.emailPreferencesFormSubmit();

expect(loaderService.hide).not.toHaveBeenCalled();
component.emailPreferencesFormSubmit();
expect(loaderService.hide).toHaveBeenCalledTimes(1);
});

it('should handle subscription completion correctly', () => {
Expand All @@ -136,11 +142,15 @@ describe('NotificationsComponent', () => {
it('should call dispatch only once per subscription change', () => {
const mockDispatch = jest.fn().mockReturnValue(of({}));
MOCK_STORE.dispatch.mockImplementation(mockDispatch);
const event = SubscriptionEvent.GlobalFileUpdated;
const frequency = SubscriptionFrequency.Daily;

component.onSubscriptionChange(event, frequency);
component.onSubscriptionChange(SubscriptionEvent.GlobalFileUpdated, SubscriptionFrequency.Daily);

expect(mockDispatch).toHaveBeenCalledTimes(1);
});

it('should default to API value', () => {
const subs = component.notificationSubscriptionsForm.value;
expect(subs.new_pending_submissions).toBe(SubscriptionFrequency.Instant);
expect(subs.global_file_updated).toBe(SubscriptionFrequency.Daily);
});
});
43 changes: 38 additions & 5 deletions src/app/features/settings/notifications/notifications.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { ToastService } from '@osf/shared/services/toast.service';
import { AccountSettings } from '../account-settings/models';
import { AccountSettingsSelectors, GetAccountSettings, UpdateAccountSettings } from '../account-settings/store';

import { SUBSCRIPTION_EVENTS } from './constants';
import { API_EVENT_TO_FORM_EVENT, FORM_EVENT_TO_API_EVENT, SUBSCRIPTION_EVENTS } from './constants';
import { EmailPreferencesForm, EmailPreferencesFormControls } from './models';
import {
GetAllGlobalNotificationSubscriptions,
Expand Down Expand Up @@ -80,7 +80,9 @@ export class NotificationsComponent implements OnInit {
notificationSubscriptionsForm = this.fb.group(
SUBSCRIPTION_EVENTS.reduce(
(control, { event }) => {
control[event] = this.fb.control<SubscriptionFrequency>(SubscriptionFrequency.Never, { nonNullable: true });
control[event as string] = this.fb.control<SubscriptionFrequency>(SubscriptionFrequency.Never, {
nonNullable: true,
});
return control;
},
{} as Record<string, FormControl<SubscriptionFrequency>>
Expand Down Expand Up @@ -128,7 +130,24 @@ export class NotificationsComponent implements OnInit {
onSubscriptionChange(event: SubscriptionEvent, frequency: SubscriptionFrequency) {
const user = this.currentUser();
if (!user) return;
const id = `${user.id}_${event}`;

const eventKey = event as string;

const apiEventName = FORM_EVENT_TO_API_EVENT[eventKey] ?? eventKey;

let id: string | undefined;

if (event === SubscriptionEvent.GlobalReviews) {
const subs = this.notificationSubscriptions();
const match = subs.find((s) => (s.event as string) === 'new_pending_submissions');
if (match) {
id = match.id;
} else {
return;
}
} else {
id = `${user.id}_${apiEventName}`;
}

this.loaderService.show();
this.actions.updateNotificationSubscription({ id, frequency }).subscribe(() => {
Expand All @@ -145,10 +164,24 @@ export class NotificationsComponent implements OnInit {
}

private updateNotificationSubscriptionsForm() {
const subs = this.notificationSubscriptions();
if (!subs?.length) {
return;
}

const patch: Record<string, SubscriptionFrequency> = {};

for (const sub of this.notificationSubscriptions()) {
patch[sub.event] = sub.frequency;
for (const sub of subs) {
const apiEvent = sub.event as string | null;
if (!apiEvent) {
continue;
}

const formEventKey = API_EVENT_TO_FORM_EVENT[apiEvent];

if (formEventKey) {
patch[formEventKey] = sub.frequency;
}
}

this.notificationSubscriptionsForm.patchValue(patch);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export enum SubscriptionEvent {
GlobalFileUpdated = 'global_file_updated',
GlobalReviews = 'global_reviews',
GlobalReviews = 'new_pending_submissions',
FileUpdated = 'file_updated',
}