overview_spec.rb 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. RSpec.describe 'Overview', type: :system do
  4. context 'when logged in as customer', authenticated_as: :customer do
  5. let!(:customer) { create(:customer) }
  6. let!(:main_overview) { create(:overview) }
  7. let!(:other_overview) do
  8. create(:overview, condition: {
  9. 'ticket.state_id' => {
  10. operator: 'is',
  11. value: Ticket::State.where(name: %w[merged]).pluck(:id),
  12. },
  13. })
  14. end
  15. it 'shows create button when customer has no tickets' do
  16. visit "ticket/view/#{main_overview.link}"
  17. within :active_content do
  18. expect(page).to have_text 'Create your first ticket'
  19. end
  20. end
  21. def authenticate
  22. Setting.set('customer_ticket_create', false)
  23. customer
  24. end
  25. it 'does not show create button when ticket creation via web is disabled', authenticated_as: :authenticate do
  26. visit "ticket/view/#{main_overview.link}"
  27. within :active_content do
  28. expect(page).to have_text 'You currently don\'t have any tickets.'
  29. end
  30. end
  31. it 'shows overview-specific message if customer has tickets in other overview', performs_jobs: true do
  32. perform_enqueued_jobs only: TicketUserTicketCounterJob do
  33. create(:ticket, customer: customer)
  34. end
  35. visit "ticket/view/#{other_overview.link}"
  36. within :active_content do
  37. expect(page).to have_text 'You have no tickets'
  38. end
  39. end
  40. it 'replaces button with overview-specific message when customer creates a ticket', performs_jobs: true do
  41. visit "ticket/view/#{other_overview.link}"
  42. visit 'customer_ticket_new'
  43. find('[name=title]').fill_in with: 'Title'
  44. find(:richtext).send_keys 'content'
  45. set_tree_select_value('group_id', Group.first.name)
  46. click '.js-submit'
  47. perform_enqueued_jobs only: TicketUserTicketCounterJob
  48. visit "ticket/view/#{other_overview.link}"
  49. within :active_content do
  50. expect(page).to have_text 'You have no tickets'
  51. end
  52. end
  53. end
  54. context 'sorting when group by is set', authenticated_as: :user do
  55. let(:user) { create(:agent, groups: [group_c, group_a, group_b]) }
  56. let(:group_a) { create(:group, name: 'aaa') }
  57. let(:group_b) { create(:group, name: 'bbb') }
  58. let(:group_c) { create(:group, name: 'ccc') }
  59. let(:ticket1) { create(:ticket, group: group_a, priority_id: 1, customer: user) }
  60. let(:ticket2) { create(:ticket, group: group_c, priority_id: 2, customer: user) }
  61. let(:ticket3) { create(:ticket, group: group_b, priority_id: 3, customer: user) }
  62. let(:overview) do
  63. create(:overview, group_by: group_key, group_direction: group_direction, condition: {
  64. 'ticket.customer_id' => {
  65. operator: 'is',
  66. value: user.id
  67. }
  68. })
  69. end
  70. before do
  71. ticket1 && ticket2 && ticket3
  72. visit "ticket/view/#{overview.link}"
  73. end
  74. context 'when grouping by priority' do
  75. let(:group_key) { 'priority' }
  76. context 'when group direction is default' do
  77. let(:group_direction) { nil }
  78. it 'sorts groups 1 > 3' do
  79. within :active_content do
  80. expect(all('.table-overview table b').map(&:text)).to eq ['1 low', '2 normal', '3 high']
  81. end
  82. end
  83. it 'does not show duplicates when any ticket attribute is updated using bulk update' do
  84. find("tr[data-id='#{ticket3.id}']").check('bulk', allow_label_click: true)
  85. select '2 normal', from: 'priority_id'
  86. click '.js-confirm'
  87. find('.js-confirm-step textarea').fill_in with: 'test tickets ordering'
  88. click '.js-submit'
  89. within :active_content do
  90. expect(page).to have_css("tr[data-id='#{ticket3.id}']", count: 1)
  91. end
  92. end
  93. end
  94. context 'when group direction is ASC' do
  95. let(:group_direction) { 'ASC' }
  96. it 'sorts groups 1 > 3' do
  97. within :active_content do
  98. expect(all('.table-overview table b').map(&:text)).to eq ['1 low', '2 normal', '3 high']
  99. end
  100. end
  101. end
  102. context 'when group direction is DESC' do
  103. let(:group_direction) { 'DESC' }
  104. it 'sorts groups 3 > 1' do
  105. within :active_content do
  106. expect(all('.table-overview table b').map(&:text)).to eq ['3 high', '2 normal', '1 low']
  107. end
  108. end
  109. end
  110. end
  111. context 'when grouping by groups' do
  112. let(:group_key) { 'group' }
  113. let(:group_direction) { 'ASC' }
  114. it 'sorts groups a > b > c' do
  115. within :active_content do
  116. expect(all('.table-overview table b').map(&:text)).to eq %w[aaa bbb ccc]
  117. end
  118. end
  119. it 'updates table grouping when updated using bulk update' do
  120. find("tr[data-id='#{ticket1.id}']").check('bulk', allow_label_click: true)
  121. find("tr[data-id='#{ticket2.id}']").check('bulk', allow_label_click: true)
  122. find("tr[data-id='#{ticket3.id}']").check('bulk', allow_label_click: true)
  123. find('[data-attribute-name="group_id"]').click
  124. find('li', text: 'aaa').click
  125. click '.js-confirm'
  126. find('.js-confirm-step textarea').fill_in with: 'test tickets grouping'
  127. click '.js-submit'
  128. within :active_content do
  129. expect(page)
  130. .to have_text('aaa')
  131. .and have_no_text('bbb')
  132. .and have_no_text('ccc')
  133. end
  134. end
  135. end
  136. context 'when grouping by tree_selects', authenticated_as: :authenticate, db_strategy: :reset do
  137. def authenticate
  138. create(:object_manager_attribute_tree_select, name: 'tree_select_field', display: 'Tree Select Field', data_option: data_option)
  139. ObjectManager::Attribute.migration_execute
  140. user
  141. end
  142. let(:data_option) do
  143. {
  144. 'options' => [
  145. {
  146. 'name' => 'a',
  147. 'value' => 'a',
  148. 'children' => [
  149. {
  150. 'name' => '1',
  151. 'value' => 'a::1',
  152. }
  153. ]
  154. },
  155. {
  156. 'name' => 'b',
  157. 'value' => 'b',
  158. 'children' => [
  159. {
  160. 'name' => '1',
  161. 'value' => 'b::1',
  162. },
  163. {
  164. 'name' => '2',
  165. 'value' => 'b::2',
  166. }
  167. ]
  168. },
  169. {
  170. 'name' => 'c',
  171. 'value' => 'c',
  172. 'children' => [
  173. {
  174. 'name' => '1',
  175. 'value' => 'c::1',
  176. },
  177. {
  178. 'name' => '2',
  179. 'value' => 'c::2',
  180. },
  181. {
  182. 'name' => '3',
  183. 'value' => 'c::3',
  184. },
  185. ]
  186. },
  187. ],
  188. 'default' => '',
  189. 'null' => true,
  190. 'relation' => '',
  191. 'maxlength' => 255,
  192. 'nulloption' => true,
  193. }
  194. end
  195. let(:ticket1) { create(:ticket, group: group_a, priority_id: 1, customer: user, tree_select_field: 'a::1') }
  196. let(:ticket2) { create(:ticket, group: group_c, priority_id: 2, customer: user, tree_select_field: 'b::2') }
  197. let(:ticket3) { create(:ticket, group: group_b, priority_id: 3, customer: user, tree_select_field: 'c::3') }
  198. let(:group_key) { 'tree_select_field' }
  199. context 'when group direction is default' do
  200. let(:group_direction) { nil }
  201. it 'sorts groups a::1 > b::2 > c::3' do
  202. within :active_content do
  203. expect(all('.table-overview table b').map(&:text)).to eq %w[a::1 b::2 c::3]
  204. end
  205. end
  206. end
  207. context 'when group direction is ASC' do
  208. let(:group_direction) { 'ASC' }
  209. it 'sorts groups a::1 > b::2 > c::3' do
  210. within :active_content do
  211. expect(all('.table-overview table b').map(&:text)).to eq %w[a::1 b::2 c::3]
  212. end
  213. end
  214. end
  215. context 'when group direction is DESC' do
  216. let(:group_direction) { 'DESC' }
  217. it 'sorts groups c::3 > b::2 > a::1' do
  218. within :active_content do
  219. expect(all('.table-overview table b').map(&:text)).to eq %w[c::3 b::2 a::1]
  220. end
  221. end
  222. end
  223. end
  224. end
  225. context 'when multiselect is choosen as column', authenticated_as: :authenticate, db_strategy: :reset do
  226. def authenticate
  227. create(:object_manager_attribute_multiselect, data_option: data_option, name: attribute_name)
  228. ObjectManager::Attribute.migration_execute
  229. user
  230. end
  231. let(:user) { create(:agent, groups: [group]) }
  232. let(:attribute_name) { 'multiselect' }
  233. let(:options_hash) do
  234. {
  235. 'key_1' => 'display_value_1',
  236. 'key_2' => 'display_value_2',
  237. 'key_3' => 'display_value_3',
  238. 'key_4' => 'display_value_4',
  239. 'key_5' => 'display_value_5'
  240. }
  241. end
  242. let(:data_option) { { options: options_hash, default: '' } }
  243. let(:group) { create(:group, name: 'aaa') }
  244. let(:ticket) { create(:ticket, group: group, customer: user, multiselect: multiselect_value) }
  245. let(:view) { { 's'=>%w[number title multiselect] } }
  246. let(:condition) do
  247. {
  248. 'ticket.customer_id' => {
  249. operator: 'is',
  250. value: user.id
  251. }
  252. }
  253. end
  254. let(:overview) { create(:overview, condition: condition, view: view) }
  255. let(:overview_table_selector) { '.table-overview .js-tableBody' }
  256. before do
  257. ticket
  258. visit "ticket/view/#{overview.link}"
  259. end
  260. context 'with nil multiselect value' do
  261. let(:multiselect_value) { nil }
  262. let(:expected_text) { '-' }
  263. it "shows dash '-' for tickets" do
  264. within :active_content, overview_table_selector do
  265. expect(page).to have_css 'tr.item td', text: expected_text
  266. end
  267. end
  268. end
  269. context 'with a single multiselect value' do
  270. let(:multiselect_value) { ['key_4'] }
  271. let(:expected_text) { 'display_value_4' }
  272. it 'shows the display value for tickets' do
  273. within :active_content, overview_table_selector do
  274. expect(page).to have_css 'tr.item td', text: expected_text
  275. end
  276. end
  277. end
  278. context 'with multiple multiselect values' do
  279. let(:multiselect_value) { %w[key_2 key_3 key_5] }
  280. let(:expected_text) { 'display_value_2, display_value_3, display_value_5' }
  281. it 'shows comma seperated diaplay value for tickets' do
  282. within :active_content, overview_table_selector do
  283. expect(page).to have_css 'tr.item td', text: expected_text
  284. end
  285. end
  286. end
  287. end
  288. context 'when only one attribute is visible', authenticated_as: :user do
  289. let(:user) { create(:agent, groups: [group]) }
  290. let(:group) { create(:group, name: 'aaa') }
  291. let(:ticket) { create(:ticket, group: group, customer: user) }
  292. let(:view) { { 's' => %w[title] } }
  293. let(:condition) do
  294. {
  295. 'ticket.customer_id' => {
  296. operator: 'is',
  297. value: user.id
  298. }
  299. }
  300. end
  301. let(:overview) { create(:overview, condition: condition, view: view) }
  302. let(:overview_table_head_selector) { 'div.table-overview table.table thead' }
  303. let(:expected_header_text) { 'TITLE' }
  304. before do
  305. ticket
  306. visit "ticket/view/#{overview.link}"
  307. end
  308. it 'shows only the title column' do
  309. within :active_content, overview_table_head_selector do
  310. expect(page).to have_css('th.js-tableHead[data-column-key="title"]', text: expected_header_text)
  311. end
  312. end
  313. end
  314. context 'when dragging table columns' do
  315. let(:overview) { create(:overview) }
  316. before do
  317. visit "ticket/view/#{overview.link}"
  318. end
  319. shared_examples 'resizing table columns' do
  320. it 'resizes table columns' do
  321. initial_number_width = find_all('.js-tableHead')[1].native.size.width.to_i
  322. initial_title_width = find_all('.js-tableHead')[2].native.size.width.to_i
  323. column_resize_handle = find_all('.js-col-resize')[0]
  324. # Drag the first column resize handle to the left.
  325. # Move the cursor horizontally by 100 pixels.
  326. # Finally, drop the handle to resize the column.
  327. page.driver.browser.action
  328. .move_to(column_resize_handle.native)
  329. .click_and_hold
  330. .move_by(-100, 0)
  331. .release
  332. .perform
  333. final_number_width = find_all('.js-tableHead')[1].native.size.width.to_i
  334. final_title_width = find_all('.js-tableHead')[2].native.size.width.to_i
  335. expect(final_number_width).to be < initial_number_width
  336. expect(final_title_width).to be > initial_title_width
  337. end
  338. end
  339. context 'with mouse input' do
  340. it_behaves_like 'resizing table columns'
  341. end
  342. # TODO: Add a test example for touch input once the tablet emulation mode starts working in the CI.
  343. end
  344. # https://github.com/zammad/zammad/issues/4409
  345. context 'when sorting by display values', authenticated_as: :authenticate, db_strategy: :reset do
  346. def authenticate
  347. custom_field
  348. ObjectManager::Attribute.migration_execute
  349. true
  350. end
  351. let(:overview) { create(:overview, view: { 's'=>%w[number title overview_test] }) }
  352. let(:custom_field) { create("object_manager_attribute_#{field_type}", name: :overview_test, data_option: data_option) }
  353. let(:data_option) { { options: options_hash, translate: translate, default: '' } }
  354. let(:translate) { false }
  355. let(:group) { Group.first }
  356. let(:options_hash) do
  357. {
  358. 'key_1' => 'A value',
  359. 'key_2' => 'D value',
  360. 'key_3' => 'B value',
  361. 'key_4' => 'C value',
  362. }
  363. end
  364. let(:custom_sorted_array) do
  365. [
  366. { value: 'key_1', name: 'C value' },
  367. { value: 'key_3', name: 'A value' },
  368. { value: 'key_2', name: 'B value' },
  369. { value: 'key_4', name: 'D value' },
  370. ]
  371. end
  372. let(:translations_hash) do
  373. {
  374. 'A value' => 'Pirma vertė',
  375. 'B value' => 'Antra vertė',
  376. 'C value' => 'Trečia vertė',
  377. 'D value' => 'Ketvirta vertė'
  378. }
  379. end
  380. before do
  381. Ticket.destroy_all
  382. translations_hash.each { |key, value| create(:translation, locale: 'en-us', source: key, target: value) }
  383. ticket_1 && ticket_2 && ticket_3 && ticket_4
  384. visit "ticket/view/#{overview.link}"
  385. find('[data-column-key=overview_test] .js-sort').click
  386. end
  387. context 'when field is select' do
  388. let(:field_type) { 'select' }
  389. let(:ticket_1) { create(:ticket, title: 'A ticket', overview_test: 'key_1', group: group) }
  390. let(:ticket_2) { create(:ticket, title: 'B ticket', overview_test: 'key_2', group: group) }
  391. let(:ticket_3) { create(:ticket, title: 'C ticket', overview_test: 'key_3', group: group) }
  392. let(:ticket_4) { create(:ticket, title: 'D ticket', overview_test: 'key_4', group: group) }
  393. context 'when custom sort is on' do
  394. let(:data_option) { { options: custom_sorted_array, default: '', customsort: 'on' } }
  395. it 'sorts tickets correctly' do
  396. within '.js-tableBody' do
  397. expect(page)
  398. .to have_css('tr:nth-child(1)', text: ticket_1.title)
  399. .and have_css('tr:nth-child(2)', text: ticket_3.title)
  400. .and have_css('tr:nth-child(3)', text: ticket_2.title)
  401. .and have_css('tr:nth-child(4)', text: ticket_4.title)
  402. end
  403. end
  404. end
  405. context 'when custom sort is off' do
  406. it 'sorts tickets correctly' do
  407. within '.js-tableBody' do
  408. expect(page)
  409. .to have_css('tr:nth-child(1)', text: ticket_1.title)
  410. .and have_css('tr:nth-child(2)', text: ticket_3.title)
  411. .and have_css('tr:nth-child(3)', text: ticket_4.title)
  412. .and have_css('tr:nth-child(4)', text: ticket_2.title)
  413. end
  414. end
  415. end
  416. context 'when display values are translated' do
  417. let(:translate) { true }
  418. it 'sorts tickets correctly' do
  419. within '.js-tableBody' do
  420. expect(page)
  421. .to have_css('tr:nth-child(1)', text: ticket_3.title)
  422. .and have_css('tr:nth-child(2)', text: ticket_2.title)
  423. .and have_css('tr:nth-child(3)', text: ticket_1.title)
  424. .and have_css('tr:nth-child(4)', text: ticket_4.title)
  425. end
  426. end
  427. end
  428. end
  429. context 'when field is multiselect' do
  430. let(:field_type) { 'multiselect' }
  431. let(:ticket_1) { create(:ticket, title: 'A ticket', overview_test: '{key_1}', group: group) }
  432. let(:ticket_2) { create(:ticket, title: 'B ticket', overview_test: '{key_2,key_3}', group: group) }
  433. let(:ticket_3) { create(:ticket, title: 'C ticket', overview_test: '{key_4,key_3,key_2}', group: group) }
  434. let(:ticket_4) { create(:ticket, title: 'D ticket', overview_test: '{key_2}', group: group) }
  435. context 'when custom sort is on' do
  436. let(:data_option) { { options: custom_sorted_array, default: '', customsort: 'on' } }
  437. it 'sorts tickets correctly' do
  438. within '.js-tableBody' do
  439. expect(page)
  440. .to have_css('tr:nth-child(1)', text: ticket_1.title)
  441. .and have_css('tr:nth-child(2)', text: ticket_2.title)
  442. .and have_css('tr:nth-child(3)', text: ticket_3.title)
  443. .and have_css('tr:nth-child(4)', text: ticket_4.title)
  444. end
  445. end
  446. end
  447. context 'when custom sort is off' do
  448. it 'sorts tickets correctly' do
  449. within '.js-tableBody' do
  450. expect(page)
  451. .to have_css('tr:nth-child(1)', text: ticket_1.title)
  452. .and have_css('tr:nth-child(2)', text: ticket_3.title)
  453. .and have_css('tr:nth-child(3)', text: ticket_2.title)
  454. .and have_css('tr:nth-child(4)', text: ticket_4.title)
  455. end
  456. end
  457. end
  458. context 'when display values are translated' do
  459. let(:translate) { true }
  460. it 'sorts tickets correctly' do
  461. within '.js-tableBody' do
  462. expect(page)
  463. .to have_css('tr:nth-child(1)', text: ticket_2.title)
  464. .and have_css('tr:nth-child(2)', text: ticket_3.title)
  465. .and have_css('tr:nth-child(3)', text: ticket_4.title)
  466. .and have_css('tr:nth-child(4)', text: ticket_1.title)
  467. end
  468. end
  469. end
  470. end
  471. end
  472. # https://github.com/zammad/zammad/issues/4409
  473. # Touched by above issue, but not directly related
  474. context 'when sorting by select field without options' do
  475. let(:overview) do
  476. create(:overview, condition: {
  477. 'ticket.state_id' => {
  478. operator: 'is',
  479. value: Ticket::State.where(name: %w[new open closed]).pluck(:id),
  480. },
  481. })
  482. end
  483. let(:ticket_1) { create(:ticket, title: 'A ticket', state_name: 'open', group: group) }
  484. let(:ticket_2) { create(:ticket, title: 'B ticket', state_name: 'closed', group: group) }
  485. let(:ticket_3) { create(:ticket, title: 'C ticket', state_name: 'new', group: group) }
  486. let(:group) { Group.first }
  487. before do
  488. if defined?(translations_hash)
  489. translations_hash.each do |key, value|
  490. Translation.find_or_create_by(locale: 'en-us', source: key).update!(target: value)
  491. end
  492. end
  493. Ticket.destroy_all
  494. ticket_1 && ticket_2 && ticket_3
  495. visit "ticket/view/#{overview.link}"
  496. find('[data-column-key=state_id] .js-sort').click
  497. end
  498. it 'sorts tickets correctly' do
  499. within '.js-tableBody' do
  500. expect(page)
  501. .to have_css('tr:nth-child(1)', text: ticket_2.title)
  502. .and have_css('tr:nth-child(2)', text: ticket_3.title)
  503. .and have_css('tr:nth-child(3)', text: ticket_1.title)
  504. end
  505. end
  506. context 'when states are translated' do
  507. let(:translations_hash) { { 'closed' => 'zzz closed' } }
  508. it 'sorts tickets correctly' do
  509. within '.js-tableBody' do
  510. expect(page)
  511. .to have_css('tr:nth-child(1)', text: ticket_3.title)
  512. .and have_css('tr:nth-child(2)', text: ticket_1.title)
  513. .and have_css('tr:nth-child(3)', text: ticket_2.title)
  514. end
  515. end
  516. end
  517. end
  518. end