42 lines
2.7 KiB
TypeScript
42 lines
2.7 KiB
TypeScript
// What deserves a desktop notification after a read (Settings > Notifications). Pure: compares the previous read with
|
|
// the new one and never fires on a first read (nothing to compare with = no flood).
|
|
import type { PortfolioResult } from '../api';
|
|
import { sameVersion } from '../../../../cli/lib/portfolio';
|
|
import { healthSeries, type HealthSnapshot, isError, PLAY_THRESHOLDS, type Review } from './model';
|
|
|
|
export interface ReleaseChange { project: string; name: string; store: 'ios' | 'android'; version: string; from: string; to: string }
|
|
|
|
/** Phase changes of the same version on the same store that the owner waits for: review in / out, approved, rejected, live, halted. */
|
|
export function releaseChanges(prev: PortfolioResult | null | undefined, next: PortfolioResult): ReleaseChange[] {
|
|
if (!prev) return [];
|
|
const WATCH = new Set(['review', 'sent', 'released', 'approved', 'rejected', 'live', 'halted']);
|
|
const out: ReleaseChange[] = [];
|
|
for (const a of next.apps) {
|
|
const before = prev.apps.find(p => p.project === a.project);
|
|
if (!before || before.release.source !== 'live' || a.release.source !== 'live') continue;
|
|
for (const r of a.release.all ?? []) {
|
|
if (!r.version) continue;
|
|
const old = (before.release.all ?? []).find(x => x.store === r.store && (x.version === r.version || sameVersion(x.version, r.version)));
|
|
if (!old || old.phase === r.phase) continue;
|
|
if (WATCH.has(r.phase) || WATCH.has(old.phase)) out.push({ project: a.project, name: a.app?.name ?? a.project, store: r.store, version: r.version, from: old.phase, to: r.phase });
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/** New 1-2 star reviews since the previous read. */
|
|
export function newLowReviews(prev: Review[] | null | undefined, next: Review[]): Review[] {
|
|
if (!prev) return [];
|
|
const seen = new Set(prev.map(r => `${r.store}:${r.id}`));
|
|
return next.filter(r => r.rating <= 2 && !seen.has(`${r.store}:${r.id}`));
|
|
}
|
|
|
|
/** Crash or ANR rate that went over the Play bad behavior threshold since the previous read. */
|
|
export function crashCrossed(prev: HealthSnapshot | null | undefined, next: HealthSnapshot): ('crash' | 'anr')[] {
|
|
if (!prev?.play || isError(prev.play) || !next.play || isError(next.play)) return [];
|
|
const out: ('crash' | 'anr')[] = [];
|
|
const was = { crash: healthSeries(prev.play.crash, 'userPerceivedCrashRate', PLAY_THRESHOLDS.crash), anr: healthSeries(prev.play.anr, 'userPerceivedAnrRate', PLAY_THRESHOLDS.anr) };
|
|
const now = { crash: healthSeries(next.play.crash, 'userPerceivedCrashRate', PLAY_THRESHOLDS.crash), anr: healthSeries(next.play.anr, 'userPerceivedAnrRate', PLAY_THRESHOLDS.anr) };
|
|
for (const k of ['crash', 'anr'] as const) if (now[k].tone === 'bad' && was[k].tone !== 'bad') out.push(k);
|
|
return out;
|
|
}
|