{"record":{"id":"c91389ceaf231258","repo":"apache/superset","slug":"please-provide-both-time-bounds-since-and-until","errorCode":null,"errorMessage":"Please provide both time bounds (Since and Until)","messagePattern":"Please provide both time bounds \\(Since and Until\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"superset-frontend/plugins/plugin-chart-calendar/src/transformData.ts","lineNumber":88,"sourceCode":"    weeks: Math.floor(days / 7),\n  };\n};\n\n/**\n * Ports the legacy CalHeatmapViz.get_data reshape: per-metric value maps\n * keyed by unix seconds, plus the domain range computed from the query's\n * time bounds exactly like the backend's relativedelta arithmetic.\n */\nexport default function transformData(\n  records: Record<string, unknown>[],\n  metricLabels: string[],\n  fromDttm: number | null | undefined,\n  toDttm: number | null | undefined,\n  domain: string,\n  subdomain: string,\n): CalHeatmapPayload {\n  if (fromDttm == null || toDttm == null) {\n    throw new Error(t('Please provide both time bounds (Since and Until)'));\n  }\n  const data: Record<string, Record<string, unknown>> = {};\n  metricLabels.forEach(metric => {\n    const values: Record<string, unknown> = {};\n    records.forEach(record => {\n      const timestamp = record[DTTM_ALIAS];\n      if (timestamp != null) {\n        values[String((timestamp as number) / 1000)] = record[metric];\n      }\n    });\n    data[metric] = values;\n  });\n\n  const start = new Date(fromDttm);\n  const end = new Date(toDttm);\n  const delta = calendarDelta(start, end);\n  const diffSecs = (toDttm - fromDttm) / 1000;\n","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/apache/superset/blob/f4587218dd19d046c3e4d00063e7d27f8a2ed354/superset-frontend/plugins/plugin-chart-calendar/src/transformData.ts#L70-L106","documentation":"Same LANGUAGE_CODE_RE validation as the JSON pack endpoint, but on the versioned script endpoint (superset/views/core.py:722, /language_pack/<lang>/<version>/script.js). This route is deliberately unauthenticated (static public translation catalog for the SPA), so the regex is the first trust-boundary check; a lang not matching ^[a-z]{2,3}(_[A-Z]{2}|_[A-Z][a-z]{3})?$ aborts with 400 before the version check or filesystem lookup.","triggerScenarios":"GET /language_pack/zh-CN/abc123def456/script.js (hyphenated tag), uppercase 'EN', 4-letter language subtag, or traversal-style input in <lang>; the version segment can be perfectly valid and this still fires because lang is checked first.","commonSituations":"Browser locale tags (BCP-47, hyphenated) passed through unmodified; stale cached index.html requesting a locale key that a config rename changed; monitoring probes hitting the URL with raw Accept-Language values.","solutions":["Normalize to Superset's underscore format before building the URL (lowercase lang, _Uppercase region)","Source the lang from the same constant/config the SPA uses for /language_pack/<lang>/ so both endpoints agree","On 400, fall back to the English script.js rather than blocking app boot"],"exampleFix":"# before\nurl = f\"/language_pack/{'zh-CN'}/{version}/script.js\"  # 400\n\n# after\nurl = f\"/language_pack/{'zh_CN'.replace('-', '_')}/{version}/script.js\"","handlingStrategy":"validation","validationCode":"import re\nLANGUAGE_CODE_RE = re.compile(r\"^[a-z]{2,3}(_[A-Z]{2}|_[A-Z][a-z]{3})?$\")\nassert LANGUAGE_CODE_RE.match(lang), f\"bad lang {lang!r} — normalize e.g. 'zh-CN' -> 'zh_CN'\"","typeGuard":"const isLanguageCode = (l: string): boolean =>\n  /^[a-z]{2,3}(_[A-Z]{2}|_[A-Z][a-z]{3})?$/.test(l);","tryCatchPattern":null,"preventionTips":["Use one shared normalization helper for both /language_pack/<lang>/ and the script.js URL so they cannot diverge","Lowercase language subtag, uppercase region subtag, underscore separator"],"tags":["superset","i18n","validation","http-400","localization"],"backgroundTag":null,"analyzedSha":"f4587218dd19d046c3e4d00063e7d27f8a2ed354","analyzedAt":"2026-08-14T22:39:27.425Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}