reports_controller.rb 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
  2. class ReportsController < ApplicationController
  3. prepend_before_action { authentication_check(permission: 'report') }
  4. # GET /api/reports/config
  5. def reporting_config
  6. if !Report.enabled?
  7. render json: {
  8. error: 'Elasticsearch need to be configured!',
  9. }
  10. return
  11. end
  12. render json: {
  13. config: Report.config,
  14. profiles: Report::Profile.list,
  15. }
  16. end
  17. # GET /api/reports/generate
  18. def generate
  19. get_params = params_all
  20. return if !get_params
  21. result = {}
  22. get_params[:metric][:backend].each do |backend|
  23. condition = get_params[:profile].condition
  24. if backend[:condition]
  25. backend[:condition].merge(condition)
  26. else
  27. backend[:condition] = condition
  28. end
  29. next if !backend[:adapter]
  30. result[backend[:name]] = backend[:adapter].aggs(
  31. range_start: get_params[:start],
  32. range_end: get_params[:stop],
  33. interval: get_params[:range],
  34. selector: backend[:condition],
  35. params: backend[:params],
  36. timezone: get_params[:timezone],
  37. timezone_offset: get_params[:timezone_offset],
  38. )
  39. end
  40. render json: {
  41. data: result
  42. }
  43. end
  44. # GET /api/reports/sets
  45. def sets
  46. get_params = params_all
  47. return if !get_params
  48. if !params[:downloadBackendSelected]
  49. render json: {
  50. error: 'No such downloadBackendSelected param',
  51. }, status: :unprocessable_entity
  52. return
  53. end
  54. # get data
  55. result = {}
  56. content = nil
  57. filename = nil
  58. get_params[:metric][:backend].each do |backend|
  59. next if params[:downloadBackendSelected] != backend[:name]
  60. condition = get_params[:profile].condition
  61. if backend[:condition]
  62. backend[:condition].merge(condition)
  63. else
  64. backend[:condition] = condition
  65. end
  66. next if !backend[:adapter]
  67. result = backend[:adapter].items(
  68. range_start: get_params[:start],
  69. range_end: get_params[:stop],
  70. interval: get_params[:range],
  71. selector: backend[:condition],
  72. params: backend[:params],
  73. sheet: params[:sheet],
  74. timezone: get_params[:timezone],
  75. timezone_offset: get_params[:timezone_offset],
  76. )
  77. result = { count: 0, ticket_ids: [] } if result.nil?
  78. # generate sheet
  79. if params[:sheet]
  80. content = sheet(get_params[:profile], backend[:display], result)
  81. filename = "tickets-#{get_params[:profile].name}-#{backend[:display]}.xls"
  82. end
  83. break
  84. end
  85. if content
  86. send_data(
  87. content,
  88. filename: filename,
  89. type: 'application/vnd.ms-excel',
  90. disposition: 'attachment'
  91. )
  92. return
  93. end
  94. render json: result
  95. end
  96. def params_all
  97. profile = nil
  98. if !params[:profiles] && !params[:profile_id]
  99. raise Exceptions::UnprocessableEntity, 'No such profiles param'
  100. end
  101. if params[:profile_id]
  102. profile = Report::Profile.find(params[:profile_id])
  103. else
  104. params[:profiles].each do |profile_id, active|
  105. next if !active
  106. profile = Report::Profile.find(profile_id)
  107. end
  108. end
  109. if !profile
  110. raise Exceptions::UnprocessableEntity, 'No such active profile'
  111. end
  112. local_config = Report.config
  113. if !local_config || !local_config[:metric] || !local_config[:metric][params[:metric].to_sym]
  114. raise Exceptions::UnprocessableEntity, "No such metric #{params[:metric]}"
  115. end
  116. metric = local_config[:metric][params[:metric].to_sym]
  117. if params[:timeRange] == 'realtime'
  118. start_at = (Time.zone.now - 60.minutes)
  119. stop_at = Time.zone.now
  120. range = 'minute'
  121. elsif params[:timeRange] == 'day'
  122. date = Date.parse("#{params[:year]}-#{params[:month]}-#{params[:day]}").to_s
  123. start_at = Time.zone.parse("#{date}T00:00:00Z")
  124. stop_at = Time.zone.parse("#{date}T23:59:59Z")
  125. range = 'hour'
  126. elsif params[:timeRange] == 'week'
  127. start_week_at = Date.commercial(params[:year].to_i, params[:week].to_i)
  128. stop_week_at = start_week_at.end_of_week
  129. start_at = Time.zone.parse("#{start_week_at.year}-#{start_week_at.month}-#{start_week_at.day}T00:00:00Z")
  130. stop_at = Time.zone.parse("#{stop_week_at.year}-#{stop_week_at.month}-#{stop_week_at.day}T23:59:59Z")
  131. range = 'week'
  132. elsif params[:timeRange] == 'month'
  133. start_at = Time.zone.parse("#{params[:year]}-#{params[:month]}-01T00:00:00Z")
  134. stop_at = Time.zone.parse("#{params[:year]}-#{params[:month]}-#{start_at.end_of_month.day}T23:59:59Z")
  135. range = 'day'
  136. else
  137. start_at = Time.zone.parse("#{params[:year]}-01-01T00:00:00Z")
  138. stop_at = Time.zone.parse("#{params[:year]}-12-31T23:59:59Z")
  139. range = 'month'
  140. end
  141. params[:timezone] ||= Setting.get('timezone_default')
  142. if params[:timezone].present? && params[:timeRange] != 'realtime'
  143. offset = stop_at.in_time_zone(params[:timezone]).utc_offset
  144. start_at -= offset
  145. stop_at -= offset
  146. end
  147. {
  148. profile: profile,
  149. metric: metric,
  150. config: local_config,
  151. start: start_at,
  152. stop: stop_at,
  153. range: range,
  154. timezone: params[:timezone],
  155. timezone_offset: offset,
  156. }
  157. end
  158. def sheet(profile, title, result)
  159. params[:timezone] ||= Setting.get('timezone_default')
  160. # Create a new Excel workbook
  161. temp_file = Tempfile.new('time_tracking.xls')
  162. workbook = WriteExcel.new(temp_file)
  163. # Add a worksheet
  164. worksheet = workbook.add_worksheet
  165. worksheet.set_row(0, 18)
  166. worksheet.set_column(0, 0, 10)
  167. worksheet.set_column(1, 1, 34)
  168. worksheet.set_column(2, 2, 10)
  169. worksheet.set_column(3, 3, 10)
  170. worksheet.set_column(4, 8, 20)
  171. worksheet.set_column(11, 0, 20)
  172. worksheet.set_column(12, 0, 20)
  173. worksheet.set_column(13, 0, 20)
  174. # Add and define a format
  175. format = workbook.add_format
  176. format.set_bold
  177. format.set_size(14)
  178. format.set_color('black')
  179. # Write a formatted and unformatted string, row and column notation.
  180. worksheet.write_string(0, 0, "Tickets: #{profile.name} (#{title})", format)
  181. format_header = workbook.add_format
  182. format_header.set_italic
  183. format_header.set_bg_color('gray')
  184. format_header.set_color('white')
  185. format_time = workbook.add_format(num_format: 'yyyy-mm-dd hh:mm:ss')
  186. format_date = workbook.add_format(num_format: 'yyyy-mm-dd')
  187. format_footer = workbook.add_format
  188. format_footer.set_italic
  189. format_footer.set_color('gray')
  190. format_footer.set_size(8)
  191. worksheet.write_string(2, 0, '#', format_header)
  192. worksheet.write_string(2, 1, 'Title', format_header)
  193. worksheet.write_string(2, 2, 'State', format_header)
  194. worksheet.write_string(2, 3, 'Priority', format_header)
  195. worksheet.write_string(2, 4, 'Group', format_header)
  196. worksheet.write_string(2, 5, 'Owner', format_header)
  197. worksheet.write_string(2, 6, 'Customer', format_header)
  198. worksheet.write_string(2, 7, 'Organization', format_header)
  199. worksheet.write_string(2, 8, 'Create Channel', format_header)
  200. worksheet.write_string(2, 9, 'Sender', format_header)
  201. worksheet.write_string(2, 10, 'Tags', format_header)
  202. worksheet.write_string(2, 11, 'Created at', format_header)
  203. worksheet.write_string(2, 12, 'Updated at', format_header)
  204. worksheet.write_string(2, 13, 'Closed at', format_header)
  205. # ObjectManager attributes
  206. header_column = 14
  207. # needs to be skipped
  208. objects = ObjectManager::Attribute.where(editable: true,
  209. active: true,
  210. to_create: false,
  211. object_lookup_id: ObjectLookup.lookup(name: 'Ticket').id)
  212. .pluck(:name, :display, :data_type, :data_option)
  213. .map { |name, display, data_type, data_option| { name: name, display: display, data_type: data_type, data_option: data_option } }
  214. objects.each do |object|
  215. worksheet.set_column(header_column, 0, 16)
  216. worksheet.write_string(2, header_column, object[:display].capitalize, format_header)
  217. header_column += 1
  218. end
  219. row = 2
  220. result[:ticket_ids].each do |ticket_id|
  221. begin
  222. ticket = Ticket.lookup(id: ticket_id)
  223. row += 1
  224. worksheet.write_string(row, 0, ticket.number)
  225. worksheet.write_string(row, 1, ticket.title)
  226. worksheet.write_string(row, 2, ticket.state.name)
  227. worksheet.write_string(row, 3, ticket.priority.name)
  228. worksheet.write_string(row, 4, ticket.group.name)
  229. worksheet.write_string(row, 5, ticket.owner.fullname)
  230. worksheet.write_string(row, 6, ticket.customer.fullname)
  231. worksheet.write_string(row, 7, ticket.try(:organization).try(:name))
  232. worksheet.write_string(row, 8, ticket.create_article_type.name)
  233. worksheet.write_string(row, 9, ticket.create_article_sender.name)
  234. worksheet.write_string(row, 10, ticket.tag_list.join(','))
  235. worksheet.write_date_time(row, 11, time_in_localtime_for_excel(ticket.created_at, params[:timezone]), format_time)
  236. worksheet.write_date_time(row, 12, time_in_localtime_for_excel(ticket.updated_at, params[:timezone]), format_time)
  237. worksheet.write_date_time(row, 13, time_in_localtime_for_excel(ticket.close_at, params[:timezone]), format_time) if ticket.close_at.present?
  238. # Object Manager attributes
  239. column = 14
  240. # We already queried ObjectManager::Attributes, so we just use objects
  241. objects.each do |object|
  242. key = object[:name]
  243. case object[:data_type]
  244. when 'boolean', 'select'
  245. value = ticket.send(key.to_sym)
  246. if object[:data_option] && object[:data_option]['options'] && object[:data_option]['options'][ticket.send(key.to_sym)]
  247. value = object[:data_option]['options'][ticket.send(key.to_sym)]
  248. end
  249. worksheet.write_string(row, column, value)
  250. when 'datetime'
  251. worksheet.write_date_time(row, column, time_in_localtime_for_excel(ticket.send(key.to_sym), params[:timezone]), format_time) if ticket.send(key.to_sym).present?
  252. when 'date'
  253. worksheet.write_date_time(row, column, ticket.send(key.to_sym).to_s, format_date) if ticket.send(key.to_sym).present?
  254. when 'integer'
  255. worksheet.write_number(row, column, ticket.send(key.to_sym))
  256. else
  257. # for text, integer and tree select
  258. worksheet.write_string(row, column, ticket.send(key.to_sym).to_s)
  259. end
  260. column += 1
  261. end
  262. rescue => e
  263. Rails.logger.error "SKIP: #{e.message}"
  264. end
  265. end
  266. row += 2
  267. worksheet.write_string(row, 0, "#{Translation.translate(current_user.locale, 'Timezone')}: #{params[:timezone]}", format_footer)
  268. workbook.close
  269. # read file again
  270. file = File.new(temp_file, 'r')
  271. contents = file.read
  272. file.close
  273. contents
  274. end
  275. def time_in_localtime_for_excel(time, timezone)
  276. return if time.blank?
  277. if timezone.present?
  278. offset = time.in_time_zone(timezone).utc_offset
  279. time -= offset
  280. end
  281. time.utc.iso8601.to_s.sub(/Z$/, '')
  282. end
  283. end