{"record":{"id":"196ce0f4598dd0e6","repo":"discourse/discourse","slug":"traffic-type","errorCode":null,"errorMessage":"traffic_type","messagePattern":"traffic_type","errorType":"exception","errorClass":"Discourse::InvalidParameters","httpStatus":400,"severity":"error","filePath":"app/services/admin_dashboard_site_traffic_explorer.rb","lineNumber":73,"sourceCode":"      errors.add(:start_date, :invalid)\n    end\n\n    def normalize_filter(key, value)\n      return nil if value.nil?\n\n      raise Discourse::InvalidParameters.new(key) if value.empty? || value.size > DIMENSION_LIMIT\n\n      value.map { |item| normalize_filter_value(key, item) }.uniq\n    end\n\n    def normalize_filter_value(key, value)\n      return \"\" if key == :referrer && value == \"\"\n      raise Discourse::InvalidParameters.new(key) if value.blank?\n\n      case key\n      when :traffic_type\n        traffic_type = value.to_s\n        raise Discourse::InvalidParameters.new(key) if !TRAFFIC_TYPE_VALUES.include?(traffic_type)\n        traffic_type\n      when :top_url, :entry_url\n        BrowserPageviewEventUrlNormalizer.normalize_site_path(value) ||\n          raise(Discourse::InvalidParameters.new(key))\n      when :country\n        country = value.to_s.upcase\n        raise Discourse::InvalidParameters.new(key) if !country.match?(/\\A[A-Z]{2}\\z/)\n        country\n      when :network\n        match = value.to_s.match(/\\AAS(\\d+)\\z/)\n        raise Discourse::InvalidParameters.new(key) if !match\n        match[1].to_i\n      when :browser\n        browser = value.to_s\n        raise Discourse::InvalidParameters.new(key) if !BROWSER_VALUES.include?(browser)\n        browser\n      when :ip\n        IPAddr.new(value.to_s).to_s","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/discourse/discourse/blob/1b2d7253e854205d2c95c80b2c6a1cdd42e45059/app/services/admin_dashboard_site_traffic_explorer.rb#L55-L91","documentation":"For the traffic_type dimension, normalize_filter_value coerces the item to a string and requires it to be in the TRAFFIC_TYPE_VALUES allowlist; anything else raises Discourse::InvalidParameters.new(:traffic_type). The allowlist is the fixed set of traffic classification buckets the explorer can query.","triggerScenarios":"?traffic_type=organic or ?traffic_type=email when those are not in TRAFFIC_TYPE_VALUES; sending a symbol-like raw value, wrong casing, or a pluralized label from a custom UI.","commonSituations":"Custom dashboards or scripts invent traffic type labels instead of using the API's canonical values; Discourse version changes alter the allowlist set; copy-paste from docs of a different version.","solutions":["Read TRAFFIC_TYPE_VALUES from the service class and drive the UI dropdown from it","Send exactly the canonical strings (e.g. the values defined in AdminDashboardSiteTrafficExplorer::TRAFFIC_TYPE_VALUES)","On error, fetch/refresh the allowlist rather than guessing"],"exampleFix":"# before\nfilters = {traffic_type: [\"Organic\"]}\n# after\nfilters = {traffic_type: AdminDashboardSiteTrafficExplorer::TRAFFIC_TYPE_VALUES.first(1)","handlingStrategy":"type-guard","validationCode":"values &= AdminDashboardSiteTrafficExplorer::TRAFFIC_TYPE_VALUES","typeGuard":"def valid_traffic_type?(v)\n  AdminDashboardSiteTrafficExplorer::TRAFFIC_TYPE_VALUES.include?(v.to_s)\nend","tryCatchPattern":"rescue Discourse::InvalidParameters => e\n  render json: { errors: [\"traffic_type must be one of the allowed values\"] }, status: 400\nend","preventionTips":["Populate the dropdown from the constant","Re-sync allowlists after upgrades","Reject unknown labels early"],"tags":["discourse","traffic-explorer","allowlist","traffic-type"],"backgroundTag":"enum-value-rejected","analyzedSha":"1b2d7253e854205d2c95c80b2c6a1cdd42e45059","analyzedAt":"2026-08-26T03:11:08.302Z","schemaVersion":2},"datasetVersion":"2026-08-26T07:17:17.940Z"}