Compare commits

...

170 Commits

Author SHA1 Message Date
Martin Edenhofer
e0c6eb0ac6 Fixed issue #206 - Wrong recipient on reply to Received call. 2016-12-14 16:06:39 +01:00
Martin Edenhofer
201b82036b Fixed issue #249 - New users won't get initial user group. 2016-12-14 16:06:25 +01:00
Martin Edenhofer
558b8a802d Improved join. 2016-12-14 03:28:07 +01:00
Timo Göllner
71785bee16 bugfix: added missing join table (#541) 2016-12-14 03:28:07 +01:00
Martin Edenhofer
cfffb6c161 Fixed issue #433 - Object manager allows method names as attributes. 2016-12-14 01:18:41 +01:00
Martin Edenhofer
83514db4d0 Fixed issue #418 - Max times for escalation 99 hours. 2016-12-14 00:33:26 +01:00
Martin Edenhofer
3bdeca5d88 Fixed issue #329 - Pending event time after 22:59 jumps back to 22:59. 2016-12-14 00:32:57 +01:00
Martin Edenhofer
775722b996 Fixed issue#502 - "Get latest Translations" fails(?) without error. 2016-12-06 12:13:56 +01:00
Martin Edenhofer
7140be848a Fixed issue#478 - Token without expiration timestamp expires "01/01/1970”. 2016-11-29 21:55:27 +01:00
Martin Edenhofer
5f7f8c5565 Fixed issue#468 - ”Password has been changed" email contains unresolved placeholders. 2016-11-28 15:55:32 +01:00
Martin Edenhofer
f3bba52a9c Fixed issue#466 - Search result is not shown if you search for "AAA", "BBB" and "AAA" again. 2016-11-25 14:21:37 +01:00
Martin Edenhofer
09850d2be9 Merge branch 'stable' of github.com:martini/zammad into stable 2016-11-25 14:04:17 +01:00
Martin Edenhofer
95534ab436 Fixed issue#402 - Missing quotation marks on sender address. 2016-11-25 13:52:19 +01:00
André Bauer
801ba9ea6a fix 415 - remove creation of Gemfile.local from pkgr.yml 2016-11-23 10:44:48 +01:00
Martin Edenhofer
23cb4fe791 Fixed issue#448 - New overview with article conditions not working. 2016-11-21 17:10:47 +01:00
Martin Edenhofer
1336cbd1b5 Fixed issue#432 - Unable to create job/scheduler if many time options are selected. 2016-11-21 08:56:48 +01:00
Martin Edenhofer
fbacb36f02 Fixed issue#437 - Customer should not have the internal attribute to set in customer interface. 2016-11-21 08:45:01 +01:00
Martin Edenhofer
e4f2b04f96 Added ES 5 support (filtered query has been deprecated and removed in ES 5.0). 2016-11-19 16:25:12 +01:00
Martin Edenhofer
e12e0642c6 Applied new coffeelint. 2016-11-19 16:24:49 +01:00
Martin Edenhofer
54f12649fe Fixed issue#441 - Unable to execute more then one trigger (e. g. adding 2 tags with 2 triggers) at one ticket at same update cycle. 2016-11-19 16:24:27 +01:00
Martin Edenhofer
436dac2d7d Added missing migration file for issue#427 - Improved ticket number generator settings (just show necessary settings based on generator selection). 2016-11-17 16:04:44 +01:00
Martin Edenhofer
49340bf3a8 Implemented issue#427 - Improved ticket number generator settings (just show necessary settings based on generator selection). 2016-11-17 16:03:43 +01:00
Martin Edenhofer
29ea3199ee Fixed issue#428 - Switching storage provider for attachments in UI has no effect. 2016-11-17 15:11:45 +01:00
Martin Edenhofer
58d06d904d Fixed ui postmaster element test. 2016-11-16 23:19:33 +01:00
Martin Edenhofer
57f470b0d6 Fixed issue#419 - Unable to set owner + customer in postmaster/email filter. 2016-11-16 15:37:01 +01:00
Martin Edenhofer
1e08ea947f Fixed issue#224 - Send all mails via sendmail - allow SMTP connection with no user/password (without auth). 2016-11-16 15:15:43 +01:00
Martin Edenhofer
1bb9fd43e3 Added missing sipgate config params. 2016-11-16 14:18:18 +01:00
Martin Edenhofer
67219097a8 Fixed issue#413 - Can't process email / Error creating ticket with changed Ticket Number Format. 2016-11-16 11:13:42 +01:00
Martin Edenhofer
8be9ce572c Merge branch 'private-issue408' into stable 2016-11-14 12:06:50 +01:00
Martin Edenhofer
03201dafab Updated changeling. 2016-11-14 08:59:10 +01:00
Martin Edenhofer
b13bb2fcaf Added change log. 2016-11-14 08:37:51 +01:00
Martin Edenhofer
d9ba369fcf Merge branch 'develop' 2016-11-14 08:36:58 +01:00
Martin Edenhofer
3adece0db5 Merge branch 'develop' 2016-11-14 00:03:57 +01:00
Martin Edenhofer
7101ffc277 Merge branch 'develop' 2016-11-12 16:44:43 +01:00
Martin Edenhofer
19a7a439b8 Merge branch 'develop' 2016-11-11 19:29:56 +01:00
Martin Edenhofer
e728320241 Merge branch 'develop' 2016-10-30 07:08:08 +01:00
Martin Edenhofer
1bc17afc53 Merge branch 'master' of git.znuny.com:zammad/zammad 2016-10-30 07:07:39 +01:00
Thorsten Eckel
ee5c58f53a Fixed bug: Zip archive doesn't contain sub folder content. 2016-10-19 08:25:51 +02:00
Martin Edenhofer
9bdf6d7b81 Merge branch 'develop' 2016-10-19 07:50:08 +02:00
Thorsten Eckel
5754fdccdd Working on CI tag/branch pushing, syncing, deployment issue. 2016-10-19 07:32:05 +02:00
Thorsten Eckel
4901050d57 Working on issue not syncing to github correctly. 2016-10-19 07:25:18 +02:00
Martin Edenhofer
7980c8cb93 Merge branch 'develop' 2016-10-19 07:21:10 +02:00
Martin Edenhofer
7e7088388d Merge branch 'develop' 2016-10-17 16:43:54 +02:00
Martin Edenhofer
2f6e52d63d Merge branch 'develop' 2016-09-28 16:08:28 +02:00
Martin Edenhofer
bde8babb43 Merge branch 'develop' 2016-09-28 08:53:02 +02:00
Martin Edenhofer
d6d68fb363 Merge branch 'develop' 2016-09-19 07:47:06 +02:00
Martin Edenhofer
814ffed0f0 Merge branch 'develop' 2016-09-15 22:11:57 +02:00
Thorsten Eckel
b013fb8cbc Merge branch 'private-otrs_import_df' 2016-09-13 12:06:56 +02:00
Martin Edenhofer
fbd8e63592 Merge branch 'develop' 2016-09-13 08:55:38 +02:00
Martin Edenhofer
e8ad3965d7 Removed build badged. 2016-09-10 16:47:33 +02:00
Martin Edenhofer
e00981ee23 Merge branch 'master' of git.znuny.com:zammad/zammad 2016-09-10 00:05:18 +02:00
Martin Edenhofer
4e74c6beda Merge branch 'develop' 2016-09-10 00:04:28 +02:00
Thorsten Eckel
2d09e556d7 Added deployment of archives. 2016-09-07 15:31:28 +02:00
Martin Edenhofer
8cd39398d6 Merge branch 'develop' 2016-09-06 16:07:33 +02:00
Martin Edenhofer
94c616ffe3 Merge branch 'develop' 2016-08-25 10:00:36 +02:00
Martin Edenhofer
57649bb888 Merge branch 'develop' 2016-08-23 09:49:40 +02:00
Martin Edenhofer
7e9442ec4a Merge branch 'develop' 2016-08-21 21:38:00 +02:00
Martin Edenhofer
521e6265fd Merge branch 'develop' 2016-08-19 13:58:26 +02:00
Martin Edenhofer
a42a9689fc Merge branch 'develop' 2016-08-19 13:48:46 +02:00
Martin Edenhofer
d9ffb8001d Merge branch 'develop' 2016-07-28 13:39:08 +02:00
Martin Edenhofer
ec86d26ae6 Merge branch 'develop' 2016-07-24 23:52:04 +02:00
Martin Edenhofer
e00efc821a Merge branch 'develop' 2016-07-18 19:35:51 +02:00
Martin Edenhofer
f04be8ca93 Merge branch 'develop' 2016-07-12 20:50:32 +02:00
Martin Edenhofer
c7ff6a323a Merge branch 'develop' 2016-07-08 11:28:43 +02:00
Martin Edenhofer
34833416bc Merge branch 'develop' 2016-06-30 09:50:09 +02:00
Martin Edenhofer
d7a6e28d81 Merge branch 'develop' 2016-06-29 00:18:44 +02:00
Martin Edenhofer
c13531d720 Merge branch 'develop' 2016-06-27 11:28:22 +02:00
Martin Edenhofer
4a40cced7d Merge branch 'develop' 2016-06-26 08:07:04 +02:00
Martin Edenhofer
450de10cc7 Merge branch 'develop' 2016-06-23 08:24:56 +02:00
Martin Edenhofer
5e587a455c Merge branch 'develop' 2016-06-22 08:54:24 +02:00
Martin Edenhofer
04aef9b258 Merge branch 'develop' 2016-06-20 15:52:43 +02:00
Martin Edenhofer
9b72557d13 Merge branch 'develop' 2016-06-15 06:58:36 +02:00
Martin Edenhofer
a5e0bc30c1 Merge branch 'develop' 2016-06-09 13:40:27 +02:00
Martin Edenhofer
458c166e9f Merge branch 'develop' 2016-06-08 16:43:29 +02:00
Martin Edenhofer
3373e5d141 Merge branch 'develop' 2016-06-08 10:48:12 +02:00
Martin Edenhofer
8f6b8de617 Merge branch 'develop' 2016-06-08 07:42:19 +02:00
Martin Edenhofer
22b21a82e7 Merge branch 'develop' 2016-06-07 07:02:25 +02:00
Martin Edenhofer
b4151b299f Merge branch 'develop' 2016-06-04 02:23:14 +02:00
Martin Edenhofer
e969fca8c4 Merge branch 'develop' 2016-06-03 07:08:59 +02:00
Martin Edenhofer
e59993a5d6 Merge branch 'develop' 2016-05-31 08:31:56 +02:00
Martin Edenhofer
3adc9d4cf1 Merge branch 'develop' 2016-05-30 13:21:46 +02:00
Martin Edenhofer
c1153250cb Merge branch 'develop' 2016-05-30 00:00:52 +02:00
Martin Edenhofer
7718dc28d3 Merge branch 'develop'
# Conflicts:
#	README.md
2016-05-27 17:30:22 +02:00
Martin Edenhofer
1894580ae6 Updated badge url. 2016-05-27 17:28:26 +02:00
Martin Edenhofer
11df714bbf Merge branch 'develop' 2016-05-20 15:18:36 +02:00
Martin Edenhofer
72adee37a8 Merge branch 'develop' 2016-05-20 06:10:19 +02:00
Martin Edenhofer
0e8f460d73 Merge branch 'develop' 2016-05-06 16:25:09 +02:00
Martin Edenhofer
4e62d5d0d2 Merge branch 'develop' 2016-05-06 09:40:16 +02:00
Martin Edenhofer
004e5ba457 Merge branch 'develop' 2016-05-04 12:13:34 +02:00
Martin Edenhofer
f1a8a8da6f Merge branch 'develop' 2016-05-03 16:01:03 +02:00
Martin Edenhofer
bfb44d1189 Merge branch 'develop' 2016-05-01 14:18:26 +02:00
Martin Edenhofer
d64afd8d97 Merge branch 'develop' 2016-04-14 17:47:03 +02:00
Martin Edenhofer
97cfdb9c4c Merge branch 'develop' 2016-04-12 08:25:06 +02:00
Martin Edenhofer
bb336f63f1 Merge branch 'develop' 2016-03-25 18:23:12 +01:00
Martin Edenhofer
80da08f447 Merge branch 'develop' 2016-03-23 09:31:02 +01:00
Martin Edenhofer
a6db2b7c90 Merge branch 'develop' 2016-03-17 17:45:04 +01:00
Martin Edenhofer
e1c3f83324 Merge branch 'develop' 2016-03-17 10:41:20 +01:00
Martin Edenhofer
8545a44ad1 Merge branch 'develop' 2016-03-16 11:17:22 +01:00
Martin Edenhofer
cfbe7510a2 Merge branch 'develop' 2016-03-16 09:10:05 +01:00
Martin Edenhofer
1479c32edb Merge branch 'develop' 2016-03-15 00:34:36 +01:00
Martin Edenhofer
a9d428bf5a Merge branch 'develop' 2016-03-11 10:38:15 +01:00
Martin Edenhofer
3a62eb11da Merge branch 'develop' 2016-03-04 07:07:55 +01:00
Martin Edenhofer
2df5f81029 Merge branch 'develop' 2016-03-03 03:42:27 +01:00
Martin Edenhofer
b88c23bc92 Merge branch 'develop' 2016-02-28 00:16:49 +01:00
Martin Edenhofer
4144fbaf5b Merge branch 'develop' 2016-02-25 23:46:10 +01:00
Martin Edenhofer
86ddc93f52 Merge branch 'develop' 2016-02-23 14:29:01 +01:00
Martin Edenhofer
8a1b583535 Merge branch 'develop' 2016-02-23 01:38:39 +01:00
Martin Edenhofer
7623628b86 Merge branch 'develop' 2016-02-12 08:53:57 +01:00
Martin Edenhofer
f753bc203f Merge branch 'develop' 2016-02-11 13:25:39 +01:00
Martin Edenhofer
ff9ba10705 Merge branch 'develop' 2016-02-11 02:11:06 +01:00
Martin Edenhofer
fdc25bdc26 Merge branch 'develop' 2016-02-09 01:36:03 +01:00
Martin Edenhofer
d154a587ad Merge branch 'develop' 2016-02-04 12:55:30 +01:00
Martin Edenhofer
a9cc4f037b Merge branch 'develop' 2016-02-04 10:41:41 +01:00
Martin Edenhofer
50d6679189 Merge branch 'develop' 2016-02-03 13:19:51 +01:00
Martin Edenhofer
b82cf4e1d6 Merge branch 'develop' 2016-01-29 15:19:23 +01:00
Martin Edenhofer
fa6eaa6710 Merge branch 'develop'
# Conflicts:
#	README.md
2016-01-26 19:59:34 +01:00
Martin Edenhofer
2ca17ac1bd Updated build status url. 2016-01-26 13:57:24 +01:00
Martin Edenhofer
862ecd7d36 Merge branch 'develop' 2016-01-22 14:47:53 +01:00
Martin Edenhofer
e38b72593b Merge branch 'develop' 2016-01-12 11:14:24 +01:00
Martin Edenhofer
81883cae8f Revert "update terminal start script"
This reverts commit 63770c9cbf.
2016-01-12 11:13:40 +01:00
Felix Niklas
63770c9cbf update terminal start script 2016-01-12 10:58:46 +01:00
Martin Edenhofer
aaf5da55a0 Merge branch 'develop' 2015-12-28 11:33:58 +01:00
Martin Edenhofer
8455d9ae17 Merge branch 'develop' 2015-12-21 16:03:31 +01:00
Martin Edenhofer
839a8e8eb4 Merge branch 'develop' 2015-12-13 15:26:35 +01:00
Martin Edenhofer
7aa3f6eaee Updated build url. 2015-12-10 09:44:16 +01:00
Martin Edenhofer
219abc69a3 Merge branch 'develop' 2015-12-10 09:21:59 +01:00
Martin Edenhofer
595822c928 Merge branch 'develop' 2015-12-02 02:27:40 +01:00
Martin Edenhofer
32112937b1 Merge branch 'develop' 2015-12-02 01:41:10 +01:00
Martin Edenhofer
a0889c5b99 Merge branch 'develop' 2015-12-01 22:03:52 +01:00
Martin Edenhofer
4bc281bfb0 Merge branch 'develop' 2015-12-01 20:04:51 +01:00
Martin Edenhofer
e12ec607d7 Merge branch 'develop' 2015-11-30 15:11:25 +01:00
Martin Edenhofer
545f393293 Merge branch 'develop' 2015-11-30 01:24:11 +01:00
Martin Edenhofer
4efa152e93 Merge branch 'master' of github.com:martini/zammad 2015-11-24 03:39:17 +01:00
Martin Edenhofer
0ebda4f32a Merge branch 'develop'
Conflicts:
	README.md
	script/websocket-server.rb
2015-11-24 03:38:55 +01:00
Martin Edenhofer
b94f54799f Merge branch 'develop' of http://ci.lab.zammad.com/core/zammad 2015-11-24 03:07:38 +01:00
Martin Edenhofer
99f0b4e2e2 Merge branch 'develop' 2015-11-19 14:27:39 +01:00
Martin Edenhofer
53cb906733 Merge branch 'develop' 2015-11-01 01:49:02 +01:00
Martin Edenhofer
2c31fe8a84 Merge branch 'develop' 2015-10-31 23:40:10 +01:00
Martin Edenhofer
6c5e673a2c Merge branch 'develop' 2015-10-02 15:31:54 +02:00
Martin Edenhofer
574b891a33 Merge branch 'develop' 2015-09-28 02:12:31 +02:00
Martin Edenhofer
9ef1d3f58b Merge branch 'develop'
Conflicts:
	extras/zammad.spec
2015-09-28 01:24:03 +02:00
Martin Edenhofer
8b3f432746 Merge branch 'develop' 2015-09-01 03:15:03 +02:00
Martin Edenhofer
14a9df261e Merge branch 'develop' 2015-08-31 15:11:27 +02:00
Martin Edenhofer
5620a2847b Merge branch 'develop' 2015-08-31 12:42:04 +02:00
Martin Edenhofer
13aaae3835 Merge branch 'develop' 2015-08-30 09:19:02 +02:00
Martin Edenhofer
2d4fcd3a4f Merge branch 'develop' 2015-08-30 08:22:27 +02:00
Martin Edenhofer
f4f70b0bc2 Merge branch 'develop' 2015-08-30 01:36:18 +02:00
Martin Edenhofer
1d279bb88f Merge branch 'develop' 2015-08-30 01:32:10 +02:00
Martin Edenhofer
fe4757cbf9 Merge branch 'develop' 2015-08-21 17:03:28 +02:00
me
1d476f163e Use also mysql on test. 2015-08-21 16:53:38 +02:00
Martin Edenhofer
513701b6d2 Merge branch 'develop'
Conflicts:
	README.md
	script/websocket-server.rb
2015-08-11 15:17:14 +02:00
Thorsten Eckel
1a1b661714 Fixed typo. 2015-07-16 09:50:18 +02:00
Roy Kaldung
6081a98279 Update README.md 2015-01-12 12:39:26 +01:00
Roy Kaldung
157991c33d Update README.md 2015-01-12 12:38:11 +01:00
Roy Kaldung
ef67be9170 We like to know if it's starting or stopping 2014-11-06 17:24:45 +01:00
Martin Edenhofer
39f3819303 Merge branch 'develop' 2014-11-06 16:13:39 +01:00
Martin Edenhofer
7a676bd563 Merge branch 'develop' into 'master', goto next level.
Conflicts:
	Gemfile
	README.md
	extras/zammad.spec
2014-10-05 20:42:39 +02:00
Roy Kaldung
80c54daa03 Delete httpd.spec 2014-05-02 12:47:51 +02:00
Roy Kaldung
99c88cde62 Delete apr.spec 2014-05-02 12:47:41 +02:00
Roy Kaldung
17d113ca20 removed old file 2014-05-02 12:47:18 +02:00
Martin Edenhofer
2fbf4324a7 Update Gemfile 2014-02-22 14:46:25 +01:00
Roy Kaldung
b46c390d30 minor change to test GitHub hook (automatic deploy) 2013-12-04 18:22:21 +01:00
Martin Edenhofer
3c3651bc52 Merge branch 'develop'
Conflicts:
	app/controllers/tickets_controller.rb
2013-09-20 13:52:53 +02:00
Roy Kaldung
abeeb5e66d Updated Build Status image URL 2013-08-23 21:00:55 +02:00
rkaldung
09d61ce4c8 added dependency 2013-08-14 20:38:09 +02:00
rkaldung
cc1c4495b2 Merge branch 'master' of github.com:martini/zammad 2013-08-14 20:27:15 +02:00
rkaldung
7b1ec4d17f modified install section 2013-08-14 20:26:25 +02:00
rkaldung
99b3201ccf added initial spec for rpm 2013-08-14 20:20:55 +02:00
Roy Kaldung
d4205d9ba6 removed Windows ;-) 2013-08-14 20:08:23 +02:00
Roy Kaldung
8d57633390 minor changes on comments 2013-08-05 19:18:52 +02:00
58 changed files with 1730 additions and 287 deletions

