added cycle completed notification toggle

This commit is contained in:
Flaminel committed 2026-10-21 01:14:27 +03:00
1 parent 12fa674246
commit 2717fcabdb
14 files changed
+50

No files matched your search

@@ -59,4 +59,5 @@ export const NEW_FEATURES: NewFeature[] = [
{ id: 'seeker-search-specials' },
{ id: 'seeker-search-anime' },
{ id: 'seeker-reset-cycle' },
{ id: 'seeker-cycle-notification' },
];
@@ -156,6 +156,7 @@ export class DocumentationService {
'onSearchItemGrabbed': 'event-configuration',
'onForceImported': 'event-configuration',
'onErroredStrike': 'event-configuration',
'onSeekerCycleCompleted': 'event-configuration',
},
'notifications/notifiarr': {
'apiKey': 'api-key',
@@ -33,6 +33,7 @@ const TYPE_COLORS: Record<string, string> = {
[EventType.DownloadCleaned]: '#22c55e',
[EventType.DownloadStopped]: '#6366f1',
[EventType.StrikeReset]: '#10b981',
[EventType.SeekerCycleCompleted]: '#a855f7',
};
const FALLBACK_COLOR = '#94a3b8';
@@ -220,6 +220,10 @@
<app-toggle label="Errored Strike" [formField]="modalForm.onErroredStrike"
hint="Notify when an errored download strike occurs"
helpKey="notifications:onErroredStrike" />
<app-toggle label="Seeker Cycle Completed" [formField]="modalForm.onSeekerCycleCompleted"
hint="Notify when Seeker has searched every eligible item on an instance once"
helpKey="notifications:onSeekerCycleCompleted"
featureId="seeker-cycle-notification" />
</div>
</div>
</div>
@@ -26,6 +26,7 @@ const DEFAULT_EVENTS = {
onSearchItemGrabbed: false,
onForceImported: false,
onErroredStrike: false,
onSeekerCycleCompleted: false,
};
const NTFY_PROVIDER: NotificationProviderDto = {
@@ -45,6 +46,7 @@ const NTFY_PROVIDER: NotificationProviderDto = {
onSearchItemGrabbed: true,
onForceImported: false,
onErroredStrike: false,
onSeekerCycleCompleted: false,
},
configuration: {
serverUrl: 'https://ntfy.example.com',
@@ -690,6 +692,30 @@ describe('NotificationProviderModalComponent', () => {
expect(onSaved).toHaveBeenCalledTimes(1);
});
it('renders the Seeker Cycle Completed toggle and sends its value on save', () => {
const { fixture, component, api } = setup({ initialType: NotificationProviderType.Discord });
const toggle = fixture.nativeElement.querySelector(
'.event-flags button[aria-label="Seeker Cycle Completed"]',
) as HTMLButtonElement;
expect(toggle).not.toBeNull();
expect(toggle.getAttribute('aria-checked')).toBe('false');
toggle.click();
fixture.detectChanges();
expect(toggle.getAttribute('aria-checked')).toBe('true');
expect(component.modalModel().onSeekerCycleCompleted).toBe(true);
component.modalModel.update((m) => ({ ...m, name: 'Alerts', webhookUrl: 'https://discord.com/hook' }));
component.saveProvider();
fixture.detectChanges();
expect(api.create).toHaveBeenCalledWith(
NOTIFICATION_PROVIDER_DESCRIPTORS[NotificationProviderType.Discord],
expect.objectContaining({ onSeekerCycleCompleted: true }),
);
});
it('updates an existing ntfy provider with the loaded configuration', () => {
const { fixture, component, api, toast, onSaved } = setup({ editingProvider: NTFY_PROVIDER });
@@ -65,6 +65,7 @@ function createDefaultModalModel(): NotificationProviderFormModel {
onFailedImportStrike: false, onStalledStrike: false, onSlowStrike: false, onQueueItemDeleted: false,
onDownloadCleaned: false, onDownloadStopped: false, onCategoryChanged: false, onSearchTriggered: false, onSearchItemGrabbed: false, onForceImported: false,
onErroredStrike: false,
onSeekerCycleCompleted: false,
};
}
@@ -358,6 +359,7 @@ export class NotificationProviderModalComponent {
model.onSearchItemGrabbed = provider.events.onSearchItemGrabbed;
model.onForceImported = provider.events.onForceImported;
model.onErroredStrike = provider.events.onErroredStrike;
model.onSeekerCycleCompleted = provider.events.onSeekerCycleCompleted;
return model;
}
@@ -68,6 +68,9 @@
@if (provider.events.onSearchItemGrabbed) {
<span class="event-tag">Search Item Grabbed</span>
}
@if (provider.events.onSeekerCycleCompleted) {
<span class="event-tag">Seeker Cycle Completed</span>
}
</div>
</div>
} @empty {
@@ -27,6 +27,7 @@ const DISCORD_PROVIDER: NotificationProviderDto = {
onSearchItemGrabbed: false,
onForceImported: false,
onErroredStrike: false,
onSeekerCycleCompleted: false,
},
configuration: { webhookUrl: 'https://discord.com/api/webhooks/abc' },
};
@@ -48,6 +49,7 @@ const TELEGRAM_PROVIDER: NotificationProviderDto = {
onSearchItemGrabbed: false,
onForceImported: false,
onErroredStrike: false,
onSeekerCycleCompleted: false,
},
configuration: { botToken: 'token', chatId: '-100' },
};
@@ -206,4 +206,5 @@ export enum EventType {
StrikeReset = 'StrikeReset',
ForceImported = 'ForceImported',
DownloadStopped = 'DownloadStopped',
SeekerCycleCompleted = 'SeekerCycleCompleted',
}
@@ -18,6 +18,7 @@ export interface NotificationEventFlags {
onSearchItemGrabbed: boolean;
onForceImported: boolean;
onErroredStrike: boolean;
onSeekerCycleCompleted: boolean;
}
export interface NotificationProviderDto {
@@ -12,6 +12,7 @@ describe('eventTypeSeverity', () => {
it.each([
[EventType.StrikeReset, 'success'],
[EventType.DownloadCleaned, 'success'],
[EventType.SeekerCycleCompleted, 'success'],
[EventType.FailedImportStrike, 'error'],
[EventType.QueueItemDeleted, 'error'],
[EventType.StalledStrike, 'warning'],
@@ -78,6 +79,7 @@ describe('eventIcon', () => {
[EventType.DownloadCleaned, 'tablerDownload'],
[EventType.QueueItemDeleted, 'tablerTrash'],
[EventType.CategoryChanged, 'tablerTag'],
[EventType.SeekerCycleCompleted, 'tablerSearch'],
[EventType.DownloadStopped, 'tablerCircle'],
])('maps %s to %s', (eventType, expected) => {
expect(eventIcon(eventType)).toBe(expected);
@@ -17,6 +17,7 @@ export function eventTypeSeverity(eventType: string): BadgeSeverity {
case EventType.StrikeReset:
case EventType.DownloadCleaned:
case EventType.ForceImported:
case EventType.SeekerCycleCompleted:
return 'success';
case EventType.FailedImportStrike:
case EventType.QueueItemDeleted:
@@ -77,6 +78,8 @@ export function eventIcon(eventType: string): string {
return 'tablerTrash';
case EventType.CategoryChanged:
return 'tablerTag';
case EventType.SeekerCycleCompleted:
return 'tablerSearch';
default:
return 'tablerCircle';
}
@@ -56,6 +56,7 @@ function baseModel(overrides: Partial<NotificationProviderFormModel> = {}): Noti
onSearchItemGrabbed: false,
onForceImported: false,
onErroredStrike: false,
onSeekerCycleCompleted: false,
...overrides,
};
}
@@ -72,6 +73,7 @@ const eventFlags = {
onSearchItemGrabbed: false,
onForceImported: false,
onErroredStrike: false,
onSeekerCycleCompleted: false,
};
describe('NOTIFICATION_PROVIDER_DESCRIPTORS', () => {
@@ -88,6 +88,7 @@ function eventFlags(m: NotificationProviderFormModel): NotificationEventFlags {
onSearchItemGrabbed: m.onSearchItemGrabbed,
onForceImported: m.onForceImported,
onErroredStrike: m.onErroredStrike,
onSeekerCycleCompleted: m.onSeekerCycleCompleted,
};
}