mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-15 05:36:17 -08:00
fix(sources): keep counting a failure's duration while the list stays open
Local Greptile (5/5, one P2) on the PR round: the row's status tag formatted "Not responding · 3 days" against the time the row mounted (or the language last changed), so a new failure never gained its duration and an existing one froze while Sources stayed open. A shared root SourceClock now ticks once a minute and only the status tag reads it, so dates and the meta line do not recompute every minute. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
dac79af54f
commit
13de38ebe5
3 files changed
+50
-1
No files matched your search
@@ -0,0 +1,22 @@
|
||||
import { DestroyRef, Injectable, inject, signal } from '@angular/core';
|
||||
|
||||
/** Often enough for durations shown in whole hours and days. */
|
||||
export const SOURCE_CLOCK_TICK_MS = 60_000;
|
||||
|
||||
/**
|
||||
* One shared minute clock for source rows, so "Not responding · 3 days"
|
||||
* moves on while a list stays open, without a timer per row.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class SourceClock {
|
||||
private readonly state = signal(Date.now());
|
||||
readonly now = this.state.asReadonly();
|
||||
|
||||
constructor() {
|
||||
const timer = setInterval(
|
||||
() => this.state.set(Date.now()),
|
||||
SOURCE_CLOCK_TICK_MS
|
||||
);
|
||||
inject(DestroyRef).onDestroy(() => clearInterval(timer));
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import { signal } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { SourceStatusService } from '@iptvnator/portal/shared/data-access';
|
||||
import { PlaylistMeta, SourceStatus } from '@iptvnator/shared/interfaces';
|
||||
import { SourceClock } from './source-clock';
|
||||
import { SourceRowComponent } from './source-row.component';
|
||||
|
||||
const M3U: PlaylistMeta = {
|
||||
@@ -132,6 +134,24 @@ describe('SourceRowComponent', () => {
|
||||
).toBe('SOURCE_HEALTH.REASON.network');
|
||||
});
|
||||
|
||||
it("keeps counting a failure's duration while the list stays open", () => {
|
||||
const start = Date.UTC(2026, 9, 13, 12);
|
||||
const now = signal(start);
|
||||
TestBed.overrideProvider(SourceClock, { useValue: { now } });
|
||||
statuses.set('m3u', {
|
||||
reachability: 'bad',
|
||||
failingSince: start - 10 * 60_000,
|
||||
});
|
||||
const element = render(M3U);
|
||||
const tag = () =>
|
||||
element.querySelector('.source-row__tag')?.textContent?.trim();
|
||||
expect(tag()).toBe('WORKSPACE.SOURCES.ROW.UNREACHABLE');
|
||||
|
||||
now.set(start + 2 * 60 * 60_000);
|
||||
fixture.detectChanges();
|
||||
expect(tag()).toBe('WORKSPACE.SOURCES.ROW.UNREACHABLE_FOR');
|
||||
});
|
||||
|
||||
it('opens on click and on Enter, but not while busy', () => {
|
||||
const element = render(M3U);
|
||||
const opened = jest.fn();
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
sourceMetaParts,
|
||||
sourceStatusTag,
|
||||
} from '@iptvnator/playlist/shared/util';
|
||||
import { SourceClock } from './source-clock';
|
||||
import { SourceTileComponent } from './source-tile.component';
|
||||
|
||||
/**
|
||||
@@ -68,6 +69,7 @@ import { SourceTileComponent } from './source-tile.component';
|
||||
})
|
||||
export class SourceRowComponent {
|
||||
private readonly statusService = inject(SourceStatusService);
|
||||
private readonly clock = inject(SourceClock);
|
||||
private readonly translate = inject(TranslateService);
|
||||
private readonly languageTick = injectTranslationTick();
|
||||
private readonly clipboard = inject(Clipboard);
|
||||
@@ -137,8 +139,13 @@ export class SourceRowComponent {
|
||||
readonly meta = computed(() =>
|
||||
sourceMetaParts(this.playlist(), this.formatContext(), this.compact())
|
||||
);
|
||||
/** Reads the shared clock, so a failure's duration keeps counting. */
|
||||
readonly tag = computed(() =>
|
||||
sourceStatusTag(this.status(), this.formatContext(), this.compact())
|
||||
sourceStatusTag(
|
||||
this.status(),
|
||||
{ ...this.formatContext(), now: this.clock.now() },
|
||||
this.compact()
|
||||
)
|
||||
);
|
||||
|
||||
constructor() {
|
||||
|
||||
Reference in new issue
Block a user