View File

@@ -27,7 +27,6 @@ before:
- uname -a
- ruby -v
- env
- echo "gem 'mysql2'" >> Gemfile.local
- "cat Gemfile.lock"
- contrib/cleanup.sh
env:

45
CHANGELOG.md Normal file
View File

@@ -0,0 +1,45 @@
# Change Log
## [1.1.0](https://github.com/zammad/zammad/tree/1.1.0) (2016-11-14)
[Full Changelog](https://github.com/zammad/zammad/compare/1.0.1...1.1.0)
**Implemented enhancements:**
- Missing translation/edit abilities \(Notification mails, contact form\) [\#388](https://github.com/zammad/zammad/issues/388) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
- Automatically join users to organizations based on the email address [\#356](https://github.com/zammad/zammad/issues/356) [[feature](https://github.com/zammad/zammad/labels/feature)]
- Create emails via fetchmail / procmail [\#326](https://github.com/zammad/zammad/issues/326) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
- Display origin url from tweet/facebook post [\#317](https://github.com/zammad/zammad/issues/317) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
- Vagrant version [\#253](https://github.com/zammad/zammad/issues/253) [[feature](https://github.com/zammad/zammad/labels/feature)]
- Page posts from facebook and tweets from twitter as tickets [\#251](https://github.com/zammad/zammad/issues/251) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
**Fixed bugs:**
- Some windows in the config area too wide in firefox [\#392](https://github.com/zammad/zammad/issues/392) [[bug](https://github.com/zammad/zammad/labels/bug)]
- \[Critical\] Short display of "Setup a new system" on public site while restarting Zammad! [\#389](https://github.com/zammad/zammad/issues/389) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Mailresponse to customer not working at all after editing a "Trigger" event text [\#385](https://github.com/zammad/zammad/issues/385) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Click on "Reports" leads to Error 500 [\#384](https://github.com/zammad/zammad/issues/384) [[bug](https://github.com/zammad/zammad/labels/bug)]
- TicketSelector in Overview or SLA not working with filter for ticket customer or owner [\#383](https://github.com/zammad/zammad/issues/383) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Changelog generator breaks packager.io builds [\#379](https://github.com/zammad/zammad/issues/379) [[bug](https://github.com/zammad/zammad/labels/bug)]
- script/scheduler.rb crashes after recreation of Zammad database [\#374](https://github.com/zammad/zammad/issues/374) [[bug](https://github.com/zammad/zammad/labels/bug)]
- ticket search cache issue [\#349](https://github.com/zammad/zammad/issues/349) [[bug](https://github.com/zammad/zammad/labels/bug)]
- IMAP mail fetching stops because of broken spam email \(invalid Content-Transfer-Encoding header\) [\#348](https://github.com/zammad/zammad/issues/348) [[bug](https://github.com/zammad/zammad/labels/bug)]
- IMAP mail fetching stops working on spam mail and invalid email format [\#345](https://github.com/zammad/zammad/issues/345) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Unable to create tickets [\#323](https://github.com/zammad/zammad/issues/323) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Admin -\> Channels -\> Email -\> Filters not shown / no name shown [\#313](https://github.com/zammad/zammad/issues/313) [[bug](https://github.com/zammad/zammad/labels/bug)]
- In object manager, blank line in object deleted all values for selections [\#312](https://github.com/zammad/zammad/issues/312) [[bug](https://github.com/zammad/zammad/labels/bug)]
- triggers do not work? [\#306](https://github.com/zammad/zammad/issues/306) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Answers from google mail shows complete quote [\#286](https://github.com/zammad/zammad/issues/286) [[minor bug](https://github.com/zammad/zammad/labels/minor%20bug)]
- Avatar in Chat not showing - wrong url [\#275](https://github.com/zammad/zammad/issues/275) [[bug](https://github.com/zammad/zammad/labels/bug)]
- chat not working / http 404 [\#274](https://github.com/zammad/zammad/issues/274) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Zammad Docker container for rpi/ARM [\#271](https://github.com/zammad/zammad/issues/271) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Zendesk: unknown article type fails import. [\#270](https://github.com/zammad/zammad/issues/270) [[bug](https://github.com/zammad/zammad/labels/bug)]
- No space at comment on ticket bulk action [\#268](https://github.com/zammad/zammad/issues/268) [[bug](https://github.com/zammad/zammad/labels/bug)]
- After adding a new attribute - zammad won't start [\#248](https://github.com/zammad/zammad/issues/248) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Images/Avatars blank [\#217](https://github.com/zammad/zammad/issues/217) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Docker/Ubuntu Not starting postgresql-9.6 service [\#213](https://github.com/zammad/zammad/issues/213) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Can´t choose Organization for Customer [\#211](https://github.com/zammad/zammad/issues/211) [[bug](https://github.com/zammad/zammad/labels/bug)]
- CentOS 7 HTTPS Error 404 - Not Found [\#210](https://github.com/zammad/zammad/issues/210) [[bug](https://github.com/zammad/zammad/labels/bug)]
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*

View File

@@ -435,7 +435,7 @@ class App.ControllerForm extends App.Controller
else
value = parseInt(value)
if param[item.name] isnt undefined
if typeof param[item.name] is 'string'
if typeof param[item.name] is 'string' || typeof param[item.name] is 'boolean' || typeof param[item.name] is 'number'
param[item.name] = [param[item.name], value]
else
param[item.name].push value

View File

@@ -6,6 +6,9 @@ class Index extends App.ControllerIntegrationBase
['This service shows you contacts of incoming calls and a caller list in realtime.']
['Also caller id of outbound calls can be changed.']
]
events:
'click .js-select': 'selectAll'
'change .js-switch input': 'switch'
render: =>
super

View File

@@ -35,10 +35,11 @@ class App.SettingsArea extends App.Controller
elements = []
for setting in settings
if setting.preferences.controller && App[setting.preferences.controller]
item = new App[setting.preferences.controller](setting: setting)
else
item = new App.SettingsAreaItem(setting: setting)
elements.push item.el
if setting.preferences.hidden isnt true
if setting.preferences.controller && App[setting.preferences.controller]
item = new App[setting.preferences.controller](setting: setting)
else
item = new App.SettingsAreaItem(setting: setting)
elements.push item.el
@html elements

View File

@@ -1,4 +1,5 @@
class App.SettingsAreaItem extends App.Controller
template: 'settings/item'
events:
'submit form': 'update'
@@ -21,8 +22,12 @@ class App.SettingsAreaItem extends App.Controller
# form
@configure_attributes = @setting.options['form']
for attribute in @configure_attributes
if attribute.tag is 'boolean'
attribute.translate = true
# item
@html App.view('settings/item')(
@html App.view(@template)(
setting: @setting
)

View File

@@ -0,0 +1,2 @@
class App.SettingsAreaStorageProvider extends App.SettingsAreaItem
template: 'settings/storage_provider'

View File

@@ -0,0 +1,2 @@
class App.SettingsAreaTicketHookPosition extends App.SettingsAreaItem
template: 'settings/ticket_hook_position'

View File

@@ -0,0 +1,132 @@
class App.SettingsAreaTicketNumber extends App.Controller
events:
'submit form': 'update'
constructor: ->
super
@render()
render: =>
# defaults
directValue = 0
for item in @setting.options['form']
directValue += 1
if directValue > 1
for item in @setting.options['form']
item['default'] = @setting.state_current.value[item.name]
else
item['default'] = @setting.state_current.value
@map =
'Ticket::Number::Increment': 'ticket_number_increment'
'Ticket::Number::Date': 'ticket_number_date'
# form
@configure_attributes = @setting.options['form']
# item
@html App.view('settings/ticket_number')(
setting: @setting
)
togglePreferences = (params, attribute, @attributes, classname, form) =>
return if attribute.name isnt 'ticket_number'
@showPreferences(params.ticket_number)
updatePreview = (params, attribute) =>
paramsParent = @formParam(@$('.js-form'))
number = "#{App.Config.get('ticket_hook')}???"
if paramsParent.ticket_number is 'Ticket::Number::Increment'
paramsItem = @paramsPreferences('Ticket::Number::Increment')
number = "#{App.Config.get('ticket_hook')}#{App.Config.get('system_id')}"
counter = '1'
if paramsItem.min_size
minSize = parseInt(paramsItem.min_size)
if paramsItem.checksum
minSize -= 1
if minSize > 1
for itemCounter in [2 .. minSize]
counter = "0#{counter}"
number += counter
if paramsItem.checksum
number += '9'
else if paramsParent.ticket_number is 'Ticket::Number::Date'
paramsItem = @paramsPreferences('Ticket::Number::Date')
current = new Date()
currentDay = current.getDate()
currentMonth = current.getMonth() + 1
currentYear = current.getFullYear()
number = "#{App.Config.get('ticket_hook')}#{currentYear}#{currentMonth}#{currentDay}#{App.Config.get('system_id')}001"
if paramsItem.checksum
number += '9'
@$('.js-preview').text(number)
new App.ControllerForm(
el: @el.find('.js-form'),
model: { configure_attributes: @configure_attributes, className: '' }
autofocus: false
handlers: [togglePreferences, updatePreview]
)
# preferences
preferences_settings = @setting.preferences.settings_included || ['ticket_number_increment', 'ticket_number_date']
for preferences_setting in preferences_settings
setting = App.Setting.findByAttribute('name', preferences_setting)
value = App.Setting.get(preferences_setting)
el = $(App.view("settings/#{preferences_setting}")(
setting: setting
))
new App.ControllerForm(
el: el.find('.js-formItem'),
model: { configure_attributes: setting.options['form'], className: '' }
autofocus: false
params: value
handlers: [updatePreview]
)
@$('.js-formPreferences').append(el)
# show current preferences
@showPreferences(item['default'])
showPreferences: (name) =>
@$('.js-formPreferencesItem').addClass('hidden')
@$(".js-formPreferencesItem[data-backend=\"#{name}\"]").removeClass('hidden')
paramsPreferences: (name) =>
@formParam(@$(".js-formPreferencesItem[data-backend=\"#{name}\"] form"))
update: (e) =>
e.preventDefault()
@formDisable(@$('.js-form'))
params = @formParam(@$('.js-form'))
if params.ticket_number
paramsItem = @paramsPreferences(params.ticket_number)
setting_name = @map[params.ticket_number]
if setting_name && paramsItem
App.Setting.set(setting_name, paramsItem)
@setting['state_current'] = {value: params.ticket_number}
ui = @
@setting.save(
done: =>
ui.formEnable(e)
App.Event.trigger 'notify', {
type: 'success'
msg: App.i18n.translateContent('Update successful!')
timeout: 2000
}
# rerender ui || get new collections and session data
App.Setting.preferencesPost(@setting)
fail: (settings, details) ->
ui.formEnable(e)
App.Event.trigger 'notify', {
type: 'error'
msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to update object!')
timeout: 2000
}
)

View File

@@ -16,8 +16,11 @@ class App.UiElement.postmaster_set
relation: 'TicketState'
},
{
value: 'x-zammad-ticket-customer'
value: 'x-zammad-ticket-customer_id'
name: 'Customer'
relation: 'User'
tag: 'user_autocompletion'
disableCreateUser: true,
},
{
@@ -26,10 +29,11 @@ class App.UiElement.postmaster_set
relation: 'Group'
},
{
value: 'x-zammad-ticket-owner'
value: 'x-zammad-ticket-owner_id'
name: 'Owner'
relation: 'User',
tag: 'user_autocompletion',
relation: 'User'
tag: 'user_autocompletion'
disableCreateUser: true,
},
{
value: 'x-zammad-ignore'

View File

@@ -102,12 +102,20 @@ class App.UiElement.user_permission
if !checked
if rolesWithGroupPlugin[role_id] is 'group'
item.find('.js-groupList').addClass('hidden')
# select groups if only one is available
if hideGroups
item.find('.js-groupList [name=group_ids]').prop('checked', false)
return
# if role with groups plugin is selected, show group selection
if rolesWithGroupPlugin[role_id] is 'group'
item.find('.js-groupList:not(.js-groupListHide)').removeClass('hidden')
# select groups if only one is available
if hideGroups
item.find('.js-groupList [name=group_ids]').prop('checked', true)
for trigger in triggers
trigger.trigger('change')
)

View File

@@ -123,6 +123,13 @@ class App.TicketZoomArticleActions extends App.Controller
icon: 'reply'
href: '#'
}
if article.sender.name is 'Agent' && article.type.name is 'phone'
actions.push {
name: 'reply'
type: 'emailReply'
icon: 'reply'
href: '#'
}
if article.type.name is 'twitter status'
actions.push {
name: 'reply'
@@ -284,6 +291,7 @@ class App.TicketZoomArticleActions extends App.Controller
# get reference article
article_id = $(e.target).parents('[data-id]').data('id')
article = App.TicketArticle.fullLocal(article_id)
ticket = App.Ticket.fullLocal(article.ticket_id)
type = App.TicketArticleType.find(article.type_id)
customer = App.User.find(article.created_by_id)
@@ -304,15 +312,31 @@ class App.TicketZoomArticleActions extends App.Controller
if type.name is 'email' || type.name is 'phone' || type.name is 'web'
if article.sender.name is 'Agent'
articleNew.to = article.to
else
articleNew.to = article.from
if type.name is 'phone'
# inbound call
if article.sender.name is 'Agent'
articleNew.to = article.to
# outbound call
else
articleNew.to = article.to
# if sender is customer but in article.from is no email, try to get
# customers email via customer user
if articleNew.to && !articleNew.to.match(/@/)
articleNew.to = article.created_by.email
articleNew.to = ticket.customer.email
else
if article.sender.name is 'Agent'
articleNew.to = article.to
else
articleNew.to = article.from
# if sender is customer but in article.from is no email, try to get
# customers email via customer user
if articleNew.to && !articleNew.to.match(/@/)
articleNew.to = article.created_by.email
# filter for uniq recipients
recipientAddresses = {}

View File

@@ -48,6 +48,7 @@ class App.TicketZoomArticleNew extends App.Controller
possibleArticleType['email'] = true
# gets referenced in @setArticleType
@internalSelector = true
@type = @defaults['type'] || 'note'
@articleTypes = []
if possibleArticleType.note
@@ -115,6 +116,9 @@ class App.TicketZoomArticleNew extends App.Controller
},
]
if @permissionCheck('ticket.customer')
@internalSelector = false
@textareaHeight =
open: 148
closed: 20
@@ -194,11 +198,12 @@ class App.TicketZoomArticleNew extends App.Controller
ticket = App.Ticket.fullLocal(@ticket_id)
@html App.view('ticket_zoom/article_new')(
ticket: ticket
articleTypes: @articleTypes
article: @defaults
form_id: @form_id
isCustomer: @permissionCheck('ticket.customer')
ticket: ticket
articleTypes: @articleTypes
article: @defaults
form_id: @form_id
isCustomer: @permissionCheck('ticket.customer')
internalSelector: @internalSelector
)
@setArticleType(@type)

View File

@@ -164,14 +164,16 @@ class Index extends App.ControllerSubContent
type: 'GET'
url: "#{@apiPath}/translations/sync/#{locale.locale}"
processData: false
complete: (data, status, xhr) =>
success: =>
loader.update(locale.name, false)
locale = locales.shift()
if _.isEmpty(locales)
hide()
return
@_syncChanges(locale, locales, loader, hide)
)
error: ->
hide()
)
updateOnServer: (params, event) =>

View File

@@ -9,7 +9,8 @@ class App.GlobalSearch
class _globalSearchSingleton extends Spine.Module
constructor: ->
@searchResultCache = {}
@searchResultCache = undefined
@searchResultCacheByKey = {}
@apiPath = App.Config.get('api_path')
execute: (params) ->
@@ -20,8 +21,8 @@ class _globalSearchSingleton extends Spine.Module
# use cache for search result
currentTime = new Date
if @searchResultCache[cacheKey] && @searchResultCache[cacheKey].time > currentTime.setSeconds(currentTime.getSeconds() - 20)
render(@searchResultCache[cacheKey].result)
if @searchResultCacheByKey[cacheKey] && @searchResultCacheByKey[cacheKey].time > currentTime.setSeconds(currentTime.getSeconds() - 20)
@renderTry(render, @searchResultCacheByKey[cacheKey].result, cacheKey)
return
App.Ajax.request(
@@ -31,7 +32,7 @@ class _globalSearchSingleton extends Spine.Module
data:
query: query
limit: limit
processData: true,
processData: true
success: (data, status, xhr) =>
App.Collection.loadAssets(data.assets)
result = {}
@@ -48,19 +49,21 @@ class _globalSearchSingleton extends Spine.Module
else
App.Log.error('_globalSearchSingleton', "No such model App.#{item.type}")
diff = false
if @searchResultCache[cacheKey]
diff = difference(@searchResultCache[cacheKey].resultRaw, data.result)
# cache search result
@searchResultCache[cacheKey] =
result: result
resultRaw: data.result
limit: limit
time: new Date
# if result hasn't changed, do not rerender
return if diff isnt false && _.isEmpty(diff)
render(result)
@renderTry(render, result, cacheKey)
)
renderTry: (render, result, cacheKey) =>
# if result hasn't changed, do not rerender
diff = false
if @searchResultCache
diff = difference(@searchResultCache, result)
return if diff isnt false && _.isEmpty(diff)
# cache search result
@searchResultCache = result
@searchResultCacheByKey[cacheKey] =
result: result
time: new Date
render(result)

View File

@@ -288,9 +288,11 @@ class _i18nSingleton extends Spine.Module
@_notTranslated[locale][key] = true
date: (time, offset) =>
return time if !time
@convert(time, offset, @mapTime['date'] || @dateFormat)
timestamp: (time, offset) =>
return time if !time
@convert(time, offset, @mapTime['timestamp'] || @timestampFormat)
convert: (time, offset, format) ->

View File

@@ -206,7 +206,7 @@ class App.Utils
# Replace all x tags with the type of replacementTag
html.find('textarea').each( ->
outer = @outerHTML;
outer = @outerHTML
# Replace opening tag
regex = new RegExp('<' + @tagName, 'i')

View File

@@ -485,13 +485,21 @@
},
highlightHour: function() {
var time = this.$element.val(),
timeArray = time.replace(/[^0-9\:]/g, '').split(':'),
hour = timeArray[0] ? timeArray[0].toString() : timeArray.toString(),
hour_max_length = hour.toString().length
this.highlightedUnit = 'hour';
this.setSelectionRange(0, 2);
this.setSelectionRange(0, hour_max_length);
},
highlightMinute: function() {
var time = this.$element.val(),
timeArray = time.replace(/[^0-9\:]/g, '').split(':'),
hour = timeArray[0] ? timeArray[0].toString() : timeArray.toString(),
hour_max_length = hour.toString().length
this.highlightedUnit = 'minute';
this.setSelectionRange(3, 5);
this.setSelectionRange(hour_max_length+1, hour_max_length+3);
},
highlightSecond: function() {
@@ -790,7 +798,8 @@
hour = timeArray[0] ? timeArray[0].toString() : timeArray.toString();
if(this.explicitMode && hour.length > 2 && (hour.length % 2) !== 0 ) {
var hour_max_length = this.maxHours.toString().length
if(this.explicitMode && hour.length > hour_max_length && (hour.length % 2) !== 0 ) {
this.clear();
return;
}
@@ -804,9 +813,9 @@
hour = hour.slice(0, -2);
}
if (hour.length > 2) {
minute = hour.slice(-2);
hour = hour.slice(0, -2);
if (hour.length > hour_max_length) {
minute = hour.slice(-hour_max_length);
hour = hour.slice(0, -hour_max_length);
}
if (minute.length > 2) {
@@ -838,9 +847,9 @@
minute = 59;
}
if (hour >= this.maxHours) {
if (hour > this.maxHours) {
// No day/date handling.
hour = this.maxHours - 1;
hour = this.maxHours;
}
if (this.showMeridian) {
@@ -859,7 +868,7 @@
} else if (hour < 12 && timeMode === 2) {
hour += 12;
} else {
if (hour >= this.maxHours) {
if (hour > this.maxHours) {
hour = this.maxHours - 1;
} else if ((hour < 0) || (hour === 12 && timeMode === 1)){
hour = 0;

View File

@@ -1,5 +1,27 @@
<form>
<h2>sipgate.io <%- @T('Settings') %></h2>
<p><%- @T('You need to configure the Zammad endpoints in the Sipgate web interface') %>:<p>
<div class="settings-entry">
<table class="settings-list" style="width: 100%;">
<thead>
<tr>
<th width="20%"><%- @T('Type') %>
<th width="80%"><%- @T('URL') %>
</thead>
<tbody>
<tr>
<td class="settings-list-row-control"><%- @T('Inbound') %>
<td class="settings-list-control-cell"><input type="url" class="form-control form-control--small js-select" readonly value="<%- @C('http_type') %>://<%- @C('fqdn') %>/api/v1/sipgate/in">
<tr>
<td class="settings-list-row-control"><%- @T('Outbound') %>
<td class="settings-list-control-cell"><input type="url" class="form-control form-control--small js-select" readonly value="<%- @C('http_type') %>://<%- @C('fqdn') %>/api/v1/sipgate/out">
</tbody>
</table>
</div>
<h2><%- @T('Inbound') %></h2>
<p><%- @T('Blocked caller ids based on sender caller id.') %>

View File

@@ -1,6 +1,6 @@
<form class="settings-entry" id="<%= @setting.name %>">
<h2><%- @T(@setting.title) %></h2>
<p class="help-text"><%- @RichText(@setting.description) %></p>
<p class="help-text"><%- @T(@setting.description) %></p>
<div class="horizontal end">
<div class="form-item flex"></div>
<button type="submit" class="btn btn--primary"><%- @T('Submit') %></button>

View File

@@ -0,0 +1,22 @@
<form class="settings-entry" id="<%= @setting.name %>">
<h2><%- @T(@setting.title) %></h2>
<p class="help-text"><%- @T('You can switch between the backend for new attachments even on a system that is already in production without any loss of data.') %></p>
<p class="help-text"><%- @T('If you want to move already stored attachments from one backend to another, you need to execute the following via console.') %></p>
</p>
<p class="help-text"><%- @T('Move all from "%s" to "%s"', 'filesystem', 'database') %>:</p>
</p>
<code>
rails&gt; Store::File.move('File', 'DB')
</code>
<p class="help-text"><%- @T('Move all from "%s" to "%s"', 'database', 'filesystem') %>:</p>
</p>
<code>
rails&gt; Store::File.move('DB', 'File')
</code>
<br>
<br>
<div class="horizontal end">
<div class="form-item flex"></div>
<button type="submit" class="btn btn--primary"><%- @T('Submit') %></button>
</div>
</form>

View File

@@ -0,0 +1,14 @@
<form class="settings-entry" id="<%= @setting.name %>">
<h2><%- @T(@setting.title) %></h2>
<p class="help-text">
<%- @T('The format of the subject.') %>
<ul>
<li><%- @T('|Right| means |Some Subject [Ticket#12345]|') %>
<li><%- @T('|Left| means |[Ticket#12345] Some Subject|') %>
<li><%- @T('|None| means |Some Subject| (without ticket number). In the last case you should enable "postmaster___follow___up___search___in" to recognize followups based on email headers and/or body.') %>
</ul>
<div class="horizontal end">
<div class="js-form form-item flex"></div>
<button type="submit" class="btn btn--primary"><%- @T('Submit') %></button>
</div>
</form>

View File

@@ -0,0 +1,19 @@
<div class="settings-entry" id="<%= @setting.name %>">
<h2><%- @T(@setting.title) %></h2>
<p class="help-text">
<%- @T('Selects the ticket number generator module.') %>
<ul>
<li><%- @T('|Increment| increments the ticket number, the SystemID and the counter are used with "SystemID.Counter" format (e.g. 1010138, 1010139).') %>
<li><%- @T('With |Date| the ticket numbers will be generated by the current date, the SystemID and the counter. The format looks like "Year.Month.Day.SystemID.Counter" (e.g. 201206231010138, 201206231010139).') %>
</ul>
<form class="js-form"></form>
<br>
<div class="js-formPreferences"></div>
<br>
<label class="formGroup-label"><%- @T('Preview') %></label>
<div class="js-preview"></div>
<br>
<form class="horizontal end">
<button type="submit" class="btn btn--primary"><%- @T('Submit') %></button>
</form>
</div>

View File

@@ -0,0 +1,6 @@
<div class="js-date js-formPreferencesItem hidden" data-backend="Ticket::Number::Date">
<label class="formGroup-label"><%- @T('Options') %></label>
<div><%- @T('With Checksum the counter will be appended with a checksum at the end. The format looks like "%s" (e. g. %s).', 'Year.Month.Day.SystemID.Counter.CheckSum', '2012070110101520, 2012070110101535') %></div>
<br>
<form class="js-formItem"></form>
</div>

View File

@@ -0,0 +1,6 @@
<div class="js-increment js-formPreferencesItem hidden" data-backend="Ticket::Number::Increment">
<label class="formGroup-label"><%- @T('Options') %></label>
<div><%- @T('With Checksum the counter will be appended with a checksum at the end. The format looks like "%s" (e. g. %s).', 'SystemID.Counter.CheckSum', '10101384, 10101392') %></div>
<br>
<form class="js-formItem"></form>
</div>

View File

@@ -21,6 +21,7 @@
<% end %>
</div>
</div>
<% if @internalSelector: %>
<div class="editControls-item is-hidden js-toggleVisibility js-selectInternalPublic">
<div class="editControls-iconHolder">
<div class="editControls-icon icon-internal" title="<%- @Ti("set to public") %>">
@@ -31,6 +32,7 @@
</div>
</div>
</div>
<% end %>
</div>
<div class="article-content bubble-gap">
<div class="internal-border">

View File

@@ -1594,7 +1594,6 @@ input[type=time] {
input.timeframe,
input.time {
width: 4.5ch;
box-sizing: content-box;
padding: 0 6px;
line-height: 42px;
@@ -1605,6 +1604,13 @@ input.time {
}
}
input.timeframe {
width: 5.5ch;
}
input.time {
width: 4.5ch;
}
input.time.time--12 {
width: 7.5ch;
}

View File

@@ -49,16 +49,21 @@ class Channel::Driver::Smtp
options[:openssl_verify_mode] = 'none'
end
mail = Channel::EmailBuild.build(attr, notification)
mail.delivery_method :smtp, {
smtp_params = {
openssl_verify_mode: options[:openssl_verify_mode],
address: options[:host],
port: options[:port],
domain: options[:domain],
user_name: options[:user],
password: options[:password],
enable_starttls_auto: options[:enable_starttls_auto],
authentication: options[:authentication],
}
# add authentication only if needed
if options[:user] && !options[:user].empty?
smtp_params[:user_name] = options[:user]
smtp_params[:password] = options[:password]
smtp_params[:authentication] = options[:authentication]
end
mail.delivery_method :smtp, smtp_params
mail.deliver
end
end

View File

@@ -126,6 +126,21 @@ module Channel::EmailBuild
mail
end
=begin
quoted_in_one_line = Channel::EmailBuild.recipient_line('Somebody @ "Company"', 'some.body@example.com')
returnes
'"Somebody @ \"Company\"" <some.body@example.com>'
=end
def self.recipient_line(realname, email)
return "#{realname} <#{email}>" if realname =~ /^[A-z]+$/i
"\"#{realname.gsub('"', '\"')}\" <#{email}>"
end
=begin
Check if string is a complete html document. If not, add head and css styles.

View File

@@ -663,6 +663,14 @@ to send no browser reload event, pass false
raise 'At least one letters is needed'
elsif name =~ /^(destroy|true|false|integer|select|drop|create|alter|index|table|varchar|blob|date|datetime|timestamp)$/
raise "#{name} is a reserved word, please choose a different one"
# do not allow model method names as attributes
else
model = Kernel.const_get(object_lookup.name)
record = model.new
if record.respond_to?(name.to_sym) && !record.attributes.key?(name)
raise "#{name} is a reserved word, please choose a different one"
end
end
true
end

View File

@@ -46,13 +46,13 @@ class Observer::Ticket::Article::FillupFromEmail < ActiveRecord::Observer
if !email_address
raise "No email address found for group '#{ticket.group.name}'"
end
system_sender = "#{email_address.realname} <#{email_address.email}>"
if record.created_by_id != 1 && Setting.get('ticket_define_email_from') == 'AgentNameSystemAddressName'
seperator = Setting.get('ticket_define_email_from_seperator')
sender = User.find(record.created_by_id)
record.from = "#{sender.firstname} #{sender.lastname} #{seperator} #{system_sender}"
seperator = Setting.get('ticket_define_email_from_seperator')
sender = User.find(record.created_by_id)
realname = "#{sender.firstname} #{sender.lastname} #{seperator} #{email_address.realname}"
record.from = Channel::EmailBuild.recipient_line(realname, email_address.email)
else
record.from = system_sender
record.from = Channel::EmailBuild.recipient_line(email_address.realname, email_address.email)
end
end
end

View File

@@ -29,6 +29,7 @@ class Observer::Transaction < ActiveRecord::Observer
sync_backends = []
Setting.where(area: 'Transaction::Backend::Sync').order(:name).each { |setting|
backend = Setting.get(setting.name)
next if params[:disable] && params[:disable].include?(backend)
sync_backends.push Kernel.const_get(backend)
}

View File

@@ -844,6 +844,22 @@ result
references
end
=begin
get all articles of a ticket in correct order (overwrite active record default method)
artilces = ticket.articles
result
[article1, articl2]
=end
def articles
Ticket::Article.where(ticket_id: id).order(:created_at, :id)
end
private
def check_generate

View File

@@ -20,7 +20,7 @@ returns
# generate number
49_999.times {
number = adapter.generate
ticket = Ticket.find_by( number: number )
ticket = Ticket.find_by(number: number)
return number if !ticket
}
raise "Can't generate new ticket number!"

View File

@@ -43,6 +43,14 @@ module Ticket::Number::Date
# vehikel number. The modulus to 10 of this sum is substracted from
# 10. See: http://www.pruefziffernberechnung.de/F/Fahrzeugnummer.shtml
# (german)
# fix for https://github.com/zammad/zammad/issues/413 - can be removed later
if config.class == FalseClass || config.class == TrueClass
config = {
checksum: config
}
end
if config[:checksum]
chksum = 0
mult = 1

View File

@@ -90,13 +90,36 @@ returns
# get only tickets with permissions
access_condition = Ticket.access_condition(user)
ticket_attributes = Ticket.new.attributes
list = []
overviews.each { |overview|
query_condition, bind_condition, tables = Ticket.selector2sql(overview.condition, user)
order_by = "#{overview.order[:by]} #{overview.order[:direction]}"
# validate direction
raise "Invalid order direction '#{overview.order[:direction]}'" if overview.order[:direction] && overview.order[:direction] !~ /^(ASC|DESC)$/i
# check if order by exists
order_by = overview.order[:by]
if !ticket_attributes.key?(order_by)
order_by = if ticket_attributes.key?("#{order_by}_id")
"#{order_by}_id"
else
'created_at'
end
end
order_by = "tickets.#{order_by} #{overview.order[:direction]}"
# check if group by exists
if overview.group_by && !overview.group_by.empty?
order_by = "#{overview.group_by}_id, #{order_by}"
group_by = overview.group_by
if !ticket_attributes.key?(group_by)
group_by = if ticket_attributes.key?("#{group_by}_id")
"#{group_by}_id"
end
end
if group_by
order_by = "tickets.#{group_by}, #{order_by}"
end
end
ticket_result = Ticket.select('id, updated_at')
@@ -115,9 +138,10 @@ returns
}
tickets.push ticket_item
}
count = Ticket.where(access_condition).where(query_condition, *bind_condition).count()
count = Ticket.where(access_condition).where(query_condition, *bind_condition).joins(tables).count()
item = {
overview: {
name: overview.name,
id: overview.id,
view: overview.link,
updated_at: overview.updated_at,

View File

@@ -162,8 +162,9 @@ returns
.order('tickets.created_at DESC')
.limit(limit)
else
query_condition, bind_condition = selector2sql(condition)
query_condition, bind_condition, tables = selector2sql(condition)
tickets_all = Ticket.select('DISTINCT(tickets.id), tickets.created_at')
.joins(tables)
.where(access_condition)
.where(query_condition, *bind_condition)
.order('tickets.created_at DESC')

View File

@@ -13,7 +13,10 @@ class Transaction
if options[:interface_handle]
ApplicationHandleInfo.current = original_interface_handle
end
Observer::Transaction.commit(disable_notification: options[:disable_notification])
Observer::Transaction.commit(
disable_notification: options[:disable_notification],
disable: options[:disable],
)
PushMessages.finish
end
end

View File

@@ -24,7 +24,9 @@ class Transaction::BackgroundJob
def perform
Setting.where(area: 'Transaction::Backend::Async').order(:name).each { |setting|
backend = Kernel.const_get(Setting.get(setting.name))
backend = Setting.get(setting.name)
next if @params[:disable] && @params[:disable].include?(backend)
backend = Kernel.const_get(backend)
Observer::Transaction.execute_singel_backend(backend, @item, @params)
}
end

View File

@@ -29,7 +29,7 @@ class Transaction::Trigger
return if @item[:object] != 'Ticket'
triggers = Trigger.where(active: true)
triggers = Trigger.where(active: true).order('LOWER(name)')
return if triggers.empty?
ticket = Ticket.lookup(id: @item[:object_id])
@@ -39,86 +39,94 @@ class Transaction::Trigger
end
original_user_id = UserInfo.current_user_id
UserInfo.current_user_id = 1
triggers.each { |trigger|
condition = trigger.condition
Transaction.execute(reset_user_id: true, disable: ['Transaction::Trigger']) do
triggers.each { |trigger|
condition = trigger.condition
# check action
if condition['ticket.action']
next if condition['ticket.action']['operator'] == 'is' && condition['ticket.action']['value'] != @item[:type]
next if condition['ticket.action']['operator'] != 'is' && condition['ticket.action']['value'] == @item[:type]
condition.delete('ticket.action')
end
# check "has changed" options
has_changed_condition_exists = false
has_changed = false
condition.each do |key, value|
next if !value
next if !value['operator']
next if !value['operator']['has changed']
has_changed_condition_exists = true
# next if has changed? && !@item[:changes][attribute]
(object_name, attribute) = key.split('.', 2)
# remove condition item, because it has changed
if @item[:changes][attribute]
has_changed = true
condition.delete(key)
next
# check action
if condition['ticket.action']
next if condition['ticket.action']['operator'] == 'is' && condition['ticket.action']['value'] != @item[:type]
next if condition['ticket.action']['operator'] != 'is' && condition['ticket.action']['value'] == @item[:type]
condition.delete('ticket.action')
end
break
end
next if has_changed_condition_exists && !has_changed
# check action
if condition['article.action']
next if !article
condition.delete('article.action')
end
# check if selector is matching
condition['ticket.id'] = {
operator: 'is',
value: ticket.id,
}
if article
condition['article.id'] = {
operator: 'is',
value: article.id,
}
end
# check "has changed" options
has_changed_condition_exists = false
has_changed = false
condition.each do |key, value|
next if !value
next if !value['operator']
next if !value['operator']['has changed']
has_changed_condition_exists = true
ticket_count, tickets = Ticket.selectors(condition, 1)
next if ticket_count.zero?
next if tickets.first.id != ticket.id
# next if has changed? && !@item[:changes][attribute]
(object_name, attribute) = key.split('.', 2)
# check if min one article attribute is used
article_selector = false
trigger.condition.each do |key, _value|
(object_name, attribute) = key.split('.', 2)
next if object_name != 'article'
next if attribute == 'id'
article_selector = true
end
# check in min one attribute has changed
if @item[:type] == 'update' && !article_selector
match = false
if has_changed_condition_exists && has_changed
match = true
else
trigger.condition.each do |key, _value|
(object_name, attribute) = key.split('.', 2)
next if object_name != 'ticket'
next if !@item[:changes][attribute]
match = true
break
# remove condition item, because it has changed
if @item[:changes][attribute]
has_changed = true
condition.delete(key)
next
end
break
end
next if !match
end
Transaction.execute do
next if has_changed_condition_exists && !has_changed
# check if selector is matching
condition['ticket.id'] = {
operator: 'is',
value: ticket.id,
}
# check if min one article attribute is used
article_selector = false
trigger.condition.each do |key, _value|
(object_name, attribute) = key.split('.', 2)
next if object_name != 'article'
next if attribute == 'id'
article_selector = true
end
next if article_selector && !article
if article_selector
condition['article.id'] = {
operator: 'is',
value: article.id,
}
end
ticket_count, tickets = Ticket.selectors(condition, 1)
next if ticket_count.zero?
next if tickets.first.id != ticket.id
# check in min one attribute has changed
if @item[:type] == 'update' && !article_selector
match = false
if has_changed_condition_exists && has_changed
match = true
else
trigger.condition.each do |key, _value|
(object_name, attribute) = key.split('.', 2)
next if object_name != 'ticket'
next if !@item[:changes][attribute]
match = true
break
end
end
next if !match
end
ticket.perform_changes(trigger.perform, 'trigger', @item)
end
}
}
end
UserInfo.current_user_id = original_user_id
end

View File

@@ -1,9 +1,9 @@
Your #{product_name} password has been changed
Your #{config.product_name} password has been changed
<p>Hi #{user.firstname},</p>
<br>
<p>The password for your #{product_name} account <b>#{user.login}</b> has been changed recently.</p>
<p>The password for your #{config.product_name} account <b>#{user.login}</b> has been changed recently.</p>
<br>
<p>This activity is not known to you? If not, contact your system administrator.</p>
<br>
<p>Your #{product_name} Team</p>
<p>Your #{config.product_name} Team</p>

View File

@@ -577,7 +577,7 @@ class CreateBase < ActiveRecord::Migration
t.string :key, limit: 250, null: true
t.integer :related_o_id, null: true
t.integer :related_stats_store_object_id, null: true
t.string :data, limit: 2500, null: true
t.string :data, limit: 5000, null: true
t.integer :created_by_id, null: false
t.timestamps limit: 3, null: false
end

View File

@@ -186,18 +186,18 @@ class CreateTicket < ActiveRecord::Migration
add_index :ticket_counters, [:generator], unique: true
create_table :overviews do |t|
t.references :role, null: false
t.column :name, :string, limit: 250, null: false
t.column :link, :string, limit: 250, null: false
t.column :prio, :integer, null: false
t.column :condition, :string, limit: 2500, null: false
t.column :order, :string, limit: 2500, null: false
t.column :group_by, :string, limit: 250, null: true
t.column :organization_shared, :boolean, null: false, default: false
t.column :view, :string, limit: 1000, null: false
t.column :active, :boolean, null: false, default: true
t.column :updated_by_id, :integer, null: false
t.column :created_by_id, :integer, null: false
t.references :role, null: false
t.column :name, :string, limit: 250, null: false
t.column :link, :string, limit: 250, null: false
t.column :prio, :integer, null: false
t.column :condition, :text, limit: 500.kilobytes + 1, null: false
t.column :order, :string, limit: 2500, null: false
t.column :group_by, :string, limit: 250, null: true
t.column :organization_shared, :boolean, null: false, default: false
t.column :view, :string, limit: 1000, null: false
t.column :active, :boolean, null: false, default: true
t.column :updated_by_id, :integer, null: false
t.column :created_by_id, :integer, null: false
t.timestamps limit: 3, null: false
end
add_index :overviews, [:name]
@@ -217,34 +217,34 @@ class CreateTicket < ActiveRecord::Migration
add_index :overviews_groups, [:group_id]
create_table :triggers do |t|
t.column :name, :string, limit: 250, null: false
t.column :condition, :string, limit: 2500, null: false
t.column :perform, :string, limit: 2500, null: false
t.column :disable_notification, :boolean, null: false, default: true
t.column :note, :string, limit: 250, null: true
t.column :active, :boolean, null: false, default: true
t.column :updated_by_id, :integer, null: false
t.column :created_by_id, :integer, null: false
t.column :name, :string, limit: 250, null: false
t.column :condition, :text, limit: 500.kilobytes + 1, null: false
t.column :perform, :text, limit: 500.kilobytes + 1, null: false
t.column :disable_notification, :boolean, null: false, default: true
t.column :note, :string, limit: 250, null: true
t.column :active, :boolean, null: false, default: true
t.column :updated_by_id, :integer, null: false
t.column :created_by_id, :integer, null: false
t.timestamps limit: 3, null: false
end
add_index :triggers, [:name], unique: true
create_table :jobs do |t|
t.column :name, :string, limit: 250, null: false
t.column :timeplan, :string, limit: 1000, null: false
t.column :condition, :string, limit: 2500, null: false
t.column :perform, :string, limit: 2500, null: false
t.column :disable_notification, :boolean, null: false, default: true
t.column :last_run_at, :timestamp, limit: 3, null: true
t.column :next_run_at, :timestamp, limit: 3, null: true
t.column :running, :boolean, null: false, default: false
t.column :processed, :integer, null: false, default: 0
t.column :matching, :integer, null: false
t.column :pid, :string, limit: 250, null: true
t.column :note, :string, limit: 250, null: true
t.column :active, :boolean, null: false, default: false
t.column :updated_by_id, :integer, null: false
t.column :created_by_id, :integer, null: false
t.column :name, :string, limit: 250, null: false
t.column :timeplan, :string, limit: 2500, null: false
t.column :condition, :text, limit: 500.kilobytes + 1, null: false
t.column :perform, :text, limit: 500.kilobytes + 1, null: false
t.column :disable_notification, :boolean, null: false, default: true
t.column :last_run_at, :timestamp, limit: 3, null: true
t.column :next_run_at, :timestamp, limit: 3, null: true
t.column :running, :boolean, null: false, default: false
t.column :processed, :integer, null: false, default: 0
t.column :matching, :integer, null: false
t.column :pid, :string, limit: 250, null: true
t.column :note, :string, limit: 250, null: true
t.column :active, :boolean, null: false, default: false
t.column :updated_by_id, :integer, null: false
t.column :created_by_id, :integer, null: false
t.timestamps limit: 3, null: false
end
add_index :jobs, [:name], unique: true
@@ -285,14 +285,14 @@ class CreateTicket < ActiveRecord::Migration
add_index :links, [:link_object_source_id, :link_object_source_value, :link_object_target_id, :link_object_target_value, :link_type_id], unique: true, name: 'links_uniq_total'
create_table :postmaster_filters do |t|
t.column :name, :string, limit: 250, null: false
t.column :channel, :string, limit: 250, null: false
t.column :match, :string, limit: 5000, null: false
t.column :perform, :string, limit: 5000, null: false
t.column :active, :boolean, null: false, default: true
t.column :note, :string, limit: 250, null: true
t.column :updated_by_id, :integer, null: false
t.column :created_by_id, :integer, null: false
t.column :name, :string, limit: 250, null: false
t.column :channel, :string, limit: 250, null: false
t.column :match, :text, limit: 500.kilobytes + 1, null: false
t.column :perform, :text, limit: 500.kilobytes + 1, null: false
t.column :active, :boolean, null: false, default: true
t.column :note, :string, limit: 250, null: true
t.column :updated_by_id, :integer, null: false
t.column :created_by_id, :integer, null: false
t.timestamps limit: 3, null: false
end
add_index :postmaster_filters, [:channel]
@@ -359,7 +359,7 @@ class CreateTicket < ActiveRecord::Migration
t.column :first_response_time, :integer, null: true
t.column :update_time, :integer, null: true
t.column :solution_time, :integer, null: true
t.column :condition, :string, limit: 5000, null: true
t.column :condition, :text, limit: 500.kilobytes + 1, null: true
t.column :updated_by_id, :integer, null: false
t.column :created_by_id, :integer, null: false
t.timestamps limit: 3, null: false
@@ -367,12 +367,12 @@ class CreateTicket < ActiveRecord::Migration
add_index :slas, [:name], unique: true
create_table :macros do |t|
t.string :name, limit: 250, null: true
t.string :perform, limit: 5000, null: false
t.boolean :active, null: false, default: true
t.string :note, limit: 250, null: true
t.integer :updated_by_id, null: false
t.integer :created_by_id, null: false
t.string :name, limit: 250, null: true
t.text :perform, limit: 500.kilobytes + 1, null: false
t.boolean :active, null: false, default: true
t.string :note, limit: 250, null: true
t.integer :updated_by_id, null: false
t.integer :created_by_id, null: false
t.timestamps limit: 3, null: false
end
add_index :macros, [:name], unique: true
@@ -436,11 +436,11 @@ class CreateTicket < ActiveRecord::Migration
add_index :chat_agents, [:created_by_id], unique: true
create_table :report_profiles do |t|
t.column :name, :string, limit: 150, null: true
t.column :condition, :string, limit: 6000, null: true
t.column :active, :boolean, null: false, default: true
t.column :updated_by_id, :integer, null: false
t.column :created_by_id, :integer, null: false
t.column :name, :string, limit: 150, null: true
t.column :condition, :text, limit: 500.kilobytes + 1, null: true
t.column :active, :boolean, null: false, default: true
t.column :updated_by_id, :integer, null: false
t.column :created_by_id, :integer, null: false
t.timestamps limit: 3, null: false
end
add_index :report_profiles, [:name], unique: true

View File

@@ -0,0 +1,44 @@
class JobUnableToCreateIssue432 < ActiveRecord::Migration
def up
# return if it's a new setup
return if !Setting.find_by(name: 'system_init_done')
ActiveRecord::Migration.change_table :jobs do |t|
t.change :timeplan, :string, limit: 2500
t.change :condition, :text, limit: 500.kilobytes + 1
t.change :perform, :text, limit: 500.kilobytes + 1
end
ActiveRecord::Migration.change_table :triggers do |t|
t.change :condition, :text, limit: 500.kilobytes + 1
t.change :perform, :text, limit: 500.kilobytes + 1
end
ActiveRecord::Migration.change_table :overviews do |t|
t.change :condition, :text, limit: 500.kilobytes + 1
end
ActiveRecord::Migration.change_table :report_profiles do |t|
t.change :condition, :text, limit: 500.kilobytes + 1
end
ActiveRecord::Migration.change_table :slas do |t|
t.change :condition, :text, limit: 500.kilobytes + 1
end
ActiveRecord::Migration.change_table :macros do |t|
t.change :perform, :text, limit: 500.kilobytes + 1
end
ActiveRecord::Migration.change_table :postmaster_filters do |t|
t.change :match, :text, limit: 500.kilobytes + 1
t.change :perform, :text, limit: 500.kilobytes + 1
end
ActiveRecord::Migration.change_table :stats_stores do |t|
t.change :data, :string, limit: 5000
end
Cache.clear
end
end

View File

@@ -0,0 +1,55 @@
class TicketNumberGeneratorIssue427 < ActiveRecord::Migration
def up
# return if it's a new setup
return if !Setting.find_by(name: 'system_init_done')
setting = Setting.find_by(name: 'ticket_number')
setting.preferences = {
settings_included: %w(ticket_number_increment ticket_number_date),
controller: 'SettingsAreaTicketNumber',
permission: ['admin.ticket'],
}
setting.save!
setting = Setting.find_by(name: 'ticket_number_increment')
setting.preferences = {
permission: ['admin.ticket'],
hidden: true,
}
setting.save!
setting = Setting.find_by(name: 'ticket_number_date')
setting.preferences = {
permission: ['admin.ticket'],
hidden: true,
}
# just to make sure that value is saved correctly - https://github.com/zammad/zammad/issues/413
if setting.state_current['value'] == true || setting.state_current['value'] == false
setting.state_current['value'] = { 'checksum' => setting.state_current['value'] }
end
setting.save!
setting = Setting.find_by(name: 'ticket_hook_position')
setting.preferences = {
controller: 'SettingsAreaTicketHookPosition',
permission: ['admin.ticket'],
}
setting.options = {
form: [
{
display: '',
null: true,
name: 'ticket_hook_position',
tag: 'select',
translate: true,
options: {
'left' => 'left',
'right' => 'right',
'none' => 'none',
},
},
],
}
setting.save!
end
end

View File

@@ -0,0 +1,30 @@
class StoreConfigNameUpdateIssue428 < ActiveRecord::Migration
def up
# return if it's a new setup
return if !Setting.find_by(name: 'system_init_done')
setting = Setting.find_by(name: 'storage')
return if !Setting
setting.name = 'storage_provider'
setting.options = {
form: [
{
display: '',
null: true,
name: 'storage_provider',
tag: 'select',
tranlate: true,
options: {
'DB' => 'Database',
'File' => 'Filesystem',
},
},
],
}
setting.preferences = {
controller: 'SettingsAreaStorageProvider',
online_service_disable: true,
permission: ['admin.system'],
}
setting.save!
end
end

View File

@@ -259,7 +259,7 @@ Setting.create_if_not_exists(
Setting.create_if_not_exists(
title: 'Storage Mechanism',
name: 'storage',
name: 'storage_provider',
area: 'System::Storage',
description: '"Database" stores all attachments in the database (not recommended for storing large amounts of data). "Filesystem" stores the data on the filesystem. You can switch between the modules even on a system that is already in production without any loss of data.',
options: {
@@ -267,17 +267,19 @@ Setting.create_if_not_exists(
{
display: '',
null: true,
name: 'storage',
name: 'storage_provider',
tag: 'select',
tranlate: true,
options: {
'DB' => 'Database',
'FS' => 'Filesystem',
'File' => 'Filesystem',
},
},
],
},
state: 'DB',
preferences: {
controller: 'SettingsAreaStorageProvider',
online_service_disable: true,
permission: ['admin.system'],
},
@@ -1137,10 +1139,10 @@ Setting.create_if_not_exists(
title: 'Ticket Hook Position',
name: 'ticket_hook_position',
area: 'Ticket::Base',
description: "@T('The format of the subject.')
* @T('**Right** means **Some Subject [Ticket#12345]**')
* @T('**Left** means **[Ticket#12345] Some Subject**')
* @T('**None** means **Some Subject** (without ticket number). In the last case you should enable *postmaster_follow_up_search_in* to recognize followups based on email headers and/or body.')",
description: "The format of the subject.
* **Right** means **Some Subject [Ticket#12345]**
* **Left** means **[Ticket#12345] Some Subject**
* **None** means **Some Subject** (without ticket number). In the last case you should enable *postmaster_follow_up_search_in* to recognize followups based on email headers and/or body.",
options: {
form: [
{
@@ -1148,6 +1150,7 @@ Setting.create_if_not_exists(
null: true,
name: 'ticket_hook_position',
tag: 'select',
translate: true,
options: {
'left' => 'left',
'right' => 'right',
@@ -1158,6 +1161,7 @@ Setting.create_if_not_exists(
},
state: 'right',
preferences: {
controller: 'SettingsAreaTicketHookPosition',
permission: ['admin.ticket'],
},
frontend: false
@@ -1166,11 +1170,9 @@ Setting.create_if_not_exists(
title: 'Ticket Number Format',
name: 'ticket_number',
area: 'Ticket::Number',
description: "@T('Selects the ticket number generator module.')
* @T('**Increment** increments the ticket number, the SystemID and the counter are used with SystemID.Counter format (e.g. 1010138, 1010139).')
* @T('With **Date** the ticket numbers will be generated by the current date, the SystemID and the counter. The format looks like Year.Month.Day.SystemID.counter (e.g. 201206231010138, 201206231010139).')
@T('With param 'Checksum => true' the counter will be appended as checksum to the string. The format looks like SystemID.Counter.CheckSum (e. g. 10101384, 10101392) or Year.Month.Day.SystemID.Counter.CheckSum (e.g. 2012070110101520, 2012070110101535).')",
description: "Selects the ticket number generator module.
* **Increment** increments the ticket number, the SystemID and the counter are used with SystemID.Counter format (e.g. 1010138, 1010139).
* With **Date** the ticket numbers will be generated by the current date, the SystemID and the counter. The format looks like Year.Month.Day.SystemID.counter (e.g. 201206231010138, 201206231010139).",
options: {
form: [
{
@@ -1178,6 +1180,7 @@ Setting.create_if_not_exists(
null: true,
name: 'ticket_number',
tag: 'select',
translate: true,
options: {
'Ticket::Number::Increment' => 'Increment (SystemID.Counter)',
'Ticket::Number::Date' => 'Date (Year.Month.Day.SystemID.Counter)',
@@ -1187,6 +1190,8 @@ Setting.create_if_not_exists(
},
state: 'Ticket::Number::Increment',
preferences: {
settings_included: %w(ticket_number_increment ticket_number_date),
controller: 'SettingsAreaTicketNumber',
permission: ['admin.ticket'],
},
frontend: false
@@ -1244,6 +1249,7 @@ Setting.create_if_not_exists(
},
preferences: {
permission: ['admin.ticket'],
hidden: true,
},
frontend: false
)
@@ -1267,10 +1273,11 @@ Setting.create_if_not_exists(
],
},
state: {
checksum: false,
checksum: false
},
preferences: {
permission: ['admin.ticket'],
hidden: true,
},
frontend: false
)

View File

@@ -339,8 +339,6 @@ get count of tickets and tickets which match on selector
end
def self.selector2query(selector, _current_user, aggs_interval, limit)
filter_must = []
filter_must_not = []
query_must = []
query_must_not = []
if selector && !selector.empty?
@@ -355,9 +353,9 @@ get count of tickets and tickets which match on selector
t[:term][key_tmp] = data['value']
end
if data['operator'] == 'is'
filter_must.push t
query_must.push t
elsif data['operator'] == 'is not'
filter_must_not.push t
query_must_not.push t
elsif data['operator'] == 'contains'
query_must.push t
elsif data['operator'] == 'contains not'
@@ -391,43 +389,18 @@ get count of tickets and tickets which match on selector
from: aggs_interval[:from],
to: aggs_interval[:to],
}
filter_must.push r
query_must.push r
end
if !query_must.empty? || !query_must_not.empty?
if !data[:query][:filtered]
data[:query][:filtered] = {}
end
if !data[:query][:filtered][:query]
data[:query][:filtered][:query] = {}
end
if !data[:query][:filtered][:query][:bool]
data[:query][:filtered][:query][:bool] = {}
end
if !data[:query][:bool]
data[:query][:bool] = {}
end
if !query_must.empty?
data[:query][:filtered][:query][:bool][:must] = query_must
data[:query][:bool][:must] = query_must
end
if !query_must_not.empty?
data[:query][:filtered][:query][:bool][:must_not] = query_must_not
end
if !filter_must.empty? || !filter_must.empty?
if !data[:query][:filtered]
data[:query][:filtered] = {}
end
if !data[:query][:filtered][:filter]
data[:query][:filtered][:filter] = {}
end
if !data[:query][:filtered][:filter][:bool]
data[:query][:filtered][:filter][:bool] = {}
end
end
if !filter_must.empty?
data[:query][:filtered][:filter][:bool][:must] = filter_must
end
if !filter_must_not.empty?
data[:query][:filtered][:filter][:bool][:must_not] = filter_must_not
data[:query][:bool][:must_not] = query_must_not
end
# add sort

View File

@@ -312,6 +312,27 @@ test('i18n', function() {
var timestamp = App.i18n.translateTimestamp('2012-11-06T21:07:24Z', offset);
equal(timestamp, '06.11.2012 21:07', 'de-de - timestamp translated correctly')
timestamp = App.i18n.translateTimestamp('', offset);
equal(timestamp, '', 'de-de - timestamp translated correctly')
timestamp = App.i18n.translateTimestamp(null, offset);
equal(timestamp, null, 'de-de - timestamp translated correctly')
timestamp = App.i18n.translateTimestamp(undefined, offset);
equal(timestamp, undefined, 'de-de - timestamp translated correctly')
var date = App.i18n.translateDate('2012-11-06', 0)
equal(date, '06.11.2012', 'de-de - date translated correctly')
date = App.i18n.translateDate('', 0)
equal(date, '', 'de-de - date translated correctly')
date = App.i18n.translateDate(null, 0)
equal(date, null, 'de-de - date translated correctly')
date = App.i18n.translateDate(undefined, 0)
equal(date, undefined, 'de-de - date translated correctly')
// en
App.i18n.set('en-us')
translated = App.i18n.translateContent('yes')
@@ -368,6 +389,27 @@ test('i18n', function() {
timestamp = App.i18n.translateTimestamp('2012-11-06T21:07:24Z', offset)
equal(timestamp, '11/06/2012 21:07', 'en - timestamp translated correctly')
timestamp = App.i18n.translateTimestamp('', offset);
equal(timestamp, '', 'en - timestamp translated correctly')
timestamp = App.i18n.translateTimestamp(null, offset);
equal(timestamp, null, 'en - timestamp translated correctly')
timestamp = App.i18n.translateTimestamp(undefined, offset);
equal(timestamp, undefined, 'en - timestamp translated correctly')
date = App.i18n.translateDate('2012-11-06', 0)
equal(date, '11/06/2012', 'en - date translated correctly')
date = App.i18n.translateDate('', 0)
equal(date, '', 'en - date translated correctly')
date = App.i18n.translateDate(null, 0)
equal(date, null, 'en - date translated correctly')
date = App.i18n.translateDate(undefined, 0)
equal(date, undefined, 'en - date translated correctly')
// locale alias test
// de
App.i18n.set('de')

View File

@@ -715,13 +715,14 @@ test("form postmaster filter", function() {
},
},
set: {
'x-zammad-ticket-customer': {
value: 'customer'
'x-zammad-ticket-customer_id': {
value: 'customer',
value_completion: ''
},
'x-zammad-ticket-group_id': {
value: '1'
},
'x-zammad-ticket-owner': {
'x-zammad-ticket-owner_id': {
value: 'owner',
value_completion: ''
},
@@ -757,13 +758,14 @@ test("form postmaster filter", function() {
}
},
set: {
'x-zammad-ticket-customer': {
value: 'customer'
'x-zammad-ticket-customer_id': {
value: 'customer',
value_completion: ''
},
'x-zammad-ticket-group_id': {
value: '1'
},
'x-zammad-ticket-owner': {
'x-zammad-ticket-owner_id': {
value: 'owner',
value_completion: ''
},
@@ -774,7 +776,7 @@ test("form postmaster filter", function() {
};
deepEqual(params, test_params, 'form param check')
el.find('[name="set::x-zammad-ticket-priority_id::value"]').closest('.js-filterElement').find('.js-remove').click()
el.find('[name="set::x-zammad-ticket-customer::value"]').closest('.js-filterElement').find('.js-remove').click()
el.find('[name="set::x-zammad-ticket-customer_id::value"]').closest('.js-filterElement').find('.js-remove').click()
App.Delay.set(function() {
test("form param check after remove click", function() {
params = App.ControllerForm.params(el)
@@ -792,7 +794,7 @@ test("form postmaster filter", function() {
}
},
set: {
'x-zammad-ticket-owner': {
'x-zammad-ticket-owner_id': {
value: 'owner',
value_completion: ''
},

View File

@@ -57,9 +57,72 @@ class AgentTicketActionLevel0Test < TestCase
css: '.modal [name="email"]',
value: "#{agent}@example.com",
)
exists_not(
css: '.modal select[name="group_ids"]',
exists(
displayed: false,
css: '.modal [name="group_ids"]',
)
exists(
css: '.modal [name="group_ids"]:checked',
)
click(
css: '.modal button.btn.btn--primary',
fast: true,
)
watch_for(
css: 'body div.modal',
value: 'Sending',
)
watch_for_disappear(
css: 'body div.modal',
value: 'Sending',
)
click(css: '#navigation a[href="#dashboard"]')
click(css: '.active.content .tab[data-area="first-steps-widgets"]')
watch_for(
css: '.active.content',
value: 'Configuration',
)
click(css: '.active.content .js-inviteAgent')
modal_ready()
set(
css: '.modal [name="firstname"]',
value: 'Bob2',
)
set(
css: '.modal [name="lastname"]',
value: 'Smith2',
)
set(
css: '.modal [name="email"]',
value: "#{agent}2@example.com",
)
# disable agent role
uncheck(
css: '.modal [name="role_ids"][value=2]',
)
exists(
displayed: false,
css: '.modal [name="group_ids"]',
)
exists_not(
css: '.modal [name="group_ids"]:checked',
)
# enable agent role
check(
css: '.modal [name="role_ids"][value=2]',
)
exists(
css: '.modal [name="group_ids"]',
)
exists(
css: '.modal [name="group_ids"]:checked',
)
click(
css: '.modal button.btn.btn--primary',
fast: true,

View File

@@ -2,6 +2,7 @@
require 'browser_test_helper'
class TranslationTest < TestCase
def test_preferences
@browser = browser_instance
login(
@@ -250,4 +251,60 @@ class TranslationTest < TestCase
)
end
def test_admin_sync
@browser = browser_instance
login(
username: 'master@example.com',
password: 'test',
url: browser_url,
)
tasks_close_all()
click(css: 'a[href="#current_user"]')
click(css: 'a[href="#profile"]')
click(css: 'a[href="#profile/language"]')
select(
css: '.language_item [name="locale"]',
value: 'Deutsch',
)
click(css: '.content.active button[type="submit"]')
watch_for(
css: 'body',
value: 'Sprache',
)
click(css: 'a[href="#manage"]')
click(css: 'a[href="#system/translation"]')
watch_for(
css: '.content.active',
value: 'Inline Übersetzung',
)
click(css: '.content.active .js-syncChanges')
watch_for(
css: '.content.active .modal',
value: 'Letzte Übersetzung laden',
)
watch_for_disappear(
css: '.content.active .modal',
timeout: 5 * 60,
)
click(css: 'a[href="#current_user"]')
click(css: 'a[href="#profile"]')
click(css: 'a[href="#profile/language"]')
select(
css: '.language_item [name="locale"]',
value: 'English (United States)',
)
click(css: '.content.active button[type="submit"]')
watch_for(
css: 'body',
value: 'Language',
)
end
end

View File

@@ -547,6 +547,12 @@ class TestCase < Test::Unit::TestCase
css: '.some_class',
)
exists(
displayed: false, # true|false
browser: browser1,
css: '.some_class',
)
=end
def exists(params)
@@ -558,6 +564,16 @@ class TestCase < Test::Unit::TestCase
screenshot(browser: instance, comment: 'exists_failed')
raise "#{params[:css]} dosn't exist, but should"
end
if params.key?(:displayed)
if params[:displayed] == true && !instance.find_elements(css: params[:css])[0].displayed?
raise "#{params[:css]} is not displayed, but should"
end
if params[:displayed] == false && instance.find_elements(css: params[:css])[0].displayed?
raise "#{params[:css]} is displayed, but should not"
end
end
true
end

View File

@@ -318,7 +318,7 @@ class TicketsControllerTest < ActionDispatch::IntegrationTest
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert_equal(ticket.id, result['ticket_id'])
assert_equal('Tickets Agent via Zammad <zammad@localhost>', result['from'])
assert_equal('"Tickets Agent via Zammad" <zammad@localhost>', result['from'])
assert_equal('some subject', result['subject'])
assert_equal('some body', result['body'])
assert_equal('text/plain', result['content_type'])
@@ -335,7 +335,7 @@ class TicketsControllerTest < ActionDispatch::IntegrationTest
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert_equal(ticket.id, result['ticket_id'])
assert_equal('Tickets Agent via Zammad <zammad@localhost>', result['from'])
assert_equal('"Tickets Agent via Zammad" <zammad@localhost>', result['from'])
assert_equal('new subject', result['subject'])
assert_equal('some body', result['body'])
assert_equal('text/plain', result['content_type'])
@@ -444,7 +444,7 @@ class TicketsControllerTest < ActionDispatch::IntegrationTest
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert_equal(ticket.id, result['ticket_id'])
assert_equal('Tickets Admin via Zammad <zammad@localhost>', result['from'])
assert_equal('"Tickets Admin via Zammad" <zammad@localhost>', result['from'])
assert_equal('some subject', result['subject'])
assert_equal('some body', result['body'])
assert_equal('text/plain', result['content_type'])

View File

@@ -211,4 +211,20 @@ text
assert_equal(html_should, html_with_fixes)
end
test 'from checks' do
quoted_in_one_line = Channel::EmailBuild.recipient_line('Somebody @ "Company"', 'some.body@example.com')
assert_equal('"Somebody @ \"Company\"" <some.body@example.com>', quoted_in_one_line)
quoted_in_one_line = Channel::EmailBuild.recipient_line('Somebody', 'some.body@example.com')
assert_equal('Somebody <some.body@example.com>', quoted_in_one_line)
quoted_in_one_line = Channel::EmailBuild.recipient_line('Somebody | Some Org', 'some.body@example.com')
assert_equal('"Somebody | Some Org" <some.body@example.com>', quoted_in_one_line)
quoted_in_one_line = Channel::EmailBuild.recipient_line('Test Master Agent via Support', 'some.body@example.com')
assert_equal('"Test Master Agent via Support" <some.body@example.com>', quoted_in_one_line)
end
end

View File

@@ -407,6 +407,26 @@ class ObjectManagerTest < ActiveSupport::TestCase
}
assert_equal(false, ObjectManager::Attribute.pending_migration?)
assert_raises(RuntimeError) {
attribute18 = ObjectManager::Attribute.add(
object: 'Ticket',
name: 'delete',
display: 'Test 18',
data_type: 'input',
data_option: {
maxlength: 200,
type: 'text',
null: false,
},
active: true,
screens: {},
position: 20,
created_by_id: 1,
updated_by_id: 1,
)
}
assert_equal(false, ObjectManager::Attribute.pending_migration?)
end
test 'b object manager attribute' do

View File

@@ -8,6 +8,14 @@ class TicketOverviewTest < ActiveSupport::TestCase
customer1 = nil
customer2 = nil
customer3 = nil
overview1 = nil
overview2 = nil
overview3 = nil
overview4 = nil
overview5 = nil
overview6 = nil
overview7 = nil
overview8 = nil
test 'aaa - setup' do
# create base
@@ -93,7 +101,7 @@ class TicketOverviewTest < ActiveSupport::TestCase
Overview.destroy_all
UserInfo.current_user_id = 1
overview_role = Role.find_by(name: 'Agent')
Overview.create_or_update(
overview1 = Overview.create_or_update(
name: 'My assigned Tickets',
link: 'my_assigned',
prio: 1000,
@@ -120,7 +128,7 @@ class TicketOverviewTest < ActiveSupport::TestCase
},
)
Overview.create_or_update(
overview2 = Overview.create_or_update(
name: 'Unassigned & Open',
link: 'all_unassigned',
prio: 1010,
@@ -146,7 +154,7 @@ class TicketOverviewTest < ActiveSupport::TestCase
view_mode_default: 's',
},
)
Overview.create_or_update(
overview3 = Overview.create_or_update(
name: 'My Tickets 2',
link: 'my_tickets_2',
prio: 1020,
@@ -173,9 +181,36 @@ class TicketOverviewTest < ActiveSupport::TestCase
view_mode_default: 's',
},
)
overview4 = Overview.create_or_update(
name: 'My Tickets only with Note',
link: 'my_tickets_onyl_with_note',
prio: 1030,
role_id: overview_role.id,
user_ids: [agent1.id],
condition: {
'article.type_id' => {
operator: 'is',
value: Ticket::Article::Type.find_by(name: 'note').id,
},
'ticket.owner_id' => {
operator: 'is',
pre_condition: 'current_user.id',
},
},
order: {
by: 'created_at',
direction: 'ASC',
},
view: {
d: %w(title customer group created_at),
s: %w(title customer group created_at),
m: %w(number title customer group created_at),
view_mode_default: 's',
},
)
overview_role = Role.find_by(name: 'Customer')
Overview.create_or_update(
overview5 = Overview.create_or_update(
name: 'My Tickets',
link: 'my_tickets',
prio: 1100,
@@ -201,7 +236,7 @@ class TicketOverviewTest < ActiveSupport::TestCase
view_mode_default: 's',
},
)
Overview.create_or_update(
overview6 = Overview.create_or_update(
name: 'My Organization Tickets',
link: 'my_organization_tickets',
prio: 1200,
@@ -228,7 +263,7 @@ class TicketOverviewTest < ActiveSupport::TestCase
view_mode_default: 's',
},
)
Overview.create_or_update(
overview7 = Overview.create_or_update(
name: 'My Organization Tickets (open)',
link: 'my_organization_tickets_open',
prio: 1200,
@@ -258,7 +293,7 @@ class TicketOverviewTest < ActiveSupport::TestCase
)
overview_role = Role.find_by(name: 'Admin')
Overview.create_or_update(
overview8 = Overview.create_or_update(
name: 'Not Shown Admin',
link: 'not_shown_admin',
prio: 9900,
@@ -282,14 +317,15 @@ class TicketOverviewTest < ActiveSupport::TestCase
)
end
test 'ticket create' do
test 'bbb overiview index' do
result = Ticket::Overviews.all(
current_user: agent1,
)
assert_equal(2, result.count)
assert_equal(3, result.count)
assert_equal('My assigned Tickets', result[0].name)
assert_equal('Unassigned & Open', result[1].name)
assert_equal('My Tickets only with Note', result[2].name)
result = Ticket::Overviews.all(
current_user: agent2,
@@ -322,4 +358,501 @@ class TicketOverviewTest < ActiveSupport::TestCase
end
test 'ccc overiview content' do
Ticket.destroy_all
result = Ticket::Overviews.index(agent1)
assert_equal(result[0][:overview][:name], 'My assigned Tickets')
assert_equal(result[0][:overview][:view], 'my_assigned')
assert_equal(result[0][:count], 0)
assert_equal(result[0][:tickets].class, Array)
assert(result[0][:tickets].empty?)
assert_equal(result[1][:overview][:name], 'Unassigned & Open')
assert_equal(result[1][:overview][:view], 'all_unassigned')
assert_equal(result[1][:tickets].class, Array)
assert(result[1][:tickets].empty?)
assert_equal(result[1][:count], 0)
assert_equal(result[2][:overview][:name], 'My Tickets only with Note')
assert_equal(result[2][:overview][:view], 'my_tickets_onyl_with_note')
assert_equal(result[2][:tickets].class, Array)
assert(result[2][:tickets].empty?)
assert_equal(result[2][:count], 0)
result = Ticket::Overviews.index(agent2)
assert_equal(result[0][:overview][:name], 'My assigned Tickets')
assert_equal(result[0][:overview][:view], 'my_assigned')
assert_equal(result[0][:count], 0)
assert_equal(result[0][:tickets].class, Array)
assert(result[0][:tickets].empty?)
assert_equal(result[1][:overview][:name], 'Unassigned & Open')
assert_equal(result[1][:overview][:view], 'all_unassigned')
assert_equal(result[1][:tickets].class, Array)
assert(result[1][:tickets].empty?)
assert_equal(result[1][:count], 0)
assert_equal(result[2][:overview][:name], 'My Tickets 2')
assert_equal(result[2][:overview][:view], 'my_tickets_2')
assert_equal(result[2][:tickets].class, Array)
assert(result[2][:tickets].empty?)
ticket1 = Ticket.create(
title: 'overview test 1',
group: Group.lookup(name: 'OverviewTest'),
customer_id: 2,
state: Ticket::State.lookup(name: 'new'),
priority: Ticket::Priority.lookup(name: '2 normal'),
updated_by_id: 1,
created_by_id: 1,
)
article1 = Ticket::Article.create(
ticket_id: ticket1.id,
from: 'some_sender@example.com',
to: 'some_recipient@example.com',
subject: 'some subject',
message_id: 'some@id',
body: 'some message... 123',
internal: false,
sender: Ticket::Article::Sender.find_by(name: 'Customer'),
type: Ticket::Article::Type.find_by(name: 'email'),
updated_by_id: 1,
created_by_id: 1,
)
result = Ticket::Overviews.index(agent1)
assert_equal(result[0][:overview][:name], 'My assigned Tickets')
assert_equal(result[0][:overview][:view], 'my_assigned')
assert_equal(result[0][:count], 0)
assert_equal(result[0][:tickets].class, Array)
assert(result[0][:tickets].empty?)
assert_equal(result[1][:overview][:name], 'Unassigned & Open')
assert_equal(result[1][:overview][:view], 'all_unassigned')
assert_equal(result[1][:tickets].class, Array)
assert_not(result[1][:tickets].empty?)
assert_equal(result[1][:tickets][0][:id], ticket1.id)
assert_equal(result[1][:count], 1)
assert_equal(result[2][:overview][:name], 'My Tickets only with Note')
assert_equal(result[2][:overview][:view], 'my_tickets_onyl_with_note')
assert_equal(result[2][:tickets].class, Array)
assert(result[2][:tickets].empty?)
assert_equal(result[2][:count], 0)
result = Ticket::Overviews.index(agent2)
assert_equal(result[0][:overview][:name], 'My assigned Tickets')
assert_equal(result[0][:overview][:view], 'my_assigned')
assert_equal(result[0][:count], 0)
assert_equal(result[0][:tickets].class, Array)
assert(result[0][:tickets].empty?)
assert_equal(result[1][:overview][:name], 'Unassigned & Open')
assert_equal(result[1][:overview][:view], 'all_unassigned')
assert_equal(result[1][:tickets].class, Array)
assert(result[1][:tickets].empty?)
assert_equal(result[1][:count], 0)
assert_equal(result[2][:overview][:name], 'My Tickets 2')
assert_equal(result[2][:overview][:view], 'my_tickets_2')
assert_equal(result[2][:tickets].class, Array)
assert(result[2][:tickets].empty?)
ticket2 = Ticket.create(
title: 'overview test 2',
group: Group.lookup(name: 'OverviewTest'),
customer_id: 2,
state: Ticket::State.lookup(name: 'new'),
priority: Ticket::Priority.lookup(name: '3 high'),
updated_by_id: 1,
created_by_id: 1,
)
article2 = Ticket::Article.create(
ticket_id: ticket2.id,
from: 'some_sender@example.com',
to: 'some_recipient@example.com',
subject: 'some subject',
message_id: 'some@id',
body: 'some message... 123',
internal: false,
sender: Ticket::Article::Sender.find_by(name: 'Agent'),
type: Ticket::Article::Type.find_by(name: 'note'),
updated_by_id: 1,
created_by_id: 1,
)
result = Ticket::Overviews.index(agent1)
assert_equal(result[0][:overview][:name], 'My assigned Tickets')
assert_equal(result[0][:overview][:view], 'my_assigned')
assert_equal(result[0][:count], 0)
assert_equal(result[0][:tickets].class, Array)
assert(result[0][:tickets].empty?)
assert_equal(result[1][:overview][:name], 'Unassigned & Open')
assert_equal(result[1][:overview][:view], 'all_unassigned')
assert_equal(result[1][:tickets].class, Array)
assert_not(result[1][:tickets].empty?)
assert_equal(result[1][:tickets][0][:id], ticket1.id)
assert_equal(result[1][:tickets][1][:id], ticket2.id)
assert_equal(result[1][:count], 2)
assert_equal(result[2][:overview][:name], 'My Tickets only with Note')
assert_equal(result[2][:overview][:view], 'my_tickets_onyl_with_note')
assert_equal(result[2][:tickets].class, Array)
assert(result[2][:tickets].empty?)
assert_equal(result[2][:count], 0)
result = Ticket::Overviews.index(agent2)
assert_equal(result[0][:overview][:name], 'My assigned Tickets')
assert_equal(result[0][:overview][:view], 'my_assigned')
assert_equal(result[0][:count], 0)
assert_equal(result[0][:tickets].class, Array)
assert(result[0][:tickets].empty?)
assert_equal(result[1][:overview][:name], 'Unassigned & Open')
assert_equal(result[1][:overview][:view], 'all_unassigned')
assert_equal(result[1][:tickets].class, Array)
assert(result[1][:tickets].empty?)
assert_equal(result[1][:count], 0)
assert_equal(result[2][:overview][:name], 'My Tickets 2')
assert_equal(result[2][:overview][:view], 'my_tickets_2')
assert_equal(result[2][:tickets].class, Array)
assert(result[2][:tickets].empty?)
ticket2.owner_id = agent1.id
ticket2.save!
result = Ticket::Overviews.index(agent1)
assert_equal(result[0][:overview][:name], 'My assigned Tickets')
assert_equal(result[0][:overview][:view], 'my_assigned')
assert_equal(result[0][:tickets].class, Array)
assert_equal(result[0][:tickets][0][:id], ticket2.id)
assert_equal(result[0][:count], 1)
assert_equal(result[0][:tickets].class, Array)
assert_equal(result[1][:overview][:name], 'Unassigned & Open')
assert_equal(result[1][:overview][:view], 'all_unassigned')
assert_equal(result[1][:tickets].class, Array)
assert_not(result[1][:tickets].empty?)
assert_equal(result[1][:tickets][0][:id], ticket1.id)
assert_equal(result[1][:count], 1)
assert_equal(result[2][:overview][:name], 'My Tickets only with Note')
assert_equal(result[2][:overview][:view], 'my_tickets_onyl_with_note')
assert_equal(result[2][:tickets].class, Array)
assert_equal(result[2][:tickets][0][:id], ticket2.id)
assert_equal(result[2][:count], 1)
result = Ticket::Overviews.index(agent2)
assert_equal(result[0][:overview][:name], 'My assigned Tickets')
assert_equal(result[0][:overview][:view], 'my_assigned')
assert_equal(result[0][:count], 0)
assert_equal(result[0][:tickets].class, Array)
assert(result[0][:tickets].empty?)
assert_equal(result[1][:overview][:name], 'Unassigned & Open')
assert_equal(result[1][:overview][:view], 'all_unassigned')
assert_equal(result[1][:tickets].class, Array)
assert(result[1][:tickets].empty?)
assert_equal(result[1][:count], 0)
assert_equal(result[2][:overview][:name], 'My Tickets 2')
assert_equal(result[2][:overview][:view], 'my_tickets_2')
assert_equal(result[2][:tickets].class, Array)
assert(result[2][:tickets].empty?)
ticket3 = Ticket.create(
title: 'overview test 3',
group: Group.lookup(name: 'OverviewTest'),
customer_id: 2,
state: Ticket::State.lookup(name: 'new'),
priority: Ticket::Priority.lookup(name: '1 low'),
updated_by_id: 1,
created_by_id: 1,
)
article3 = Ticket::Article.create(
ticket_id: ticket3.id,
from: 'some_sender@example.com',
to: 'some_recipient@example.com',
subject: 'some subject',
message_id: 'some@id',
body: 'some message... 123',
internal: false,
sender: Ticket::Article::Sender.find_by(name: 'Customer'),
type: Ticket::Article::Type.find_by(name: 'email'),
updated_by_id: 1,
created_by_id: 1,
)
result = Ticket::Overviews.index(agent1)
assert_equal(result[0][:overview][:id], overview1.id)
assert_equal(result[0][:overview][:name], 'My assigned Tickets')
assert_equal(result[0][:overview][:view], 'my_assigned')
assert_equal(result[0][:tickets].class, Array)
assert_equal(result[0][:tickets][0][:id], ticket2.id)
assert_equal(result[0][:count], 1)
assert_equal(result[0][:tickets].class, Array)
assert_equal(result[1][:overview][:id], overview2.id)
assert_equal(result[1][:overview][:name], 'Unassigned & Open')
assert_equal(result[1][:overview][:view], 'all_unassigned')
assert_equal(result[1][:tickets].class, Array)
assert_not(result[1][:tickets].empty?)
assert_equal(result[1][:tickets][0][:id], ticket1.id)
assert_equal(result[1][:tickets][1][:id], ticket3.id)
assert_equal(result[1][:count], 2)
assert_equal(result[2][:overview][:id], overview4.id)
assert_equal(result[2][:overview][:name], 'My Tickets only with Note')
assert_equal(result[2][:overview][:view], 'my_tickets_onyl_with_note')
assert_equal(result[2][:tickets].class, Array)
assert_equal(result[2][:tickets][0][:id], ticket2.id)
assert_equal(result[2][:count], 1)
result = Ticket::Overviews.index(agent2)
assert_equal(result[0][:overview][:id], overview1.id)
assert_equal(result[0][:overview][:name], 'My assigned Tickets')
assert_equal(result[0][:overview][:view], 'my_assigned')
assert_equal(result[0][:count], 0)
assert_equal(result[0][:tickets].class, Array)
assert(result[0][:tickets].empty?)
assert_equal(result[1][:overview][:id], overview2.id)
assert_equal(result[1][:overview][:name], 'Unassigned & Open')
assert_equal(result[1][:overview][:view], 'all_unassigned')
assert_equal(result[1][:tickets].class, Array)
assert(result[1][:tickets].empty?)
assert_equal(result[1][:count], 0)
assert_equal(result[2][:overview][:id], overview3.id)
assert_equal(result[2][:overview][:name], 'My Tickets 2')
assert_equal(result[2][:overview][:view], 'my_tickets_2')
assert_equal(result[2][:tickets].class, Array)
assert(result[2][:tickets].empty?)
overview2.order = {
by: 'created_at',
direction: 'DESC',
}
overview2.save!
result = Ticket::Overviews.index(agent1)
assert_equal(result[0][:overview][:id], overview1.id)
assert_equal(result[0][:overview][:name], 'My assigned Tickets')
assert_equal(result[0][:overview][:view], 'my_assigned')
assert_equal(result[0][:tickets].class, Array)
assert_equal(result[0][:tickets][0][:id], ticket2.id)
assert_equal(result[0][:count], 1)
assert_equal(result[0][:tickets].class, Array)
assert_equal(result[1][:overview][:id], overview2.id)
assert_equal(result[1][:overview][:name], 'Unassigned & Open')
assert_equal(result[1][:overview][:view], 'all_unassigned')
assert_equal(result[1][:tickets].class, Array)
assert_not(result[1][:tickets].empty?)
assert_equal(result[1][:tickets][0][:id], ticket3.id)
assert_equal(result[1][:tickets][1][:id], ticket1.id)
assert_equal(result[1][:count], 2)
assert_equal(result[2][:overview][:id], overview4.id)
assert_equal(result[2][:overview][:name], 'My Tickets only with Note')
assert_equal(result[2][:overview][:view], 'my_tickets_onyl_with_note')
assert_equal(result[2][:tickets].class, Array)
assert_equal(result[2][:tickets][0][:id], ticket2.id)
assert_equal(result[2][:count], 1)
result = Ticket::Overviews.index(agent2)
assert_equal(result[0][:overview][:id], overview1.id)
assert_equal(result[0][:overview][:name], 'My assigned Tickets')
assert_equal(result[0][:overview][:view], 'my_assigned')
assert_equal(result[0][:count], 0)
assert_equal(result[0][:tickets].class, Array)
assert(result[0][:tickets].empty?)
assert_equal(result[1][:overview][:id], overview2.id)
assert_equal(result[1][:overview][:name], 'Unassigned & Open')
assert_equal(result[1][:overview][:view], 'all_unassigned')
assert_equal(result[1][:tickets].class, Array)
assert(result[1][:tickets].empty?)
assert_equal(result[1][:count], 0)
assert_equal(result[2][:overview][:id], overview3.id)
assert_equal(result[2][:overview][:name], 'My Tickets 2')
assert_equal(result[2][:overview][:view], 'my_tickets_2')
assert_equal(result[2][:tickets].class, Array)
assert(result[2][:tickets].empty?)
overview2.order = {
by: 'priority_id',
direction: 'DESC',
}
overview2.save!
result = Ticket::Overviews.index(agent1)
assert_equal(result[0][:overview][:id], overview1.id)
assert_equal(result[0][:overview][:name], 'My assigned Tickets')
assert_equal(result[0][:overview][:view], 'my_assigned')
assert_equal(result[0][:tickets].class, Array)
assert_equal(result[0][:tickets][0][:id], ticket2.id)
assert_equal(result[0][:count], 1)
assert_equal(result[0][:tickets].class, Array)
assert_equal(result[1][:overview][:id], overview2.id)
assert_equal(result[1][:overview][:name], 'Unassigned & Open')
assert_equal(result[1][:overview][:view], 'all_unassigned')
assert_equal(result[1][:tickets].class, Array)
assert_not(result[1][:tickets].empty?)
assert_equal(result[1][:tickets][0][:id], ticket1.id)
assert_equal(result[1][:tickets][1][:id], ticket3.id)
assert_equal(result[1][:count], 2)
assert_equal(result[2][:overview][:id], overview4.id)
assert_equal(result[2][:overview][:name], 'My Tickets only with Note')
assert_equal(result[2][:overview][:view], 'my_tickets_onyl_with_note')
assert_equal(result[2][:tickets].class, Array)
assert_equal(result[2][:tickets][0][:id], ticket2.id)
assert_equal(result[2][:count], 1)
result = Ticket::Overviews.index(agent2)
assert_equal(result[0][:overview][:id], overview1.id)
assert_equal(result[0][:overview][:name], 'My assigned Tickets')
assert_equal(result[0][:overview][:view], 'my_assigned')
assert_equal(result[0][:count], 0)
assert_equal(result[0][:tickets].class, Array)
assert(result[0][:tickets].empty?)
assert_equal(result[1][:overview][:id], overview2.id)
assert_equal(result[1][:overview][:name], 'Unassigned & Open')
assert_equal(result[1][:overview][:view], 'all_unassigned')
assert_equal(result[1][:tickets].class, Array)
assert(result[1][:tickets].empty?)
assert_equal(result[1][:count], 0)
assert_equal(result[2][:overview][:id], overview3.id)
assert_equal(result[2][:overview][:name], 'My Tickets 2')
assert_equal(result[2][:overview][:view], 'my_tickets_2')
assert_equal(result[2][:tickets].class, Array)
assert(result[2][:tickets].empty?)
overview2.order = {
by: 'priority_id',
direction: 'ASC',
}
overview2.save!
result = Ticket::Overviews.index(agent1)
assert_equal(result[0][:overview][:id], overview1.id)
assert_equal(result[0][:overview][:name], 'My assigned Tickets')
assert_equal(result[0][:overview][:view], 'my_assigned')
assert_equal(result[0][:tickets].class, Array)
assert_equal(result[0][:tickets][0][:id], ticket2.id)
assert_equal(result[0][:count], 1)
assert_equal(result[0][:tickets].class, Array)
assert_equal(result[1][:overview][:id], overview2.id)
assert_equal(result[1][:overview][:name], 'Unassigned & Open')
assert_equal(result[1][:overview][:view], 'all_unassigned')
assert_equal(result[1][:tickets].class, Array)
assert_not(result[1][:tickets].empty?)
assert_equal(result[1][:tickets][0][:id], ticket3.id)
assert_equal(result[1][:tickets][1][:id], ticket1.id)
assert_equal(result[1][:count], 2)
assert_equal(result[2][:overview][:id], overview4.id)
assert_equal(result[2][:overview][:name], 'My Tickets only with Note')
assert_equal(result[2][:overview][:view], 'my_tickets_onyl_with_note')
assert_equal(result[2][:tickets].class, Array)
assert_equal(result[2][:tickets][0][:id], ticket2.id)
assert_equal(result[2][:count], 1)
result = Ticket::Overviews.index(agent2)
assert_equal(result[0][:overview][:id], overview1.id)
assert_equal(result[0][:overview][:name], 'My assigned Tickets')
assert_equal(result[0][:overview][:view], 'my_assigned')
assert_equal(result[0][:count], 0)
assert_equal(result[0][:tickets].class, Array)
assert(result[0][:tickets].empty?)
assert_equal(result[1][:overview][:id], overview2.id)
assert_equal(result[1][:overview][:name], 'Unassigned & Open')
assert_equal(result[1][:overview][:view], 'all_unassigned')
assert_equal(result[1][:tickets].class, Array)
assert(result[1][:tickets].empty?)
assert_equal(result[1][:count], 0)
assert_equal(result[2][:overview][:id], overview3.id)
assert_equal(result[2][:overview][:name], 'My Tickets 2')
assert_equal(result[2][:overview][:view], 'my_tickets_2')
assert_equal(result[2][:tickets].class, Array)
assert(result[2][:tickets].empty?)
overview2.order = {
by: 'priority',
direction: 'DESC',
}
overview2.save!
result = Ticket::Overviews.index(agent1)
assert_equal(result[0][:overview][:id], overview1.id)
assert_equal(result[0][:overview][:name], 'My assigned Tickets')
assert_equal(result[0][:overview][:view], 'my_assigned')
assert_equal(result[0][:tickets].class, Array)
assert_equal(result[0][:tickets][0][:id], ticket2.id)
assert_equal(result[0][:count], 1)
assert_equal(result[0][:tickets].class, Array)
assert_equal(result[1][:overview][:id], overview2.id)
assert_equal(result[1][:overview][:name], 'Unassigned & Open')
assert_equal(result[1][:overview][:view], 'all_unassigned')
assert_equal(result[1][:tickets].class, Array)
assert_not(result[1][:tickets].empty?)
assert_equal(result[1][:tickets][0][:id], ticket1.id)
assert_equal(result[1][:tickets][1][:id], ticket3.id)
assert_equal(result[1][:count], 2)
assert_equal(result[2][:overview][:id], overview4.id)
assert_equal(result[2][:overview][:name], 'My Tickets only with Note')
assert_equal(result[2][:overview][:view], 'my_tickets_onyl_with_note')
assert_equal(result[2][:tickets].class, Array)
assert_equal(result[2][:tickets][0][:id], ticket2.id)
assert_equal(result[2][:count], 1)
result = Ticket::Overviews.index(agent2)
assert_equal(result[0][:overview][:id], overview1.id)
assert_equal(result[0][:overview][:name], 'My assigned Tickets')
assert_equal(result[0][:overview][:view], 'my_assigned')
assert_equal(result[0][:count], 0)
assert_equal(result[0][:tickets].class, Array)
assert(result[0][:tickets].empty?)
assert_equal(result[1][:overview][:id], overview2.id)
assert_equal(result[1][:overview][:name], 'Unassigned & Open')
assert_equal(result[1][:overview][:view], 'all_unassigned')
assert_equal(result[1][:tickets].class, Array)
assert(result[1][:tickets].empty?)
assert_equal(result[1][:count], 0)
assert_equal(result[2][:overview][:id], overview3.id)
assert_equal(result[2][:overview][:name], 'My Tickets 2')
assert_equal(result[2][:overview][:view], 'my_tickets_2')
assert_equal(result[2][:tickets].class, Array)
assert(result[2][:tickets].empty?)
overview2.order = {
by: 'priority',
direction: 'ASC',
}
overview2.save!
result = Ticket::Overviews.index(agent1)
assert_equal(result[0][:overview][:id], overview1.id)
assert_equal(result[0][:overview][:name], 'My assigned Tickets')
assert_equal(result[0][:overview][:view], 'my_assigned')
assert_equal(result[0][:tickets].class, Array)
assert_equal(result[0][:tickets][0][:id], ticket2.id)
assert_equal(result[0][:count], 1)
assert_equal(result[0][:tickets].class, Array)
assert_equal(result[1][:overview][:id], overview2.id)
assert_equal(result[1][:overview][:name], 'Unassigned & Open')
assert_equal(result[1][:overview][:view], 'all_unassigned')
assert_equal(result[1][:tickets].class, Array)
assert_not(result[1][:tickets].empty?)
assert_equal(result[1][:tickets][0][:id], ticket3.id)
assert_equal(result[1][:tickets][1][:id], ticket1.id)
assert_equal(result[1][:count], 2)
assert_equal(result[2][:overview][:id], overview4.id)
assert_equal(result[2][:overview][:name], 'My Tickets only with Note')
assert_equal(result[2][:overview][:view], 'my_tickets_onyl_with_note')
assert_equal(result[2][:tickets].class, Array)
assert_equal(result[2][:tickets][0][:id], ticket2.id)
assert_equal(result[2][:count], 1)
result = Ticket::Overviews.index(agent2)
assert_equal(result[0][:overview][:id], overview1.id)
assert_equal(result[0][:overview][:name], 'My assigned Tickets')
assert_equal(result[0][:overview][:view], 'my_assigned')
assert_equal(result[0][:count], 0)
assert_equal(result[0][:tickets].class, Array)
assert(result[0][:tickets].empty?)
assert_equal(result[1][:overview][:id], overview2.id)
assert_equal(result[1][:overview][:name], 'Unassigned & Open')
assert_equal(result[1][:overview][:view], 'all_unassigned')
assert_equal(result[1][:tickets].class, Array)
assert(result[1][:tickets].empty?)
assert_equal(result[1][:count], 0)
assert_equal(result[2][:overview][:id], overview3.id)
assert_equal(result[2][:overview][:name], 'My Tickets 2')
assert_equal(result[2][:overview][:view], 'my_tickets_2')
assert_equal(result[2][:tickets].class, Array)
assert(result[2][:tickets].empty?)
end
end

View File

@@ -4,8 +4,37 @@ require 'test_helper'
class TicketTriggerTest < ActiveSupport::TestCase
test '1 basic' do
trigger1 = Trigger.create_or_update(
name: 'aaa loop check',
condition: {
'article.subject' => {
'operator' => 'contains',
'value' => 'Thanks for your inquiry',
},
},
perform: {
'ticket.tags' => {
'operator' => 'add',
'value' => 'should_not_loop',
},
'notification.email' => {
'body' => 'some lala',
'recipient' => 'ticket_customer',
'subject' => 'Thanks for your inquiry - loop check (#{ticket.title})!',
},
},
disable_notification: true,
active: true,
created_by_id: 1,
updated_by_id: 1,
)
trigger2 = Trigger.create_or_update(
name: 'auto reply',
condition: {
'ticket.action' => {
'operator' => 'is',
'value' => 'create',
},
'ticket.state_id' => {
'operator' => 'is',
'value' => Ticket::State.lookup(name: 'new').id.to_s,
@@ -31,7 +60,54 @@ class TicketTriggerTest < ActiveSupport::TestCase
updated_by_id: 1,
)
trigger2 = Trigger.create_or_update(
trigger3 = Trigger.create_or_update(
name: 'auto tag 1',
condition: {
'ticket.action' => {
'operator' => 'is',
'value' => 'update',
},
'ticket.state_id' => {
'operator' => 'is',
'value' => Ticket::State.lookup(name: 'new').id.to_s,
}
},
perform: {
'ticket.priority_id' => {
'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
},
'ticket.tags' => {
'operator' => 'remove',
'value' => 'kk',
},
},
disable_notification: true,
active: true,
created_by_id: 1,
updated_by_id: 1,
)
trigger4 = Trigger.create_or_update(
name: 'auto tag 2',
condition: {
'ticket.state_id' => {
'operator' => 'is',
'value' => Ticket::State.lookup(name: 'new').id.to_s,
}
},
perform: {
'ticket.tags' => {
'operator' => 'add',
'value' => 'abc',
},
},
disable_notification: true,
active: true,
created_by_id: 1,
updated_by_id: 1,
)
trigger5 = Trigger.create_or_update(
name: 'not matching',
condition: {
'ticket.state_id' => {
@@ -50,6 +126,31 @@ class TicketTriggerTest < ActiveSupport::TestCase
updated_by_id: 1,
)
trigger6 = Trigger.create_or_update(
name: 'zzz last',
condition: {
'article.subject' => {
'operator' => 'contains',
'value' => 'some subject 1234',
},
},
perform: {
'ticket.tags' => {
'operator' => 'add',
'value' => 'article_create_trigger',
},
'notification.email' => {
'body' => 'some lala',
'recipient' => 'ticket_customer',
'subject' => 'Thanks for your inquiry - 1234 check (#{ticket.title})!',
},
},
disable_notification: true,
active: true,
created_by_id: 1,
updated_by_id: 1,
)
ticket1 = Ticket.create(
title: "some <b>title</b>\n äöüß",
group: Group.lookup(name: 'Users'),
@@ -89,7 +190,7 @@ class TicketTriggerTest < ActiveSupport::TestCase
assert_equal('new', ticket1.state.name, 'ticket1.state verify')
assert_equal('3 high', ticket1.priority.name, 'ticket1.priority verify')
assert_equal(2, ticket1.articles.count, 'ticket1.articles verify')
assert_equal(%w(aa kk), Tag.tag_list(object: 'Ticket', o_id: ticket1.id))
assert_equal(%w(aa kk abc), Tag.tag_list(object: 'Ticket', o_id: ticket1.id))
article1 = ticket1.articles.last
assert_match('Zammad <zammad@localhost>', article1.from)
assert_match('nicole.braun@zammad.org', article1.to)
@@ -108,7 +209,7 @@ class TicketTriggerTest < ActiveSupport::TestCase
assert_equal('new', ticket1.state.name, 'ticket1.state verify')
assert_equal('2 normal', ticket1.priority.name, 'ticket1.priority verify')
assert_equal(2, ticket1.articles.count, 'ticket1.articles verify')
assert_equal(%w(aa kk), Tag.tag_list(object: 'Ticket', o_id: ticket1.id))
assert_equal(%w(aa kk abc), Tag.tag_list(object: 'Ticket', o_id: ticket1.id))
ticket1.state = Ticket::State.lookup(name: 'open')
ticket1.save
@@ -120,7 +221,7 @@ class TicketTriggerTest < ActiveSupport::TestCase
assert_equal('open', ticket1.state.name, 'ticket1.state verify')
assert_equal('2 normal', ticket1.priority.name, 'ticket1.priority verify')
assert_equal(2, ticket1.articles.count, 'ticket1.articles verify')
assert_equal(%w(aa kk), Tag.tag_list(object: 'Ticket', o_id: ticket1.id))
assert_equal(%w(aa kk abc), Tag.tag_list(object: 'Ticket', o_id: ticket1.id))
ticket1.state = Ticket::State.lookup(name: 'new')
ticket1.save
@@ -132,14 +233,8 @@ class TicketTriggerTest < ActiveSupport::TestCase
assert_equal('Users', ticket1.group.name, 'ticket1.group verify')
assert_equal('new', ticket1.state.name, 'ticket1.state verify')
assert_equal('3 high', ticket1.priority.name, 'ticket1.priority verify')
assert_equal(3, ticket1.articles.count, 'ticket1.articles verify')
assert_equal(%w(aa kk), Tag.tag_list(object: 'Ticket', o_id: ticket1.id))
article1 = ticket1.articles.last
assert_match('Zammad <zammad@localhost>', article1.from)
assert_match('nicole.braun@zammad.org', article1.to)
assert_match('Thanks for your inquiry (some <b>title</b> äöüß)!', article1.subject)
assert_match('Braun<br>some &lt;b&gt;title&lt;/b&gt;', article1.body)
assert_equal('text/html', article1.content_type)
assert_equal(2, ticket1.articles.count, 'ticket1.articles verify')
assert_equal(%w(aa abc), Tag.tag_list(object: 'Ticket', o_id: ticket1.id))
ticket2 = Ticket.create(
title: "some title\n äöüß",
@@ -179,11 +274,12 @@ class TicketTriggerTest < ActiveSupport::TestCase
created_by_id: 1,
)
assert(ticket3, 'ticket3 created')
Ticket::Article.create(
ticket_id: ticket3.id,
from: 'some_sender@example.com',
to: 'some_recipient@example.com',
subject: 'some subject',
subject: 'some subject 1234',
message_id: 'some@id',
content_type: 'text/html',
body: 'some message <b>note</b><br>new line',
@@ -208,9 +304,9 @@ class TicketTriggerTest < ActiveSupport::TestCase
assert_equal('Users', ticket3.group.name, 'ticket3.group verify')
assert_equal('new', ticket3.state.name, 'ticket3.state verify')
assert_equal('3 high', ticket3.priority.name, 'ticket3.priority verify')
assert_equal(2, ticket3.articles.count, 'ticket3.articles verify')
assert_equal(%w(aa kk), Tag.tag_list(object: 'Ticket', o_id: ticket3.id))
article3 = ticket3.articles.last
assert_equal(3, ticket3.articles.count, 'ticket3.articles verify')
assert_equal(%w(aa kk abc article_create_trigger), Tag.tag_list(object: 'Ticket', o_id: ticket3.id))
article3 = ticket3.articles[1]
assert_match('Zammad <zammad@localhost>', article3.from)
assert_match('nicole.braun@zammad.org', article3.to)
assert_match('Thanks for your inquiry (some <b>title</b> äöüß3)!', article3.subject)
@@ -218,6 +314,61 @@ class TicketTriggerTest < ActiveSupport::TestCase
assert_match('&gt; some message note<br>&gt; new line', article3.body)
assert_no_match('&gt; some message &lt;b&gt;note&lt;/b&gt;<br>&gt; new line', article3.body)
assert_equal('text/html', article3.content_type)
article3 = ticket3.articles[2]
assert_match('Zammad <zammad@localhost>', article3.from)
assert_match('nicole.braun@zammad.org', article3.to)
assert_match('Thanks for your inquiry - 1234 check (some <b>title</b> äöüß3)!', article3.subject)
assert_equal('text/html', article3.content_type)
Ticket::Article.create(
ticket_id: ticket3.id,
from: 'some_sender@example.com',
to: 'some_recipient@example.com',
subject: 'some subject - not 1234',
message_id: 'some@id',
content_type: 'text/html',
body: 'some message <b>note</b><br>new line',
internal: false,
sender: Ticket::Article::Sender.find_by(name: 'Agent'),
type: Ticket::Article::Type.find_by(name: 'note'),
updated_by_id: 1,
created_by_id: 1,
)
Observer::Transaction.commit
ticket3 = Ticket.lookup(id: ticket3.id)
assert_equal('some <b>title</b> äöüß3', ticket3.title, 'ticket3.title verify')
assert_equal('Users', ticket3.group.name, 'ticket3.group verify')
assert_equal('new', ticket3.state.name, 'ticket3.state verify')
assert_equal('3 high', ticket3.priority.name, 'ticket3.priority verify')
assert_equal(4, ticket3.articles.count, 'ticket3.articles verify')
assert_equal(%w(aa kk abc article_create_trigger), Tag.tag_list(object: 'Ticket', o_id: ticket3.id))
Ticket::Article.create(
ticket_id: ticket3.id,
from: 'some_sender@example.com',
to: 'some_recipient@example.com',
subject: 'some subject 1234',
message_id: 'some@id',
content_type: 'text/html',
body: 'some message <b>note</b><br>new line',
internal: false,
sender: Ticket::Article::Sender.find_by(name: 'Agent'),
type: Ticket::Article::Type.find_by(name: 'note'),
updated_by_id: 1,
created_by_id: 1,
)
Observer::Transaction.commit
ticket3 = Ticket.lookup(id: ticket3.id)
assert_equal('some <b>title</b> äöüß3', ticket3.title, 'ticket3.title verify')
assert_equal('Users', ticket3.group.name, 'ticket3.group verify')
assert_equal('new', ticket3.state.name, 'ticket3.state verify')
assert_equal('3 high', ticket3.priority.name, 'ticket3.priority verify')
assert_equal(5, ticket3.articles.count, 'ticket3.articles verify')
assert_equal(%w(aa kk abc article_create_trigger), Tag.tag_list(object: 'Ticket', o_id: ticket3.id))
Trigger.destroy_all
end