object_manager_spec.rb 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883
  1. # Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. RSpec.describe 'System > Objects', type: :system do
  4. context 'when trying to create invalid attributes' do
  5. RSpec.shared_examples 'cannot create new object attribute' do |name, error_message|
  6. context "when trying to create a new attibute '#{name}'" do
  7. before do
  8. visit '/#system/object_manager'
  9. page.find('.js-new').click
  10. end
  11. it "fails with '#{error_message}'" do
  12. in_modal do
  13. fill_in 'name', with: name || 'fallback'
  14. fill_in 'display', with: 'Not allowed'
  15. click '.js-submit'
  16. expect(find('.js-alert')).to have_text(error_message)
  17. end
  18. end
  19. end
  20. end
  21. include_examples 'cannot create new object attribute', 'customer_id', 'This object already exists.'
  22. ['some_other_id', 'some_other_ids', 'some spaces'].each do |name|
  23. include_examples 'cannot create new object attribute', name, 'are not allowed'
  24. end
  25. end
  26. context 'when creating a new attribute' do
  27. before do
  28. visit '/#system/object_manager'
  29. find('.js-new').click
  30. end
  31. it 'has the position feild with no default value' do
  32. in_modal do
  33. expect(page).to have_field('position', with: '')
  34. end
  35. end
  36. end
  37. context 'when creating but then discarding fields again' do
  38. before do
  39. visit '/#system/object_manager'
  40. end
  41. it 'discards the changes again' do
  42. page.find('.js-new').click
  43. in_modal do
  44. fill_in 'name', with: 'new_field'
  45. fill_in 'display', with: 'New field'
  46. click '.js-submit'
  47. end
  48. click '.js-discard'
  49. expect(page).to have_no_css('.js-discard')
  50. end
  51. end
  52. context 'when creating and removing a field with migration', db_strategy: :reset do
  53. shared_examples 'create and remove field with migration' do |data_type|
  54. context "for data_type '#{data_type}'" do
  55. before do
  56. visit '/#system/object_manager'
  57. end
  58. it 'creates and removes the field correctly' do
  59. # Create
  60. page.find('.js-new').click
  61. in_modal do
  62. fill_in 'name', with: 'new_field'
  63. fill_in 'display', with: 'New field'
  64. select data_type, from: 'data_type'
  65. page.scroll_to :bottom
  66. click '.js-submit'
  67. end
  68. expect(page).to have_text('New field')
  69. expect(page).to have_text('Database Update Required')
  70. click '.js-execute', wait: 7.minutes
  71. expect(page).to have_text('Zammad requires a restart')
  72. refresh_with_wait
  73. # Update
  74. click 'tbody tr:last-child'
  75. in_modal do
  76. fill_in 'display', with: 'New field updated'
  77. page.scroll_to :bottom
  78. click '.js-submit'
  79. end
  80. expect(page).to have_text('New field updated')
  81. expect(page).to have_text('Database Update Required')
  82. click '.js-execute', wait: 7.minutes
  83. expect(page).to have_text('please reload your browser')
  84. in_modal do
  85. click '.js-submit'
  86. end
  87. # After the reload, we must explictly wait for the app to be completely ready.
  88. wait_for_loading_to_complete(wait_ws: true)
  89. # Delete
  90. click 'tbody tr:last-child .js-delete'
  91. expect(page).to have_text('Database Update Required')
  92. click '.js-execute', wait: 7.minutes
  93. expect(page).to have_text('Zammad requires a restart')
  94. refresh_with_wait
  95. expect(page).to have_no_text('New field updated')
  96. end
  97. end
  98. end
  99. ['Text field', 'Single selection field', 'Integer field', 'Date & time field', 'Date field', 'Boolean field', 'Single tree selection field'].each do |data_type|
  100. include_examples 'create and remove field with migration', data_type
  101. end
  102. context 'with Multiselect' do
  103. include_examples 'create and remove field with migration', 'Multiple selection field'
  104. end
  105. end
  106. context 'when creating and modifying tree select fields', db_strategy: :reset do
  107. let(:object_attribute) do
  108. attribute = create(:object_manager_attribute_tree_select, name: 'undeletable_field', display: 'Undeletable Field', position: 999)
  109. ObjectManager::Attribute.migration_execute
  110. attribute
  111. end
  112. it 'creates and updates the fields correctly' do
  113. # Create the field via API.
  114. object_attribute
  115. visit '/#system/object_manager'
  116. click 'tbody tr:last-child'
  117. in_modal do
  118. # Add two new attributes to the field.
  119. 2.times do |i|
  120. click 'tbody tr:last-child .js-addRow'
  121. find('tbody tr:last-child .js-key').fill_in(with: "new tree option #{i}")
  122. end
  123. click '.js-submit'
  124. end
  125. expect(page).to have_text('Database Update Required')
  126. click '.js-execute', wait: 7.minutes
  127. expect(page).to have_text('please reload your browser')
  128. in_modal do
  129. click 'button.js-submit'
  130. end
  131. # Check that the attributes were correctly saved.
  132. expect(ObjectManager::Attribute.last.data_option[:options][-2..]).to eq([{ 'name' => 'new tree option 0', 'value' => 'new tree option 0' }, { 'name' => 'new tree option 1', 'value' => 'new tree option 1' }])
  133. end
  134. end
  135. context 'when trying to delete undeletable fields', db_strategy: :reset do
  136. let(:object_attribute) do
  137. attribute = create(:object_manager_attribute_text, name: 'undeletable_field', display: 'Undeletable Field', position: 999)
  138. ObjectManager::Attribute.migration_execute
  139. attribute
  140. end
  141. before do
  142. create(:overview, condition: {
  143. "ticket.#{object_attribute.name}" => {
  144. operator: 'is',
  145. value: 'dummy',
  146. },
  147. })
  148. visit '/#system/object_manager'
  149. end
  150. it 'field referenced by an overview is not deletable' do
  151. expect(page).to have_text(object_attribute.display)
  152. expect(page).to have_css('tbody tr:last-child span.is-disabled .icon-trash')
  153. end
  154. end
  155. context 'when checking field sorting', db_strategy: :reset do
  156. # lexicographically ordered list of option strings
  157. let(:options) { %w[0 000.000 1 100.100 100.200 2 200.100 200.200 3 ä b n ö p sr ß st t ü v] }
  158. let(:options_hash) { options.reverse.to_h { |o| [o, o] } }
  159. let(:cutomsort_options) { ['0', '1', '2', '3', 'v', 'ü', 't', 'st', 'ß', 'sr', 'p', 'ö', 'n', 'b', 'ä', '200.200', '200.100', '100.200', '100.100', '000.000'] }
  160. before do
  161. object_attribute
  162. ObjectManager::Attribute.migration_execute
  163. visit '/#system/object_manager'
  164. click 'tbody tr:last-child td:first-child'
  165. end
  166. shared_examples 'sorting options correctly' do
  167. shared_examples 'preserving the sorting correctly' do
  168. it 'preserves the sorting correctly' do
  169. sorted_dialog_values = all('table.settings-list tbody tr td input.js-key').map(&:value).reject { |x| x == '' }
  170. expect(sorted_dialog_values).to eq(expected_options)
  171. visit '/#ticket/create'
  172. sorted_ticket_values = all("select[name=#{object_attribute.name}] option").map(&:value).reject { |x| x == '' }
  173. expect(sorted_ticket_values).to eq(expected_options)
  174. end
  175. end
  176. context 'with no customsort' do
  177. let(:data_option) { { options: options_hash, default: 0 } }
  178. let(:expected_options) { options } # sort lexicographically
  179. it_behaves_like 'preserving the sorting correctly'
  180. end
  181. context 'with customsort' do
  182. let(:options_hash) { options.reverse.collect { |o| { name: o, value: o } } }
  183. let(:data_option) { { options: options_hash, default: 0, customsort: 'on' } }
  184. let(:expected_options) { options.reverse } # preserves sorting from backend
  185. it_behaves_like 'preserving the sorting correctly'
  186. end
  187. end
  188. shared_examples 'sorting options correctly using drag and drop' do
  189. shared_examples 'preserving drag and drop sorting correctly' do
  190. it 'preserves drag and drop sorting correctly' do
  191. sorted_dialog_values = all('table.settings-list tbody tr td input.js-key').map(&:value).reject { |x| x == '' }
  192. expect(sorted_dialog_values).to eq(expected_options)
  193. end
  194. end
  195. context 'with drag and drop sorting' do
  196. let(:options) { %w[0 1 d u w] }
  197. let(:options_hash) { options.to_h { |o| [o, o] } }
  198. before do
  199. # use drag and drop to reverse sort the options
  200. in_modal do
  201. wait.until_constant do
  202. find('tbody.table-sortable')
  203. .evaluate_script("$(this).sortable('instance').ready")
  204. end
  205. within '.js-dataMap table.js-Table .table-sortable' do
  206. rows = all('tr.input-data-row td.table-draggable .icon')
  207. target = rows.last
  208. rows.each do |row|
  209. row.drag_to target
  210. end
  211. end
  212. click_on 'Submit'
  213. end
  214. click 'tbody tr:last-child td:first-child'
  215. end
  216. context 'with no customsort' do
  217. let(:data_option) { { options: options_hash, default: 0 } }
  218. let(:expected_options) { options } # sort lexicographically
  219. it_behaves_like 'preserving drag and drop sorting correctly'
  220. end
  221. context 'with customsort' do
  222. let(:data_option) { { options: options_hash, default: 0, customsort: 'on' } }
  223. let(:expected_options) { options.reverse } # preserves sorting from backend
  224. it_behaves_like 'preserving drag and drop sorting correctly'
  225. end
  226. end
  227. end
  228. context 'with multiselect attribute' do
  229. let(:object_attribute) { create(:object_manager_attribute_multiselect, data_option: data_option, position: 999) }
  230. it_behaves_like 'sorting options correctly'
  231. it_behaves_like 'sorting options correctly using drag and drop'
  232. end
  233. context 'with select attribute' do
  234. let(:object_attribute) { create(:object_manager_attribute_select, data_option: data_option, position: 999) }
  235. it_behaves_like 'sorting options correctly'
  236. it_behaves_like 'sorting options correctly using drag and drop'
  237. end
  238. end
  239. context 'when checking selection options removal', db_strategy: :reset do
  240. let(:options) { %w[äöü cat delete dog ß].index_with { |x| "#{x.capitalize} Display" } }
  241. let(:options_no_dog) { options.except('dog') }
  242. let(:options_no_dog_no_delete) { options_no_dog.except('delete') }
  243. let(:screens) { { 'create_middle' => { 'ticket.agent'=>{ 'shown' => true, 'required' => false, 'item_class' => 'column' } }, 'edit' => { 'ticket.agent'=>{ 'shown' => true, 'required' => false } } } }
  244. let(:object_attribute) do
  245. attribute = create(:object_manager_attribute_select, data_option: { options: options, default: 0 }, screens: screens, position: 999)
  246. ObjectManager::Attribute.migration_execute
  247. attribute
  248. end
  249. it 'handles removed options correctly' do
  250. object_attribute
  251. # Make sure option is present in the first place.
  252. ticket = create(:ticket, group: Group.find_by(name: 'Users'), object_attribute.name => 'delete')
  253. visit "/#ticket/zoom/#{ticket.id}"
  254. sorted_ticket_values = all("select[name=#{object_attribute.name}] option").map(&:value).reject { |x| x == '' }
  255. expect(sorted_ticket_values).to eq(options.keys)
  256. expect(find("select[name=#{object_attribute.name}] option:checked").value).to eq('delete')
  257. expect(find("select[name=#{object_attribute.name}] option:checked").text).to eq('Delete Display')
  258. # Remove 'delete' and 'dog' options from field via GUI to make sure that the :historical_options attribute is saved.
  259. visit '/#system/object_manager'
  260. click 'tbody tr:last-child'
  261. in_modal do
  262. 2.times { find('tr:nth-child(3) .icon-trash').click }
  263. click '.js-submit'
  264. end
  265. expect(page).to have_text('Database Update Required')
  266. click '.js-execute', wait: 7.minutes
  267. expect(page).to have_text('please reload your browser')
  268. in_modal do
  269. click '.js-submit'
  270. end
  271. # Make sure option is still available in already saved ticket, even though the option was removed from the object attribute.
  272. # This is done via the :historical_options.
  273. visit "/#ticket/zoom/#{ticket.id}"
  274. # Ticket data is loaded from a front end cache first, so wait until there is a consistent state.
  275. expect(page).to have_css("select[name=#{object_attribute.name}] option[value='delete']")
  276. expect(page).to have_no_css("select[name=#{object_attribute.name}] option[value='dog']")
  277. sorted_ticket_values = all("select[name=#{object_attribute.name}] option").map(&:value).reject { |x| x == '' }
  278. expect(sorted_ticket_values).to eq(options_no_dog.keys)
  279. expect(find("select[name=#{object_attribute.name}] option:checked").value).to eq('delete')
  280. expect(find("select[name=#{object_attribute.name}] option:checked").text).to eq('Delete Display')
  281. # Make sure deleted option is missing for new tickets.
  282. visit '/#ticket/create'
  283. sorted_ticket_values = all("select[name=#{object_attribute.name}] option").map(&:value).reject { |x| x == '' }
  284. expect(sorted_ticket_values).to eq(options_no_dog_no_delete.keys)
  285. end
  286. end
  287. context 'when checking boolean user attributes', db_strategy: :reset do
  288. let(:organization_object_attribute) do
  289. attribute = create(:object_manager_attribute_boolean, object_name: 'Organization', data_option: { default: true, options: { true => 'organization:true', false => 'organization:false' } }, screens: screens, position: 999)
  290. ObjectManager::Attribute.migration_execute
  291. attribute
  292. end
  293. let(:user_object_attribute) do
  294. attribute = create(:object_manager_attribute_boolean, object_name: 'User', data_option: { default: true, options: { true => 'user:true', false => 'user:false' } }, screens: screens, position: 999)
  295. ObjectManager::Attribute.migration_execute
  296. attribute
  297. end
  298. let(:organization) { create(:organization, organization_object_attribute.name => false) }
  299. let(:customer) { create(:customer, user_object_attribute.name => false, organization: organization) }
  300. let(:screens) { { 'create' => { 'ticket.agent'=>{ 'shown' => true, 'required' => false, 'item_class' => 'column' } }, 'edit' => { 'ticket.agent'=>{ 'shown' => true, 'required' => false } }, 'view' => { 'ticket.agent'=>{ 'shown' => true, 'required' => false } } } }
  301. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users'), customer: customer) }
  302. it 'shows user and organization attributes even if they are set to false' do
  303. organization_object_attribute
  304. user_object_attribute
  305. visit "/#ticket/zoom/#{ticket.id}"
  306. click('.content.active .tabsSidebar-tab[data-tab="organization"]')
  307. expect(page).to have_text('organization:false')
  308. click('.content.active .tabsSidebar-tab[data-tab="customer"]')
  309. expect(page).to have_text('user:false')
  310. end
  311. end
  312. context 'when creating new fields' do
  313. before do
  314. visit '/#system/object_manager'
  315. page.find('.js-new').click
  316. end
  317. it 'verifies option creation order of new tree select options' do
  318. # set meta information
  319. fill_in 'Name', with: 'tree1'
  320. fill_in 'Display', with: 'tree1'
  321. page.find('select[name=data_type]').select('Single tree selection field')
  322. # create 3 childs
  323. first_add_child = page.first('div.js-addChild')
  324. first_add_child.click
  325. first_add_child.click
  326. first_add_child.click
  327. # create 1 top level node sibling
  328. page.first('div.js-addRow').click
  329. # create 3 childs for the new top level node
  330. page.all('div.js-addChild').last.click
  331. page.all('div.js-addChild').last.click
  332. page.all('div.js-addChild').last.click
  333. # create new top level nodes by first and second top level node
  334. add_rows = page.all('div.js-addRow')
  335. add_rows[0].click
  336. add_rows[4].click
  337. # add numbers to all inputs to verify order in config later
  338. number = 1
  339. page.all('input.js-key').each do |input|
  340. input.send_keys(number)
  341. number += 1
  342. end
  343. page.find('.js-submit').click
  344. expected_data_options = { 'options' =>
  345. [{ 'name' => '1',
  346. 'value' => '1',
  347. 'children' => [{ 'name' => '2', 'value' => '1::2' }, { 'name' => '3', 'value' => '1::3' }, { 'name' => '4', 'value' => '1::4' }] },
  348. { 'name' => '5', 'value' => '5' },
  349. { 'name' => '6',
  350. 'value' => '6',
  351. 'children' =>
  352. [{ 'name' => '7',
  353. 'value' => '6::7',
  354. 'children' => [{ 'name' => '8', 'value' => '6::7::8', 'children' => [{ 'name' => '9', 'value' => '6::7::8::9' }] }] }] },
  355. { 'name' => '10', 'value' => '10' }],
  356. 'default' => '',
  357. 'null' => true,
  358. 'relation' => '',
  359. 'nulloption' => true,
  360. 'maxlength' => 255,
  361. 'translate' => false }
  362. expect(ObjectManager::Attribute.last.data_option).to eq(expected_data_options)
  363. end
  364. it 'checks smart defaults for select field' do
  365. fill_in 'Name', with: 'select1'
  366. find('input[name=display]').set('select1')
  367. page.find('select[name=data_type]').select('Single selection field')
  368. page.first('div.js-add').click
  369. page.first('div.js-add').click
  370. page.first('div.js-add').click
  371. counter = 0
  372. page.all('.js-key').each do |field|
  373. field.set(counter)
  374. counter += 1
  375. end
  376. page.all('.js-value')[-2].set('special 2')
  377. page.find('.js-submit').click
  378. expected_data_options = {
  379. '0' => '0',
  380. '1' => '1',
  381. '2' => 'special 2',
  382. }
  383. expect(ObjectManager::Attribute.last.data_option['options']).to eq(expected_data_options)
  384. end
  385. it 'checks smart defaults for multiselect field' do
  386. fill_in 'Name', with: 'multiselect1'
  387. find('input[name=display]').set('multiselect1')
  388. page.find('select[name=data_type]').select('Multiple selection field')
  389. page.first('div.js-add').click
  390. page.first('div.js-add').click
  391. page.first('div.js-add').click
  392. counter = 0
  393. page.all('.js-key').each do |field|
  394. field.set(counter)
  395. counter += 1
  396. end
  397. page.all('.js-value')[-2].set('special 2')
  398. page.find('.js-submit').click
  399. expected_data_options = {
  400. '0' => '0',
  401. '1' => '1',
  402. '2' => 'special 2',
  403. }
  404. expect(ObjectManager::Attribute.last.data_option['options']).to eq(expected_data_options)
  405. end
  406. it 'checks smart defaults for boolean field' do
  407. fill_in 'Name', with: 'bool1'
  408. find('input[name=display]').set('bool1')
  409. page.find('select[name=data_type]').select('Boolean field')
  410. page.find('.js-valueFalse').set('HELL NOO')
  411. page.find('.js-submit').click
  412. expected_data_options = {
  413. true => 'yes',
  414. false => 'HELL NOO',
  415. }
  416. expect(ObjectManager::Attribute.last.data_option['options']).to eq(expected_data_options)
  417. end
  418. it 'checks default boolean value visibility' do
  419. fill_in 'Name', with: 'bool1'
  420. find('input[name=display]').set('Bool 1')
  421. page.find('select[name=data_type]').select('Boolean field')
  422. choose('data_option::default', option: 'true')
  423. page.find('.js-submit').click
  424. td = page.find(:css, 'td', text: 'bool1')
  425. tr = td.find(:xpath, './parent::tr')
  426. tr.click
  427. expect(page).to have_checked_field('data_option::default', with: 'true')
  428. end
  429. end
  430. # https://github.com/zammad/zammad/issues/3647
  431. context 'when setting Min/Max values for integer' do
  432. before do
  433. visit '/#system/object_manager'
  434. page.find('.js-new').click
  435. in_modal disappears: false do
  436. fill_in 'Name', with: 'integer1'
  437. fill_in 'Display', with: 'Integer1'
  438. page.find('select[name=data_type]').select('Integer field')
  439. end
  440. end
  441. it 'verifies max value does not go above limit' do
  442. in_modal do
  443. fill_in 'Maximal', with: '999999999999'
  444. page.find('.js-submit').click
  445. expect(page).to have_text 'Maximal value must be lower than 2147483648'
  446. end
  447. end
  448. it 'verifies max value does not go below limit' do
  449. in_modal do
  450. fill_in 'Minimal', with: '-9999999999999'
  451. fill_in 'Maximal', with: '-999999999999'
  452. page.find('.js-submit').click
  453. expect(page).to have_text 'Maximal value must be higher than -2147483648'
  454. end
  455. end
  456. it 'verifies max value can be set' do
  457. in_modal do
  458. fill_in 'Maximal', with: '128'
  459. page.find('.js-submit').click
  460. end
  461. expect(page).to have_text 'Integer1'
  462. end
  463. it 'verifies max value can be set to a negative value' do
  464. in_modal do
  465. fill_in 'Minimal', with: '-256'
  466. fill_in 'Maximal', with: '-128'
  467. page.find('.js-submit').click
  468. end
  469. expect(page).to have_text 'Integer1'
  470. end
  471. it 'verifies min value does not go above limit' do
  472. in_modal do
  473. fill_in 'Minimal', with: '999999999999'
  474. fill_in 'Maximal', with: '123'
  475. page.find('.js-submit').click
  476. expect(page).to have_text 'Minimal value must be lower than 2147483648'
  477. end
  478. end
  479. it 'verifies min value does not go below limit' do
  480. in_modal do
  481. fill_in 'Minimal', with: '-999999999999'
  482. page.find('.js-submit').click
  483. expect(page).to have_text 'Minimal value must be higher than -2147483648'
  484. end
  485. end
  486. it 'verifies min value can be set' do
  487. in_modal do
  488. fill_in 'Minimal', with: '128'
  489. page.find('.js-submit').click
  490. end
  491. expect(page).to have_text 'Integer1'
  492. end
  493. it 'verifies min value can be set to a negative value' do
  494. in_modal do
  495. fill_in 'Minimal', with: '-128'
  496. page.find('.js-submit').click
  497. end
  498. expect(page).to have_text 'Integer1'
  499. end
  500. it 'verifies min value must be lower than max' do
  501. in_modal do
  502. fill_in 'Minimal', with: '128'
  503. fill_in 'Maximal', with: '-128'
  504. page.find('.js-submit').click
  505. expect(page).to have_text 'Maximal value must be higher than or equal to minimal value'
  506. end
  507. end
  508. end
  509. context 'when creating with no diff' do
  510. before do
  511. visit '/#system/object_manager'
  512. page.find('.js-new').click
  513. in_modal disappears: false do
  514. fill_in 'Name', with: 'nodiff'
  515. fill_in 'Display', with: 'NoDiff'
  516. end
  517. end
  518. it 'date attribute' do
  519. in_modal do
  520. page.find('select[name=data_type]').select('Date field')
  521. fill_in 'Default time diff (hours)', with: ''
  522. expect { page.find('.js-submit').click }.to change(ObjectManager::Attribute, :count).by(1)
  523. end
  524. end
  525. it 'datetime attribute' do
  526. in_modal do
  527. page.find('select[name=data_type]').select('Date & time field')
  528. fill_in 'Default time diff (minutes)', with: ''
  529. expect { page.find('.js-submit').click }.to change(ObjectManager::Attribute, :count).by(1)
  530. end
  531. end
  532. end
  533. context 'with drag and drop custom sort', db_strategy: :reset do
  534. before do
  535. visit '/#system/object_manager'
  536. page.find('.js-new').click
  537. in_modal disappears: false do
  538. page.find('select[name=data_type]').select data_type
  539. fill_in 'Name', with: attribute_name
  540. find('input[name=display]').set attribute_name
  541. end
  542. end
  543. let(:data_options) do
  544. {
  545. '1' => 'one',
  546. '2' => 'two',
  547. '3' => 'three',
  548. '4' => 'four',
  549. '5' => 'five'
  550. }
  551. end
  552. def find_attribute
  553. ObjectManager::Attribute.find_by(name: attribute_name)
  554. end
  555. shared_examples 'having a custom sort option' do
  556. it 'has a custom option checkbox' do
  557. in_modal do
  558. expect(page).to have_field('data_option::customsort', type: 'checkbox', visible: :all)
  559. end
  560. end
  561. context 'a context' do
  562. before do
  563. in_modal disappears: false do
  564. within 'tr.input-add-row' do
  565. 5.times.each { first('div.js-add').click }
  566. end
  567. keys = data_options.keys
  568. all_value_input = all('tr.input-data-row .js-value')
  569. all_key_input = all('tr.input-data-row .js-key')
  570. keys.each_with_index do |key, index|
  571. all_key_input[index].set key
  572. all_value_input[index].set data_options[key]
  573. end
  574. end
  575. end
  576. context 'with custom checkbox checked' do
  577. it 'saves a customsort data option attribute' do
  578. in_modal do
  579. check 'data_option::customsort', allow_label_click: true
  580. click_on 'Submit'
  581. end
  582. # Update Database
  583. click 'div.js-execute'
  584. wait.until { find_attribute }
  585. expect(find_attribute['data_option']['customsort']).to eq('on')
  586. end
  587. end
  588. context 'with custom checkbox unchecked' do
  589. it 'does not have a customsort data option attribute' do
  590. in_modal do
  591. uncheck 'data_option::customsort', allow_label_click: true
  592. click_on 'Submit'
  593. end
  594. # Update Database
  595. click 'div.js-execute'
  596. wait.until { find_attribute }
  597. expect(find_attribute['data_option']['customsort']).to be_nil
  598. end
  599. end
  600. end
  601. end
  602. context 'when attribute is multiselect' do
  603. let(:data_type) { 'Multiple selection field' }
  604. let(:attribute_name) { 'multiselect_test' }
  605. it_behaves_like 'having a custom sort option'
  606. end
  607. context 'when attribute is select' do
  608. let(:data_type) { 'Single selection field' }
  609. let(:attribute_name) { 'select_test' }
  610. it_behaves_like 'having a custom sort option'
  611. end
  612. end
  613. describe 'Adding tree selection attribute form may get stuck with no options #4466' do
  614. before do
  615. visit '#system/object_manager'
  616. end
  617. it 'does always show rows to configure select options' do
  618. click '.js-new'
  619. select 'Multiple tree selection field', from: 'data_type'
  620. click '.js-remove'
  621. expect(page).to have_css('.js-dataMap table tbody tr')
  622. end
  623. end
  624. describe 'with external data source format', db_adapter: :postgresql, searchindex: true do
  625. let(:users) { create_list(:user, 10) }
  626. let(:link_prefix) { "#{Setting.get('http_type')}://#{Setting.get('fqdn')}/#user/profile/" }
  627. before do
  628. users
  629. searchindex_model_reload([User])
  630. end
  631. shared_examples 'showing preview table below data options' do
  632. it 'shows preview table below data options' do
  633. fill_in 'Preview', with: '*'
  634. users.each do |user|
  635. within ".js-searchResultSample tr[data-id='#{user.id}']" do
  636. expect(page.find('.search-result-value')).to have_text(user.id)
  637. expect(page.find('.search-result-label')).to have_text(user.email)
  638. expect(page.find('.search-result-link')).to have_css("a[href='#{link_prefix}#{user.id}']")
  639. end
  640. end
  641. end
  642. end
  643. context 'when creating new fields' do
  644. before do
  645. visit '/#system/object_manager'
  646. page.find('.js-new').click
  647. fill_in 'Name', with: 'test_json'
  648. set_select_field_label('data_type', 'External data source field')
  649. fill_in 'Search URL', with: "#{Setting.get('es_url')}/#{Setting.get('es_index')}_test_user/_search?q=\#{search.term}"
  650. fill_in 'Search result list key', with: 'hits.hits'
  651. fill_in 'Search result value key', with: '_id'
  652. fill_in 'Search result label key', with: '_source.email'
  653. fill_in 'Link template', with: "#{link_prefix}\#{ticket.test_json}"
  654. end
  655. it_behaves_like 'showing preview table below data options'
  656. it 'displays helpful messages' do
  657. within '.preview' do
  658. expect(page).to have_text('To trigger the preview, please enter some search term(s) above.')
  659. end
  660. fill_in 'Search URL', with: ''
  661. fill_in 'Preview', with: '*'
  662. within '.preview' do
  663. expect(page).to have_text('Search URL is missing.')
  664. end
  665. fill_in 'Search URL', with: "#{Setting.get('es_url')}/#{Setting.get('es_index')}_test_user/_search?q=\#{search.term}"
  666. fill_in 'Search result list key', with: ''
  667. within '.preview' do
  668. expect(page).to have_text('Search result list is not an array.')
  669. end
  670. end
  671. end
  672. context 'with existing field' do
  673. let(:search_url) { "#{Setting.get('es_url')}/#{Setting.get('es_index')}_test_user/_search?q=\#{search.term}" }
  674. let(:attribute) { create(:object_manager_attribute_autocompletion_ajax_external_data_source, :elastic_search, search_url: search_url) }
  675. before do
  676. attribute
  677. visit '/#system/object_manager'
  678. click "tr[data-id='#{attribute.id}']"
  679. end
  680. it_behaves_like 'showing preview table below data options'
  681. end
  682. end
  683. describe 'New created Boolean Ticket Attribute does not work is "No" is set as default #5075', authenticated_as: :agent, db_strategy: :reset do
  684. let(:agent) { create(:agent, groups: Group.all) }
  685. let(:attribute) do
  686. attribute = create(:object_manager_attribute_boolean, :required_screen, name: SecureRandom.uuid)
  687. ObjectManager::Attribute.migration_execute
  688. attribute
  689. end
  690. it 'does show the ticket without unsaved changes if a new boolean field is created' do
  691. # preload ticket to build up backend and frontend cache and close it afterwards
  692. visit "#ticket/zoom/#{Ticket.first.id}"
  693. ensure_websocket
  694. expect(page).to have_text(Ticket.first.title)
  695. # add attribute and reload app
  696. attribute
  697. refresh
  698. await_empty_ajax_queue
  699. expect(page).to have_no_css('.icon-loading')
  700. # revisit ticket and watch out for unsaved changes
  701. # we have trigger the form diff by entering something into the textarea
  702. # for more stability
  703. find('.js-textarea').send_keys('some note')
  704. expect(page).to have_text('Discard your unsaved changes.')
  705. expect(page).to have_no_css(".sidebar-content div[data-attribute-name='#{attribute.name}'].is-changed")
  706. end
  707. end
  708. end