mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-07 03:44:00 +00:00
Merge pull request #1595 from bilawalsidhu/fix/traffic-flow-error-labels
Traffic: name an account's traffic limit instead of a daily budget
This commit is contained in:
15 files changed
+119
-1
No files matched your search
@@ -30,6 +30,28 @@ test('a superseded flow fetch is not an outage', () => {
|
||||
assert.equal(deriveTrafficFlowError(undefined), null);
|
||||
});
|
||||
|
||||
test('hosted account limits and service failures name their own reason', () => {
|
||||
const failure = (status, code) =>
|
||||
deriveTrafficFlowError(
|
||||
Object.assign(new Error(`TomTom flow unavailable (HTTP ${status})`), {
|
||||
status,
|
||||
code,
|
||||
}),
|
||||
);
|
||||
assert.equal(
|
||||
failure(429, 'USAGE_LIMIT_REACHED'),
|
||||
'Monthly traffic allowance used',
|
||||
);
|
||||
assert.equal(failure(429, 'RATE_LIMITED'), 'Traffic rate limit reached');
|
||||
assert.equal(failure(429, 'budget'), 'TomTom daily budget reached');
|
||||
assert.equal(failure(503, 'no_key'), 'TomTom key unavailable');
|
||||
assert.equal(failure(503, null), 'TomTom key unavailable');
|
||||
assert.equal(
|
||||
failure(503, 'USAGE_UNAVAILABLE'),
|
||||
'Traffic service unavailable',
|
||||
);
|
||||
});
|
||||
|
||||
test('flow failures map onto short, specific reasons', () => {
|
||||
const reason = (message) => deriveTrafficFlowError(new Error(message));
|
||||
assert.equal(reason('flow tile 12/1/1: HTTP 503'), 'TomTom key unavailable');
|
||||
|
||||
@@ -1749,6 +1749,9 @@
|
||||
"Hybrid": "هجين",
|
||||
"TomTom key unavailable": "مفتاح TomTom غير متاح",
|
||||
"TomTom daily budget reached": "بلغت الحصة اليومية لـ TomTom",
|
||||
"Monthly traffic allowance used": "تم استهلاك حصة حركة المرور الشهرية",
|
||||
"Traffic rate limit reached": "تم بلوغ حد طلبات حركة المرور",
|
||||
"Traffic service unavailable": "خدمة حركة المرور غير متاحة",
|
||||
"TomTom upstream unreachable": "تعذّر الوصول إلى خادم TomTom",
|
||||
"TomTom flow unavailable": "تدفق TomTom غير متاح",
|
||||
"SIMULATED — {reason}": "محاكى — {reason}",
|
||||
|
||||
@@ -1667,6 +1667,9 @@
|
||||
"Hybrid": "Hybrid",
|
||||
"TomTom key unavailable": "TomTom-Schlüssel nicht verfügbar",
|
||||
"TomTom daily budget reached": "TomTom-Tagesbudget erreicht",
|
||||
"Monthly traffic allowance used": "Monatliches Verkehrskontingent aufgebraucht",
|
||||
"Traffic rate limit reached": "Verkehrs-Anfragelimit erreicht",
|
||||
"Traffic service unavailable": "Verkehrsdienst nicht verfügbar",
|
||||
"TomTom upstream unreachable": "TomTom-Upstream nicht erreichbar",
|
||||
"TomTom flow unavailable": "TomTom-Verkehrsfluss nicht verfügbar",
|
||||
"SIMULATED — {reason}": "SIMULIERT – {reason}",
|
||||
|
||||
@@ -2274,6 +2274,9 @@
|
||||
"SIMULATED — {reason}": "SIMULADO — {reason}",
|
||||
"TomTom key unavailable": "Clave de TomTom no disponible",
|
||||
"TomTom daily budget reached": "Se alcanzó el límite diario de TomTom",
|
||||
"Monthly traffic allowance used": "Cupo mensual de tráfico agotado",
|
||||
"Traffic rate limit reached": "Límite de solicitudes de tráfico alcanzado",
|
||||
"Traffic service unavailable": "Servicio de tráfico no disponible",
|
||||
"TomTom upstream unreachable": "Servidor de TomTom inaccesible",
|
||||
"TomTom flow unavailable": "Flujo de TomTom no disponible",
|
||||
"API key rejected — check AISSTREAM_API_KEY": "Clave de API rechazada — revisa AISSTREAM_API_KEY",
|
||||
|
||||
@@ -1105,6 +1105,9 @@
|
||||
"Hybrid": "Hybride",
|
||||
"TomTom key unavailable": "Clé TomTom indisponible",
|
||||
"TomTom daily budget reached": "Quota quotidien TomTom atteint",
|
||||
"Monthly traffic allowance used": "Quota mensuel de trafic épuisé",
|
||||
"Traffic rate limit reached": "Limite de requêtes de trafic atteinte",
|
||||
"Traffic service unavailable": "Service de trafic indisponible",
|
||||
"TomTom upstream unreachable": "Serveur TomTom injoignable",
|
||||
"TomTom flow unavailable": "Flux TomTom indisponible",
|
||||
"SIMULATED — {reason}": "SIMULÉ — {reason}",
|
||||
|
||||
@@ -1105,6 +1105,9 @@
|
||||
"Hybrid": "Ibrido",
|
||||
"TomTom key unavailable": "Chiave TomTom non disponibile",
|
||||
"TomTom daily budget reached": "Budget giornaliero TomTom esaurito",
|
||||
"Monthly traffic allowance used": "Quota mensile del traffico esaurita",
|
||||
"Traffic rate limit reached": "Limite di richieste del traffico raggiunto",
|
||||
"Traffic service unavailable": "Servizio traffico non disponibile",
|
||||
"TomTom upstream unreachable": "Servizio TomTom irraggiungibile",
|
||||
"TomTom flow unavailable": "Flusso TomTom non disponibile",
|
||||
"SIMULATED — {reason}": "SIMULATO: {reason}",
|
||||
|
||||
@@ -1073,6 +1073,9 @@
|
||||
"Hybrid": "ハイブリッド",
|
||||
"TomTom key unavailable": "TomTomキーを利用できません",
|
||||
"TomTom daily budget reached": "TomTomの1日の上限に達しました",
|
||||
"Monthly traffic allowance used": "今月の交通情報の上限に達しました",
|
||||
"Traffic rate limit reached": "交通情報のリクエスト上限に達しました",
|
||||
"Traffic service unavailable": "交通情報サービスを利用できません",
|
||||
"TomTom upstream unreachable": "TomTomのサービスに接続できません",
|
||||
"TomTom flow unavailable": "TomTomの交通流を利用できません",
|
||||
"SIMULATED — {reason}": "シミュレーション:{reason}",
|
||||
|
||||
@@ -1103,6 +1103,9 @@
|
||||
"Hybrid": "Híbrido",
|
||||
"TomTom key unavailable": "Chave do TomTom indisponível",
|
||||
"TomTom daily budget reached": "Limite diário do TomTom atingido",
|
||||
"Monthly traffic allowance used": "Cota mensal de trânsito esgotada",
|
||||
"Traffic rate limit reached": "Limite de solicitações de trânsito atingido",
|
||||
"Traffic service unavailable": "Serviço de trânsito indisponível",
|
||||
"TomTom upstream unreachable": "Servidor do TomTom inacessível",
|
||||
"TomTom flow unavailable": "Fluxo do TomTom indisponível",
|
||||
"SIMULATED — {reason}": "SIMULADO — {reason}",
|
||||
|
||||
@@ -2313,6 +2313,9 @@
|
||||
"SIMULATED — {reason}": "СИМУЛЯЦИЯ — {reason}",
|
||||
"TomTom key unavailable": "Ключ TomTom недоступен",
|
||||
"TomTom daily budget reached": "Исчерпан дневной лимит TomTom",
|
||||
"Monthly traffic allowance used": "Месячный лимит данных о трафике исчерпан",
|
||||
"Traffic rate limit reached": "Достигнут лимит запросов данных о трафике",
|
||||
"Traffic service unavailable": "Сервис данных о трафике недоступен",
|
||||
"TomTom upstream unreachable": "Сервер TomTom не отвечает",
|
||||
"TomTom flow unavailable": "Поток TomTom недоступен",
|
||||
"API key rejected — check AISSTREAM_API_KEY": "Ключ API отклонён — проверьте AISSTREAM_API_KEY",
|
||||
|
||||
@@ -1120,6 +1120,9 @@
|
||||
"Hybrid": "Гібрид",
|
||||
"TomTom key unavailable": "Ключ TomTom недоступний",
|
||||
"TomTom daily budget reached": "Вичерпано денний ліміт TomTom",
|
||||
"Monthly traffic allowance used": "Місячний ліміт даних про трафік вичерпано",
|
||||
"Traffic rate limit reached": "Досягнуто ліміту запитів даних про трафік",
|
||||
"Traffic service unavailable": "Сервіс даних про трафік недоступний",
|
||||
"TomTom upstream unreachable": "Сервер TomTom не відповідає",
|
||||
"TomTom flow unavailable": "Потік TomTom недоступний",
|
||||
"SIMULATED — {reason}": "ІМІТАЦІЯ — {reason}",
|
||||
|
||||
@@ -1049,6 +1049,9 @@
|
||||
"Hybrid": "混合",
|
||||
"TomTom key unavailable": "TomTom 密钥不可用",
|
||||
"TomTom daily budget reached": "已达 TomTom 每日配额",
|
||||
"Monthly traffic allowance used": "本月交通流量额度已用完",
|
||||
"Traffic rate limit reached": "已达到交通数据请求频率上限",
|
||||
"Traffic service unavailable": "交通服务不可用",
|
||||
"TomTom upstream unreachable": "TomTom 上游服务无法连接",
|
||||
"TomTom flow unavailable": "TomTom 路况不可用",
|
||||
"SIMULATED — {reason}": "模拟——{reason}",
|
||||
|
||||
@@ -1044,6 +1044,9 @@
|
||||
"Hybrid": "混合",
|
||||
"TomTom key unavailable": "TomTom 密鑰不可用",
|
||||
"TomTom daily budget reached": "已達 TomTom 每日配額",
|
||||
"Monthly traffic allowance used": "本月交通流量額度已用完",
|
||||
"Traffic rate limit reached": "已達交通資料請求頻率上限",
|
||||
"Traffic service unavailable": "交通服務無法使用",
|
||||
"TomTom upstream unreachable": "TomTom 上游服務無法連接",
|
||||
"TomTom flow unavailable": "TomTom 路況不可用",
|
||||
"SIMULATED — {reason}": "模擬——{reason}",
|
||||
|
||||
@@ -38,7 +38,15 @@ export function createFlow({ state: layerState, services, parts, source }) {
|
||||
const status = Number.isFinite(error.status)
|
||||
? error.status
|
||||
: Number(message.match(/HTTP (\d{3})/)?.[1]);
|
||||
const code = typeof error.code === 'string' ? error.code : null;
|
||||
// English in state; trafficFeedPresentation translates it for the panel.
|
||||
// A hosted deployment limits each account (code from the server); the
|
||||
// local proxy has one daily budget (`budget`) and answers `no_key`.
|
||||
if (code === 'USAGE_LIMIT_REACHED')
|
||||
return msg('Monthly traffic allowance used');
|
||||
if (code === 'RATE_LIMITED') return msg('Traffic rate limit reached');
|
||||
if (status === 503 && code && code !== 'no_key')
|
||||
return msg('Traffic service unavailable');
|
||||
if (status === 503) return msg('TomTom key unavailable');
|
||||
if (status === 429) return msg('TomTom daily budget reached');
|
||||
if (status === 502 || status === 504)
|
||||
|
||||
@@ -11,6 +11,26 @@ const FLOW_TILE_PATH = (z, x, y) => `/api/tomtom/flow/${z}/${x}/${y}.pbf`;
|
||||
* (STALE-BUDGET once the daily budget is spent, STALE-ERROR when TomTom
|
||||
* fails), and when the proxy fetched it.
|
||||
*/
|
||||
/**
|
||||
* The machine-readable reason a failed tile carries: `code` (hosted usage,
|
||||
* e.g. USAGE_LIMIT_REACHED or RATE_LIMITED) or the local proxy's `error`
|
||||
* (`no_key`, `budget`). Only short identifiers are kept; prose is ignored.
|
||||
*/
|
||||
async function failureCode(response) {
|
||||
if (!/json/i.test(response.headers?.get?.('content-type') || '')) return null;
|
||||
try {
|
||||
const text = await response.text();
|
||||
if (text.length > 4096) return null;
|
||||
const body = JSON.parse(text);
|
||||
const code = body?.code ?? body?.error;
|
||||
return typeof code === 'string' && /^[A-Za-z_]{1,40}$/.test(code)
|
||||
? code
|
||||
: null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function tileFreshness(response) {
|
||||
const status = response.headers?.get?.('x-tomtom-cache') || '';
|
||||
const fetchedAt = Number(response.headers?.get?.('x-tomtom-fetched-at'));
|
||||
@@ -60,7 +80,16 @@ export function createFlowTileSource({
|
||||
},
|
||||
fetchImpl: async (url, init) => {
|
||||
const response = await fetchImpl(url, init);
|
||||
if (response.ok && !init.signal.aborted) {
|
||||
if (!response.ok) {
|
||||
const code = await failureCode(response);
|
||||
await response.body?.cancel().catch(() => {});
|
||||
throw Object.assign(
|
||||
// Classified for display by deriveTrafficFlowError.
|
||||
new Error(`TomTom flow unavailable (HTTP ${response.status})`),
|
||||
{ status: response.status, code },
|
||||
);
|
||||
}
|
||||
if (!init.signal.aborted) {
|
||||
const path = String(url);
|
||||
const entry = {
|
||||
mark: tileFreshness(response),
|
||||
|
||||
@@ -1050,3 +1050,29 @@ test('cancelling a flow body releases its freshness before a new request', async
|
||||
assert.equal(getEventListeners(requestSignal, 'abort').length, 0);
|
||||
assert.equal(source.getFlowSessionStats().cacheEntries, 0);
|
||||
});
|
||||
|
||||
test('a failed flow tile carries the server reason for its label', async () => {
|
||||
for (const [status, body, code] of [
|
||||
[
|
||||
429,
|
||||
{ error: 'Monthly usage limit reached', code: 'USAGE_LIMIT_REACHED' },
|
||||
'USAGE_LIMIT_REACHED',
|
||||
],
|
||||
[
|
||||
429,
|
||||
{ error: 'Request rate limit reached', code: 'RATE_LIMITED' },
|
||||
'RATE_LIMITED',
|
||||
],
|
||||
[503, { error: 'no_key' }, 'no_key'],
|
||||
[503, { error: 'Usage enforcement is temporarily unavailable' }, null],
|
||||
]) {
|
||||
const source = createFlowTileSource({
|
||||
fetchImpl: async () => Response.json(body, { status }),
|
||||
});
|
||||
await assert.rejects(source.fetchFlowForBounds(bounds), (error) => {
|
||||
assert.equal(error.status, status);
|
||||
assert.equal(error.code, code);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user