diff --git a/code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.html b/code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.html index 71dc79f3..b3c04f7e 100644 --- a/code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.html +++ b/code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.html @@ -112,8 +112,8 @@ class="instance-card__reset" variant="ghost" size="sm" - [disabled]="inst.cycleItemsSearched === 0 || resettingId() === inst.instanceId" - [loading]="resettingId() === inst.instanceId" + [disabled]="inst.cycleItemsSearched === 0 || resettingIds().has(inst.instanceId)" + [loading]="resettingIds().has(inst.instanceId)" (clicked)="resetCycle(inst)" > Reset cycle diff --git a/code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.spec.ts b/code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.spec.ts index 6d39a960..bf529f1f 100644 --- a/code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.spec.ts +++ b/code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.spec.ts @@ -572,6 +572,37 @@ describe('SearchesTabComponent', () => { expect(resetButton(fixture, 'Zulu').disabled).toBe(false); }); + it('keeps a reset in progress when another instance finishes meanwhile', async () => { + const { fixture, component, confirm, resetCycle } = setup(); + const zulu = new Subject(); + const beta = new Subject(); + resetCycle.mockImplementation((id: string) => (id === 'radarr-zulu' ? zulu : beta)); + + const zuluAccepted = component.resetCycle(RADARR_ZULU); + confirm.accept(); + await zuluAccepted; + fixture.detectChanges(); + const betaAccepted = component.resetCycle(SONARR_BETA); + confirm.accept(); + await betaAccepted; + fixture.detectChanges(); + + beta.next(null); + beta.complete(); + fixture.detectChanges(); + + expect(resetButton(fixture, 'Beta').disabled).toBe(false); + expect(resetButton(fixture, 'Zulu').disabled).toBe(true); + expect(resetButton(fixture, 'Zulu').classList).toContain('btn--loading'); + + zulu.next(null); + zulu.complete(); + fixture.detectChanges(); + + expect(resetButton(fixture, 'Zulu').disabled).toBe(false); + expect(resetButton(fixture, 'Zulu').classList).not.toContain('btn--loading'); + }); + it('re-enables the button when the reset fails', async () => { const { fixture, component, confirm, resetCycle } = setup(); const inFlight = new Subject(); diff --git a/code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.ts b/code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.ts index 3cc55cc6..9264f33b 100644 --- a/code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.ts +++ b/code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.ts @@ -115,7 +115,7 @@ export class SearchesTabComponent { ); readonly selectedInstanceId = signal(''); - readonly resettingId = signal(null); + readonly resettingIds = signal>(new Set()); readonly instanceOptions = computed(() => { return [ { label: 'All Instances', value: '' }, @@ -323,6 +323,14 @@ export class SearchesTabComponent { this.eventsResource.reload(); } + private finishReset(instanceId: string): void { + this.resettingIds.update((ids) => { + const next = new Set(ids); + next.delete(instanceId); + return next; + }); + } + async resetCycle(inst: InstanceSearchStat): Promise { const confirmed = await this.confirmService.confirm({ title: 'Reset Cycle', @@ -334,15 +342,15 @@ export class SearchesTabComponent { return; } - this.resettingId.set(inst.instanceId); + this.resettingIds.update((ids) => new Set(ids).add(inst.instanceId)); this.api.resetCycle(inst.instanceId).subscribe({ next: () => { - this.resettingId.set(null); + this.finishReset(inst.instanceId); this.toast.success(`Cycle reset for ${inst.instanceName}`); this.summaryResource.reload(); }, error: (err: ApiError) => { - this.resettingId.set(null); + this.finishReset(inst.instanceId); this.toast.error(err.message); }, });