mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-10 21:32:23 +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);
|
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', () => {
|
test('flow failures map onto short, specific reasons', () => {
|
||||||
const reason = (message) => deriveTrafficFlowError(new Error(message));
|
const reason = (message) => deriveTrafficFlowError(new Error(message));
|
||||||
assert.equal(reason('flow tile 12/1/1: HTTP 503'), 'TomTom key unavailable');
|
assert.equal(reason('flow tile 12/1/1: HTTP 503'), 'TomTom key unavailable');
|
||||||
|
|||||||
@@ -1749,6 +1749,9 @@
|
|||||||
"Hybrid": "هجين",
|
"Hybrid": "هجين",
|
||||||
"TomTom key unavailable": "مفتاح TomTom غير متاح",
|
"TomTom key unavailable": "مفتاح TomTom غير متاح",
|
||||||
"TomTom daily budget reached": "بلغت الحصة اليومية لـ TomTom",
|
"TomTom daily budget reached": "بلغت الحصة اليومية لـ TomTom",
|
||||||
|
"Monthly traffic allowance used": "تم استهلاك حصة حركة المرور الشهرية",
|
||||||
|
"Traffic rate limit reached": "تم بلوغ حد طلبات حركة المرور",
|
||||||
|
"Traffic service unavailable": "خدمة حركة المرور غير متاحة",
|
||||||
"TomTom upstream unreachable": "تعذّر الوصول إلى خادم TomTom",
|
"TomTom upstream unreachable": "تعذّر الوصول إلى خادم TomTom",
|
||||||
"TomTom flow unavailable": "تدفق TomTom غير متاح",
|
"TomTom flow unavailable": "تدفق TomTom غير متاح",
|
||||||
"SIMULATED — {reason}": "محاكى — {reason}",
|
"SIMULATED — {reason}": "محاكى — {reason}",
|
||||||
|
|||||||
@@ -1667,6 +1667,9 @@
|
|||||||
"Hybrid": "Hybrid",
|
"Hybrid": "Hybrid",
|
||||||
"TomTom key unavailable": "TomTom-Schlüssel nicht verfügbar",
|
"TomTom key unavailable": "TomTom-Schlüssel nicht verfügbar",
|
||||||
"TomTom daily budget reached": "TomTom-Tagesbudget erreicht",
|
"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 upstream unreachable": "TomTom-Upstream nicht erreichbar",
|
||||||
"TomTom flow unavailable": "TomTom-Verkehrsfluss nicht verfügbar",
|
"TomTom flow unavailable": "TomTom-Verkehrsfluss nicht verfügbar",
|
||||||
"SIMULATED — {reason}": "SIMULIERT – {reason}",
|
"SIMULATED — {reason}": "SIMULIERT – {reason}",
|
||||||
|
|||||||
@@ -2274,6 +2274,9 @@
|
|||||||
"SIMULATED — {reason}": "SIMULADO — {reason}",
|
"SIMULATED — {reason}": "SIMULADO — {reason}",
|
||||||
"TomTom key unavailable": "Clave de TomTom no disponible",
|
"TomTom key unavailable": "Clave de TomTom no disponible",
|
||||||
"TomTom daily budget reached": "Se alcanzó el límite diario de TomTom",
|
"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 upstream unreachable": "Servidor de TomTom inaccesible",
|
||||||
"TomTom flow unavailable": "Flujo de TomTom no disponible",
|
"TomTom flow unavailable": "Flujo de TomTom no disponible",
|
||||||
"API key rejected — check AISSTREAM_API_KEY": "Clave de API rechazada — revisa AISSTREAM_API_KEY",
|
"API key rejected — check AISSTREAM_API_KEY": "Clave de API rechazada — revisa AISSTREAM_API_KEY",
|
||||||
|
|||||||
@@ -1105,6 +1105,9 @@
|
|||||||
"Hybrid": "Hybride",
|
"Hybrid": "Hybride",
|
||||||
"TomTom key unavailable": "Clé TomTom indisponible",
|
"TomTom key unavailable": "Clé TomTom indisponible",
|
||||||
"TomTom daily budget reached": "Quota quotidien TomTom atteint",
|
"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 upstream unreachable": "Serveur TomTom injoignable",
|
||||||
"TomTom flow unavailable": "Flux TomTom indisponible",
|
"TomTom flow unavailable": "Flux TomTom indisponible",
|
||||||
"SIMULATED — {reason}": "SIMULÉ — {reason}",
|
"SIMULATED — {reason}": "SIMULÉ — {reason}",
|
||||||
|
|||||||
@@ -1105,6 +1105,9 @@
|
|||||||
"Hybrid": "Ibrido",
|
"Hybrid": "Ibrido",
|
||||||
"TomTom key unavailable": "Chiave TomTom non disponibile",
|
"TomTom key unavailable": "Chiave TomTom non disponibile",
|
||||||
"TomTom daily budget reached": "Budget giornaliero TomTom esaurito",
|
"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 upstream unreachable": "Servizio TomTom irraggiungibile",
|
||||||
"TomTom flow unavailable": "Flusso TomTom non disponibile",
|
"TomTom flow unavailable": "Flusso TomTom non disponibile",
|
||||||
"SIMULATED — {reason}": "SIMULATO: {reason}",
|
"SIMULATED — {reason}": "SIMULATO: {reason}",
|
||||||
|
|||||||
@@ -1073,6 +1073,9 @@
|
|||||||
"Hybrid": "ハイブリッド",
|
"Hybrid": "ハイブリッド",
|
||||||
"TomTom key unavailable": "TomTomキーを利用できません",
|
"TomTom key unavailable": "TomTomキーを利用できません",
|
||||||
"TomTom daily budget reached": "TomTomの1日の上限に達しました",
|
"TomTom daily budget reached": "TomTomの1日の上限に達しました",
|
||||||
|
"Monthly traffic allowance used": "今月の交通情報の上限に達しました",
|
||||||
|
"Traffic rate limit reached": "交通情報のリクエスト上限に達しました",
|
||||||
|
"Traffic service unavailable": "交通情報サービスを利用できません",
|
||||||
"TomTom upstream unreachable": "TomTomのサービスに接続できません",
|
"TomTom upstream unreachable": "TomTomのサービスに接続できません",
|
||||||
"TomTom flow unavailable": "TomTomの交通流を利用できません",
|
"TomTom flow unavailable": "TomTomの交通流を利用できません",
|
||||||
"SIMULATED — {reason}": "シミュレーション:{reason}",
|
"SIMULATED — {reason}": "シミュレーション:{reason}",
|
||||||
|
|||||||
@@ -1103,6 +1103,9 @@
|
|||||||
"Hybrid": "Híbrido",
|
"Hybrid": "Híbrido",
|
||||||
"TomTom key unavailable": "Chave do TomTom indisponível",
|
"TomTom key unavailable": "Chave do TomTom indisponível",
|
||||||
"TomTom daily budget reached": "Limite diário do TomTom atingido",
|
"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 upstream unreachable": "Servidor do TomTom inacessível",
|
||||||
"TomTom flow unavailable": "Fluxo do TomTom indisponível",
|
"TomTom flow unavailable": "Fluxo do TomTom indisponível",
|
||||||
"SIMULATED — {reason}": "SIMULADO — {reason}",
|
"SIMULATED — {reason}": "SIMULADO — {reason}",
|
||||||
|
|||||||
@@ -2313,6 +2313,9 @@
|
|||||||
"SIMULATED — {reason}": "СИМУЛЯЦИЯ — {reason}",
|
"SIMULATED — {reason}": "СИМУЛЯЦИЯ — {reason}",
|
||||||
"TomTom key unavailable": "Ключ TomTom недоступен",
|
"TomTom key unavailable": "Ключ TomTom недоступен",
|
||||||
"TomTom daily budget reached": "Исчерпан дневной лимит TomTom",
|
"TomTom daily budget reached": "Исчерпан дневной лимит TomTom",
|
||||||
|
"Monthly traffic allowance used": "Месячный лимит данных о трафике исчерпан",
|
||||||
|
"Traffic rate limit reached": "Достигнут лимит запросов данных о трафике",
|
||||||
|
"Traffic service unavailable": "Сервис данных о трафике недоступен",
|
||||||
"TomTom upstream unreachable": "Сервер TomTom не отвечает",
|
"TomTom upstream unreachable": "Сервер TomTom не отвечает",
|
||||||
"TomTom flow unavailable": "Поток TomTom недоступен",
|
"TomTom flow unavailable": "Поток TomTom недоступен",
|
||||||
"API key rejected — check AISSTREAM_API_KEY": "Ключ API отклонён — проверьте AISSTREAM_API_KEY",
|
"API key rejected — check AISSTREAM_API_KEY": "Ключ API отклонён — проверьте AISSTREAM_API_KEY",
|
||||||
|
|||||||
@@ -1120,6 +1120,9 @@
|
|||||||
"Hybrid": "Гібрид",
|
"Hybrid": "Гібрид",
|
||||||
"TomTom key unavailable": "Ключ TomTom недоступний",
|
"TomTom key unavailable": "Ключ TomTom недоступний",
|
||||||
"TomTom daily budget reached": "Вичерпано денний ліміт TomTom",
|
"TomTom daily budget reached": "Вичерпано денний ліміт TomTom",
|
||||||
|
"Monthly traffic allowance used": "Місячний ліміт даних про трафік вичерпано",
|
||||||
|
"Traffic rate limit reached": "Досягнуто ліміту запитів даних про трафік",
|
||||||
|
"Traffic service unavailable": "Сервіс даних про трафік недоступний",
|
||||||
"TomTom upstream unreachable": "Сервер TomTom не відповідає",
|
"TomTom upstream unreachable": "Сервер TomTom не відповідає",
|
||||||
"TomTom flow unavailable": "Потік TomTom недоступний",
|
"TomTom flow unavailable": "Потік TomTom недоступний",
|
||||||
"SIMULATED — {reason}": "ІМІТАЦІЯ — {reason}",
|
"SIMULATED — {reason}": "ІМІТАЦІЯ — {reason}",
|
||||||
|
|||||||
@@ -1049,6 +1049,9 @@
|
|||||||
"Hybrid": "混合",
|
"Hybrid": "混合",
|
||||||
"TomTom key unavailable": "TomTom 密钥不可用",
|
"TomTom key unavailable": "TomTom 密钥不可用",
|
||||||
"TomTom daily budget reached": "已达 TomTom 每日配额",
|
"TomTom daily budget reached": "已达 TomTom 每日配额",
|
||||||
|
"Monthly traffic allowance used": "本月交通流量额度已用完",
|
||||||
|
"Traffic rate limit reached": "已达到交通数据请求频率上限",
|
||||||
|
"Traffic service unavailable": "交通服务不可用",
|
||||||
"TomTom upstream unreachable": "TomTom 上游服务无法连接",
|
"TomTom upstream unreachable": "TomTom 上游服务无法连接",
|
||||||
"TomTom flow unavailable": "TomTom 路况不可用",
|
"TomTom flow unavailable": "TomTom 路况不可用",
|
||||||
"SIMULATED — {reason}": "模拟——{reason}",
|
"SIMULATED — {reason}": "模拟——{reason}",
|
||||||
|
|||||||
@@ -1044,6 +1044,9 @@
|
|||||||
"Hybrid": "混合",
|
"Hybrid": "混合",
|
||||||
"TomTom key unavailable": "TomTom 密鑰不可用",
|
"TomTom key unavailable": "TomTom 密鑰不可用",
|
||||||
"TomTom daily budget reached": "已達 TomTom 每日配額",
|
"TomTom daily budget reached": "已達 TomTom 每日配額",
|
||||||
|
"Monthly traffic allowance used": "本月交通流量額度已用完",
|
||||||
|
"Traffic rate limit reached": "已達交通資料請求頻率上限",
|
||||||
|
"Traffic service unavailable": "交通服務無法使用",
|
||||||
"TomTom upstream unreachable": "TomTom 上游服務無法連接",
|
"TomTom upstream unreachable": "TomTom 上游服務無法連接",
|
||||||
"TomTom flow unavailable": "TomTom 路況不可用",
|
"TomTom flow unavailable": "TomTom 路況不可用",
|
||||||
"SIMULATED — {reason}": "模擬——{reason}",
|
"SIMULATED — {reason}": "模擬——{reason}",
|
||||||
|
|||||||
@@ -38,7 +38,15 @@ export function createFlow({ state: layerState, services, parts, source }) {
|
|||||||
const status = Number.isFinite(error.status)
|
const status = Number.isFinite(error.status)
|
||||||
? error.status
|
? error.status
|
||||||
: Number(message.match(/HTTP (\d{3})/)?.[1]);
|
: 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.
|
// 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 === 503) return msg('TomTom key unavailable');
|
||||||
if (status === 429) return msg('TomTom daily budget reached');
|
if (status === 429) return msg('TomTom daily budget reached');
|
||||||
if (status === 502 || status === 504)
|
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
|
* (STALE-BUDGET once the daily budget is spent, STALE-ERROR when TomTom
|
||||||
* fails), and when the proxy fetched it.
|
* 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) {
|
function tileFreshness(response) {
|
||||||
const status = response.headers?.get?.('x-tomtom-cache') || '';
|
const status = response.headers?.get?.('x-tomtom-cache') || '';
|
||||||
const fetchedAt = Number(response.headers?.get?.('x-tomtom-fetched-at'));
|
const fetchedAt = Number(response.headers?.get?.('x-tomtom-fetched-at'));
|
||||||
@@ -60,7 +80,16 @@ export function createFlowTileSource({
|
|||||||
},
|
},
|
||||||
fetchImpl: async (url, init) => {
|
fetchImpl: async (url, init) => {
|
||||||
const response = await fetchImpl(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 path = String(url);
|
||||||
const entry = {
|
const entry = {
|
||||||
mark: tileFreshness(response),
|
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(getEventListeners(requestSignal, 'abort').length, 0);
|
||||||
assert.equal(source.getFlowSessionStats().cacheEntries, 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