record_loader_spec.rb 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. # Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. RSpec.describe Gql::RecordLoader, type: :graphql do
  4. let(:agent) { create(:agent) }
  5. let(:debug) { false }
  6. before do
  7. loops.times do
  8. organization = create(:organization)
  9. user = create(:user, organization: organization)
  10. group = create(:group)
  11. tickets_per_loop.times do
  12. source_ticket = create(:ticket, customer: user, organization: organization, group: group, created_by_id: user.id, state: Ticket::State.all.sample)
  13. if articles_per_ticket
  14. create_list(:ticket_article, articles_per_ticket, ticket_id: source_ticket.id, from: 'asdf1@record_loader_test.org', created_by_id: user.id)
  15. end
  16. end
  17. agent.group_names_access_map = Group.all.to_h { |g| [g.name, ['full']] }
  18. end
  19. end
  20. def trace_queries(total_queries, uncached_queries)
  21. callback = lambda do |*, payload|
  22. total_queries[payload[:name]] ||= 0
  23. total_queries[payload[:name]] += 1
  24. if !payload[:cached]
  25. uncached_queries[payload[:name]] ||= 0
  26. uncached_queries[payload[:name]] += 1
  27. end
  28. next if !debug
  29. # rubocop:disable Rails/Output
  30. puts payload[:name], payload[:sql], payload[:cached]
  31. caller.reject { |line| line.match(%r{gems|spec}) }.first(30).each do |relevant_caller_line|
  32. puts(" ↳ #{relevant_caller_line.sub(Rails.root.join('/').to_s, '')}")
  33. end
  34. puts ''
  35. # rubocop:enable Rails/Output
  36. end
  37. ActiveSupport::Notifications.subscribed(callback, 'sql.active_record') do
  38. ActiveRecord::Base.cache do
  39. # ActiveRecord::Base.logger = Logger.new(STDOUT)
  40. return yield
  41. end
  42. end
  43. end
  44. context 'when querying multiple tickets' do
  45. let(:loops) { 5 }
  46. let(:tickets_per_loop) { 5 }
  47. let(:articles_per_ticket) { 1 }
  48. let(:overview_id) do
  49. condition = {
  50. 'article.from' => {
  51. operator: 'contains',
  52. value: 'record_loader_test.org',
  53. },
  54. }
  55. overview = create(:overview, condition: condition)
  56. Gql::ZammadSchema.id_from_object(overview, Gql::Types::OverviewType, {})
  57. end
  58. it 'performs only the expected amount of DB queries', authenticated_as: :agent do # rubocop:disable RSpec/ExampleLength, RSpec/MultipleExpectations
  59. query = read_graphql_file('apps/mobile/graphql/fragments/objectAttributeValues.graphql') +
  60. read_graphql_file('apps/mobile/graphql/queries/ticketsByOverview.graphql')
  61. variables = { overviewId: overview_id }
  62. total_queries = {}
  63. uncached_queries = {}
  64. result = trace_queries(total_queries, uncached_queries) do
  65. graphql_execute(query, variables: variables)
  66. end
  67. expect(result['data']['ticketsByOverview']['edges'].count).to eq(10)
  68. expect(total_queries).to include(
  69. {
  70. 'Overview Load' => 1,
  71. 'ObjectLookup Load' => 1,
  72. 'Permission Load' => 25,
  73. 'Permission Exists?' => 24,
  74. 'Group Load' => 11,
  75. 'UserGroup Exists?' => 10,
  76. 'Ticket Load' => 1,
  77. 'Ticket Exists?' => 1,
  78. 'User Load' => 1,
  79. 'Organization Load' => 1,
  80. 'Ticket::State Load' => 1,
  81. 'Ticket::Priority Load' => 1,
  82. 'Ticket::StateType Load' => 1
  83. }
  84. )
  85. expect(uncached_queries).to include(
  86. {
  87. 'Overview Load' => 1,
  88. 'ObjectLookup Load' => 1,
  89. 'Permission Load' => 4,
  90. 'Permission Exists?' => 4,
  91. 'Group Load' => 3,
  92. 'UserGroup Exists?' => 2,
  93. 'Ticket Load' => 1,
  94. 'Ticket Exists?' => 1,
  95. 'User Load' => 1,
  96. 'Organization Load' => 1,
  97. 'Ticket::State Load' => 1,
  98. 'Ticket::Priority Load' => 1,
  99. 'Ticket::StateType Load' => 1
  100. }
  101. )
  102. end
  103. end
  104. context 'when querying one ticket with many articles' do
  105. let(:loops) { 1 }
  106. let(:tickets_per_loop) { 1 }
  107. let(:articles_per_ticket) { 100 }
  108. let(:ticket_id) { Gql::ZammadSchema.id_from_object(Ticket.last, Gql::Types::TicketType, {}) }
  109. it 'performs only the expected amount of DB queries', authenticated_as: :agent do # rubocop:disable RSpec/ExampleLength, RSpec/MultipleExpectations
  110. query = read_graphql_file('apps/mobile/graphql/fragments/objectAttributeValues.graphql') +
  111. read_graphql_file('apps/mobile/graphql/queries/ticketById.graphql')
  112. variables = { ticketId: ticket_id, withArticles: true }
  113. total_queries = {}
  114. uncached_queries = {}
  115. result = trace_queries(total_queries, uncached_queries) do
  116. graphql_execute(query, variables: variables)
  117. end
  118. expect(result['data']['ticketById']['id']).to eq(ticket_id)
  119. expect(total_queries).to include(
  120. {
  121. 'Permission Load' => 5,
  122. 'Permission Exists?' => 4,
  123. # The next lines are unfortunately high, caused by Pundit layer, but mitigated by SQL cache.
  124. 'Group Load' => 102,
  125. 'UserGroup Exists?' => 101,
  126. 'Ticket Load' => 101,
  127. 'Ticket::Article Load' => 1,
  128. 'User Load' => 1,
  129. 'Organization Load' => 1,
  130. 'Ticket::State Load' => 1,
  131. 'Ticket::Priority Load' => 1,
  132. 'Ticket::StateType Load' => 1
  133. }
  134. )
  135. adapter = ActiveRecord::Base.connection_db_config.configuration_hash[:adapter]
  136. expect(uncached_queries).to include(
  137. {
  138. 'Permission Load' => 3,
  139. 'Permission Exists?' => 3,
  140. 'Group Load' => 2,
  141. 'UserGroup Exists?' => 1,
  142. 'Ticket Load' => adapter == 'mysql2' ? 1 : 2, # differs for some reason, not sure why
  143. 'Ticket::Article Load' => 1,
  144. 'User Load' => 1,
  145. 'Organization Load' => 1,
  146. 'Ticket::State Load' => 1,
  147. 'Ticket::Priority Load' => 1,
  148. 'Ticket::StateType Load' => 1
  149. }
  150. )
  151. end
  152. end
  153. end