external_data_source.rb 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. class ExternalDataSource
  3. attr_reader :json, :parsed_items, :options, :term
  4. def initialize(options:, term:, render_context:, limit: 10)
  5. @term = term
  6. @options = options
  7. @limit = limit
  8. @render_context = render_context
  9. end
  10. def process
  11. @json = fetch_json
  12. @parsed_items = get_items_list(@json)
  13. @parsed_items
  14. .slice(...@limit)
  15. .map do |elem|
  16. {
  17. value: get_item_value(elem),
  18. label: get_item_label(elem)
  19. }
  20. end
  21. end
  22. private
  23. def fetch_json
  24. response = UserAgent.get(
  25. search_url,
  26. {},
  27. HttpOptions.new(options).build
  28. )
  29. raise Errors::NetworkError.new(self, response.error) if !response.success?
  30. response.data
  31. rescue ArgumentError, URI::InvalidURIError
  32. raise Errors::SearchUrlInvalidError, self
  33. end
  34. def search_url
  35. raise Errors::SearchUrlMissingError, self if options[:search_url].blank?
  36. NotificationFactory::Renderer.new(
  37. objects: {
  38. **@render_context,
  39. # Extend the render context with the current object instance.
  40. # This will allow for usage of `search.term` in the search URL, as this property is readily available here.
  41. # Only this approach will guarantee that the template variable is replaced with a properly URL encoded value.
  42. # https://github.com/zammad/zammad/issues/4980
  43. search: self,
  44. },
  45. template: options[:search_url],
  46. escape: false,
  47. url_encode: true,
  48. ).render(debug_errors: false)
  49. end
  50. def get_items_list(input)
  51. path = options[:search_result_list_key]
  52. array = if path.present?
  53. input.dig(*path.split('.'))
  54. else
  55. input
  56. end
  57. raise TypeError if !array
  58. raise Errors::ListNotArrayParsingError.new(self, path) if !array.is_a?(Array)
  59. array
  60. rescue TypeError
  61. raise Errors::ListPathParsingError.new(self, path)
  62. end
  63. def get_item_value(input)
  64. get_textual_value(:value, input)
  65. end
  66. def get_item_label(input)
  67. get_textual_value(:label, input)
  68. end
  69. def get_textual_value(key, input)
  70. options_key = "search_result_#{key}_key"
  71. path = options[options_key]
  72. value = if path.present?
  73. input.dig(*path.split('.'))
  74. else
  75. input
  76. end
  77. raise TypeError if value.nil?
  78. if [String, Numeric, TrueClass, FalseClass].none? { |elem| value.is_a?(elem) }
  79. raise Errors::ParsingError.class_for(key, :invalid).new(self, path)
  80. end
  81. value
  82. rescue TypeError
  83. raise Errors::ParsingError.class_for(key, :path).new(self, path)
  84. end
  85. end