{"record":{"id":"d646680f840194c6","repo":"apache/superset","slug":"validation-error","errorCode":"validation_error","errorMessage":"EChart options validation failed","messagePattern":"EChart options validation failed","errorType":"exception","errorClass":"EChartOptionsParseError","httpStatus":null,"severity":"warning","filePath":"superset-frontend/plugins/plugin-chart-echarts/src/utils/safeEChartOptionsParser.ts","lineNumber":161,"sourceCode":"      // For primitive properties (animation, backgroundColor, etc.), validate with full schema\n      const primitiveResult =\n        customEChartOptionsSchema.shape[\n          key as keyof typeof customEChartOptionsSchema.shape\n        ]?.safeParse(value);\n\n      if (primitiveResult?.success) {\n        result[key] = primitiveResult.data;\n      } else if (primitiveResult) {\n        validationErrors.push(\n          `Invalid property \"${key}\": ${primitiveResult.error?.issues.map(e => e.message).join(', ') ?? 'Invalid value'}`,\n        );\n      }\n      // Unknown properties are silently ignored\n    }\n  }\n\n  if (validationErrors.length > 0) {\n    throw new EChartOptionsParseError(\n      'EChart options validation failed',\n      'validation_error',\n      validationErrors,\n    );\n  }\n\n  return result as CustomEChartOptions;\n}\n\n// =============================================================================\n// AST Safety Validation\n// =============================================================================\n\n/**\n * Safe AST node types that are allowed in EChart options.\n * These represent static data structures without executable code.\n */\nconst SAFE_NODE_TYPES = new Set([","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/apache/superset/blob/f4587218dd19d046c3e4d00063e7d27f8a2ed354/superset-frontend/plugins/plugin-chart-echarts/src/utils/safeEChartOptionsParser.ts#L143-L179","documentation":"RedirectView.redirect_warning (superset/views/redirect.py:63, public route /redirect/, gated on the ALERT_REPORTS feature flag) aborts 400 'Missing URL parameter' when the url query param is absent, empty, or whitespace-only after strip(). The endpoint renders the external-link warning page used in alert/report emails; without a target there is nothing to warn about.","triggerScenarios":"GET /redirect/ with no ?url=, ?url=%20 (only spaces), or ?url= (empty value); also clients that URL-encode the key differently (?target=, ?u=) so request.args.get('url') returns ''.","commonSituations":"Email template links whose merge fields for the URL are empty for a particular alert row; manual edits to report templates dropping the url param; crawlers hitting /redirect/ bare.","solutions":["Always include a non-blank url query parameter: GET /redirect/?url=<urlencoded target>","In alert/report templates, guard the link generation so no link is emitted when the target field is empty","For automated checks of link health, treat 400 here as 'malformed link in message', not an outage"],"exampleFix":"{# before: email template emits broken link when value empty #}\n<a href=\"/redirect/?url={{ chart_url }}\">view</a>\n\n{# after #}\n{% if chart_url %}<a href=\"/redirect/?url={{ chart_url | urlencode }}\">view</a>{% endif %}","handlingStrategy":"validation","validationCode":"def redirect_url(target: str | None) -> str | None:\n    if not target or not target.strip():\n        return None\n    return target.strip()","typeGuard":"const hasRedirectTarget = (u: string | null | undefined): boolean =>\n  typeof u === \"string\" && u.trim().length > 0;","tryCatchPattern":null,"preventionTips":["In alert/report templates, only emit /redirect/?url=... when the merge field is non-empty","Always urlencode the target value"],"tags":["superset","alerts-reports","http-400","redirect","validation"],"backgroundTag":null,"analyzedSha":"f4587218dd19d046c3e4d00063e7d27f8a2ed354","analyzedAt":"2026-08-14T22:39:27.425Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}