Compare commits
329 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee6c8413c9 | ||
|
|
0e376a32a3 | ||
|
|
a989921b15 | ||
|
|
1c4e55d53e | ||
|
|
a7aca59c73 | ||
|
|
e0f616846f | ||
|
|
1a8df8e61f | ||
|
|
84110b7d14 | ||
|
|
0b5b32a7c0 | ||
|
|
c46b95927a | ||
|
|
6440b6095f | ||
|
|
11448d599c | ||
|
|
7f0d74b815 | ||
|
|
8fb7d9903f | ||
|
|
2067423c3d | ||
|
|
2b2a7095e3 | ||
|
|
1e967a02b5 | ||
|
|
ff4e43659f | ||
|
|
c3d5c611ec | ||
|
|
d6eeff4ce2 | ||
|
|
ba57b907b4 | ||
|
|
8834f3f526 | ||
|
|
5fc7c2ea3c | ||
|
|
23a480d2a6 | ||
|
|
7e3a10af26 | ||
|
|
15ae7aedcb | ||
|
|
330d9f1173 | ||
|
|
ee551209d4 | ||
|
|
0086c46e56 | ||
|
|
3890e3b4eb | ||
|
|
e2664a647c | ||
|
|
426fce7160 | ||
|
|
020611f671 | ||
|
|
ed1b6f3b6f | ||
|
|
0640aa2222 | ||
|
|
ba06ec871d | ||
|
|
9a64a36cd0 | ||
|
|
41c091bd78 | ||
|
|
3cf1217bea | ||
|
|
48e44e92b5 | ||
|
|
29e2749dd3 | ||
|
|
e30a000504 | ||
|
|
ef4ab4e79d | ||
|
|
dea0f28755 | ||
|
|
399c166e50 | ||
|
|
f6002bf8cb | ||
|
|
53de7302e7 | ||
|
|
4423b44a13 | ||
|
|
0a91e6dba3 | ||
|
|
d8ee543c0d | ||
|
|
3a822db8ae | ||
|
|
e7f4f831be | ||
|
|
3849e04b54 | ||
|
|
c63ad1ff50 | ||
|
|
4c615a968f | ||
|
|
b1ab4d6eb9 | ||
|
|
51247a305f | ||
|
|
ee1353113f | ||
|
|
fe89a3a960 | ||
|
|
5f504fab9c | ||
|
|
db5d4de8ad | ||
|
|
efd4c38f99 | ||
|
|
d13ff15956 | ||
|
|
d4c5d81373 | ||
|
|
66c0975877 | ||
|
|
76efb008a8 | ||
|
|
abc0541244 | ||
|
|
c9f8022f83 | ||
|
|
dfc757968e | ||
|
|
52bc45c0f8 | ||
|
|
cbc8ba5467 | ||
|
|
5f071e7d7f | ||
|
|
1dcd6a243c | ||
|
|
679deb1cf3 | ||
|
|
6f6ac44978 | ||
|
|
0bb341cb7e | ||
|
|
6c122e046b | ||
|
|
57c2dad0a3 | ||
|
|
faefd529b4 | ||
|
|
1481a12035 | ||
|
|
2bf0635e0d | ||
|
|
2211274bc5 | ||
|
|
c229c42838 | ||
|
|
a3c71f6558 | ||
|
|
db8b85f767 | ||
|
|
f1664c3841 | ||
|
|
151c298056 | ||
|
|
38f51ce6b2 | ||
|
|
434f3e963e | ||
|
|
b853dd1597 | ||
|
|
3b5b6f612f | ||
|
|
962043c520 | ||
|
|
8394be87ed | ||
|
|
5b547ceb3c | ||
|
|
789ee08485 | ||
|
|
490937859a | ||
|
|
dfc306a2ac | ||
|
|
dea06015a1 | ||
|
|
2711e3392d | ||
|
|
f5e1fe0c2f | ||
|
|
dbbb4ac7a1 | ||
|
|
8ec2ffdde1 | ||
|
|
b76664e7ae | ||
|
|
704c537749 | ||
|
|
633c99bb72 | ||
|
|
44051d8023 | ||
|
|
50dc143c1b | ||
|
|
6d44275869 | ||
|
|
b1b3bf6d3c | ||
|
|
77081ecd4d | ||
|
|
b823b960d8 | ||
|
|
e08f634125 | ||
|
|
ab5e8d9338 | ||
|
|
9a29ea8f14 | ||
|
|
cf87e00b21 | ||
|
|
6233b0a9ec | ||
|
|
04b1bc638c | ||
|
|
a78938f7c2 | ||
|
|
1d23308454 | ||
|
|
6bc9c00ab8 | ||
|
|
bc131742be | ||
|
|
5a12aa0167 | ||
|
|
b3ab9d0055 | ||
|
|
1bde8f91bc | ||
|
|
47ef1b7e5d | ||
|
|
88c7816f5c | ||
|
|
82339c6c18 | ||
|
|
04a609c3a8 | ||
|
|
60430139aa | ||
|
|
7d1c655ee3 | ||
|
|
e34c1c9b79 | ||
|
|
3c430b35ef | ||
|
|
7ad3c70311 | ||
|
|
c270bc1e67 | ||
|
|
3d19bfdcf7 | ||
|
|
dc4a55cc5c | ||
|
|
6e8886277d | ||
|
|
74e96e92b2 | ||
|
|
287b1f0d01 | ||
|
|
feee46e29b | ||
|
|
991efe36e4 | ||
|
|
a2aacb20fa | ||
|
|
592e978062 | ||
|
|
a4769c8bc7 | ||
|
|
d1378d53f1 | ||
|
|
f0b91d3269 | ||
|
|
48c77dd688 | ||
|
|
d86d904395 | ||
|
|
5efc455f0c | ||
|
|
be3bbeba20 | ||
|
|
31ab1b614c | ||
|
|
4bc5fa4693 | ||
|
|
418a64f401 | ||
|
|
abe46270a6 | ||
|
|
1249a77150 | ||
|
|
cfa6b9ed16 | ||
|
|
aa2303f999 | ||
|
|
d5616684f8 | ||
|
|
0f75ba57bd | ||
|
|
c83675bbba | ||
|
|
17d16d405c | ||
|
|
76795ca302 | ||
|
|
5d145bd09f | ||
|
|
82471552a8 | ||
|
|
dc60574129 | ||
|
|
d33a96dee2 | ||
|
|
0098d4ab82 | ||
|
|
bb03b5eab8 | ||
|
|
17eb0f84ec | ||
|
|
56d0523c1f | ||
|
|
9fa18758bc | ||
|
|
d2f2c55b06 | ||
|
|
f4a409bd6e | ||
|
|
971b724d4c | ||
|
|
3263c6f2ba | ||
|
|
359b026535 | ||
|
|
c63518fd3e | ||
|
|
24ba3cff44 | ||
|
|
f7066e1995 | ||
|
|
6844499309 | ||
|
|
afa3e47eab | ||
|
|
5f3e9298f1 | ||
|
|
e4dd303357 | ||
|
|
2fa06eee3d | ||
|
|
dc0ac03334 | ||
|
|
c5a1d69ec5 | ||
|
|
9891981f92 | ||
|
|
2c721f71e7 | ||
|
|
0b4a4be0b7 | ||
|
|
b13bb2fcaf | ||
|
|
d9ba369fcf | ||
|
|
3adece0db5 | ||
|
|
7101ffc277 | ||
|
|
19a7a439b8 | ||
|
|
e728320241 | ||
|
|
1bc17afc53 | ||
|
|
ee5c58f53a | ||
|
|
9bdf6d7b81 | ||
|
|
5754fdccdd | ||
|
|
4901050d57 | ||
|
|
7980c8cb93 | ||
|
|
7e7088388d | ||
|
|
2f6e52d63d | ||
|
|
bde8babb43 | ||
|
|
d6d68fb363 | ||
|
|
814ffed0f0 | ||
|
|
b013fb8cbc | ||
|
|
fbd8e63592 | ||
|
|
e8ad3965d7 | ||
|
|
e00981ee23 | ||
|
|
4e74c6beda | ||
|
|
2d09e556d7 | ||
|
|
8cd39398d6 | ||
|
|
94c616ffe3 | ||
|
|
57649bb888 | ||
|
|
7e9442ec4a | ||
|
|
521e6265fd | ||
|
|
a42a9689fc | ||
|
|
d9ffb8001d | ||
|
|
ec86d26ae6 | ||
|
|
e00efc821a | ||
|
|
f04be8ca93 | ||
|
|
c7ff6a323a | ||
|
|
34833416bc | ||
|
|
d7a6e28d81 | ||
|
|
c13531d720 | ||
|
|
4a40cced7d | ||
|
|
450de10cc7 | ||
|
|
5e587a455c | ||
|
|
04aef9b258 | ||
|
|
9b72557d13 | ||
|
|
a5e0bc30c1 | ||
|
|
458c166e9f | ||
|
|
3373e5d141 | ||
|
|
8f6b8de617 | ||
|
|
22b21a82e7 | ||
|
|
b4151b299f | ||
|
|
e969fca8c4 | ||
|
|
e59993a5d6 | ||
|
|
3adc9d4cf1 | ||
|
|
c1153250cb | ||
|
|
7718dc28d3 | ||
|
|
1894580ae6 | ||
|
|
11df714bbf | ||
|
|
72adee37a8 | ||
|
|
0e8f460d73 | ||
|
|
4e62d5d0d2 | ||
|
|
004e5ba457 | ||
|
|
f1a8a8da6f | ||
|
|
bfb44d1189 | ||
|
|
d64afd8d97 | ||
|
|
97cfdb9c4c | ||
|
|
bb336f63f1 | ||
|
|
80da08f447 | ||
|
|
a6db2b7c90 | ||
|
|
e1c3f83324 | ||
|
|
8545a44ad1 | ||
|
|
cfbe7510a2 | ||
|
|
1479c32edb | ||
|
|
a9d428bf5a | ||
|
|
3a62eb11da | ||
|
|
2df5f81029 | ||
|
|
b88c23bc92 | ||
|
|
4144fbaf5b | ||
|
|
86ddc93f52 | ||
|
|
8a1b583535 | ||
|
|
7623628b86 | ||
|
|
f753bc203f | ||
|
|
ff9ba10705 | ||
|
|
fdc25bdc26 | ||
|
|
d154a587ad | ||
|
|
a9cc4f037b | ||
|
|
50d6679189 | ||
|
|
b82cf4e1d6 | ||
|
|
fa6eaa6710 | ||
|
|
2ca17ac1bd | ||
|
|
862ecd7d36 | ||
|
|
e38b72593b | ||
|
|
81883cae8f | ||
|
|
63770c9cbf | ||
|
|
aaf5da55a0 | ||
|
|
8455d9ae17 | ||
|
|
839a8e8eb4 | ||
|
|
7aa3f6eaee | ||
|
|
219abc69a3 | ||
|
|
595822c928 | ||
|
|
32112937b1 | ||
|
|
a0889c5b99 | ||
|
|
4bc281bfb0 | ||
|
|
e12ec607d7 | ||
|
|
545f393293 | ||
|
|
4efa152e93 | ||
|
|
0ebda4f32a | ||
|
|
b94f54799f | ||
|
|
99f0b4e2e2 | ||
|
|
53cb906733 | ||
|
|
2c31fe8a84 | ||
|
|
6c5e673a2c | ||
|
|
574b891a33 | ||
|
|
9ef1d3f58b | ||
|
|
8b3f432746 | ||
|
|
14a9df261e | ||
|
|
5620a2847b | ||
|
|
13aaae3835 | ||
|
|
2d4fcd3a4f | ||
|
|
f4f70b0bc2 | ||
|
|
1d279bb88f | ||
|
|
fe4757cbf9 | ||
|
|
1d476f163e | ||
|
|
513701b6d2 | ||
|
|
1a1b661714 | ||
|
|
6081a98279 | ||
|
|
157991c33d | ||
|
|
ef67be9170 | ||
|
|
39f3819303 | ||
|
|
7a676bd563 | ||
|
|
80c54daa03 | ||
|
|
99c88cde62 | ||
|
|
17d113ca20 | ||
|
|
2fbf4324a7 | ||
|
|
b46c390d30 | ||
|
|
3c3651bc52 | ||
|
|
abeeb5e66d | ||
|
|
09d61ce4c8 | ||
|
|
cc1c4495b2 | ||
|
|
7b1ec4d17f | ||
|
|
99b3201ccf | ||
|
|
d4205d9ba6 | ||
|
|
8d57633390 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -29,6 +29,9 @@
|
||||
/config/locales*.yml
|
||||
/config/translations/*.yml
|
||||
|
||||
# Ignore generated documentation
|
||||
/doc
|
||||
|
||||
# Ignore coverage stuff
|
||||
/coverage
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ test:rspec:mysql:
|
||||
- rake db:create
|
||||
- rake db:migrate
|
||||
- rake db:seed
|
||||
- rspec
|
||||
- bundle exec rspec
|
||||
- rake db:drop
|
||||
|
||||
test:rspec:postgresql:
|
||||
@@ -64,7 +64,7 @@ test:rspec:postgresql:
|
||||
- rake db:create
|
||||
- rake db:migrate
|
||||
- rake db:seed
|
||||
- rspec
|
||||
- bundle exec rspec
|
||||
- rake db:drop
|
||||
|
||||
test:unit:mysql:
|
||||
@@ -480,8 +480,8 @@ test:browser:integration:api_client_ruby:
|
||||
- git clone git@github.com:zammad/zammad-api-client-ruby.git || script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1
|
||||
- cd zammad-api-client-ruby
|
||||
- bundle install --jobs 8
|
||||
- export TEST_URL=http://$IP:$BROWSER_PORT
|
||||
- rspec || (cd .. && script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1)
|
||||
- export TEST_URL=http://$IP:$BROWSER_PORT/
|
||||
- bundle exec rspec || (cd .. && script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 1 1)
|
||||
- cd .. && script/build/test_shutdown.sh $RAILS_ENV $BROWSER_PORT $WS_PORT 0 1
|
||||
|
||||
test:browser:integration:api_client_php:
|
||||
|
||||
@@ -28,6 +28,12 @@ targets:
|
||||
- elasticsearch
|
||||
- nginx|apache2
|
||||
- postgresql|mysql-server|mariadb-server|sqlite
|
||||
ubuntu-18.04:
|
||||
dependencies:
|
||||
- curl
|
||||
- elasticsearch
|
||||
- nginx|apache2
|
||||
- postgresql|mysql-server|mariadb-server|sqlite
|
||||
sles-12:
|
||||
dependencies:
|
||||
- curl
|
||||
|
||||
67
CHANGELOG.md
67
CHANGELOG.md
@@ -1,16 +1,71 @@
|
||||
# Change Log
|
||||
|
||||
## [2.4.0](https://github.com/zammad/zammad/tree/2.4.0) (2018-xx-xx)
|
||||
## [2.4.1](https://github.com/zammad/zammad/tree/2.4.1) (2018-06-05)
|
||||
[Full Changelog](https://github.com/zammad/zammad/compare/2.4.0...2.4.1)
|
||||
|
||||
**Implemented enhancements:**
|
||||
- Missing CC autocomplete / type ahead function in ticket create screen \(\#1018286\) [\#1990](https://github.com/zammad/zammad/issues/1990) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
|
||||
|
||||
**Fixed bugs:**
|
||||
- "Create new Customer" form delayed display [\#1957](https://github.com/zammad/zammad/issues/1957) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Zendesk import: To/From extraction for system comments fails [\#2040](https://github.com/zammad/zammad/issues/2040) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Import fails for Object Attribute with non-ASCII name [\#2039](https://github.com/zammad/zammad/issues/2039) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Import job \(LDAP/Exchange\) hangs on scheduler restart [\#2014](https://github.com/zammad/zammad/issues/2014) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Scheduler destroys variables in text \(after a while\) [\#2001](https://github.com/zammad/zammad/issues/2001) [[bug](https://github.com/zammad/zammad/labels/bug)] [[notification](https://github.com/zammad/zammad/labels/notification)] [[trigger](https://github.com/zammad/zammad/labels/trigger)]
|
||||
- Admin UI does not allow to select Timezone in "Calendars" options [\#1971](https://github.com/zammad/zammad/issues/1971) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Ticket waiting time report backend creates high CPU load with script/scheduler.rb [\#1995](https://github.com/zammad/zammad/issues/1995) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Use of wrong recipient if sender is also an agent [\#1996](https://github.com/zammad/zammad/issues/1996) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- ObjectManager: New select attributes with empty selection will lead block UI [\#1980](https://github.com/zammad/zammad/issues/1980) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- If fingerprint is an integer background job for verifying will fail [\#1974](https://github.com/zammad/zammad/issues/1974) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Ticket templates disappear after logout/login in same browser session [\#1669](https://github.com/zammad/zammad/issues/1669) [[bug](https://github.com/zammad/zammad/labels/bug)] [[ticket templates](https://github.com/zammad/zammad/labels/ticket%20templates)]
|
||||
- CalendarimportSync-Errors in Outlook 2016 [\#1332](https://github.com/zammad/zammad/issues/1332) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Generic OAuth2 authentication: incorrect redirect\_uri during access token request, does not match authorization request due to query parameters being appended [\#1642](https://github.com/zammad/zammad/issues/1642) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Failing scheduler job retries to fast without any time offset [\#1950](https://github.com/zammad/zammad/issues/1950) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Use mime type of attachments if present for outgoing emails [\#1919](https://github.com/zammad/zammad/issues/1919) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- New ticket attributes is also show for customers \(which should not\). [\#1952](https://github.com/zammad/zammad/issues/1952) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Can't find organization anymore after index rebuild [\#1933](https://github.com/zammad/zammad/issues/1933) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Sometimes notifications are not marked as seen if somebody else already closed the ticket [\#1920](https://github.com/zammad/zammad/issues/1920) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Generated chat widget code misses required option chatId [\#1936](https://github.com/zammad/zammad/issues/1936) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Pulldowns are not sorted currently [\#1923](https://github.com/zammad/zammad/issues/1923) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Only images in new note are lost on submit [\#1917](https://github.com/zammad/zammad/issues/1917) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Stored Exchange remote\_id in login causes sync to update wrong user [\#1905](https://github.com/zammad/zammad/issues/1905) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Ticket-Property of type Tree-Select mixes up children [\#1660](https://github.com/zammad/zammad/issues/1660) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
|
||||
|
||||
## [2.4.0](https://github.com/zammad/zammad/tree/2.4.0) (2018-03-29)
|
||||
[Full Changelog](https://github.com/zammad/zammad/compare/2.3.0...2.4.0)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
|
||||
|
||||
- Autocomplete Email-Address-Fields [\#338](https://github.com/zammad/zammad/issues/338) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
|
||||
- API: Ticket creation - wrong user in notifcations, activity stream [\#1805](https://github.com/zammad/zammad/issues/1805) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
|
||||
- Admin interface: Generic clone function for objects
|
||||
- RichText: Adjustment of the image size which were inserted from the clipboard
|
||||
- Admin users can be disabled by Agents [\#1794](https://github.com/zammad/zammad/issues/1794) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Failing Scheduler Delayed::Job - jobs are not listed in the monitoring result [\#1866](https://github.com/zammad/zammad/issues/1866) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
|
||||
- Reset owner of ticket on followup if owner is invalid or has no access to group anymore. [\#1816](https://github.com/zammad/zammad/issues/1816) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
|
||||
- Clearing the entered value in a not required ticket field [\#1882](https://github.com/zammad/zammad/issues/1882)
|
||||
- Pending till date not displayed correctly? [\#1716](https://github.com/zammad/zammad/issues/1716) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Login fails after fast logout right after previous login [\#1859](https://github.com/zammad/zammad/issues/1859) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- customer field in customer\_ticket\_new when customer is logged in [\#1856](https://github.com/zammad/zammad/issues/1856) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Direct access to organization tickets possible even though shared is deactivated [\#1857](https://github.com/zammad/zammad/issues/1857) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Can't create email-account with local MTA as outgoing server [\#1852](https://github.com/zammad/zammad/issues/1852) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Role - Group cant be unchecked [\#1488](https://github.com/zammad/zammad/issues/1488) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Can't un-link i-doit Objects in a Ticket [\#1733](https://github.com/zammad/zammad/issues/1733) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Issues in the rights-management [\#1810](https://github.com/zammad/zammad/issues/1810) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Sometimes pending time is show as first ticket attribute [\#949](https://github.com/zammad/zammad/issues/949) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Taskbar item race condition causes JS exception [\#1841](https://github.com/zammad/zammad/issues/1841) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- The column widths of a table are shifted after manual change and use of pagination. [\#1829](https://github.com/zammad/zammad/issues/1829) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Auto assignment of unassigned tickets at first open time [\#1825](https://github.com/zammad/zammad/issues/1825) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
|
||||
- Boolean default option in object manager not selecting when it is already saved [\#1792](https://github.com/zammad/zammad/issues/1792)
|
||||
- Ticket shown multiple times in overview [\#1769](https://github.com/zammad/zammad/issues/1769) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- JS error on creating new customer [\#1813](https://github.com/zammad/zammad/issues/1813) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Customer response via note does not trigger escalation [\#1322](https://github.com/zammad/zammad/issues/1322) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Reporting: profile condition is ignored [\#1809](https://github.com/zammad/zammad/issues/1809) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- LDAP authentication doen't work in 2.3.x [\#1795](https://github.com/zammad/zammad/issues/1795) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Missing create/add customer from ticket [\#1522](https://github.com/zammad/zammad/issues/1522) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- XSS issue in ticket overview [\#1869](https://github.com/zammad/zammad/issues/1869) [[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)*
|
||||
@@ -676,6 +676,7 @@ class App.ControllerModal extends App.Controller
|
||||
closeOnAnyClick: false
|
||||
initalFormParams: {}
|
||||
initalFormParamsIgnore: false
|
||||
showTrySupport: false
|
||||
showTryMax: 10
|
||||
showTrydelay: 1000
|
||||
|
||||
@@ -722,7 +723,7 @@ class App.ControllerModal extends App.Controller
|
||||
content = @contentInline
|
||||
else
|
||||
content = @content()
|
||||
modal = $(App.view('modal')
|
||||
modal = $(App.view('modal')(
|
||||
head: @head
|
||||
headPrefix: @headPrefix
|
||||
message: @message
|
||||
@@ -734,7 +735,7 @@ class App.ControllerModal extends App.Controller
|
||||
buttonClass: @buttonClass
|
||||
centerButtons: @centerButtons
|
||||
leftButtons: @leftButtons
|
||||
)
|
||||
))
|
||||
modal.find('.modal-body').html(content)
|
||||
if !@initRenderingDone
|
||||
@initRenderingDone = true
|
||||
@@ -750,7 +751,7 @@ class App.ControllerModal extends App.Controller
|
||||
@el
|
||||
|
||||
render: =>
|
||||
if @modalAlreadyExists() && @showTryCount <= @showTryMax
|
||||
if @showTrySupport is true && @modalAlreadyExists() && @showTryCount <= @showTryMax
|
||||
@showDelayed()
|
||||
return
|
||||
|
||||
@@ -827,7 +828,7 @@ class App.ControllerModal extends App.Controller
|
||||
|
||||
localOnClosed: (e) =>
|
||||
@onClosed(e)
|
||||
$('.modal').remove()
|
||||
@el.modal('remove')
|
||||
|
||||
onClosed: (e) ->
|
||||
# do nothing
|
||||
@@ -851,6 +852,8 @@ class App.ControllerModal extends App.Controller
|
||||
@onSubmit(e)
|
||||
|
||||
class App.SessionMessage extends App.ControllerModal
|
||||
showTrySupport: true
|
||||
|
||||
onCancel: (e) =>
|
||||
if @forceReload
|
||||
@windowReload(e)
|
||||
|
||||
@@ -443,6 +443,8 @@ class App.ControllerForm extends App.Controller
|
||||
if item.type is 'boolean'
|
||||
if value is ''
|
||||
value = undefined
|
||||
else if value is undefined
|
||||
value = false
|
||||
else if value is 'true'
|
||||
value = true
|
||||
else if value is 'false'
|
||||
@@ -463,21 +465,31 @@ class App.ControllerForm extends App.Controller
|
||||
# verify if we have not checked checkboxes
|
||||
uncheckParam = {}
|
||||
lookupForm.find('input[type=checkbox]').each( (index) ->
|
||||
checked = $(@).attr('checked')
|
||||
type = $(@).data('field-type')
|
||||
checked = $(@).prop('checked')
|
||||
name = $(@).attr('name')
|
||||
if name && !checked && (!(name of param) || param[name] is '')
|
||||
if name && !checked && !(name of param)
|
||||
if !(name of uncheckParam)
|
||||
uncheckParam[name] = undefined
|
||||
if type is 'boolean'
|
||||
uncheckParam[name] = false
|
||||
else
|
||||
uncheckParam[name] = undefined
|
||||
else
|
||||
uncheckParam[name] = []
|
||||
true
|
||||
)
|
||||
|
||||
# verify if we have not checked radios
|
||||
lookupForm.find('input[type=radio]').each( (index) ->
|
||||
checked = $(@).attr('checked')
|
||||
type = $(@).data('field-type')
|
||||
checked = $(@).prop('checked')
|
||||
name = $(@).attr('name')
|
||||
if name && !checked && !(name of param)
|
||||
uncheckParam[name] = undefined
|
||||
if type is 'boolean'
|
||||
uncheckParam[name] = false
|
||||
else
|
||||
uncheckParam[name] = undefined
|
||||
true
|
||||
)
|
||||
|
||||
# apply empty checkboxes & radio values to params
|
||||
|
||||
@@ -3,6 +3,7 @@ class App.ControllerGenericNew extends App.ControllerModal
|
||||
buttonCancel: true
|
||||
buttonSubmit: true
|
||||
headPrefix: 'New'
|
||||
showTrySupport: true
|
||||
|
||||
content: =>
|
||||
@head = @pageData.head || @pageData.object
|
||||
@@ -236,6 +237,7 @@ class App.ControllerGenericDescription extends App.ControllerModal
|
||||
|
||||
class App.ControllerModalLoading extends App.Controller
|
||||
className: 'modal fade'
|
||||
showTrySupport: true
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
@@ -322,6 +324,7 @@ class App.ControllerErrorModal extends App.ControllerModal
|
||||
head: 'Error'
|
||||
#small: true
|
||||
#shown: true
|
||||
showTrySupport: true
|
||||
|
||||
content: ->
|
||||
@message
|
||||
|
||||
@@ -348,7 +348,7 @@ class App.ChannelChat extends App.ControllerSubContent
|
||||
params[key] = value
|
||||
paramString = ''
|
||||
for key, value of params
|
||||
if !_.isEmpty(value)
|
||||
if _.isNumber(value) || _.isBoolean(value) || !_.isEmpty(value)
|
||||
if paramString != ''
|
||||
# coffeelint: disable=no_unnecessary_double_quotes
|
||||
paramString += ",\n"
|
||||
|
||||
@@ -454,9 +454,9 @@ class ConnectionWizard extends App.WizardModal
|
||||
for key in ['source', 'dest']
|
||||
if !_.isArray(attributes[key])
|
||||
attributes[key] = [attributes[key]]
|
||||
attributes_local =
|
||||
item_id: 'login'
|
||||
length = attributes.source.length-1
|
||||
|
||||
attributes_local = {}
|
||||
length = attributes.source.length-1
|
||||
for count in [0..length]
|
||||
if attributes.source[count] && attributes.dest[count]
|
||||
attributes_local[attributes.source[count]] = attributes.dest[count]
|
||||
@@ -465,18 +465,8 @@ class ConnectionWizard extends App.WizardModal
|
||||
@tryShow()
|
||||
|
||||
buildRowsUserMap: (user_attribute_map) =>
|
||||
|
||||
# show static login row
|
||||
userUidDisplayValue = @wizardConfig.wizardData.backend_attributes['item_id']
|
||||
el = [
|
||||
$(App.view('integration/ldap_user_attribute_row_read_only')(
|
||||
key: userUidDisplayValue,
|
||||
value: 'Login'
|
||||
))
|
||||
]
|
||||
|
||||
el = []
|
||||
for source, dest of user_attribute_map
|
||||
continue if source == 'item_id'
|
||||
continue if !(source of @wizardConfig.wizardData.backend_attributes)
|
||||
el.push @buildRowUserAttribute(source, dest)
|
||||
el
|
||||
|
||||
@@ -6,7 +6,7 @@ class App.UiElement.ApplicationUiElement
|
||||
# skip sorting if it is disabled by config
|
||||
return if attribute.sortBy == null
|
||||
|
||||
return if !attribute.options
|
||||
return if _.isEmpty(attribute.options)
|
||||
|
||||
# arrays can only get ordered
|
||||
if _.isArray(attribute.options)
|
||||
@@ -43,7 +43,7 @@ class App.UiElement.ApplicationUiElement
|
||||
attribute.options[''] = '-'
|
||||
|
||||
@getConfigOptionList: (attribute) ->
|
||||
return if !attribute.options
|
||||
return if _.isEmpty(attribute.options)
|
||||
selection = attribute.options
|
||||
attribute.options = []
|
||||
if _.isArray(selection)
|
||||
@@ -54,6 +54,7 @@ class App.UiElement.ApplicationUiElement
|
||||
else
|
||||
order = _.sortBy(
|
||||
_.keys(selection), (item) ->
|
||||
return '' if !selection[item] || !selection[item].toString
|
||||
selection[item].toString().toLowerCase()
|
||||
)
|
||||
for key in order
|
||||
@@ -69,7 +70,7 @@ class App.UiElement.ApplicationUiElement
|
||||
@getRelationOptionList: (attribute, params) ->
|
||||
|
||||
# build options list based on relation
|
||||
return if !attribute.relation
|
||||
return if _.isEmpty(attribute.relation)
|
||||
return if !App[attribute.relation]
|
||||
|
||||
attribute.options = []
|
||||
@@ -175,7 +176,7 @@ class App.UiElement.ApplicationUiElement
|
||||
# execute filter
|
||||
@filterOption: (attribute) ->
|
||||
return if !attribute.filter
|
||||
return if !attribute.options
|
||||
return if _.isEmpty(attribute.options)
|
||||
|
||||
return if typeof attribute.filter isnt 'function'
|
||||
App.Log.debug 'ControllerForm', '_filterOption:filter-function'
|
||||
@@ -218,10 +219,16 @@ class App.UiElement.ApplicationUiElement
|
||||
value = valueOrigin
|
||||
if value is null || value is undefined
|
||||
value = ''
|
||||
recordValue = record.value
|
||||
if recordValue is null || recordValue is undefined
|
||||
recordValue = ''
|
||||
recordName = record.name
|
||||
if recordName is null || recordName is undefined
|
||||
recordName = ''
|
||||
if typeof value is 'string' || typeof value is 'number' || typeof value is 'boolean'
|
||||
if record.value.toString() is value.toString() || record.name.toString() is value.toString()
|
||||
if recordValue.toString() is value.toString() || recordName.toString() is value.toString()
|
||||
return true
|
||||
else if ( value && record.value && _.include(value, record.value) ) || ( value && record.name && _.include(value, record.name) )
|
||||
else if ( value && recordValue && _.include(value, recordValue) ) || ( value && recordName && _.include(value, recordName) )
|
||||
return true
|
||||
false
|
||||
|
||||
|
||||
@@ -134,6 +134,17 @@ class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUi
|
||||
init = false
|
||||
if params && !params.id
|
||||
init = true
|
||||
|
||||
data = objects[object]
|
||||
if init
|
||||
for role, screenOptions of data
|
||||
for screen, options of screenOptions
|
||||
for key, defaultValue of options
|
||||
params.screens ||= {}
|
||||
params.screens[screen] ||= {}
|
||||
params.screens[screen][role] ||= {}
|
||||
params.screens[screen][role][key] = defaultValue
|
||||
|
||||
item = $(App.view('object_manager/screens')(
|
||||
attribute: attribute
|
||||
data: objects[object]
|
||||
|
||||
@@ -33,6 +33,7 @@ class App.TicketCreate extends App.Controller
|
||||
@bind('ui:rerender', =>
|
||||
return if !@authenticateCheck()
|
||||
@renderQueue()
|
||||
@tokanice()
|
||||
)
|
||||
|
||||
# listen to rerender sidebars
|
||||
@@ -55,6 +56,7 @@ class App.TicketCreate extends App.Controller
|
||||
changeFormType: (e) =>
|
||||
type = $(e.currentTarget).data('type')
|
||||
@setFormTypeInUi(type)
|
||||
@tokanice()
|
||||
|
||||
setFormTypeInUi: (type) =>
|
||||
|
||||
@@ -220,8 +222,7 @@ class App.TicketCreate extends App.Controller
|
||||
|
||||
renderQueue: (template = {}) =>
|
||||
localeRender = =>
|
||||
@render(options: template)
|
||||
@render()
|
||||
@render(template)
|
||||
App.QueueManager.add(@queueKey, localeRender)
|
||||
return if !@formMeta
|
||||
App.QueueManager.run(@queueKey)
|
||||
@@ -348,6 +349,11 @@ class App.TicketCreate extends App.Controller
|
||||
# update taskbar with new meta data
|
||||
App.TaskManager.touch(@taskKey)
|
||||
|
||||
@tokanice()
|
||||
|
||||
tokanice: ->
|
||||
App.Utils.tokaniceEmails('.content.active input[name=cc]')
|
||||
|
||||
localUserInfo: (e) =>
|
||||
return if !@sidebarWidget
|
||||
params = App.ControllerForm.params($(e.target).closest('form'))
|
||||
|
||||
@@ -12,15 +12,18 @@ class Index extends App.ControllerSubContent
|
||||
super
|
||||
@subscribeId = App.Calendar.subscribe(@render)
|
||||
|
||||
callback = (data) =>
|
||||
App.Config.set('ical_feeds', data.ical_feeds)
|
||||
App.Config.set('timezones', data.timezones)
|
||||
@stopLoading()
|
||||
@render()
|
||||
@startLoading()
|
||||
App.Calendar.fetchFull(
|
||||
callback
|
||||
clear: true
|
||||
@ajax(
|
||||
id: 'calendar_index'
|
||||
type: 'GET'
|
||||
url: @apiPath + '/calendars_init'
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
App.Config.set('ical_feeds', data.ical_feeds)
|
||||
App.Config.set('timezones', data.timezones)
|
||||
App.Collection.loadAssets(data.assets)
|
||||
@stopLoading()
|
||||
@render()
|
||||
)
|
||||
|
||||
render: =>
|
||||
@@ -59,7 +62,7 @@ class Index extends App.ControllerSubContent
|
||||
if !_.isEmpty(calendars)
|
||||
showDescription = true
|
||||
else
|
||||
description = marked(App[ @genericObject ].description)
|
||||
description = marked(App.Calendar.description)
|
||||
|
||||
@html App.view('calendar/index')(
|
||||
calendars: calendars
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
# coffeelint: disable=duplicate_key
|
||||
treeParams = (e, params) ->
|
||||
tree = []
|
||||
lastLevel = 0
|
||||
lastLevels = []
|
||||
valueLevels = []
|
||||
previousValueLevels = []
|
||||
|
||||
$(e.target).closest('.modal').find('.js-treeTable .js-key').each( ->
|
||||
$element = $(@)
|
||||
@@ -19,20 +18,19 @@ treeParams = (e, params) ->
|
||||
lastLevels[level-1].children.push item
|
||||
else
|
||||
console.log('ERROR', item)
|
||||
if level is 0
|
||||
valueLevels = []
|
||||
else if lastLevel is level
|
||||
valueLevels.pop()
|
||||
else if lastLevel > level
|
||||
down = lastLevel - level
|
||||
for count in [1..down]
|
||||
valueLevels.pop()
|
||||
if lastLevel <= level
|
||||
valueLevels.push name
|
||||
|
||||
valueLevels = []
|
||||
# add previous level values
|
||||
if level > 0
|
||||
for previousLevel in [0..level - 1]
|
||||
valueLevels.push(previousValueLevels[previousLevel])
|
||||
|
||||
# add current level value
|
||||
valueLevels.push(name)
|
||||
|
||||
item.value = valueLevels.join('::')
|
||||
lastLevels[level] = item
|
||||
lastLevel = level
|
||||
previousValueLevels = valueLevels
|
||||
|
||||
)
|
||||
if tree[0]
|
||||
|
||||
@@ -102,22 +102,7 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||
)
|
||||
|
||||
tokanice: ->
|
||||
source = "#{App.Config.get('api_path')}/users/search"
|
||||
a = ->
|
||||
$('.content.active .js-to, .js-cc, js-bcc').tokenfield(
|
||||
createTokensOnBlur: true
|
||||
autocomplete: {
|
||||
source: source
|
||||
minLength: 2
|
||||
},
|
||||
).on('tokenfield:createtoken', (e) ->
|
||||
if !e.attrs.value.match(/@/) || e.attrs.value.match(/\s/)
|
||||
e.preventDefault()
|
||||
return false
|
||||
e.attrs.label = e.attrs.value
|
||||
true
|
||||
)
|
||||
App.Delay.set(a, 500, undefined, 'tags')
|
||||
App.Utils.tokaniceEmails('.content.active .js-to, .js-cc, js-bcc')
|
||||
|
||||
setPossibleArticleTypes: =>
|
||||
actionConfig = App.Config.get('TicketZoomArticleAction')
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
class App.WidgetTemplate extends App.Controller
|
||||
events:
|
||||
'click [data-type=template_save]': 'create'
|
||||
'click [data-type=template_select]': 'select'
|
||||
'click [data-type=template_delete]': 'delete'
|
||||
'click .templates-welcome .create': 'showManage'
|
||||
'click .templates-manage .js-save': 'create'
|
||||
'click .templates-manage .js-apply': 'select'
|
||||
'click .templates-manage .js-delete': 'delete'
|
||||
'click .templates-welcome .js-create': 'showManage'
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
@@ -15,12 +15,12 @@ class App.WidgetTemplate extends App.Controller
|
||||
|
||||
render: =>
|
||||
@configure_attributes = [
|
||||
{ name: 'template_id', display: '', tag: 'select', multiple: false, null: true, nulloption: true, relation: 'Template', default: @template_id },
|
||||
{ name: 'id', display: '', tag: 'select', multiple: false, null: true, nulloption: true, relation: 'Template', default: @id },
|
||||
]
|
||||
|
||||
template = {}
|
||||
if @template_id && App.Template.exists(@template_id)
|
||||
template = App.Template.find(@template_id)
|
||||
if @id && App.Template.exists(@id)
|
||||
template = App.Template.find(@id)
|
||||
|
||||
# insert data
|
||||
@html App.view('widget/template')(
|
||||
@@ -57,11 +57,11 @@ class App.WidgetTemplate extends App.Controller
|
||||
params = @formParam(e.target)
|
||||
|
||||
# check if template is selected
|
||||
return if !params['template_id']
|
||||
return if !params['id']
|
||||
|
||||
template = App.Template.find(params['template_id'])
|
||||
template = App.Template.find(params['id'])
|
||||
if confirm('Sure?')
|
||||
@template_id = false
|
||||
@id = false
|
||||
template.destroy()
|
||||
|
||||
select: (e) =>
|
||||
@@ -71,12 +71,12 @@ class App.WidgetTemplate extends App.Controller
|
||||
params = @formParam(e.target)
|
||||
|
||||
# check if template is selected
|
||||
return if !params['template_id']
|
||||
return if !params['id']
|
||||
|
||||
# remember template (to select it after rerender)
|
||||
@template_id = params['template_id']
|
||||
@id = params['id']
|
||||
|
||||
template = App.Template.find(params['template_id'])
|
||||
template = App.Template.find(params['id'])
|
||||
App.Event.trigger 'ticket_create_rerender', template.attributes()
|
||||
|
||||
create: (e) =>
|
||||
@@ -85,7 +85,7 @@ class App.WidgetTemplate extends App.Controller
|
||||
# get params
|
||||
form = @formParam($(e.target).closest('.content').find('.ticket-create'))
|
||||
params = @formParam(e.target)
|
||||
name = params['template_name']
|
||||
name = params['name']
|
||||
return if !name
|
||||
|
||||
template = App.Template.findByAttribute('name', name)
|
||||
@@ -93,7 +93,7 @@ class App.WidgetTemplate extends App.Controller
|
||||
template = new App.Template
|
||||
|
||||
template.load(
|
||||
name: params['template_name']
|
||||
name: params['name']
|
||||
options: form
|
||||
)
|
||||
|
||||
@@ -107,7 +107,7 @@ class App.WidgetTemplate extends App.Controller
|
||||
ui = @
|
||||
template.save(
|
||||
done: ->
|
||||
ui.template_id = @id
|
||||
ui.id = @id
|
||||
|
||||
fail: =>
|
||||
@log 'error', 'save failed!'
|
||||
|
||||
@@ -977,7 +977,9 @@ class App.Utils
|
||||
if !_.isEmpty(article.to)
|
||||
recipients = App.Utils.parseAddressListLocal(article.to)
|
||||
if recipients && recipients[0]
|
||||
recipientIsLocal = isLocalAddress(recipients[0])
|
||||
for localRecipient in recipients
|
||||
recipientIsLocal = isLocalAddress(localRecipient)
|
||||
break if recipientIsLocal is true
|
||||
|
||||
# sender is local
|
||||
if senderIsLocal
|
||||
@@ -1041,3 +1043,23 @@ class App.Utils
|
||||
articleNew.cc = addAddresses(article.cc, articleNew.cc)
|
||||
|
||||
articleNew
|
||||
|
||||
# apply email token field with autocompletion
|
||||
@tokaniceEmails: (selector) ->
|
||||
source = "#{App.Config.get('api_path')}/users/search"
|
||||
a = ->
|
||||
$(selector).tokenfield(
|
||||
createTokensOnBlur: true
|
||||
autocomplete: {
|
||||
source: source
|
||||
minLength: 2
|
||||
},
|
||||
).on('tokenfield:createtoken', (e) ->
|
||||
if !e.attrs.value.match(/@/) || e.attrs.value.match(/\s/)
|
||||
e.preventDefault()
|
||||
return false
|
||||
e.attrs.label = e.attrs.value
|
||||
true
|
||||
)
|
||||
App.Delay.set(a, 500, undefined, 'tags')
|
||||
|
||||
|
||||
@@ -540,7 +540,7 @@
|
||||
|
||||
// for validation, do not retrun empty content by empty tags
|
||||
text_plain = this.$element.text().trim()
|
||||
if ( !text_plain || text_plain == '' ) {
|
||||
if ( (!text_plain || text_plain == '') && !this.$element.find('img').get(0) ) {
|
||||
return text_plain
|
||||
}
|
||||
return this.$element.html().trim()
|
||||
|
||||
@@ -182,6 +182,13 @@
|
||||
})
|
||||
}
|
||||
|
||||
// me - 2018-05-24
|
||||
// cleanup element on hide - cleanup dom with old modal dialogs
|
||||
Modal.prototype.remove = function () {
|
||||
console.log('remove', this.$element)
|
||||
this.$element.remove()
|
||||
}
|
||||
|
||||
Modal.prototype.removeBackdrop = function () {
|
||||
this.$backdrop && this.$backdrop.remove()
|
||||
this.$backdrop = null
|
||||
|
||||
@@ -237,8 +237,8 @@ set new attributes of model (remove already available attributes)
|
||||
attributesNew = {}
|
||||
if screen
|
||||
for attribute in attributes
|
||||
if attribute && attribute.screen && attribute.screen[ screen ] && !_.isEmpty(attribute.screen[ screen ])
|
||||
for item, value of attribute.screen[ screen ]
|
||||
if attribute && attribute.screen && attribute.screen[screen] && (!_.isEmpty(attribute.screen[screen]) && (attribute.screen[screen].shown is true || attribute.screen[screen].shown is undefined))
|
||||
for item, value of attribute.screen[screen]
|
||||
attribute[item] = value
|
||||
attributesNew[ attribute.name ] = attribute
|
||||
|
||||
@@ -406,10 +406,12 @@ set new attributes of model (remove already available attributes)
|
||||
clear = true
|
||||
if param.clear is true || param.clear is false
|
||||
clear = param.clear
|
||||
if !@initFetchActive
|
||||
if !@initFetchActives && @count() is 0
|
||||
@initFetchActive = true
|
||||
@one 'refresh', (collection) ->
|
||||
@one('refresh', (collection) =>
|
||||
@initFetchActive = false
|
||||
callback(collection)
|
||||
)
|
||||
@fetchFull(
|
||||
->
|
||||
clear: clear
|
||||
@@ -867,5 +869,7 @@ set new attributes of model (remove already available attributes)
|
||||
@configure_attributes = $.extend(true, [], @org_configure_attributes)
|
||||
|
||||
@resetCallbacks: ->
|
||||
@SUBSCRIPTION_ITEM = {}
|
||||
@SUBSCRIPTION_COLLECTION = {}
|
||||
if @SUBSCRIPTION_ITEM
|
||||
@SUBSCRIPTION_ITEM = {}
|
||||
if @SUBSCRIPTION_COLLECTION
|
||||
@SUBSCRIPTION_COLLECTION = {}
|
||||
|
||||
@@ -5,7 +5,7 @@ class App.Calendar extends App.Model
|
||||
|
||||
@configure_attributes = [
|
||||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
|
||||
{ name: 'timezone', display: 'Time zone', tag: 'timezone', null: false }
|
||||
{ name: 'timezone', display: 'Time zone', tag: 'timezone', null: false }
|
||||
{ name: 'business_hours', display: 'Business Hours', tag: 'business_hours', null: true }
|
||||
{ name: 'ical_url', display: 'Holidays iCalendar Feed', tag: 'ical_feed', placeholder: 'http://example.com/public_holidays.ical', null: true }
|
||||
{ name: 'public_holidays',display: 'Holidays', tag: 'holiday_selector', null: true }
|
||||
|
||||
@@ -20,3 +20,4 @@ class App.PostmasterFilter extends App.Model
|
||||
@configure_overview = [
|
||||
'name',
|
||||
]
|
||||
@configure_clone = true
|
||||
|
||||
@@ -33,3 +33,4 @@ class App.Signature extends App.Model
|
||||
@configure_overview = [
|
||||
'name',
|
||||
]
|
||||
@configure_clone = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="dropdown-toggle" data-toggle="dropdown">
|
||||
<input
|
||||
class="searchableSelect-shadow form-control js-shadow"
|
||||
id="<%= @attribute.id %>"
|
||||
<% if @attribute.id: %>id="<%= @attribute.id %>"<% end %>
|
||||
name="<%= @attribute.name %>"
|
||||
<%= @attribute.required %>
|
||||
<%= @attribute.autofocus %>
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<tr class="js-entry">
|
||||
<td style="max-width: 240px" class="settings-list-row-control">
|
||||
<div class="u-textTruncate"><%= @key %></div>
|
||||
<td class="settings-list-row-control">
|
||||
<%= @value %>
|
||||
<td class="settings-list-row-control">
|
||||
@@ -1,5 +1,6 @@
|
||||
<div class="js-data">
|
||||
<div class="js-dataType"></div>
|
||||
<div class="js-dataMap" style="padding: 30px 0 0 30px;"></div>
|
||||
<div class="js-dataScreens" style="padding: 30px 0 0 30px;"></div>
|
||||
<div class="" style="padding-bottom: 10px"><%- @T('Here you define which authorization has access to the attribute.') %></div>
|
||||
<div class="js-dataScreens"></div>
|
||||
</div>
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
<div>
|
||||
<div class="js-top"></div>
|
||||
<div class="js-middle" style="padding-left: 70px;">
|
||||
<div class="js-input">input settings</div>
|
||||
<div class="js-textarea">textarea settings</div>
|
||||
<div class="js-datetime">date time settings</div>
|
||||
<div class="js-date">date settings</div>
|
||||
<div class="js-select">select settings</div>
|
||||
<div class="js-tree_selection">tree selection settings</div>
|
||||
<div class="js-checkbox">checkbox settings</div>
|
||||
<div class="js-boolean">boolean settings</div>
|
||||
<div class="js-richtext">richtext settings</div>
|
||||
</div>
|
||||
|
||||
<div class="boolean form-group">
|
||||
<div class="horizontal">
|
||||
<label for=""><%- @T('Screens') %> <span>*</span></label>
|
||||
</div>
|
||||
<div class="controls">
|
||||
|
||||
1
|
||||
|
||||
2
|
||||
|
||||
3
|
||||
|
||||
</diV
|
||||
</div>
|
||||
|
||||
|
||||
<div class="js-bottom"></div>
|
||||
</div>
|
||||
@@ -2,7 +2,7 @@
|
||||
<table class="settings-list" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><%- @T('Role') %>
|
||||
<th><%- @T('Permissions') %>
|
||||
<th><%- @T('Screen') %>
|
||||
<th style="width: 50%;"><%- @T('Options') %>
|
||||
</thead>
|
||||
@@ -20,11 +20,10 @@
|
||||
<%= screen %>
|
||||
<td class="settings-list-row-control">
|
||||
<% for key, defaultValue of options: %>
|
||||
<%- @T(key) %>: <input class="js-boolean" name="screens::<%= screen %>::<%= role %>::<%= key %>" type="checkbox" <% if (@init && defaultValue is true) || (@params && @params.screens && @params.screens[screen] && @params.screens[screen][role] && @params.screens[screen][role][key] is true) : %>checked<% end %> value="true">
|
||||
<%- @T(key) %>: <input class="js-boolean" name="screens::<%= screen %>::<%= role %>::<%= key %>" type="checkbox" <% if @params && @params.screens && @params.screens[screen] && @params.screens[screen][role] && @params.screens[screen][role][key] is true: %>checked<% end %> value="true">
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -5,18 +5,18 @@
|
||||
<div id="form-template"></div>
|
||||
</div>
|
||||
<div class="horizontal">
|
||||
<button type="submit" class="btn btn--action" data-type="template_delete"><%- @T('Delete') %></button>
|
||||
<button type="submit" class="btn btn--action align-right" data-type="template_select"><%- @T('Apply') %></button>
|
||||
<button type="submit" class="btn btn--action js-delete"><%- @T('Delete') %></button>
|
||||
<button type="submit" class="btn btn--action align-right js-apply"><%- @T('Apply') %></button>
|
||||
</div>
|
||||
</form>
|
||||
<hr>
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label class="" for="template_name"><%- @T('Save as template') %></label>
|
||||
<input type="text" name="template_name" id="template_name" class="form-control" value="<%= @template.name %>"/>
|
||||
<input type="text" name="name" id="template_name" class="form-control js-name" value="<%= @template.name %>"/>
|
||||
</div>
|
||||
<div class="horizontal">
|
||||
<button type="submit" class="btn btn--action align-right" data-type="template_save"><%- @T('Save') %></button>
|
||||
<button type="submit" class="btn btn--action align-right js-save"><%- @T('Save') %></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -25,5 +25,5 @@
|
||||
<p><%- @T('No template created yet.') %></p>
|
||||
<p><%- @T('With templates you can pre-fill ticket attributes.') %></p>
|
||||
<p><%- @T('Choose attributes and then save them as a new template.') %></p>
|
||||
<p><a class="create u-clickable"><%- @T('Save new template') %></a></p>
|
||||
<p><a class="js-create u-clickable"><%- @T('Save new template') %></a></p>
|
||||
</div>
|
||||
|
||||
@@ -47,6 +47,7 @@ module ApplicationController::HandlesDevices
|
||||
raise Exceptions::UnprocessableEntity, 'Need fingerprint param!'
|
||||
end
|
||||
if params[:fingerprint]
|
||||
UserDevice.fingerprint_validation(params[:fingerprint])
|
||||
session[:user_device_fingerprint] = params[:fingerprint]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,26 +3,28 @@
|
||||
class CalendarsController < ApplicationController
|
||||
prepend_before_action { authentication_check(permission: 'admin.calendar') }
|
||||
|
||||
def index
|
||||
|
||||
# calendars
|
||||
def init
|
||||
assets = {}
|
||||
calendar_ids = []
|
||||
record_ids = []
|
||||
Calendar.all.order(:name, :created_at).each do |calendar|
|
||||
calendar_ids.push calendar.id
|
||||
record_ids.push calendar.id
|
||||
assets = calendar.assets(assets)
|
||||
end
|
||||
|
||||
ical_feeds = Calendar.ical_feeds
|
||||
timezones = Calendar.timezones
|
||||
render json: {
|
||||
calendar_ids: calendar_ids,
|
||||
record_ids: record_ids,
|
||||
ical_feeds: ical_feeds,
|
||||
timezones: timezones,
|
||||
assets: assets,
|
||||
}, status: :ok
|
||||
end
|
||||
|
||||
def index
|
||||
model_index_render(Calendar, params)
|
||||
end
|
||||
|
||||
def show
|
||||
model_show_render(Calendar, params)
|
||||
end
|
||||
|
||||
@@ -4,6 +4,7 @@ class FormController < ApplicationController
|
||||
skip_before_action :verify_csrf_token
|
||||
before_action :cors_preflight_check_execute
|
||||
after_action :set_access_control_headers_execute
|
||||
skip_before_action :user_device_check
|
||||
|
||||
def configuration
|
||||
return if !enabled?
|
||||
|
||||
@@ -127,7 +127,8 @@ class ObjectManagerAttributesController < ApplicationController
|
||||
permitted[:data_option][:null] = true
|
||||
end
|
||||
|
||||
if !permitted[:data_option][:options].is_a?(Hash)
|
||||
if !permitted[:data_option][:options].is_a?(Hash) &&
|
||||
!permitted[:data_option][:options].is_a?(Array)
|
||||
permitted[:data_option][:options] = {}
|
||||
end
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ get assets and record_ids of selector
|
||||
begin
|
||||
attribute_class = attribute[0].to_classname.constantize
|
||||
rescue => e
|
||||
next if attribute[0] == 'article'
|
||||
logger.error "Unable to get asset for '#{attribute[0]}': #{e.inspect}"
|
||||
next
|
||||
end
|
||||
|
||||
@@ -105,13 +105,13 @@ module Channel::EmailBuild
|
||||
attr[:attachments]&.each do |attachment|
|
||||
if attachment.class == Hash
|
||||
attachment['content-id'] = nil
|
||||
mail.attachments[ attachment[:filename] ] = attachment
|
||||
mail.attachments[attachment[:filename]] = attachment
|
||||
else
|
||||
next if attachment.preferences['Content-ID'].present?
|
||||
filename = attachment.filename
|
||||
encoded_filename = Mail::Encodings.decode_encode filename, :encode
|
||||
disposition = attachment.preferences['Content-Disposition'] || 'attachment'
|
||||
content_type = attachment.preferences['Content-Type'] || 'application/octet-stream'
|
||||
content_type = attachment.preferences['Content-Type'] || attachment.preferences['Mime-Type'] || 'application/octet-stream'
|
||||
mail.attachments[attachment.filename] = {
|
||||
content_disposition: "#{disposition}; filename=\"#{encoded_filename}\"",
|
||||
content_type: "#{content_type}; filename=\"#{encoded_filename}\"",
|
||||
@@ -152,9 +152,13 @@ Check if string is a complete html document. If not, add head and css styles.
|
||||
|
||||
return html if html.match?(/<html>/i)
|
||||
|
||||
html_email_body = File.read(Rails.root.join('app', 'views', 'mailer', 'application_wrapper.html.erb').to_s)
|
||||
|
||||
html_email_body.gsub!('###html_email_css_font###', Setting.get('html_email_css_font'))
|
||||
|
||||
# use block form because variable html could contain backslashes and e. g. '\1' that
|
||||
# must not be handled as back-references for regular expressions
|
||||
Rails.configuration.html_email_body.sub('###html###') { html }
|
||||
html_email_body.sub('###html###') { html }
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
@@ -525,6 +525,8 @@ returns
|
||||
# parse email
|
||||
mail = parse(msg)
|
||||
|
||||
Rails.logger.info "Process email with msgid '#{mail[:message_id]}'"
|
||||
|
||||
# run postmaster pre filter
|
||||
UserInfo.current_user_id = 1
|
||||
filters = {}
|
||||
|
||||
@@ -45,7 +45,7 @@ class Channel::Filter::MonitoringBase
|
||||
|
||||
# icinga - get state by body - new templates
|
||||
if result['state'].blank?
|
||||
if mail[:body] =~ /==>.*\sis\s(.+?)\!\s+?<==/
|
||||
if mail[:body] =~ /.+?\sis\s(.+?)\!/
|
||||
result['state'] = $1
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,6 +5,8 @@ class ImportJob < ApplicationModel
|
||||
store :payload
|
||||
store :result
|
||||
|
||||
scope :running, -> { where(finished_at: nil, dry_run: false).where.not(started_at: nil) }
|
||||
|
||||
# Starts the import backend class based on the name attribute.
|
||||
# Import backend class is initialized with the current instance.
|
||||
# Logs the start and end time (if ended successfully) and logs
|
||||
|
||||
25
app/models/observer/ticket/escalation_update.rb
Normal file
25
app/models/observer/ticket/escalation_update.rb
Normal file
@@ -0,0 +1,25 @@
|
||||
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class Observer::Ticket::EscalationUpdate < ActiveRecord::Observer
|
||||
observe 'ticket'
|
||||
|
||||
def after_create(record)
|
||||
_check(record)
|
||||
end
|
||||
|
||||
def after_update(record)
|
||||
_check(record)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def _check(record)
|
||||
|
||||
# return if we run import mode
|
||||
return false if Setting.get('import_mode')
|
||||
|
||||
return false if !record.saved_changes?
|
||||
|
||||
record.escalation_calculation
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,7 @@
|
||||
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class Scheduler < ApplicationModel
|
||||
extend ::Mixin::StartFinishLogger
|
||||
|
||||
# rubocop:disable Style/ClassVars
|
||||
@@jobs_started = {}
|
||||
@@ -120,18 +121,38 @@ class Scheduler < ApplicationModel
|
||||
raise 'This method should only get called when Scheduler.threads are initialized. Use `force: true` to start anyway.'
|
||||
end
|
||||
|
||||
Delayed::Job.all.each do |job|
|
||||
cleanup_delayed(job)
|
||||
start_time = Time.zone.now
|
||||
|
||||
cleanup_delayed_jobs(start_time)
|
||||
cleanup_import_jobs(start_time)
|
||||
end
|
||||
|
||||
# Checks for locked delayed jobs and tries to reschedule or destroy each of them.
|
||||
#
|
||||
# @param [ActiveSupport::TimeWithZone] after the time the cleanup was started
|
||||
#
|
||||
# @example
|
||||
# Scheduler.cleanup_delayed_jobs(TimeZone.now)
|
||||
#
|
||||
# return [nil]
|
||||
def self.cleanup_delayed_jobs(after)
|
||||
log_start_finish(:info, "Cleanup of left over locked delayed jobs #{after}") do
|
||||
|
||||
Delayed::Job.where('updated_at < ?', after).where.not(locked_at: nil).each do |job|
|
||||
log_start_finish(:info, "Checking left over delayed job #{job.inspect}") do
|
||||
cleanup_delayed(job)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Checks if the given job can be rescheduled or destroys it. Logs the action as warn.
|
||||
# Works only for locked jobs. Jobs that are not locked are ignored and
|
||||
# Checks if the given delayed job can be rescheduled or destroys it. Logs the action as warn.
|
||||
# Works only for locked delayed jobs. Delayed jobs that are not locked are ignored and
|
||||
# should get destroyed directly.
|
||||
# Checks the delayed job object for a method called .reschedule?. The memthod is called
|
||||
# with the delayed job as a parameter. The result value is expected as a Boolean. If the
|
||||
# result is true the lock gets removed and the delayed job gets rescheduled. If the return
|
||||
# value is false it will get destroyed which is the default behaviour.
|
||||
# Checks the Delayed::Job instance for a method called .reschedule?. The method is called
|
||||
# with the Delayed::Job instance as a parameter. The result value is expected to be a Boolean.
|
||||
# If the result is true the lock gets removed and the delayed job gets rescheduled.
|
||||
# If the return value is false it will get destroyed which is the default behaviour.
|
||||
#
|
||||
# @param [Delayed::Job] job the job that should get checked for destroying/rescheduling.
|
||||
#
|
||||
@@ -175,6 +196,33 @@ class Scheduler < ApplicationModel
|
||||
Rails.logger.warn "#{action} locked delayed job: #{job_name}"
|
||||
end
|
||||
|
||||
# Checks for killed import jobs and marks them as finished and adds a note.
|
||||
#
|
||||
# @param [ActiveSupport::TimeWithZone] after the time the cleanup was started
|
||||
#
|
||||
# @example
|
||||
# Scheduler.cleanup_import_jobs(TimeZone.now)
|
||||
#
|
||||
# return [nil]
|
||||
def self.cleanup_import_jobs(after)
|
||||
log_start_finish(:info, "Cleanup of left over import jobs #{after}") do
|
||||
error = 'Interrupted by scheduler restart. Please restart manually or wait till next execution time.'.freeze
|
||||
|
||||
# we need to exclude jobs that were updated at or since we started
|
||||
# cleaning up (via the #reschedule? call) because they might
|
||||
# were started `.delay`-ed and are flagged for restart
|
||||
ImportJob.running.where('updated_at < ?', after).each do |job|
|
||||
|
||||
job.update!(
|
||||
finished_at: after,
|
||||
result: {
|
||||
error: error
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.start_job(job)
|
||||
|
||||
# start job and return thread handle
|
||||
@@ -245,6 +293,7 @@ class Scheduler < ApplicationModel
|
||||
|
||||
# restart job again
|
||||
if try_run_max > try_count
|
||||
sleep(try_count) # wait between retries (see https://github.com/zammad/zammad/issues/1950)
|
||||
_start_job(job, try_count, try_run_time)
|
||||
else
|
||||
# release thread lock and remove thread handle
|
||||
|
||||
@@ -23,9 +23,7 @@ class Ticket < ApplicationModel
|
||||
|
||||
store :preferences
|
||||
before_create :check_generate, :check_defaults, :check_title, :set_default_state, :set_default_priority
|
||||
after_create :check_escalation_update
|
||||
before_update :check_defaults, :check_title, :reset_pending_time, :check_owner_active
|
||||
after_update :check_escalation_update
|
||||
|
||||
validates :group_id, presence: true
|
||||
|
||||
@@ -1188,11 +1186,6 @@ result
|
||||
true
|
||||
end
|
||||
|
||||
def check_escalation_update
|
||||
escalation_calculation
|
||||
true
|
||||
end
|
||||
|
||||
def set_default_state
|
||||
return true if state_id
|
||||
default_ticket_state = Ticket::State.find_by(default_create: true)
|
||||
|
||||
@@ -40,8 +40,8 @@ class User < ApplicationModel
|
||||
include User::SearchIndex
|
||||
|
||||
before_validation :check_name, :check_email, :check_login, :check_mail_delivery_failed, :ensure_uniq_email, :ensure_password, :ensure_roles, :ensure_identifier
|
||||
before_create :check_preferences_default, :validate_ooo, :domain_based_assignment, :set_locale
|
||||
before_update :check_preferences_default, :validate_ooo, :reset_login_failed, :validate_agent_limit_by_attributes, :last_admin_check_by_attribute
|
||||
before_create :check_preferences_default, :validate_preferences, :validate_ooo, :domain_based_assignment, :set_locale
|
||||
before_update :check_preferences_default, :validate_preferences, :validate_ooo, :reset_login_failed, :validate_agent_limit_by_attributes, :last_admin_check_by_attribute
|
||||
after_create :avatar_for_email_check
|
||||
after_update :avatar_for_email_check
|
||||
before_destroy :avatar_destroy, :user_device_destroy, :cit_caller_id_destroy, :task_destroy
|
||||
@@ -1006,6 +1006,22 @@ returns
|
||||
true
|
||||
end
|
||||
|
||||
def validate_preferences
|
||||
return true if !changes
|
||||
return true if !changes['preferences']
|
||||
return true if preferences.blank?
|
||||
return true if !preferences[:notification_sound]
|
||||
return true if !preferences[:notification_sound][:enabled]
|
||||
if preferences[:notification_sound][:enabled] == 'true'
|
||||
preferences[:notification_sound][:enabled] = true
|
||||
elsif preferences[:notification_sound][:enabled] == 'false'
|
||||
preferences[:notification_sound][:enabled] = false
|
||||
end
|
||||
class_name = preferences[:notification_sound][:enabled].class.to_s
|
||||
raise Exceptions::UnprocessableEntity, "preferences.notification_sound.enabled need to be an boolean, but it was a #{class_name}" if class_name != 'TrueClass' && class_name != 'FalseClass'
|
||||
true
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
checks if the current user is the last one with admin permissions.
|
||||
|
||||
@@ -5,6 +5,9 @@ class UserDevice < ApplicationModel
|
||||
store :location_details
|
||||
validates :name, presence: true
|
||||
|
||||
before_create :fingerprint_validation
|
||||
before_update :fingerprint_validation
|
||||
|
||||
=begin
|
||||
|
||||
store new device for user if device not already known
|
||||
@@ -34,7 +37,8 @@ store new device for user if device not already known
|
||||
|
||||
# find device by fingerprint
|
||||
device_exists_by_fingerprint = false
|
||||
if fingerprint
|
||||
if fingerprint.present?
|
||||
UserDevice.fingerprint_validation(fingerprint)
|
||||
user_devices = UserDevice.where(
|
||||
user_id: user_id,
|
||||
fingerprint: fingerprint,
|
||||
@@ -221,4 +225,24 @@ delete device devices of user
|
||||
def self.remove(user_id)
|
||||
UserDevice.where(user_id: user_id).destroy_all
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
check fingerprint string
|
||||
|
||||
UserDevice.fingerprint_validation(fingerprint)
|
||||
|
||||
=end
|
||||
|
||||
def self.fingerprint_validation(fingerprint)
|
||||
return true if fingerprint.blank?
|
||||
raise Exceptions::UnprocessableEntity, "fingerprint is #{fingerprint.to_s.length} chars but can only be 160 chars!" if fingerprint.to_s.length > 160
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fingerprint_validation
|
||||
UserDevice.fingerprint_validation(fingerprint)
|
||||
end
|
||||
end
|
||||
|
||||
76
app/views/mailer/application_wrapper.html.erb
Normal file
76
app/views/mailer/application_wrapper.html.erb
Normal file
@@ -0,0 +1,76 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||
<style type="text/css">
|
||||
body {
|
||||
###html_email_css_font###;
|
||||
}
|
||||
img {
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
}
|
||||
a img {
|
||||
border: none;
|
||||
}
|
||||
table td {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
border: none;
|
||||
table-layout: auto;
|
||||
display: block;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
word-break: keep-all;
|
||||
}
|
||||
table,
|
||||
pre,
|
||||
blockquote {
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
td, th {
|
||||
padding: 7px 12px;
|
||||
border: 1px solid hsl(0,0%,87%);
|
||||
}
|
||||
th {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
tbody tr:nth-child(even) {
|
||||
background: hsl(0,0%,97%);
|
||||
}
|
||||
col {
|
||||
width: auto;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
code {
|
||||
border: none;
|
||||
background: hsl(0,0%,97%);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
blockquote {
|
||||
padding: 8px 12px;
|
||||
border-left: 5px solid #eee;
|
||||
}
|
||||
pre {
|
||||
padding: 12px 15px;
|
||||
font-size: 13px;
|
||||
line-height: 1.45;
|
||||
background: hsl(0,0%,97%);
|
||||
white-space: pre-wrap;
|
||||
border-radius: 3px;
|
||||
border: none;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="###html_email_css_font###">###html###</body>
|
||||
</html>
|
||||
16
app/views/tests/model_binding.html.erb
Normal file
16
app/views/tests/model_binding.html.erb
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
<link rel="stylesheet" href="/assets/tests/qunit-1.21.0.css">
|
||||
<script src="/assets/tests/qunit-1.21.0.js"></script>
|
||||
<script src="/assets/tests/model_binding.js"></script>
|
||||
|
||||
<style type="text/css">
|
||||
body {
|
||||
padding-top: 0px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
</script>
|
||||
|
||||
<div id="qunit" class="u-dontfold"></div>
|
||||
|
||||
@@ -2,6 +2,9 @@ require File.expand_path('../boot', __FILE__)
|
||||
|
||||
require 'rails/all'
|
||||
|
||||
# DO NOT REMOVE THIS LINE - see issue #2037
|
||||
Bundler.setup
|
||||
|
||||
# Require the gems listed in Gemfile, including any gems
|
||||
# you've limited to :test, :development, or :production.
|
||||
Bundler.require(*Rails.groups)
|
||||
@@ -35,6 +38,7 @@ module Zammad
|
||||
'observer::_ticket::_ref_object_touch',
|
||||
'observer::_ticket::_online_notification_seen',
|
||||
'observer::_ticket::_stats_reopen',
|
||||
'observer::_ticket::_escalation_update',
|
||||
'observer::_tag::_ticket_history',
|
||||
'observer::_user::_ref_object_touch',
|
||||
'observer::_user::_ticket_organization',
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
if ActiveRecord::Base.connection.table_exists?( Setting.table_name )
|
||||
|
||||
html_email_css_font = Setting.get('html_email_css_font')
|
||||
|
||||
Rails.application.config.html_email_body = <<~HERE
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||
<style type="text/css">
|
||||
body {
|
||||
#{html_email_css_font};
|
||||
}
|
||||
img {
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
}
|
||||
a img {
|
||||
border: none;
|
||||
}
|
||||
table td {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
border: none;
|
||||
table-layout: auto;
|
||||
display: block;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
word-break: keep-all;
|
||||
}
|
||||
table,
|
||||
pre,
|
||||
blockquote {
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
td, th {
|
||||
padding: 7px 12px;
|
||||
border: 1px solid hsl(0,0%,87%);
|
||||
}
|
||||
th {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
tbody tr:nth-child(even) {
|
||||
background: hsl(0,0%,97%);
|
||||
}
|
||||
col {
|
||||
width: auto;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
code {
|
||||
border: none;
|
||||
background: hsl(0,0%,97%);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
blockquote {
|
||||
padding: 8px 12px;
|
||||
border-left: 5px solid #eee;
|
||||
}
|
||||
pre {
|
||||
padding: 12px 15px;
|
||||
font-size: 13px;
|
||||
line-height: 1.45;
|
||||
background: hsl(0,0%,97%);
|
||||
white-space: pre-wrap;
|
||||
border-radius: 3px;
|
||||
border: none;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="#{html_email_css_font}">###html###</body>
|
||||
</html>
|
||||
HERE
|
||||
end
|
||||
@@ -50,3 +50,11 @@ Rails.application.config.middleware.use OmniAuth::Builder do
|
||||
provider :weibo_database, 'not_change_will_be_set_by_database', 'not_change_will_be_set_by_database'
|
||||
|
||||
end
|
||||
|
||||
# This fixes issue #1642 and is required for setups in which Zammad is used
|
||||
# with a reverse proxy (like e.g. NGINX) handling the HTTPS stuff.
|
||||
# This leads to the generation of a wrong redirect_uri because Rack detects a
|
||||
# HTTP request which breaks OAuth2.
|
||||
OmniAuth.config.full_host = proc {
|
||||
"#{Setting.get('http_type')}://#{Setting.get('fqdn')}"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ Zammad::Application.routes.draw do
|
||||
api_path = Rails.configuration.api_path
|
||||
|
||||
# calendars
|
||||
match api_path + '/calendars_init', to: 'calendars#init', via: :get
|
||||
match api_path + '/calendars', to: 'calendars#index', via: :get
|
||||
match api_path + '/calendars/:id', to: 'calendars#show', via: :get
|
||||
match api_path + '/calendars', to: 'calendars#create', via: :post
|
||||
|
||||
@@ -3,6 +3,7 @@ Zammad::Application.routes.draw do
|
||||
match '/tests_core', to: 'tests#core', via: :get
|
||||
match '/tests_ui', to: 'tests#ui', via: :get
|
||||
match '/tests_model', to: 'tests#model', via: :get
|
||||
match '/tests_model_binding', to: 'tests#model_binding', via: :get
|
||||
match '/tests_model_ui', to: 'tests#model_ui', via: :get
|
||||
match '/tests_form', to: 'tests#form', via: :get
|
||||
match '/tests_form_tree_select', to: 'tests#form_tree_select', via: :get
|
||||
|
||||
@@ -21,12 +21,13 @@ class CheckForObjectAttributes < ActiveRecord::Migration[5.1]
|
||||
end
|
||||
|
||||
def fix_nil_data_option(attribute)
|
||||
return if attribute[:data_option].is_a?(Hash)
|
||||
return if attribute[:data_option].is_a?(Hash) || attribute[:data_option][:options].is_a?(Array)
|
||||
attribute[:data_option] = {}
|
||||
end
|
||||
|
||||
def fix_options(attribute)
|
||||
return if attribute[:data_option][:options].is_a?(Hash)
|
||||
return if attribute[:data_option][:options].is_a?(Array)
|
||||
attribute[:data_option][:options] = {}
|
||||
end
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
class CleanupUserPreferencesNotificationSound < ActiveRecord::Migration[5.1]
|
||||
def up
|
||||
User.with_permissions('ticket.agent').each do |user|
|
||||
local_to_h!(user.preferences)
|
||||
user.save!
|
||||
end
|
||||
|
||||
User.with_permissions('ticket.agent').each do |user|
|
||||
next if !user.preferences
|
||||
next if !user.preferences[:notification_sound]
|
||||
next if !user.preferences[:notification_sound][:enabled]
|
||||
if user.preferences[:notification_sound][:enabled] == 'true'
|
||||
user.preferences[:notification_sound][:enabled] = true
|
||||
user.save!
|
||||
next
|
||||
end
|
||||
next if user.preferences[:notification_sound][:enabled] != 'false'
|
||||
user.preferences[:notification_sound][:enabled] = false
|
||||
user.save!
|
||||
next
|
||||
end
|
||||
end
|
||||
|
||||
def local_to_h!(value)
|
||||
if value.class == ActionController::Parameters
|
||||
value = value.permit!.to_h
|
||||
end
|
||||
if value.class == Hash || value.class == ActiveSupport::HashWithIndifferentAccess
|
||||
value.each_key do |local_key|
|
||||
value[local_key] = local_to_h!(value[local_key])
|
||||
end
|
||||
end
|
||||
value
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,44 @@
|
||||
class Issue1660FixTreeSelectConfigurations < ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
|
||||
# return if it's a new setup
|
||||
return if !Setting.find_by(name: 'system_init_done')
|
||||
|
||||
return if attributes.blank?
|
||||
attributes.each do |attribute|
|
||||
|
||||
next if attribute.data_option.blank?
|
||||
next if attribute.data_option[:options].blank?
|
||||
|
||||
fixed_options = fix(attribute.data_option[:options])
|
||||
|
||||
attribute.data_option[:options] = fixed_options
|
||||
|
||||
attribute.save!
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def attributes
|
||||
@attributes ||= ObjectManager::Attribute.where(data_type: 'tree_select')
|
||||
end
|
||||
|
||||
def fix(options, namespace = nil)
|
||||
|
||||
options.tap do |ref|
|
||||
|
||||
ref.each do |option|
|
||||
|
||||
option_namespace = Array(namespace.dup)
|
||||
option_namespace.push(option['name'])
|
||||
|
||||
option['value'] = option_namespace.join('::')
|
||||
|
||||
next if option['children'].blank?
|
||||
|
||||
option['children'] = fix(option['children'], option_namespace)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
class Issue1905ExchangeLoginFromRemoteId < ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
|
||||
# return if it's a new setup
|
||||
return if !Setting.find_by(name: 'system_init_done')
|
||||
|
||||
config = Import::Exchange.config
|
||||
return if config.blank?
|
||||
return if config[:attributes].blank?
|
||||
return if config[:attributes][:item_id].blank?
|
||||
return if config[:attributes][:item_id] != 'login'
|
||||
|
||||
config[:attributes].delete(:item_id)
|
||||
|
||||
Import::Exchange.config = config
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,57 @@
|
||||
class CleanupUserPreferencesNotificationSound2 < ActiveRecord::Migration[5.1]
|
||||
|
||||
def local_to_h!(value)
|
||||
if value.class == ActionController::Parameters
|
||||
value = value.permit!.to_h
|
||||
end
|
||||
if value.class == Hash || value.class == ActiveSupport::HashWithIndifferentAccess
|
||||
value.each_key do |local_key|
|
||||
value[local_key] = local_to_h!(value[local_key])
|
||||
end
|
||||
end
|
||||
value
|
||||
end
|
||||
|
||||
def local_clear_preferences(user)
|
||||
return false if !user
|
||||
return false if !user.preferences
|
||||
return false if !user.preferences[:notification_sound]
|
||||
return false if !user.preferences[:notification_sound][:enabled]
|
||||
if user.preferences[:notification_sound][:enabled] == 'true'
|
||||
user.preferences[:notification_sound][:enabled] = true
|
||||
user.save!
|
||||
return true
|
||||
end
|
||||
return false if user.preferences[:notification_sound][:enabled] != 'false'
|
||||
user.preferences[:notification_sound][:enabled] = false
|
||||
user.save!
|
||||
true
|
||||
end
|
||||
|
||||
def up
|
||||
User.with_permissions('ticket.agent').each do |user|
|
||||
local_to_h!(user.preferences)
|
||||
user.save!
|
||||
end
|
||||
|
||||
items = SearchIndexBackend.search('preferences.notification_sound.enabled:*', 3000, 'User') || []
|
||||
items.each do |item|
|
||||
next if !item[:id]
|
||||
user = User.find_by(id: item[:id])
|
||||
local_to_h!(user.preferences)
|
||||
local_clear_preferences(user)
|
||||
end
|
||||
|
||||
Organization.all.each do |organization|
|
||||
organization.members.each do |user|
|
||||
local_to_h!(user.preferences)
|
||||
local_clear_preferences(user)
|
||||
end
|
||||
end
|
||||
|
||||
Delayed::Job.all.each do |job|
|
||||
Delayed::Worker.new.run(job)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -55,8 +55,8 @@ class CalendarSubscriptions
|
||||
events_data.each do |event_data|
|
||||
|
||||
cal.event do |e|
|
||||
e.dtstart = event_data[:dtstart]
|
||||
e.dtend = event_data[:dtend]
|
||||
e.dtstart = Icalendar::Values::DateTime.new(event_data[:dtstart], 'tzid' => 'UTC')
|
||||
e.dtend = Icalendar::Values::DateTime.new(event_data[:dtend], 'tzid' => 'UTC')
|
||||
if event_data[:alarm]
|
||||
e.alarm do |a|
|
||||
a.action = 'DISPLAY'
|
||||
|
||||
@@ -18,42 +18,34 @@ examples how to use
|
||||
|
||||
def to_s
|
||||
strip_html
|
||||
|
||||
@template
|
||||
end
|
||||
|
||||
def strip_html
|
||||
# some browsers start adding HTML tags
|
||||
# fixes https://github.com/zammad/zammad/issues/385
|
||||
@template.gsub!(/\#\{\s*t\((.+?)\)\s*\}/m) do
|
||||
@template.gsub(/\#\{\s*t\((.+?)\)\s*\}/m) do
|
||||
content = $1
|
||||
if content =~ /^'(.+?)'$/
|
||||
"<%= t \"#{strip_content($1)}\", #{@escape} %>"
|
||||
%(<%= t "#{strip_content($1)}", #{@escape} %>)
|
||||
else
|
||||
"<%= t d\"#{strip_variable(content)}\", #{@escape} %>"
|
||||
%(<%= t d"#{strip_variable(content)}", #{@escape} %>)
|
||||
end
|
||||
end
|
||||
@template.gsub!(/\#\{\s*config\.(.+?)\s*\}/m) do
|
||||
"<%= c \"#{strip_variable($1)}\", #{@escape} %>"
|
||||
end
|
||||
@template.gsub!(/\#\{(.*?)\}/m) do
|
||||
"<%= d \"#{strip_variable($1)}\", #{@escape} %>"
|
||||
end.gsub(/\#\{\s*config\.(.+?)\s*\}/m) do
|
||||
%(<%= c "#{strip_variable($1)}", #{@escape} %>)
|
||||
end.gsub(/\#\{(.*?)\}/m) do
|
||||
%(<%= d "#{strip_variable($1)}", #{@escape} %>)
|
||||
end
|
||||
end
|
||||
|
||||
def strip_content(string)
|
||||
return string if !string
|
||||
string.gsub!(/\t|\r|\n/, '')
|
||||
string.gsub!(/"/, '\"')
|
||||
string
|
||||
string&.gsub(/\t|\r|\n/, '')
|
||||
&.gsub(/"/, '\"')
|
||||
end
|
||||
|
||||
def strip_variable(string)
|
||||
return string if !string
|
||||
string.gsub!(/\t|\r|\n|"|'|§|;/, '')
|
||||
string.gsub!(/\s*/, '')
|
||||
string.gsub!(/<.+?>/, '')
|
||||
string
|
||||
string&.gsub(/\t|\r|\n|"|'|§|;/, '')
|
||||
&.gsub(/\s*/, '')
|
||||
&.gsub(/<.+?>/, '')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -606,12 +606,12 @@ return true if backend is configured
|
||||
|
||||
message = if response&.error&.match?('Connection refused')
|
||||
"Elasticsearch is not reachable, probably because it's not running or even installed."
|
||||
elsif url.end_with?('pipeline/zammad-attachment') && response.code == 400
|
||||
elsif url.end_with?('pipeline/zammad-attachment', 'pipeline=zammad-attachment') && response.code == 400
|
||||
'The installed attachment plugin could not handle the request payload. Ensure that the correct attachment plugin is installed (5.6 => ingest-attachment, 2.4 - 5.5 => mapper-attachments).'
|
||||
else
|
||||
'Check the response and payload for detailed information: '
|
||||
end
|
||||
|
||||
"#{prefix}#{message}#{suffix}"
|
||||
"#{prefix} #{message}#{suffix}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,7 +8,8 @@ class Sequencer
|
||||
[
|
||||
'Import::Exchange::FolderContact::RemoteId',
|
||||
'Import::Common::RemoteId::CaseSensitive',
|
||||
'Import::Exchange::FolderContact::Mapping',
|
||||
'Import::Exchange::FolderContact::Mapping::FromConfig',
|
||||
'Import::Exchange::FolderContact::Mapping::Login',
|
||||
'Import::Common::Model::Skip::Blank::Mapped',
|
||||
'Common::ModelClass::User',
|
||||
'Import::Exchange::FolderContact::ExternalSyncSource',
|
||||
|
||||
@@ -7,7 +7,6 @@ class Sequencer
|
||||
include ::Sequencer::Unit::Import::Common::Mapping::Mixin::ProvideMapped
|
||||
|
||||
uses :resource
|
||||
provides :mapped
|
||||
|
||||
def process
|
||||
provide_mapped do
|
||||
|
||||
@@ -12,6 +12,7 @@ class Sequencer
|
||||
# model_nos
|
||||
# model_name
|
||||
# model_name
|
||||
# model_name
|
||||
without_double_underscores.gsub(/_id(s?)$/, '_no\1')
|
||||
end
|
||||
|
||||
@@ -20,6 +21,7 @@ class Sequencer
|
||||
# model_ids
|
||||
# model_name
|
||||
# model_name
|
||||
# model_name
|
||||
without_spaces_and_slashes.gsub(/_{2,}/, '_')
|
||||
end
|
||||
|
||||
@@ -28,7 +30,17 @@ class Sequencer
|
||||
# model_ids
|
||||
# model___name
|
||||
# model_name
|
||||
unsanitized_name.gsub(%r{[\s\/]}, '_').underscore
|
||||
# model_name
|
||||
transliterated.gsub(%r{[\s\/]}, '_').underscore
|
||||
end
|
||||
|
||||
def transliterated
|
||||
# Model ID
|
||||
# Model IDs
|
||||
# Model / Name
|
||||
# Model Name
|
||||
# Model Name
|
||||
::ActiveSupport::Inflector.transliterate(unsanitized_name, '_'.freeze)
|
||||
end
|
||||
|
||||
def unsanitized_name
|
||||
@@ -36,6 +48,9 @@ class Sequencer
|
||||
# Model IDs
|
||||
# Model / Name
|
||||
# Model Name
|
||||
# rubocop:disable Style/AsciiComments
|
||||
# Mödel Nâmé
|
||||
# rubocop:enable Style/AsciiComments
|
||||
raise 'Missing implementation for unsanitized_name method'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Exchange
|
||||
module FolderContact
|
||||
class Mapping < Sequencer::Unit::Import::Common::Mapping::FlatKeys
|
||||
|
||||
uses :import_job
|
||||
|
||||
private
|
||||
|
||||
def mapping
|
||||
from_import_job || ::Import::Exchange.config[:attributes]
|
||||
end
|
||||
|
||||
def from_import_job
|
||||
return if !state.provided?(:import_job)
|
||||
payload = import_job.payload
|
||||
return if payload.blank?
|
||||
payload[:ews_attributes]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Exchange
|
||||
module FolderContact
|
||||
module Mapping
|
||||
class FromConfig < Sequencer::Unit::Import::Common::Mapping::FlatKeys
|
||||
|
||||
uses :import_job
|
||||
|
||||
private
|
||||
|
||||
def mapping
|
||||
from_import_job || ::Import::Exchange.config[:attributes]
|
||||
end
|
||||
|
||||
def from_import_job
|
||||
return if !state.provided?(:import_job)
|
||||
payload = import_job.payload
|
||||
return if payload.blank?
|
||||
payload[:ews_attributes]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Exchange
|
||||
module FolderContact
|
||||
module Mapping
|
||||
class Login < Sequencer::Unit::Import::Common::Mapping::FlatKeys
|
||||
include ::Sequencer::Unit::Import::Common::Mapping::Mixin::ProvideMapped
|
||||
|
||||
uses :remote_id
|
||||
|
||||
def process
|
||||
provide_mapped do
|
||||
{
|
||||
login: remote_id
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -9,9 +9,12 @@ class Sequencer
|
||||
uses :resource
|
||||
|
||||
def value
|
||||
method_name = resource.via.channel.to_sym
|
||||
return if !respond_to?(method_name, true)
|
||||
send(method_name)
|
||||
return if private_methods(false).exclude?(value_method_name)
|
||||
send(value_method_name)
|
||||
end
|
||||
|
||||
def value_method_name
|
||||
@value_method_name ||= resource.via.channel.to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,12 +7,14 @@ class Stats::TicketWaitingTime
|
||||
# get users groups
|
||||
group_ids = user.group_ids_access('full')
|
||||
|
||||
own_waiting = Ticket.where(
|
||||
'owner_id = ? AND group_id IN (?) AND updated_at > ?', user.id, group_ids, Time.zone.today
|
||||
)
|
||||
all_waiting = Ticket.where(
|
||||
'group_id IN (?) AND updated_at > ?', group_ids, Time.zone.today
|
||||
)
|
||||
own_waiting = []
|
||||
all_waiting = []
|
||||
Ticket.where('group_id IN (?) AND updated_at > ?', group_ids.sort, Time.zone.today).pluck(:id, :owner_id).each do |ticket|
|
||||
all_waiting.push ticket[0]
|
||||
if ticket[1] == user.id
|
||||
own_waiting.push ticket[0]
|
||||
end
|
||||
end
|
||||
|
||||
handling_time = calculate_average(own_waiting, Time.zone.today)
|
||||
if handling_time.positive?
|
||||
@@ -51,20 +53,24 @@ class Stats::TicketWaitingTime
|
||||
result
|
||||
end
|
||||
|
||||
def self.calculate_average(tickets, start_time)
|
||||
def self.calculate_average(ticket_ids, start_time)
|
||||
average_time = 0
|
||||
count_articles = 0
|
||||
last_ticket_id = nil
|
||||
count_time = nil
|
||||
|
||||
tickets.each do |ticket|
|
||||
count_time = 0
|
||||
ticket.articles.joins(:type).where('ticket_articles.created_at > ? AND ticket_articles.internal = ? AND ticket_article_types.communication = ?', start_time, false, true).each do |article|
|
||||
if article.sender.name == 'Customer'
|
||||
count_time = article.created_at.to_i
|
||||
elsif count_time.positive?
|
||||
average_time += article.created_at.to_i - count_time
|
||||
count_articles += 1
|
||||
count_time = 0
|
||||
end
|
||||
Ticket::Article.joins(:type).joins(:sender).where('ticket_articles.ticket_id IN (?) AND ticket_articles.created_at > ? AND ticket_articles.internal = ? AND ticket_article_types.communication = ?', ticket_ids, start_time, false, true).order(:ticket_id, :created_at).pluck(:created_at, :sender_id, :ticket_id, :id).each do |article|
|
||||
if last_ticket_id != article[2]
|
||||
last_ticket_id = article[2]
|
||||
count_time = 0
|
||||
end
|
||||
sender = Ticket::Article::Sender.lookup(id: article[1])
|
||||
if sender.name == 'Customer'
|
||||
count_time = article[0].to_i
|
||||
elsif count_time.positive?
|
||||
average_time += article[0].to_i - count_time
|
||||
count_articles += 1
|
||||
count_time = 0
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ test("form elements check", function() {
|
||||
datetime1: Date.parse('2015-01-11T12:40:00Z'),
|
||||
checkbox1: [],
|
||||
checkbox2: '1',
|
||||
boolean1: true,
|
||||
boolean2: false,
|
||||
}
|
||||
new App.ControllerForm({
|
||||
el: el,
|
||||
@@ -49,6 +51,9 @@ test("form elements check", function() {
|
||||
{ name: 'datetime2', display: 'Datetime2', tag: 'datetime', null: false, default: defaults['datetime2'] },
|
||||
{ name: 'checkbox1', display: 'Checkbox1', tag: 'checkbox', null: false, default: defaults['checkbox1'], options: { a: 'AA', b: 'BB' } },
|
||||
{ name: 'checkbox2', display: 'Checkbox2', tag: 'checkbox', null: false, default: defaults['checkbox2'], options: { 1: '11' } },
|
||||
{ name: 'boolean1', display: 'Boolean1', tag: 'boolean', null: false, default: defaults['boolean1'] },
|
||||
{ name: 'boolean2', display: 'Boolean2', tag: 'boolean', null: false, default: defaults['boolean2'] },
|
||||
{ name: 'boolean3', display: 'Boolean3', tag: 'boolean', null: false, default: defaults['boolean3'] },
|
||||
]
|
||||
},
|
||||
autofocus: true
|
||||
@@ -108,6 +113,9 @@ test("form elements check", function() {
|
||||
equal(el.find('[name="checkbox1"]').last().is(":checked"), false)
|
||||
equal(el.find('[name="checkbox2"]').is(":checked"), true)
|
||||
|
||||
equal(el.find('[name="boolean1"]').val(), 'true')
|
||||
equal(el.find('[name="boolean1"]').val(), 'true')
|
||||
equal(el.find('[name="boolean2"]').val(), 'false')
|
||||
});
|
||||
|
||||
test("form params check", function() {
|
||||
@@ -147,6 +155,8 @@ test("form params check", function() {
|
||||
checkbox3: 'd',
|
||||
radiobox1: undefined,
|
||||
radiobox2: 'a',
|
||||
boolean1: true,
|
||||
boolean2: false,
|
||||
}
|
||||
new App.ControllerForm({
|
||||
el: el,
|
||||
@@ -192,6 +202,9 @@ test("form params check", function() {
|
||||
{ name: 'checkbox4', display: 'Checkbox4', tag: 'checkbox', null: false, default: defaults['checkbox4'], options: { aa: 'AA', bb: 'BB' } },
|
||||
{ name: 'radiobox1', display: 'Radiobox1', tag: 'radio', null: false, default: defaults['radiobox1'], options: { a: 'AA', b: 'BB' } },
|
||||
{ name: 'radiobox2', display: 'Radiobox2', tag: 'radio', null: false, default: defaults['radiobox2'], options: { a: '11' } },
|
||||
{ name: 'boolean1', display: 'Boolean1', tag: 'boolean', null: false, default: defaults['boolean1'] },
|
||||
{ name: 'boolean2', display: 'Boolean2', tag: 'boolean', null: false, default: defaults['boolean2'] },
|
||||
{ name: 'boolean3', display: 'Boolean3', tag: 'boolean', null: false, default: defaults['boolean3'] },
|
||||
],
|
||||
},
|
||||
params: defaults,
|
||||
@@ -296,6 +309,9 @@ test("form params check", function() {
|
||||
checkbox4: [],
|
||||
radiobox1: undefined,
|
||||
radiobox2: 'a',
|
||||
boolean1: true,
|
||||
boolean2: false,
|
||||
boolean3: true,
|
||||
}
|
||||
deepEqual(params, test_params, 'form param check')
|
||||
|
||||
@@ -995,5 +1011,290 @@ test("form params check direct", function() {
|
||||
//console.log('params', params)
|
||||
//console.log('test_params', test_params)
|
||||
deepEqual(params, test_params, 'form param check')
|
||||
});
|
||||
|
||||
test("object manager form 1", function() {
|
||||
|
||||
$('#forms').append('<hr><h1>object manager 1</h1><form id="form11"></form>')
|
||||
var el = $('#form11')
|
||||
|
||||
var defaults = {}
|
||||
new App.ControllerForm({
|
||||
el: el,
|
||||
model: {
|
||||
configure_attributes: [
|
||||
{ name: 'data_type', display: 'Format', tag: 'object_manager_attribute', null: false },
|
||||
],
|
||||
},
|
||||
params: $.extend(defaults, { object: 'Ticket' }),
|
||||
autofocus: true
|
||||
});
|
||||
|
||||
var params = App.ControllerForm.params(el)
|
||||
var test_params = {
|
||||
data_option: {
|
||||
default: "",
|
||||
maxlength: 120,
|
||||
type: "text"
|
||||
},
|
||||
data_type: "input",
|
||||
screens: {
|
||||
create_middle: {
|
||||
"ticket.agent": {
|
||||
shown: true,
|
||||
required: false,
|
||||
},
|
||||
"ticket.customer": {
|
||||
shown: true,
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
edit: {
|
||||
"ticket.agent": {
|
||||
shown: true,
|
||||
required: false,
|
||||
},
|
||||
"ticket.customer": {
|
||||
shown: true,
|
||||
required: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deepEqual(params, test_params, 'form param check')
|
||||
|
||||
el.find('[name=data_type]').val('datetime').trigger('change')
|
||||
|
||||
params = App.ControllerForm.params(el)
|
||||
var test_params = {
|
||||
data_option: {
|
||||
diff: 24,
|
||||
future: true,
|
||||
past: true
|
||||
},
|
||||
data_type: "datetime",
|
||||
screens: {
|
||||
create_middle: {
|
||||
"ticket.agent": {
|
||||
shown: true,
|
||||
required: false,
|
||||
},
|
||||
"ticket.customer": {
|
||||
shown: true,
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
edit: {
|
||||
"ticket.agent": {
|
||||
shown: true,
|
||||
required: false,
|
||||
},
|
||||
"ticket.customer": {
|
||||
shown: true,
|
||||
required: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
deepEqual(params, test_params, 'form param check')
|
||||
|
||||
});
|
||||
|
||||
test("object manager form 2", function() {
|
||||
|
||||
$('#forms').append('<hr><h1>object manager 2</h1><form id="form12"></form>')
|
||||
var el = $('#form12')
|
||||
|
||||
var defaults = {
|
||||
id: 123,
|
||||
data_option: {
|
||||
default: "",
|
||||
maxlength: 120,
|
||||
type: "text"
|
||||
},
|
||||
data_type: "input",
|
||||
screens: {
|
||||
create_middle: {
|
||||
"ticket.agent": {
|
||||
shown: true,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
"ticket.agent": {
|
||||
shown: true,
|
||||
required: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new App.ControllerForm({
|
||||
el: el,
|
||||
model: {
|
||||
configure_attributes: [
|
||||
{ name: 'data_type', display: 'Format', tag: 'object_manager_attribute', null: false },
|
||||
],
|
||||
},
|
||||
params: $.extend(defaults, { object: 'Ticket' }),
|
||||
autofocus: true
|
||||
});
|
||||
|
||||
var params = App.ControllerForm.params(el)
|
||||
var test_params = {
|
||||
data_option: {
|
||||
default: "",
|
||||
maxlength: 120,
|
||||
type: "text"
|
||||
},
|
||||
data_type: "input",
|
||||
screens: {
|
||||
create_middle: {
|
||||
"ticket.agent": {
|
||||
shown: true,
|
||||
required: false,
|
||||
},
|
||||
"ticket.customer": {
|
||||
shown: false,
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
edit: {
|
||||
"ticket.agent": {
|
||||
shown: true,
|
||||
required: false,
|
||||
},
|
||||
"ticket.customer": {
|
||||
shown: false,
|
||||
required: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deepEqual(params, test_params, 'form param check')
|
||||
|
||||
});
|
||||
|
||||
test("object manager form 3", function() {
|
||||
|
||||
$('#forms').append('<hr><h1>object manager 3</h1><form id="form13"></form>')
|
||||
var el = $('#form13')
|
||||
|
||||
var defaults = {}
|
||||
new App.ControllerForm({
|
||||
el: el,
|
||||
model: {
|
||||
configure_attributes: [
|
||||
{ name: 'data_type', display: 'Format', tag: 'object_manager_attribute', null: false },
|
||||
],
|
||||
},
|
||||
params: $.extend(defaults, { object: 'Ticket' }),
|
||||
autofocus: true
|
||||
});
|
||||
|
||||
var params = App.ControllerForm.params(el)
|
||||
var test_params = {
|
||||
data_option: {
|
||||
default: "",
|
||||
maxlength: 120,
|
||||
type: "text"
|
||||
},
|
||||
data_type: "input",
|
||||
screens: {
|
||||
create_middle: {
|
||||
"ticket.agent": {
|
||||
shown: true,
|
||||
required: false,
|
||||
},
|
||||
"ticket.customer": {
|
||||
shown: true,
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
edit: {
|
||||
"ticket.agent": {
|
||||
shown: true,
|
||||
required: false,
|
||||
},
|
||||
"ticket.customer": {
|
||||
shown: true,
|
||||
required: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deepEqual(params, test_params, 'form param check')
|
||||
|
||||
el.find('[name="screens::create_middle::ticket.customer::shown"]').click()
|
||||
el.find('[name="screens::edit::ticket.customer::shown"]').click()
|
||||
|
||||
params = App.ControllerForm.params(el)
|
||||
test_params = {
|
||||
data_option: {
|
||||
default: "",
|
||||
maxlength: 120,
|
||||
type: "text"
|
||||
},
|
||||
data_type: "input",
|
||||
screens: {
|
||||
create_middle: {
|
||||
"ticket.agent": {
|
||||
shown: true,
|
||||
required: false,
|
||||
},
|
||||
"ticket.customer": {
|
||||
shown: false,
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
edit: {
|
||||
"ticket.agent": {
|
||||
shown: true,
|
||||
required: false,
|
||||
},
|
||||
"ticket.customer": {
|
||||
shown: false,
|
||||
required: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
deepEqual(params, test_params, 'form param check')
|
||||
|
||||
});
|
||||
|
||||
test("form select with empty option list", function() {
|
||||
|
||||
$('#forms').append('<hr><h1>form select with empty option list</h1><form id="form14"></form>')
|
||||
var el = $('#form14')
|
||||
var defaults = {}
|
||||
new App.ControllerForm({
|
||||
el: el,
|
||||
model: {
|
||||
configure_attributes: [
|
||||
{ name: 'select1', display: 'Select1', tag: 'select', null: true, default: '', options: {}, relation: '', maxlength: 255 },
|
||||
{ name: 'select2', display: 'Select2', tag: 'select', null: true, default: '', options: {}, relation: '', maxlength: 255, nulloption: true },
|
||||
{ name: 'select3', display: 'Select3', tag: 'select', null: true, default: '', options: { undefined: 'A', null: 'B'} },
|
||||
{ name: 'select4', display: 'Select4', tag: 'select', null: true, default: '', options: { 'A': undefined, 'B': null} },
|
||||
{ name: 'select5', display: 'Select5', tag: 'select', null: true, default: 'A', options: { 'A': undefined, 'B': null} },
|
||||
{ name: 'select6', display: 'Select6', tag: 'select', null: true, default: undefined, options: { 'A': undefined, 'B': null} },
|
||||
],
|
||||
},
|
||||
params: defaults,
|
||||
autofocus: true
|
||||
});
|
||||
|
||||
params = App.ControllerForm.params(el)
|
||||
test_params = {
|
||||
select2: '',
|
||||
select3: 'undefined',
|
||||
select4: 'B',
|
||||
select5: 'A',
|
||||
select6: 'B',
|
||||
}
|
||||
deepEqual(params, test_params)
|
||||
|
||||
});
|
||||
|
||||
@@ -2,14 +2,8 @@
|
||||
// form
|
||||
test('form checks', function() {
|
||||
|
||||
// use unsorted order to check if the frontend is sorting correctly
|
||||
App.TicketPriority.refresh([
|
||||
{
|
||||
id: 1,
|
||||
name: '1 low',
|
||||
note: 'some note 1',
|
||||
active: true,
|
||||
created_at: '2014-06-10T11:17:34.000Z',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '2 normal',
|
||||
@@ -38,6 +32,13 @@ test('form checks', function() {
|
||||
active: false,
|
||||
created_at: '2014-06-10T10:17:56.000Z',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: '1 low',
|
||||
note: 'some note 1',
|
||||
active: true,
|
||||
created_at: '2014-06-10T11:17:34.000Z',
|
||||
},
|
||||
])
|
||||
|
||||
App.TicketState.refresh([
|
||||
@@ -145,8 +146,8 @@ test('form checks', function() {
|
||||
el: el,
|
||||
model: {
|
||||
configure_attributes: [
|
||||
{ name: 'priority1_id', display: 'Priroity1', tag: 'select', relation: 'TicketPriority', null: true },
|
||||
{ name: 'priority2_id', display: 'Priroity2', tag: 'select', multiple: true, relation: 'TicketPriority', null: true },
|
||||
{ name: 'priority1_id', display: 'Priroity1', tag: 'select', relation: 'TicketPriority', null: true, options: {} },
|
||||
{ name: 'priority2_id', display: 'Priroity2', tag: 'select', multiple: true, relation: 'TicketPriority', null: true, options: {} },
|
||||
{ name: 'priority3_id', display: 'Priroity3', tag: 'select', relation: 'TicketPriority', null: true },
|
||||
{ name: 'escalation_times', display: 'Times', tag: 'sla_times', null: true },
|
||||
{ name: 'working_hours', display: 'Hours', tag: 'business_hours', null: true },
|
||||
|
||||
@@ -2603,6 +2603,45 @@ test('check getRecipientArticle format', function() {
|
||||
verify = App.Utils.getRecipientArticle(ticket, article, agent, article.type, email_addresses, false)
|
||||
deepEqual(verify, result)
|
||||
|
||||
agent = {
|
||||
login: 'login',
|
||||
firstname: 'firstname',
|
||||
lastname: 'lastname',
|
||||
email: 'agent@example.com',
|
||||
}
|
||||
ticket = {
|
||||
customer: agent,
|
||||
}
|
||||
article = {
|
||||
message_id: 'message_id20',
|
||||
created_by: agent,
|
||||
type: {
|
||||
name: 'email',
|
||||
},
|
||||
sender: {
|
||||
name: 'Agent',
|
||||
},
|
||||
from: 'Agent <Agent@Example.com>',
|
||||
to: 'somebodyelse@example.com, Zammad <zammad@example.com>',
|
||||
cc: '',
|
||||
}
|
||||
result = {
|
||||
to: 'agent@example.com',
|
||||
cc: '',
|
||||
body: '',
|
||||
in_reply_to: 'message_id20',
|
||||
}
|
||||
email_addresses = [
|
||||
{
|
||||
email: 'zammad@example.com',
|
||||
},
|
||||
{
|
||||
email: 'zammad2@example.com',
|
||||
}
|
||||
]
|
||||
verify = App.Utils.getRecipientArticle(ticket, article, agent, article.type, email_addresses, false)
|
||||
deepEqual(verify, result)
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
52
public/assets/tests/model_binding.js
Normal file
52
public/assets/tests/model_binding.js
Normal file
@@ -0,0 +1,52 @@
|
||||
window.onload = function() {
|
||||
|
||||
test("model bindings and rebinding", function() {
|
||||
|
||||
var callback_count1 = 0
|
||||
var callbacks = App.Template._callbacks
|
||||
Object.keys(callbacks).forEach(function(key) {
|
||||
callback_count1 = callback_count1 + callbacks[key].length
|
||||
});
|
||||
equal(callback_count1, 2)
|
||||
|
||||
equal(App.Template.SUBSCRIPTION_COLLECTION, undefined)
|
||||
|
||||
App.Template.clearInMemory()
|
||||
|
||||
equal(App.Template.SUBSCRIPTION_COLLECTION, undefined)
|
||||
|
||||
var callback_count2 = 0
|
||||
callbacks = App.Template._callbacks
|
||||
Object.keys(callbacks).forEach(function(key) {
|
||||
callback_count2 = callback_count2 + callbacks[key].length
|
||||
});
|
||||
equal(callback_count2, callback_count1)
|
||||
|
||||
var render = function() {}
|
||||
var subscribe_id = App.Template.subscribe(render, {initFetch: true})
|
||||
|
||||
ok(_.isObject(App.Template.SUBSCRIPTION_COLLECTION))
|
||||
ok(!_.isEmpty(App.Template.SUBSCRIPTION_COLLECTION))
|
||||
|
||||
var callback_count3 = 0
|
||||
callbacks = App.Template._callbacks
|
||||
Object.keys(callbacks).forEach(function(key) {
|
||||
callback_count3 = callback_count3 + callbacks[key].length
|
||||
});
|
||||
equal(callback_count3, 6)
|
||||
|
||||
App.Template.clearInMemory()
|
||||
|
||||
ok(_.isObject(App.Template.SUBSCRIPTION_COLLECTION))
|
||||
ok(_.isEmpty(App.Template.SUBSCRIPTION_COLLECTION))
|
||||
|
||||
var callback_count4 = 0
|
||||
callbacks = App.Template._callbacks
|
||||
Object.keys(callbacks).forEach(function(key) {
|
||||
callback_count4 = callback_count4 + callbacks[key].length
|
||||
});
|
||||
equal(callback_count4, callback_count3)
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
@@ -53,22 +53,22 @@ if [ "$RDB" == 'postgresql' ]; then
|
||||
echo " pool: 50" >> $DBFILE
|
||||
echo " timeout: 5000" >> $DBFILE
|
||||
echo " encoding: utf8" >> $DBFILE
|
||||
echo " username: root" >> $DBFILE
|
||||
echo " password: pass" >> $DBFILE
|
||||
echo " username: gitlab-runner" >> $DBFILE
|
||||
echo " password:" >> $DBFILE
|
||||
echo "test:" >> $DBFILE
|
||||
echo " adapter: postgresql" >> $DBFILE
|
||||
echo " database: $DB_TEST" >> $DBFILE
|
||||
echo " pool: 50" >> $DBFILE
|
||||
echo " timeout: 5000" >> $DBFILE
|
||||
echo " encoding: utf8" >> $DBFILE
|
||||
echo " username: root" >> $DBFILE
|
||||
echo " password: pass" >> $DBFILE
|
||||
echo " username: gitlab-runner" >> $DBFILE
|
||||
echo " password:" >> $DBFILE
|
||||
echo "development:" >> $DBFILE
|
||||
echo " adapter: postgresql" >> $DBFILE
|
||||
echo " database: $DB_DEVELOPMENT" >> $DBFILE
|
||||
echo " pool: 50" >> $DBFILE
|
||||
echo " timeout: 5000" >> $DBFILE
|
||||
echo " encoding: utf8" >> $DBFILE
|
||||
echo " username: root" >> $DBFILE
|
||||
echo " password: pass" >> $DBFILE
|
||||
echo " username: gitlab-runner" >> $DBFILE
|
||||
echo " password:" >> $DBFILE
|
||||
fi
|
||||
@@ -15,14 +15,18 @@ if [ "$LEVEL" == '1' ]; then
|
||||
# test/browser/aac_basic_richtext_test.rb
|
||||
rm test/browser/abb_one_group_test.rb
|
||||
rm test/browser/admin_channel_email_test.rb
|
||||
rm test/browser/admin_calendar_sla_test.rb
|
||||
rm test/browser/admin_object_manager_test.rb
|
||||
rm test/browser/admin_object_manager_tree_select_test.rb
|
||||
rm test/browser/admin_overview_test.rb
|
||||
rm test/browser/admin_role_test.rb
|
||||
# test/browser/agent_navigation_and_title_test.rb
|
||||
# test/browser/agent_organization_profile_test.rb
|
||||
rm test/browser/agent_ticket_attachment_test.rb
|
||||
rm test/browser/agent_ticket_auto_assignment_test.rb
|
||||
rm test/browser/agent_ticket_create_cc_tokenizer_test.rb
|
||||
rm test/browser/agent_ticket_create_reset_customer_selection_test.rb
|
||||
rm test/browser/agent_ticket_create_template_test.rb
|
||||
rm test/browser/agent_ticket_email_reply_keep_body_test.rb
|
||||
rm test/browser/agent_ticket_email_signature_test.rb
|
||||
rm test/browser/agent_ticket_link_test.rb
|
||||
@@ -78,14 +82,18 @@ elif [ "$LEVEL" == '2' ]; then
|
||||
rm test/browser/aac_basic_richtext_test.rb
|
||||
# test/browser/abb_one_group_test.rb
|
||||
rm test/browser/admin_channel_email_test.rb
|
||||
rm test/browser/admin_calendar_sla_test.rb
|
||||
rm test/browser/admin_object_manager_test.rb
|
||||
rm test/browser/admin_object_manager_tree_select_test.rb
|
||||
rm test/browser/admin_overview_test.rb
|
||||
#rm test/browser/admin_role_test.rb
|
||||
rm test/browser/agent_navigation_and_title_test.rb
|
||||
rm test/browser/agent_organization_profile_test.rb
|
||||
rm test/browser/agent_ticket_attachment_test.rb
|
||||
rm test/browser/agent_ticket_auto_assignment_test.rb
|
||||
rm test/browser/agent_ticket_create_cc_tokenizer_test.rb
|
||||
rm test/browser/agent_ticket_create_reset_customer_selection_test.rb
|
||||
rm test/browser/agent_ticket_create_template_test.rb
|
||||
rm test/browser/agent_ticket_email_reply_keep_body_test.rb
|
||||
rm test/browser/agent_ticket_email_signature_test.rb
|
||||
rm test/browser/agent_ticket_link_test.rb
|
||||
@@ -141,14 +149,18 @@ elif [ "$LEVEL" == '3' ]; then
|
||||
rm test/browser/aac_basic_richtext_test.rb
|
||||
# test/browser/abb_one_group_test.rb
|
||||
rm test/browser/admin_channel_email_test.rb
|
||||
rm test/browser/admin_calendar_sla_test.rb
|
||||
rm test/browser/admin_object_manager_test.rb
|
||||
rm test/browser/admin_object_manager_tree_select_test.rb
|
||||
rm test/browser/admin_overview_test.rb
|
||||
rm test/browser/admin_role_test.rb
|
||||
rm test/browser/agent_navigation_and_title_test.rb
|
||||
rm test/browser/agent_organization_profile_test.rb
|
||||
# test/browser/agent_ticket_attachment_test.rb
|
||||
# test/browser/agent_ticket_auto_assignment_test.rb
|
||||
#rm test/browser/agent_ticket_create_cc_tokenizer_test.rb
|
||||
# test/browser/agent_ticket_create_reset_customer_selection_test.rb
|
||||
# test/browser/agent_ticket_create_template_test.rb
|
||||
# test/browser/agent_ticket_email_reply_keep_body_test.rb
|
||||
# test/browser/agent_ticket_email_signature_test.rb
|
||||
# test/browser/agent_ticket_link_test.rb
|
||||
@@ -204,14 +216,18 @@ elif [ "$LEVEL" == '4' ]; then
|
||||
rm test/browser/aac_basic_richtext_test.rb
|
||||
# test/browser/abb_one_group_test.rb
|
||||
rm test/browser/admin_channel_email_test.rb
|
||||
rm test/browser/admin_calendar_sla_test.rb
|
||||
rm test/browser/admin_object_manager_test.rb
|
||||
rm test/browser/admin_object_manager_tree_select_test.rb
|
||||
rm test/browser/admin_overview_test.rb
|
||||
rm test/browser/admin_role_test.rb
|
||||
rm test/browser/agent_navigation_and_title_test.rb
|
||||
rm test/browser/agent_organization_profile_test.rb
|
||||
rm test/browser/agent_ticket_attachment_test.rb
|
||||
rm test/browser/agent_ticket_auto_assignment_test.rb
|
||||
rm test/browser/agent_ticket_create_cc_tokenizer_test.rb
|
||||
rm test/browser/agent_ticket_create_reset_customer_selection_test.rb
|
||||
rm test/browser/agent_ticket_create_template_test.rb
|
||||
rm test/browser/agent_ticket_email_reply_keep_body_test.rb
|
||||
rm test/browser/agent_ticket_email_signature_test.rb
|
||||
rm test/browser/agent_ticket_link_test.rb
|
||||
@@ -266,14 +282,18 @@ elif [ "$LEVEL" == '5' ]; then
|
||||
rm test/browser/aac_basic_richtext_test.rb
|
||||
# test/browser/abb_one_group_test.rb
|
||||
# test/browser/admin_channel_email_test.rb
|
||||
# test/browser/admin_calendar_sla_test.rb
|
||||
# test/browser/admin_object_manager_test.rb
|
||||
# test/browser/admin_object_manager_tree_select_test.rb
|
||||
# test/browser/admin_overview_test.rb
|
||||
rm test/browser/admin_role_test.rb
|
||||
rm test/browser/agent_navigation_and_title_test.rb
|
||||
rm test/browser/agent_organization_profile_test.rb
|
||||
rm test/browser/agent_ticket_attachment_test.rb
|
||||
rm test/browser/agent_ticket_auto_assignment_test.rb
|
||||
rm test/browser/agent_ticket_create_cc_tokenizer_test.rb
|
||||
rm test/browser/agent_ticket_create_reset_customer_selection_test.rb
|
||||
rm test/browser/agent_ticket_create_template_test.rb
|
||||
rm test/browser/agent_ticket_email_reply_keep_body_test.rb
|
||||
rm test/browser/agent_ticket_email_signature_test.rb
|
||||
rm test/browser/agent_ticket_link_test.rb
|
||||
@@ -331,14 +351,18 @@ elif [ "$LEVEL" == '6' ]; then
|
||||
rm test/browser/aac_basic_richtext_test.rb
|
||||
rm test/browser/abb_one_group_test.rb
|
||||
rm test/browser/admin_channel_email_test.rb
|
||||
rm test/browser/admin_calendar_sla_test.rb
|
||||
rm test/browser/admin_object_manager_test.rb
|
||||
rm test/browser/admin_object_manager_tree_select_test.rb
|
||||
rm test/browser/admin_overview_test.rb
|
||||
rm test/browser/admin_role_test.rb
|
||||
rm test/browser/agent_navigation_and_title_test.rb
|
||||
rm test/browser/agent_organization_profile_test.rb
|
||||
rm test/browser/agent_ticket_attachment_test.rb
|
||||
rm test/browser/agent_ticket_auto_assignment_test.rb
|
||||
rm test/browser/agent_ticket_create_cc_tokenizer_test.rb
|
||||
rm test/browser/agent_ticket_create_reset_customer_selection_test.rb
|
||||
rm test/browser/agent_ticket_create_template_test.rb
|
||||
rm test/browser/agent_ticket_email_reply_keep_body_test.rb
|
||||
rm test/browser/agent_ticket_email_signature_test.rb
|
||||
rm test/browser/agent_ticket_link_test.rb
|
||||
|
||||
@@ -2,9 +2,7 @@ require 'rails_helper'
|
||||
|
||||
RSpec.describe CheckForObjectAttributes, type: :db_migration do
|
||||
|
||||
it 'performs no action for new systems' do
|
||||
system_init_done(false)
|
||||
|
||||
it 'performs no action for new systems', system_init_done: false do
|
||||
migrate do |instance|
|
||||
expect(instance).not_to receive(:attributes)
|
||||
end
|
||||
@@ -13,8 +11,6 @@ RSpec.describe CheckForObjectAttributes, type: :db_migration do
|
||||
context 'valid [:data_option]' do
|
||||
|
||||
it 'does not change converted text attribute' do
|
||||
system_init_done
|
||||
|
||||
attribute = create(:object_manager_attribute_text)
|
||||
|
||||
expect do
|
||||
@@ -25,8 +21,6 @@ RSpec.describe CheckForObjectAttributes, type: :db_migration do
|
||||
end
|
||||
|
||||
it 'does not change select attribute' do
|
||||
system_init_done
|
||||
|
||||
attribute = create(:object_manager_attribute_select)
|
||||
|
||||
expect do
|
||||
@@ -35,13 +29,21 @@ RSpec.describe CheckForObjectAttributes, type: :db_migration do
|
||||
attribute.reload.data_option
|
||||
}
|
||||
end
|
||||
|
||||
it 'does not change tree_select attribute' do
|
||||
attribute = create(:object_manager_attribute_tree_select)
|
||||
|
||||
expect do
|
||||
migrate
|
||||
end.not_to change {
|
||||
attribute.reload.data_option
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
context '[:data_option]' do
|
||||
|
||||
it 'ensures an empty Hash' do
|
||||
system_init_done
|
||||
|
||||
attribute = create(:object_manager_attribute_text, data_option: nil)
|
||||
migrate
|
||||
attribute.reload
|
||||
@@ -53,8 +55,6 @@ RSpec.describe CheckForObjectAttributes, type: :db_migration do
|
||||
context '[:data_option][:options]' do
|
||||
|
||||
it 'ensures an empty Hash' do
|
||||
system_init_done
|
||||
|
||||
attribute = create(:object_manager_attribute_text, data_option: {})
|
||||
migrate
|
||||
attribute.reload
|
||||
@@ -63,8 +63,6 @@ RSpec.describe CheckForObjectAttributes, type: :db_migration do
|
||||
end
|
||||
|
||||
it 'converts String to Hash' do
|
||||
system_init_done
|
||||
|
||||
wrong = {
|
||||
default: '',
|
||||
options: '',
|
||||
@@ -84,8 +82,6 @@ RSpec.describe CheckForObjectAttributes, type: :db_migration do
|
||||
context '[:data_option][:relation]' do
|
||||
|
||||
it 'ensures an empty String' do
|
||||
system_init_done
|
||||
|
||||
wrong = {
|
||||
default: '',
|
||||
options: {},
|
||||
@@ -100,8 +96,6 @@ RSpec.describe CheckForObjectAttributes, type: :db_migration do
|
||||
end
|
||||
|
||||
it 'converts Hash to String' do
|
||||
system_init_done
|
||||
|
||||
wrong = {
|
||||
default: '',
|
||||
options: {},
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Issue1660FixTreeSelectConfigurations, type: :db_migration do
|
||||
|
||||
it 'corrects broken data_option options' do
|
||||
|
||||
# as provided in issue #1775
|
||||
expected = [
|
||||
{
|
||||
'name' => 'Blaak',
|
||||
'value' => 'Blaak',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'BL.-1',
|
||||
'value' => 'Blaak::BL.-1',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'BL.-1.3',
|
||||
'value' => 'Blaak::BL.-1::BL.-1.3',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.-1.19',
|
||||
'value' => 'Blaak::BL.-1::BL.-1.19',
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'name' => 'BL.0',
|
||||
'value' => 'Blaak::BL.0',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'BL.00.10a',
|
||||
'value' => 'Blaak::BL.0::BL.00.10a',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.00.7',
|
||||
'value' => 'Blaak::BL.0::BL.00.7',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.01.18',
|
||||
'value' => 'Blaak::BL.0::BL.01.18',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.00.12',
|
||||
'value' => 'Blaak::BL.0::BL.00.12',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.e0.3',
|
||||
'value' => 'Blaak::BL.0::BL.e0.3',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
'name' => 'BL.1',
|
||||
'value' => 'Blaak::BL.1',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'BL.01.i',
|
||||
'value' => 'Blaak::BL.1::BL.01.i',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
'name' => 'BL.2',
|
||||
'value' => 'Blaak::BL.2',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'BL.02.2',
|
||||
'value' => 'Blaak::BL.2::BL.02.2',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.02.4',
|
||||
'value' => 'Blaak::BL.2::BL.02.4',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.02.5',
|
||||
'value' => 'Blaak::BL.2::BL.02.5',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.02.6',
|
||||
'value' => 'Blaak::BL.2::BL.02.6',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.02.7',
|
||||
'value' => 'Blaak::BL.2::BL.02.7',
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
broken = [
|
||||
{
|
||||
'name' => 'Blaak',
|
||||
'value' => 'Blaak',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'BL.-1',
|
||||
'value' => 'Blaak::BL.-1',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'BL.-1.3',
|
||||
'value' => 'Blaak::BL.2::BL.-1.3',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.-1.19',
|
||||
'value' => 'Blaak::BL.2::BL.-1.19',
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'name' => 'BL.0',
|
||||
'value' => 'Blaak::BL.0',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'BL.00.10a',
|
||||
'value' => 'Blaak::BL.2::BL.00.10a',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.00.7',
|
||||
'value' => 'Blaak::BL.2::BL.00.7',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.01.18',
|
||||
'value' => 'Blaak::BL.2::BL.01.18',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.00.12',
|
||||
'value' => 'Blaak::BL.2::BL.00.12',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.e0.3',
|
||||
'value' => 'Blaak::BL.2::BL.e0.3',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
'name' => 'BL.1',
|
||||
'value' => 'Blaak::BL.1',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'BL.01.i',
|
||||
'value' => 'Blaak::BL.2::BL.01.i',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
'name' => 'BL.2',
|
||||
'value' => 'Blaak::BL.2',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'BL.02.2',
|
||||
'value' => 'Blaak::BL.2::BL.02.2',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.02.4',
|
||||
'value' => 'Blaak::BL.2::BL.02.4',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.02.5',
|
||||
'value' => 'Blaak::BL.2::BL.02.5',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.02.6',
|
||||
'value' => 'Blaak::BL.2::BL.02.6',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.02.7',
|
||||
'value' => 'Blaak::BL.2::BL.02.7',
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
attribute = create(:object_manager_attribute_tree_select, data_option: { options: broken })
|
||||
|
||||
expect do
|
||||
migrate
|
||||
end.to change {
|
||||
attribute.reload.data_option[:options]
|
||||
}
|
||||
|
||||
expect(attribute.data_option[:options]).to eq(expected)
|
||||
end
|
||||
|
||||
it 'performs no action for new systems', system_init_done: false do
|
||||
migrate do |instance|
|
||||
expect(instance).not_to receive(:attributes)
|
||||
end
|
||||
end
|
||||
|
||||
it 'skips blank data_option' do
|
||||
attribute = create(:object_manager_attribute_tree_select, data_option: {})
|
||||
|
||||
expect do
|
||||
migrate
|
||||
end.not_to change {
|
||||
attribute.reload.data_option
|
||||
}
|
||||
end
|
||||
|
||||
it 'skips blank data_option options' do
|
||||
attribute = create(:object_manager_attribute_tree_select, data_option: { options: [] })
|
||||
|
||||
expect do
|
||||
migrate
|
||||
end.not_to change {
|
||||
attribute.reload.data_option[:options]
|
||||
}
|
||||
end
|
||||
|
||||
it 'does not change correct data_option options' do
|
||||
attribute = create(:object_manager_attribute_tree_select)
|
||||
|
||||
expect do
|
||||
migrate
|
||||
end.not_to change {
|
||||
attribute.reload.data_option[:options]
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,82 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Issue1905ExchangeLoginFromRemoteId, type: :db_migration do
|
||||
|
||||
let(:backend) { ::Import::Exchange }
|
||||
|
||||
it 'removes :item_id from attributes' do
|
||||
|
||||
invalid_config = {
|
||||
attributes: {
|
||||
item_id: 'login',
|
||||
some: 'other',
|
||||
}
|
||||
}
|
||||
|
||||
valid_config = ActiveSupport::HashWithIndifferentAccess.new(
|
||||
attributes: {
|
||||
some: 'other',
|
||||
}
|
||||
)
|
||||
|
||||
expect(backend).to receive(:config).and_return(invalid_config)
|
||||
allow(backend).to receive(:config).and_call_original
|
||||
|
||||
migrate
|
||||
|
||||
expect(backend.config).to eq(valid_config)
|
||||
end
|
||||
|
||||
context 'no changes' do
|
||||
|
||||
it 'performs no action for new systems', system_init_done: false do
|
||||
expect(backend).not_to receive(:config)
|
||||
migrate
|
||||
end
|
||||
|
||||
shared_examples 'irrelevant config' do
|
||||
it 'does not change the config' do
|
||||
expect(backend).to receive(:config).and_return(config)
|
||||
expect(backend).not_to receive(:config=)
|
||||
migrate
|
||||
end
|
||||
end
|
||||
|
||||
context 'blank config' do
|
||||
let(:config) { nil }
|
||||
it_behaves_like 'irrelevant config'
|
||||
end
|
||||
|
||||
context 'blank attributes' do
|
||||
let(:config) do
|
||||
{
|
||||
some: 'config'
|
||||
}
|
||||
end
|
||||
it_behaves_like 'irrelevant config'
|
||||
end
|
||||
|
||||
context 'blank attribute :item_id' do
|
||||
let(:config) do
|
||||
{
|
||||
attributes: {
|
||||
some: 'mapping'
|
||||
}
|
||||
}
|
||||
end
|
||||
it_behaves_like 'irrelevant config'
|
||||
end
|
||||
|
||||
context 'attribute :item_id not mapping to login' do
|
||||
|
||||
let(:config) do
|
||||
{
|
||||
attributes: {
|
||||
item_id: 'other_local_attribute'
|
||||
}
|
||||
}
|
||||
end
|
||||
it_behaves_like 'irrelevant config'
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -72,4 +72,115 @@ FactoryBot.define do
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
factory :object_manager_attribute_tree_select, parent: :object_manager_attribute do
|
||||
data_type 'tree_select'
|
||||
data_option do
|
||||
{
|
||||
'options' => [
|
||||
{
|
||||
'name' => 'Incident',
|
||||
'value' => 'Incident',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'Hardware',
|
||||
'value' => 'Incident::Hardware',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'Monitor',
|
||||
'value' => 'Incident::Hardware::Monitor'
|
||||
},
|
||||
{
|
||||
'name' => 'Mouse',
|
||||
'value' => 'Incident::Hardware::Mouse'
|
||||
},
|
||||
{
|
||||
'name' => 'Keyboard',
|
||||
'value' => 'Incident::Hardware::Keyboard'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'name' => 'Softwareproblem',
|
||||
'value' => 'Incident::Softwareproblem',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'CRM',
|
||||
'value' => 'Incident::Softwareproblem::CRM'
|
||||
},
|
||||
{
|
||||
'name' => 'EDI',
|
||||
'value' => 'Incident::Softwareproblem::EDI'
|
||||
},
|
||||
{
|
||||
'name' => 'SAP',
|
||||
'value' => 'Incident::Softwareproblem::SAP',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'Authentication',
|
||||
'value' => 'Incident::Softwareproblem::SAP::Authentication'
|
||||
},
|
||||
{
|
||||
'name' => 'Not reachable',
|
||||
'value' => 'Incident::Softwareproblem::SAP::Not reachable'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'name' => 'MS Office',
|
||||
'value' => 'Incident::Softwareproblem::MS Office',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'Excel',
|
||||
'value' => 'Incident::Softwareproblem::MS Office::Excel'
|
||||
},
|
||||
{
|
||||
'name' => 'PowerPoint',
|
||||
'value' => 'Incident::Softwareproblem::MS Office::PowerPoint'
|
||||
},
|
||||
{
|
||||
'name' => 'Word',
|
||||
'value' => 'Incident::Softwareproblem::MS Office::Word'
|
||||
},
|
||||
{
|
||||
'name' => 'Outlook',
|
||||
'value' => 'Incident::Softwareproblem::MS Office::Outlook'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'name' => 'Service request',
|
||||
'value' => 'Service request',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'New software requirement',
|
||||
'value' => 'Service request::New software requirement'
|
||||
},
|
||||
{
|
||||
'name' => 'New hardware',
|
||||
'value' => 'Service request::New hardware'
|
||||
},
|
||||
{
|
||||
'name' => 'Consulting',
|
||||
'value' => 'Service request::Consulting'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'name' => 'Change request',
|
||||
'value' => 'Change request'
|
||||
}
|
||||
],
|
||||
'default' => '',
|
||||
'null' => true,
|
||||
'relation' => '',
|
||||
'maxlength' => 255,
|
||||
'nulloption' => true,
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,7 +11,7 @@ RSpec.describe Sequencer::Unit::Import::Common::ObjectAttribute::SanitizedName,
|
||||
|
||||
context 'sanitizes' do
|
||||
|
||||
it 'whitespaces' do
|
||||
it 'replaces whitespaces' do
|
||||
provided = process do |instance|
|
||||
expect(instance).to receive(:unsanitized_name).and_return('model name')
|
||||
end
|
||||
@@ -19,7 +19,7 @@ RSpec.describe Sequencer::Unit::Import::Common::ObjectAttribute::SanitizedName,
|
||||
expect(provided[:sanitized_name]).to eq('model_name')
|
||||
end
|
||||
|
||||
it 'dashes' do
|
||||
it 'replaces dashes' do
|
||||
provided = process do |instance|
|
||||
expect(instance).to receive(:unsanitized_name).and_return('model-name')
|
||||
end
|
||||
@@ -27,7 +27,7 @@ RSpec.describe Sequencer::Unit::Import::Common::ObjectAttribute::SanitizedName,
|
||||
expect(provided[:sanitized_name]).to eq('model_name')
|
||||
end
|
||||
|
||||
it 'ids suffix' do
|
||||
it 'replaces ids suffix' do
|
||||
provided = process do |instance|
|
||||
expect(instance).to receive(:unsanitized_name).and_return('Model Ids')
|
||||
end
|
||||
@@ -35,12 +35,20 @@ RSpec.describe Sequencer::Unit::Import::Common::ObjectAttribute::SanitizedName,
|
||||
expect(provided[:sanitized_name]).to eq('model_nos')
|
||||
end
|
||||
|
||||
it 'id suffix' do
|
||||
it 'replaces id suffix' do
|
||||
provided = process do |instance|
|
||||
expect(instance).to receive(:unsanitized_name).and_return('Model Id')
|
||||
end
|
||||
|
||||
expect(provided[:sanitized_name]).to eq('model_no')
|
||||
end
|
||||
|
||||
it 'replaces non-ASCII characters' do
|
||||
provided = process do |instance|
|
||||
expect(instance).to receive(:unsanitized_name).and_return('Ærøskøbing Ät Mödél')
|
||||
end
|
||||
|
||||
expect(provided[:sanitized_name]).to eq('a_eroskobing_at_model')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Sequencer::Unit::Import::Zendesk::Ticket::Comment::SourceBased, sequencer: :unit do
|
||||
|
||||
before(:all) do
|
||||
|
||||
described_class.class_eval do
|
||||
|
||||
private
|
||||
|
||||
def email
|
||||
'test@example.com'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def parameters_for_channel(channel)
|
||||
{
|
||||
resource: double(
|
||||
via: double(
|
||||
channel: channel
|
||||
)
|
||||
)
|
||||
}
|
||||
end
|
||||
|
||||
context 'for resource.via.channel attribute' do
|
||||
|
||||
it 'provides from existing method' do
|
||||
provided = process(parameters_for_channel('email'))
|
||||
expect(provided[:source_based]).to eq('test@example.com')
|
||||
end
|
||||
|
||||
it 'provides nil for non existing method' do
|
||||
provided = process(parameters_for_channel('system'))
|
||||
expect(provided[:source_based]).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -98,56 +98,24 @@ RSpec.describe Scheduler do
|
||||
described_class.cleanup
|
||||
end
|
||||
|
||||
it 'keeps unlocked Delayed::Job-s' do
|
||||
# meta :)
|
||||
described_class.delay.cleanup
|
||||
context 'Delayed::Job' do
|
||||
|
||||
expect do
|
||||
simulate_threads_call
|
||||
end.not_to change {
|
||||
Delayed::Job.count
|
||||
}
|
||||
end
|
||||
|
||||
context 'locked Delayed::Job' do
|
||||
|
||||
it 'gets destroyed' do
|
||||
it 'keeps unlocked' do
|
||||
# meta :)
|
||||
described_class.delay.cleanup
|
||||
|
||||
# lock job (simluates interrupted scheduler task)
|
||||
locked_job = Delayed::Job.last
|
||||
locked_job.update!(locked_at: Time.zone.now)
|
||||
|
||||
expect do
|
||||
simulate_threads_call
|
||||
end.to change {
|
||||
end.not_to change {
|
||||
Delayed::Job.count
|
||||
}.by(-1)
|
||||
}
|
||||
end
|
||||
|
||||
context 'respond to reschedule?' do
|
||||
context 'locked' do
|
||||
|
||||
it 'gets rescheduled for positive responses' do
|
||||
SpecSpace::DelayedJobBackend.reschedule = true
|
||||
SpecSpace::DelayedJobBackend.delay.start
|
||||
|
||||
# lock job (simluates interrupted scheduler task)
|
||||
locked_job = Delayed::Job.last
|
||||
locked_job.update!(locked_at: Time.zone.now)
|
||||
|
||||
expect do
|
||||
simulate_threads_call
|
||||
end.to not_change {
|
||||
Delayed::Job.count
|
||||
}.and change {
|
||||
Delayed::Job.last.locked_at
|
||||
}
|
||||
end
|
||||
|
||||
it 'gets destroyed for negative responses' do
|
||||
SpecSpace::DelayedJobBackend.reschedule = false
|
||||
SpecSpace::DelayedJobBackend.delay.start
|
||||
it 'gets destroyed' do
|
||||
# meta :)
|
||||
described_class.delay.cleanup
|
||||
|
||||
# lock job (simluates interrupted scheduler task)
|
||||
locked_job = Delayed::Job.last
|
||||
@@ -159,6 +127,78 @@ RSpec.describe Scheduler do
|
||||
Delayed::Job.count
|
||||
}.by(-1)
|
||||
end
|
||||
|
||||
context 'respond to reschedule?' do
|
||||
|
||||
it 'gets rescheduled for positive responses' do
|
||||
SpecSpace::DelayedJobBackend.reschedule = true
|
||||
SpecSpace::DelayedJobBackend.delay.start
|
||||
|
||||
# lock job (simluates interrupted scheduler task)
|
||||
locked_job = Delayed::Job.last
|
||||
locked_job.update!(locked_at: Time.zone.now)
|
||||
|
||||
expect do
|
||||
simulate_threads_call
|
||||
end.to not_change {
|
||||
Delayed::Job.count
|
||||
}.and change {
|
||||
Delayed::Job.last.locked_at
|
||||
}
|
||||
end
|
||||
|
||||
it 'gets destroyed for negative responses' do
|
||||
SpecSpace::DelayedJobBackend.reschedule = false
|
||||
SpecSpace::DelayedJobBackend.delay.start
|
||||
|
||||
# lock job (simluates interrupted scheduler task)
|
||||
locked_job = Delayed::Job.last
|
||||
locked_job.update!(locked_at: Time.zone.now)
|
||||
|
||||
expect do
|
||||
simulate_threads_call
|
||||
end.to change {
|
||||
Delayed::Job.count
|
||||
}.by(-1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'ImportJob' do
|
||||
|
||||
context 'affected job' do
|
||||
|
||||
let(:job) { create(:import_job, started_at: 5.minutes.ago) }
|
||||
|
||||
it 'finishes stuck jobs' do
|
||||
|
||||
expect do
|
||||
simulate_threads_call
|
||||
end.to change {
|
||||
job.reload.finished_at
|
||||
}
|
||||
end
|
||||
|
||||
it 'adds an error message to the result' do
|
||||
|
||||
expect do
|
||||
simulate_threads_call
|
||||
end.to change {
|
||||
job.reload.result[:error]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
it "doesn't change jobs added after stop" do
|
||||
|
||||
job = create(:import_job)
|
||||
|
||||
expect do
|
||||
simulate_threads_call
|
||||
end.not_to change {
|
||||
job.reload
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -175,7 +175,7 @@ RSpec.describe Ticket do
|
||||
|
||||
describe '#perform_changes' do
|
||||
|
||||
it 'performes a ticket state change on a ticket' do
|
||||
it 'performs a ticket state change on a ticket' do
|
||||
source_ticket = create(:ticket)
|
||||
|
||||
changes = {
|
||||
@@ -188,7 +188,7 @@ RSpec.describe Ticket do
|
||||
expect(source_ticket.state.name).to eq('closed')
|
||||
end
|
||||
|
||||
it 'performes a ticket deletion on a ticket' do
|
||||
it 'performs a ticket deletion on a ticket' do
|
||||
source_ticket = create(:ticket)
|
||||
|
||||
changes = {
|
||||
@@ -201,6 +201,25 @@ RSpec.describe Ticket do
|
||||
expect(ticket_with_source_ids).to match_array([])
|
||||
end
|
||||
|
||||
# Regression test for https://github.com/zammad/zammad/issues/2001
|
||||
it 'does not modify its arguments' do
|
||||
trigger = Trigger.new(
|
||||
perform: {
|
||||
# rubocop:disable Lint/InterpolationCheck
|
||||
'notification.email' => {
|
||||
body: "Hello \#{ticket.customer.firstname} \#{ticket.customer.lastname},",
|
||||
recipient: %w[article_last_sender ticket_owner ticket_customer ticket_agents],
|
||||
subject: "Autoclose (\#{ticket.title})"
|
||||
}
|
||||
# rubocop:enable Lint/InterpolationCheck
|
||||
}
|
||||
)
|
||||
|
||||
expect { Ticket.first.perform_changes(trigger.perform, 'trigger', {}, 1) }
|
||||
.to not_change { trigger.perform['notification.email'][:body] }
|
||||
.and not_change { trigger.perform['notification.email'][:subject] }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe '#selectors' do
|
||||
|
||||
@@ -27,6 +27,27 @@ module DbMigrationHelper
|
||||
instance.migrate(direction)
|
||||
end
|
||||
end
|
||||
|
||||
def self.included(base)
|
||||
|
||||
# Execute in RSpec class context
|
||||
base.class_exec do
|
||||
|
||||
# This method simulates a system that is is already initialized
|
||||
# aka `Setting.exists?(name: 'system_init_done')`
|
||||
# It's possible to simulate a not yet initialized system by adding the
|
||||
# meta tag `system_init_done` to `false` to the needing example:
|
||||
#
|
||||
# @example
|
||||
# it 'does stuff in an unitialized system', system_init_done: false do
|
||||
#
|
||||
before(:each) do |example|
|
||||
initialized = example.metadata.fetch(:system_init_done, true)
|
||||
system_init_done(initialized)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
RSpec.configure do |config|
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
module SystemInitDoneHelper
|
||||
def system_init_done(state = true)
|
||||
# generally allow all calls to Setting.find_by to avoid
|
||||
# RSpec errors where a different Setting is accessed
|
||||
allow(Setting).to receive(:find_by).and_call_original
|
||||
|
||||
# just mock the Setting check for `system_init_done`
|
||||
# and return the given parameter value
|
||||
expect(Setting).to receive(:find_by).with(name: 'system_init_done').and_return(state)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -28,6 +28,13 @@ class AAbUnitTest < TestCase
|
||||
value: '0',
|
||||
)
|
||||
|
||||
location(url: browser_url + '/tests_model_binding')
|
||||
sleep 8
|
||||
match(
|
||||
css: '.result .failed',
|
||||
value: '0',
|
||||
)
|
||||
|
||||
location(url: browser_url + '/tests_model_ui')
|
||||
sleep 8
|
||||
match(
|
||||
|
||||
84
test/browser/admin_calendar_sla_test.rb
Normal file
84
test/browser/admin_calendar_sla_test.rb
Normal file
@@ -0,0 +1,84 @@
|
||||
|
||||
require 'browser_test_helper'
|
||||
|
||||
class AdminCalendarSlaTest < TestCase
|
||||
def test_calendar
|
||||
@browser = browser_instance
|
||||
login(
|
||||
username: 'master@example.com',
|
||||
password: 'test',
|
||||
url: browser_url,
|
||||
)
|
||||
tasks_close_all()
|
||||
|
||||
calendar_name = "ZZZ some calendar #{rand(99_999_999)}"
|
||||
sla_name = "ZZZ some sla #{rand(99_999_999)}"
|
||||
timezone = 'Europe/Berlin'
|
||||
timezone_verify = 'Europe/Berlin (GMT+2)'
|
||||
calendar_create(
|
||||
data: {
|
||||
name: calendar_name,
|
||||
timezone: timezone,
|
||||
}
|
||||
)
|
||||
|
||||
# got to maintanance
|
||||
click(css: '[href="#manage"]')
|
||||
click(css: '[href="#system/maintenance"]')
|
||||
watch_for(
|
||||
css: '.content.active',
|
||||
value: 'Enable or disable the maintenance mode',
|
||||
timeout: 4,
|
||||
)
|
||||
|
||||
# go back
|
||||
click(css: '[href="#manage"]')
|
||||
click(css: '[href="#manage/calendars"]')
|
||||
watch_for(
|
||||
css: '.content.active',
|
||||
value: calendar_name,
|
||||
timeout: 4,
|
||||
)
|
||||
|
||||
logout()
|
||||
|
||||
login(
|
||||
username: 'master@example.com',
|
||||
password: 'test',
|
||||
)
|
||||
|
||||
# check if admin exists
|
||||
click(css: '[href="#manage"]')
|
||||
click(css: '[href="#manage/calendars"]')
|
||||
watch_for(
|
||||
css: '.content.active',
|
||||
value: calendar_name,
|
||||
timeout: 4,
|
||||
)
|
||||
|
||||
#@browser.execute_script('$(\'.content.active table tr td:contains(" ' + data[:name] + '")\').first().click()')
|
||||
@browser.execute_script('$(\'.content.active .main .js-edit\').last().click()')
|
||||
|
||||
modal_ready(browser: @browser)
|
||||
watch_for(
|
||||
css: '.content.active .modal input[name=name]',
|
||||
value: calendar_name,
|
||||
timeout: 4,
|
||||
)
|
||||
watch_for(
|
||||
css: '.content.active .modal input.js-input',
|
||||
value: Regexp.quote(timezone_verify),
|
||||
timeout: 4,
|
||||
)
|
||||
modal_close()
|
||||
|
||||
sla_create(
|
||||
data: {
|
||||
name: sla_name,
|
||||
calendar: "#{calendar_name} - #{timezone}",
|
||||
first_response_time_in_text: 61
|
||||
},
|
||||
)
|
||||
|
||||
end
|
||||
end
|
||||
119
test/browser/admin_object_manager_tree_select_test.rb
Normal file
119
test/browser/admin_object_manager_tree_select_test.rb
Normal file
@@ -0,0 +1,119 @@
|
||||
require 'browser_test_helper'
|
||||
|
||||
class AdminObjectManagerTreeSelectTest < TestCase
|
||||
|
||||
def test_basic_a
|
||||
|
||||
@browser = browser_instance
|
||||
login(
|
||||
username: 'master@example.com',
|
||||
password: 'test',
|
||||
url: browser_url,
|
||||
)
|
||||
tasks_close_all()
|
||||
|
||||
object_manager_attribute_create(
|
||||
data: {
|
||||
name: 'browser_test_tree_select1',
|
||||
display: 'Browser Test TreeSelect1',
|
||||
data_type: 'Tree Select',
|
||||
data_option: {
|
||||
options: {
|
||||
'Incident' => {
|
||||
'Hardware' => {
|
||||
'Monitor' => {},
|
||||
'Mouse' => {},
|
||||
'Keyboard' => {},
|
||||
},
|
||||
'Softwareproblem' => {
|
||||
'CRM' => {},
|
||||
'EDI' => {},
|
||||
'SAP' => {
|
||||
'Authentication' => {},
|
||||
'Not reachable' => {},
|
||||
},
|
||||
'MS Office' => {
|
||||
'Excel' => {},
|
||||
'PowerPoint' => {},
|
||||
'Word' => {},
|
||||
'Outlook' => {},
|
||||
},
|
||||
},
|
||||
},
|
||||
'Service request' => {
|
||||
'New software requirement' => {},
|
||||
'New hardware' => {},
|
||||
'Consulting' => {},
|
||||
},
|
||||
'Change request' => {},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
watch_for(
|
||||
css: '.content.active',
|
||||
value: 'Database Update required',
|
||||
)
|
||||
click(css: '.content.active .tab-pane.active div.js-execute')
|
||||
watch_for(
|
||||
css: '.modal',
|
||||
value: 'restart',
|
||||
)
|
||||
watch_for_disappear(
|
||||
css: '.modal',
|
||||
timeout: 120,
|
||||
)
|
||||
sleep 5
|
||||
watch_for(
|
||||
css: '.content.active',
|
||||
)
|
||||
|
||||
# discard new attribute
|
||||
click(css: 'a[href="#manage"]')
|
||||
click(css: 'a[href="#system/object_manager"]')
|
||||
watch_for(
|
||||
css: '.content.active table',
|
||||
value: 'browser_test_tree_select1',
|
||||
)
|
||||
match_not(
|
||||
css: '.content.active',
|
||||
value: 'Database Update required',
|
||||
)
|
||||
object_manager_attribute_delete(
|
||||
data: {
|
||||
name: 'browser_test_tree_select1',
|
||||
},
|
||||
)
|
||||
watch_for(
|
||||
css: '.content.active',
|
||||
value: 'Database Update required',
|
||||
)
|
||||
watch_for(
|
||||
css: '.content.active table',
|
||||
value: 'browser_test_tree_select1',
|
||||
)
|
||||
click(css: '.content.active .tab-pane.active div.js-execute')
|
||||
watch_for(
|
||||
css: '.modal',
|
||||
value: 'restart',
|
||||
)
|
||||
watch_for_disappear(
|
||||
css: '.modal',
|
||||
timeout: 120,
|
||||
)
|
||||
sleep 5
|
||||
watch_for(
|
||||
css: '.content.active',
|
||||
)
|
||||
match_not(
|
||||
css: '.content.active',
|
||||
value: 'Database Update required',
|
||||
)
|
||||
match_not(
|
||||
css: '.content.active table',
|
||||
value: 'browser_test_tree_select1',
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
33
test/browser/agent_ticket_create_cc_tokenizer_test.rb
Normal file
33
test/browser/agent_ticket_create_cc_tokenizer_test.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
require 'browser_test_helper'
|
||||
|
||||
# Regression test for UI bugfix
|
||||
# https://github.com/zammad/zammad/issues/1990
|
||||
#
|
||||
# Ensure that CC field when creating a new ticket is autocompleting user emails
|
||||
|
||||
class AgentTicketCreateCcTokenizerTest < TestCase
|
||||
def test_tokenizer
|
||||
@browser = browser_instance
|
||||
|
||||
login(
|
||||
username: 'agent1@example.com',
|
||||
password: 'test',
|
||||
url: browser_url,
|
||||
)
|
||||
tasks_close_all()
|
||||
|
||||
click(
|
||||
css: 'a[href="#ticket/create"]'
|
||||
)
|
||||
|
||||
email_out_css = '.content.active li[data-type=email-out]'
|
||||
|
||||
watch_for(
|
||||
css: email_out_css
|
||||
)
|
||||
|
||||
click(css: email_out_css)
|
||||
|
||||
token_verify('.content.active input[name=cc]', 'test@example.com')
|
||||
end
|
||||
end
|
||||
97
test/browser/agent_ticket_create_template_test.rb
Normal file
97
test/browser/agent_ticket_create_template_test.rb
Normal file
@@ -0,0 +1,97 @@
|
||||
|
||||
require 'browser_test_helper'
|
||||
|
||||
# Regression test for UI bugfix
|
||||
# https://github.com/zammad/zammad/issues/1669
|
||||
#
|
||||
# After creating a new ticket template, logging out, and logging back in,
|
||||
# ensure that the template selection menu still contains the new entry.
|
||||
class AgentTicketCreateTemplateTest < TestCase
|
||||
def test_ticket_template_creation
|
||||
@browser = browser_instance
|
||||
login(
|
||||
username: 'agent1@example.com',
|
||||
password: 'test',
|
||||
url: browser_url,
|
||||
)
|
||||
tasks_close_all()
|
||||
|
||||
click(
|
||||
css: 'a[href="#ticket/create"]'
|
||||
)
|
||||
watch_for(
|
||||
css: '.active .templates-welcome',
|
||||
displayed: true
|
||||
)
|
||||
|
||||
set(
|
||||
css: 'input[name="title"]',
|
||||
value: 'my first ticket'
|
||||
)
|
||||
click(
|
||||
css: '.active .templates-welcome .js-create'
|
||||
)
|
||||
watch_for(
|
||||
css: '.active .templates-manage',
|
||||
displayed: true,
|
||||
timeout: 3,
|
||||
)
|
||||
exists_not(
|
||||
css: '.active .templates-manage select[name="id"] > option:not([value=""])'
|
||||
)
|
||||
|
||||
# save new template
|
||||
set(
|
||||
css: '.active .templates-manage .js-name',
|
||||
value: 'test template'
|
||||
)
|
||||
click(
|
||||
css: '.active .templates-manage .js-save'
|
||||
)
|
||||
exists(
|
||||
css: '.active .templates-manage select[name="id"] > option:not([value=""])',
|
||||
displayed: true
|
||||
)
|
||||
|
||||
# check if relogin temlates are still available
|
||||
logout
|
||||
login(
|
||||
username: 'agent1@example.com',
|
||||
password: 'test',
|
||||
)
|
||||
|
||||
click(
|
||||
css: '.navigation > .tasks > a.task'
|
||||
)
|
||||
exists(
|
||||
css: '.active .templates-manage',
|
||||
displayed: true
|
||||
)
|
||||
exists(
|
||||
css: '.active .templates-manage select[name="id"] > option:not([value=""])',
|
||||
displayed: true
|
||||
)
|
||||
|
||||
# apply new tempalte
|
||||
tasks_close_all()
|
||||
click(
|
||||
css: 'a[href="#ticket/create"]'
|
||||
)
|
||||
watch_for(
|
||||
css: '.active .templates-manage',
|
||||
displayed: true,
|
||||
timeout: 3,
|
||||
)
|
||||
select(
|
||||
css: '.active .templates-manage select[name="id"]',
|
||||
value: 'test template',
|
||||
)
|
||||
click(
|
||||
css: '.active .templates-manage .js-apply'
|
||||
)
|
||||
exists(
|
||||
css: '.active .newTicket input[name="title"]',
|
||||
value: 'my first ticket'
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -2,7 +2,7 @@
|
||||
require 'browser_test_helper'
|
||||
|
||||
class AgentUserManageTest < TestCase
|
||||
def test_agent_user
|
||||
def test_agent_customer_ticket_create
|
||||
customer_user_email = 'customer-test-' + rand(999_999).to_s + '@example.com'
|
||||
firstname = 'Customer Firstname'
|
||||
lastname = 'Customer Lastname'
|
||||
@@ -16,58 +16,66 @@ class AgentUserManageTest < TestCase
|
||||
)
|
||||
tasks_close_all()
|
||||
|
||||
sleep 1
|
||||
|
||||
# create customer
|
||||
click(css: 'a[href="#new"]', only_if_exists: true)
|
||||
click(css: 'a[href="#ticket/create"]')
|
||||
click(css: '.active .newTicket [name="customer_id_completion"]')
|
||||
|
||||
watch_for(
|
||||
css: '.content.active .newTicket',
|
||||
timeout: 1,
|
||||
)
|
||||
|
||||
click(css: '.content.active .newTicket [name="customer_id_completion"]')
|
||||
|
||||
# check if pulldown is open, it's not working stable via selenium
|
||||
@browser.execute_script( "$('.active .newTicket .js-recipientDropdown').addClass('open')" )
|
||||
@browser.execute_script("$('.active .newTicket .js-recipientDropdown').addClass('open')")
|
||||
|
||||
sleep 1
|
||||
sendkey(value: :arrow_down)
|
||||
sleep 0.5
|
||||
click(css: '.active .newTicket .recipientList-entry.js-objectNew')
|
||||
sleep 1
|
||||
click(css: '.content.active .newTicket .recipientList-entry.js-objectNew')
|
||||
|
||||
watch_for(
|
||||
css: '.content.active .modal',
|
||||
timeout: 1,
|
||||
)
|
||||
|
||||
set(
|
||||
css: '.modal input[name="firstname"]',
|
||||
css: '.content.active .modal input[name="firstname"]',
|
||||
value: firstname,
|
||||
)
|
||||
set(
|
||||
css: '.modal input[name="lastname"]',
|
||||
css: '.content.active .modal input[name="lastname"]',
|
||||
value: lastname,
|
||||
)
|
||||
set(
|
||||
css: '.modal input[name="email"]',
|
||||
css: '.content.active .modal input[name="email"]',
|
||||
value: customer_user_email,
|
||||
)
|
||||
|
||||
click(css: '.modal button.js-submit')
|
||||
click(css: '.content.active .modal button.js-submit')
|
||||
sleep 4
|
||||
|
||||
# check is used to check selected
|
||||
match(
|
||||
css: '.active input[name="customer_id"]',
|
||||
css: '.content.active .newTicket input[name="customer_id"]',
|
||||
value: '^\d+$',
|
||||
no_quote: true,
|
||||
)
|
||||
match(
|
||||
css: '.active input[name="customer_id_completion"]',
|
||||
css: '.content.active .newTicket input[name="customer_id_completion"]',
|
||||
value: firstname,
|
||||
)
|
||||
match(
|
||||
css: '.active input[name="customer_id_completion"]',
|
||||
css: '.content.active .newTicket input[name="customer_id_completion"]',
|
||||
value: lastname,
|
||||
)
|
||||
match(
|
||||
css: '.active input[name="customer_id_completion"]',
|
||||
css: '.content.active .newTicket input[name="customer_id_completion"]',
|
||||
value: customer_user_email,
|
||||
)
|
||||
match(
|
||||
css: '.active input[name="customer_id_completion"]',
|
||||
css: '.content.active .newTicket input[name="customer_id_completion"]',
|
||||
value: fullname,
|
||||
)
|
||||
sleep 4
|
||||
@@ -77,47 +85,162 @@ class AgentUserManageTest < TestCase
|
||||
|
||||
click(css: 'a[href="#new"]', only_if_exists: true)
|
||||
click(css: 'a[href="#ticket/create"]')
|
||||
sleep 2
|
||||
|
||||
watch_for(
|
||||
css: '.content.active .newTicket',
|
||||
timeout: 1,
|
||||
)
|
||||
|
||||
match(
|
||||
css: '.active input[name="customer_id"]',
|
||||
css: '.content.active .newTicket input[name="customer_id"]',
|
||||
value: '',
|
||||
)
|
||||
match(
|
||||
css: '.active input[name="customer_id_completion"]',
|
||||
css: '.content.active .newTicket input[name="customer_id_completion"]',
|
||||
value: '',
|
||||
)
|
||||
set(
|
||||
css: '.active .newTicket input[name="customer_id_completion"]',
|
||||
css: '.content.active .newTicket input[name="customer_id_completion"]',
|
||||
value: customer_user_email,
|
||||
)
|
||||
sleep 3
|
||||
|
||||
click(css: '.active .newTicket .recipientList-entry.js-object.is-active')
|
||||
click(css: '.content.active .newTicket .recipientList-entry.js-object.is-active')
|
||||
sleep 1
|
||||
|
||||
# check is used to check selected
|
||||
match(
|
||||
css: '.active input[name="customer_id"]',
|
||||
css: '.content.active .newTicket input[name="customer_id"]',
|
||||
value: '^\d+$',
|
||||
no_quote: true,
|
||||
)
|
||||
match(
|
||||
css: '.active input[name="customer_id_completion"]',
|
||||
css: '.content.active .newTicket input[name="customer_id_completion"]',
|
||||
value: firstname,
|
||||
)
|
||||
match(
|
||||
css: '.active input[name="customer_id_completion"]',
|
||||
css: '.content.active .newTicket input[name="customer_id_completion"]',
|
||||
value: lastname,
|
||||
)
|
||||
match(
|
||||
css: '.active input[name="customer_id_completion"]',
|
||||
css: '.content.active .newTicket input[name="customer_id_completion"]',
|
||||
value: customer_user_email,
|
||||
)
|
||||
match(
|
||||
css: '.active input[name="customer_id_completion"]',
|
||||
css: '.content.active .newTicket input[name="customer_id_completion"]',
|
||||
value: fullname,
|
||||
)
|
||||
end
|
||||
|
||||
def test_agent_customer_ticket_zoom
|
||||
customer_user_email = 'customer-test-' + rand(999_999).to_s + '@example.com'
|
||||
firstname = 'Customer Firstname'
|
||||
lastname = 'Customer Lastname'
|
||||
fullname = "#{firstname} #{lastname} <#{customer_user_email}>"
|
||||
|
||||
@browser = browser_instance
|
||||
login(
|
||||
username: 'agent1@example.com',
|
||||
password: 'test',
|
||||
url: browser_url,
|
||||
)
|
||||
tasks_close_all()
|
||||
|
||||
ticket_create(
|
||||
data: {
|
||||
customer: 'nico',
|
||||
group: 'Users',
|
||||
title: 'some changes',
|
||||
body: 'some body',
|
||||
},
|
||||
)
|
||||
|
||||
watch_for(
|
||||
css: '.content.active .ticketZoom-header .ticket-number',
|
||||
value: '\d',
|
||||
)
|
||||
|
||||
click(css: '.content.active .tabsSidebar-tabs .tabsSidebar-tab[data-tab="customer"]')
|
||||
|
||||
match(
|
||||
css: '.content.active .tabsSidebar .sidebar[data-tab="customer"]',
|
||||
value: 'Nicole Braun',
|
||||
)
|
||||
|
||||
click(css: '.content.active .tabsSidebar .sidebar[data-tab="customer"] .js-actions')
|
||||
click(css: '.content.active .tabsSidebar .sidebar[data-tab="customer"] .js-actions li[data-type="customer-change"]')
|
||||
|
||||
watch_for(
|
||||
css: '.content.active .modal',
|
||||
)
|
||||
|
||||
click(css: '.content.active .modal [name="customer_id_completion"]')
|
||||
|
||||
# check if pulldown is open, it's not working stable via selenium
|
||||
@browser.execute_script("$('.active .modal .js-recipientDropdown').addClass('open')")
|
||||
|
||||
sleep 1
|
||||
sendkey(value: :arrow_down)
|
||||
sleep 0.5
|
||||
click(css: '.content.active .modal .recipientList-entry.js-objectNew')
|
||||
|
||||
watch_for(
|
||||
css: '.content.active .modal input[name="firstname"]',
|
||||
timeout: 1,
|
||||
)
|
||||
|
||||
set(
|
||||
css: '.content.active .modal input[name="firstname"]',
|
||||
value: firstname,
|
||||
)
|
||||
set(
|
||||
css: '.content.active .modal input[name="lastname"]',
|
||||
value: lastname,
|
||||
)
|
||||
set(
|
||||
css: '.content.active .modal input[name="email"]',
|
||||
value: customer_user_email,
|
||||
)
|
||||
|
||||
# there are 2 models, take the correct one
|
||||
#click(css: '.content.active .modal button.js-submit')
|
||||
@browser.execute_script("$('.content.active .modal input[name=\"firstname\"]').closest('form').find('button.js-submit').click()")
|
||||
|
||||
# check is used to check selected
|
||||
watch_for(
|
||||
css: '.content.active .modal input[name="customer_id"]',
|
||||
value: '^\d+$',
|
||||
no_quote: true,
|
||||
)
|
||||
match(
|
||||
css: '.content.active .modal input[name="customer_id_completion"]',
|
||||
value: firstname,
|
||||
)
|
||||
match(
|
||||
css: '.content.active .modal input[name="customer_id_completion"]',
|
||||
value: lastname,
|
||||
)
|
||||
match(
|
||||
css: '.content.active .modal input[name="customer_id_completion"]',
|
||||
value: customer_user_email,
|
||||
)
|
||||
match(
|
||||
css: '.content.active .modal input[name="customer_id_completion"]',
|
||||
value: fullname,
|
||||
)
|
||||
|
||||
click(css: '.content.active .modal button.js-submit')
|
||||
|
||||
watch_for_disappear(
|
||||
css: '.content.active .modal',
|
||||
)
|
||||
|
||||
watch_for(
|
||||
css: '.content.active .tabsSidebar .sidebar[data-tab="customer"]',
|
||||
value: customer_user_email,
|
||||
timeout: 4,
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -44,9 +44,7 @@ class TestCase < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def browser_instance
|
||||
if !@browsers
|
||||
@browsers = {}
|
||||
end
|
||||
@browsers ||= {}
|
||||
if ENV['REMOTE_URL'].blank?
|
||||
local_browser = Selenium::WebDriver.for(browser.to_sym, profile: profile)
|
||||
@browsers[local_browser.hash] = local_browser
|
||||
@@ -136,7 +134,7 @@ class TestCase < Test::Unit::TestCase
|
||||
browser: browser1,
|
||||
username: 'someuser',
|
||||
password: 'somepassword',
|
||||
url: 'some url', # optional
|
||||
url: 'some url', # optional, in case of aleady opened brower a reload is done because url is called again
|
||||
remember_me: true, # optional
|
||||
auto_wizard: false, # optional, in case of auto wizard, skip login
|
||||
success: false, #optional
|
||||
@@ -487,6 +485,26 @@ class TestCase < Test::Unit::TestCase
|
||||
screenshot(browser: instance, comment: 'scroll_to_after')
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
modal_close(
|
||||
browser: browser1,
|
||||
)
|
||||
|
||||
=end
|
||||
|
||||
def modal_close(params = {})
|
||||
switch_window_focus(params)
|
||||
log('modal_close', params)
|
||||
|
||||
instance = params[:browser] || @browser
|
||||
|
||||
element = instance.find_elements(css: '.modal .js-close')[0]
|
||||
raise "No such modal to close #{params.inspect}" if !element
|
||||
|
||||
element.click
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
modal_ready(
|
||||
@@ -1251,7 +1269,9 @@ set type of task (closeTab, closeNextInOverview, stayOnTab)
|
||||
|
||||
watch_for(
|
||||
browser: browser1,
|
||||
css: '#content .text-1',
|
||||
container: element # optional, defaults to browser, must exist at the time of dispatch
|
||||
css: '#content .text-1', # xpath or css required
|
||||
xpath: '/content[contains(@class,".text-1")]', # xpath or css required
|
||||
value: 'some text',
|
||||
attribute: 'some_attribute' # optional
|
||||
timeout: 16, # in sec, default 16
|
||||
@@ -1263,7 +1283,15 @@ set type of task (closeTab, closeNextInOverview, stayOnTab)
|
||||
switch_window_focus(params)
|
||||
log('watch_for', params)
|
||||
|
||||
instance = params[:browser] || @browser
|
||||
browser = params[:browser] || @browser
|
||||
instance = params[:container] || browser
|
||||
|
||||
selector = params[:css] || params[:xpath]
|
||||
selector_type = if params.key?(:css)
|
||||
:css
|
||||
elsif params.key?(:xpath)
|
||||
:xpath
|
||||
end
|
||||
|
||||
timeout = 16
|
||||
if params[:timeout]
|
||||
@@ -1272,12 +1300,12 @@ set type of task (closeTab, closeNextInOverview, stayOnTab)
|
||||
loops = timeout.to_i * 2
|
||||
text = ''
|
||||
(1..loops).each do
|
||||
element = instance.find_elements(css: params[:css])[0]
|
||||
element = instance.find_elements(selector_type => selector)[0]
|
||||
if element #&& element.displayed?
|
||||
begin
|
||||
# watch for selector
|
||||
if !params[:attribute] && !params[:value]
|
||||
assert(true, "'#{params[:css]}' found")
|
||||
assert(true, "'#{selector}' found")
|
||||
sleep 0.5
|
||||
return true
|
||||
|
||||
@@ -1285,7 +1313,7 @@ set type of task (closeTab, closeNextInOverview, stayOnTab)
|
||||
else
|
||||
text = if params[:attribute]
|
||||
element.attribute(params[:attribute])
|
||||
elsif params[:css].match?(/(input|textarea)/i)
|
||||
elsif selector.match?(/(input|textarea)/i)
|
||||
element.attribute('value')
|
||||
else
|
||||
element.text
|
||||
@@ -1302,9 +1330,9 @@ set type of task (closeTab, closeNextInOverview, stayOnTab)
|
||||
end
|
||||
sleep 0.5
|
||||
end
|
||||
screenshot(browser: instance, comment: 'watch_for_failed')
|
||||
screenshot(browser: browser, comment: 'watch_for_failed')
|
||||
if !params[:attribute] && !params[:value]
|
||||
raise "'#{params[:css]}' not found"
|
||||
raise "'#{selector}' not found"
|
||||
end
|
||||
raise "'#{params[:value]}' not found in '#{text}'"
|
||||
end
|
||||
@@ -2717,12 +2745,72 @@ wait untill text in selector disabppears
|
||||
assert(true, 'user created')
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
calendar_create(
|
||||
browser: browser2,
|
||||
data: {
|
||||
name: 'some calendar' + random,
|
||||
first_response_time_in_text: 61
|
||||
},
|
||||
)
|
||||
|
||||
=end
|
||||
|
||||
def calendar_create(params = {})
|
||||
switch_window_focus(params)
|
||||
log('calendar_create', params)
|
||||
|
||||
instance = params[:browser] || @browser
|
||||
data = params[:data]
|
||||
|
||||
click(
|
||||
browser: instance,
|
||||
css: 'a[href="#manage"]',
|
||||
mute_log: true,
|
||||
)
|
||||
click(
|
||||
browser: instance,
|
||||
css: '.content.active a[href="#manage/calendars"]',
|
||||
mute_log: true,
|
||||
)
|
||||
sleep 4
|
||||
click(
|
||||
browser: instance,
|
||||
css: '.content.active a.js-new',
|
||||
mute_log: true,
|
||||
)
|
||||
modal_ready(browser: instance)
|
||||
element = instance.find_elements(css: '.content.active .modal input[name=name]')[0]
|
||||
element.clear
|
||||
element.send_keys(data[:name])
|
||||
element = instance.find_elements(css: '.content.active .modal .js-input')[0]
|
||||
element.clear
|
||||
element.send_keys(data[:timezone])
|
||||
element.send_keys(:enter)
|
||||
instance.find_elements(css: '.modal button.js-submit')[0].click
|
||||
modal_disappear(browser: instance)
|
||||
7.times do
|
||||
element = instance.find_elements(css: 'body')[0]
|
||||
text = element.text
|
||||
if text.match?(/#{Regexp.quote(data[:name])}/)
|
||||
assert(true, 'calendar created')
|
||||
sleep 1
|
||||
return true
|
||||
end
|
||||
sleep 1
|
||||
end
|
||||
screenshot(browser: instance, comment: 'calendar_create_failed')
|
||||
raise 'calendar creation failed'
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
sla_create(
|
||||
browser: browser2,
|
||||
data: {
|
||||
name: 'some sla' + random,
|
||||
calendar: 'some calendar name',
|
||||
first_response_time_in_text: 61
|
||||
},
|
||||
)
|
||||
@@ -2755,6 +2843,11 @@ wait untill text in selector disabppears
|
||||
element = instance.find_elements(css: '.modal input[name=name]')[0]
|
||||
element.clear
|
||||
element.send_keys(data[:name])
|
||||
if data[:calendar].present?
|
||||
element = instance.find_elements(css: '.modal select[name="calendar_id"]')[0]
|
||||
dropdown = Selenium::WebDriver::Support::Select.new(element)
|
||||
dropdown.select_by(:text, data[:calendar])
|
||||
end
|
||||
element = instance.find_elements(css: '.modal input[name=first_response_time_in_text]')[0]
|
||||
element.clear
|
||||
element.send_keys(data[:first_response_time_in_text])
|
||||
@@ -3362,6 +3455,11 @@ wait untill text in selector disabppears
|
||||
element.clear
|
||||
element.send_keys(data[:data_option][:options][:false])
|
||||
# rubocop:enable Lint/BooleanSymbol
|
||||
elsif data[:data_type] == 'Tree Select'
|
||||
add_tree_options(
|
||||
instance: instance,
|
||||
options: data[:data_option][:options],
|
||||
)
|
||||
else
|
||||
data[:data_option][:options].each do |key, value|
|
||||
element = instance.find_elements(css: '.modal .js-Table .js-key').last
|
||||
@@ -3695,4 +3793,80 @@ wait untill text in selector disabppears
|
||||
return if params[:mute_log]
|
||||
puts "#{Time.zone.now}/#{method}: #{params.inspect}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_tree_options(instance:, options:)
|
||||
|
||||
# first level entries have to get added in regular order
|
||||
options.each_key.with_index do |option, index|
|
||||
|
||||
if index != 0
|
||||
element = instance.find_elements(css: '.modal .js-treeTable .js-addRow')[index - 1]
|
||||
element.click
|
||||
end
|
||||
|
||||
element = instance.find_elements(css: '.modal .js-treeTable .js-key')[index]
|
||||
element.clear
|
||||
element.send_keys(option)
|
||||
end
|
||||
|
||||
add_sub_tree_recursion(
|
||||
instance: instance,
|
||||
options: options,
|
||||
)
|
||||
end
|
||||
|
||||
def add_sub_tree_recursion(instance:, options:, offset: 0)
|
||||
options.each_value.inject(offset) do |child_offset, children|
|
||||
|
||||
child_offset += 1
|
||||
|
||||
# put your recursion glasses on 8-)
|
||||
add_sub_tree_options(
|
||||
instance: instance,
|
||||
options: children,
|
||||
offset: child_offset,
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def add_sub_tree_options(instance:, options:, offset:)
|
||||
|
||||
# sub level entries have to get added in reversed order
|
||||
level_options = options.to_a.reverse.to_h.keys
|
||||
|
||||
level_options.each do |option|
|
||||
|
||||
# sub level entries have to get added via 'add child row' link
|
||||
click_index = offset - 1
|
||||
|
||||
element = instance.find_elements(css: '.modal .js-treeTable .js-addChild')[click_index]
|
||||
element.click
|
||||
|
||||
element = instance.find_elements(css: '.modal .js-treeTable .js-key')[offset]
|
||||
element.clear
|
||||
element.send_keys(option)
|
||||
sleep 0.25
|
||||
end
|
||||
|
||||
add_sub_tree_recursion(
|
||||
instance: instance,
|
||||
options: options,
|
||||
offset: offset,
|
||||
)
|
||||
end
|
||||
|
||||
def token_verify(css, value)
|
||||
original_element = @browser.find_element(:css, css)
|
||||
elem = original_element.find_element(xpath: '../input[contains(@class, "token-input")]')
|
||||
elem.send_keys value
|
||||
elem.send_keys :enter
|
||||
|
||||
watch_for(
|
||||
xpath: '../*/span[contains(@class,"token-label")]',
|
||||
value: value,
|
||||
container: original_element
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
90
test/controllers/calendar_controller_test.rb
Normal file
90
test/controllers/calendar_controller_test.rb
Normal file
@@ -0,0 +1,90 @@
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
class CalendarControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
|
||||
# set accept header
|
||||
@headers = { 'ACCEPT' => 'application/json', 'CONTENT_TYPE' => 'application/json' }
|
||||
|
||||
# create agent
|
||||
roles = Role.where(name: %w[Admin Agent])
|
||||
groups = Group.all
|
||||
|
||||
UserInfo.current_user_id = 1
|
||||
@admin = User.create_or_update(
|
||||
login: 'calendar-admin',
|
||||
firstname: 'Packages',
|
||||
lastname: 'Admin',
|
||||
email: 'calendar-admin@example.com',
|
||||
password: 'adminpw',
|
||||
active: true,
|
||||
roles: roles,
|
||||
groups: groups,
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
test '01 calendar index with nobody' do
|
||||
|
||||
get '/api/v1/calendars', params: {}, headers: @headers
|
||||
assert_response(401)
|
||||
|
||||
result = JSON.parse(@response.body)
|
||||
assert_equal(Hash, result.class)
|
||||
assert_equal('authentication failed', result['error'])
|
||||
|
||||
get '/api/v1/calendars_init', params: {}, headers: @headers
|
||||
assert_response(401)
|
||||
|
||||
result = JSON.parse(@response.body)
|
||||
assert_equal(Hash, result.class)
|
||||
assert_equal('authentication failed', result['error'])
|
||||
end
|
||||
|
||||
test '02 calendar index with admin' do
|
||||
|
||||
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('calendar-admin@example.com', 'adminpw')
|
||||
|
||||
# index
|
||||
get '/api/v1/calendars', params: {}, headers: @headers.merge('Authorization' => credentials)
|
||||
assert_response(200)
|
||||
result = JSON.parse(@response.body)
|
||||
assert_equal(Array, result.class)
|
||||
assert(result)
|
||||
assert_equal(1, result.count)
|
||||
|
||||
get '/api/v1/calendars?expand=true', params: {}, headers: @headers.merge('Authorization' => credentials)
|
||||
assert_response(200)
|
||||
result = JSON.parse(@response.body)
|
||||
assert_equal(Array, result.class)
|
||||
assert(result)
|
||||
assert_equal(1, result.count)
|
||||
|
||||
get '/api/v1/calendars?full=true', params: {}, headers: @headers.merge('Authorization' => credentials)
|
||||
assert_response(200)
|
||||
result = JSON.parse(@response.body)
|
||||
assert_equal(Hash, result.class)
|
||||
assert(result)
|
||||
assert(result['record_ids'])
|
||||
assert_equal(1, result['record_ids'].count)
|
||||
assert(result['assets'])
|
||||
assert(result['assets'].present?)
|
||||
|
||||
# index
|
||||
get '/api/v1/calendars_init', params: {}, headers: @headers.merge('Authorization' => credentials)
|
||||
assert_response(200)
|
||||
result = JSON.parse(@response.body)
|
||||
assert_equal(Hash, result.class)
|
||||
assert(result['record_ids'])
|
||||
assert(result['ical_feeds'])
|
||||
assert_equal('Denmark', result['ical_feeds']['http://www.google.com/calendar/ical/da.danish%23holiday%40group.v.calendar.google.com/public/basic.ics'])
|
||||
assert_equal('Austria', result['ical_feeds']['http://www.google.com/calendar/ical/de.austrian%23holiday%40group.v.calendar.google.com/public/basic.ics'])
|
||||
assert(result['timezones'])
|
||||
assert_equal(2, result['timezones']['Africa/Johannesburg'])
|
||||
assert_equal(-8, result['timezones']['America/Sitka'])
|
||||
assert(result['assets'])
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
70
test/controllers/sla_controller_test.rb
Normal file
70
test/controllers/sla_controller_test.rb
Normal file
@@ -0,0 +1,70 @@
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
class SlaControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
|
||||
# set accept header
|
||||
@headers = { 'ACCEPT' => 'application/json', 'CONTENT_TYPE' => 'application/json' }
|
||||
|
||||
# create agent
|
||||
roles = Role.where(name: %w[Admin Agent])
|
||||
groups = Group.all
|
||||
|
||||
UserInfo.current_user_id = 1
|
||||
@admin = User.create_or_update(
|
||||
login: 'sla-admin',
|
||||
firstname: 'Packages',
|
||||
lastname: 'Admin',
|
||||
email: 'sla-admin@example.com',
|
||||
password: 'adminpw',
|
||||
active: true,
|
||||
roles: roles,
|
||||
groups: groups,
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
test '01 sla index with nobody' do
|
||||
|
||||
get '/api/v1/slas', params: {}, headers: @headers
|
||||
assert_response(401)
|
||||
|
||||
result = JSON.parse(@response.body)
|
||||
assert_equal(Hash, result.class)
|
||||
assert_equal('authentication failed', result['error'])
|
||||
|
||||
end
|
||||
|
||||
test '02 sla index with admin' do
|
||||
|
||||
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('sla-admin@example.com', 'adminpw')
|
||||
|
||||
get '/api/v1/slas', params: {}, headers: @headers.merge('Authorization' => credentials)
|
||||
assert_response(200)
|
||||
result = JSON.parse(@response.body)
|
||||
assert_equal(Array, result.class)
|
||||
assert(result)
|
||||
assert_equal(0, result.count)
|
||||
|
||||
get '/api/v1/slas?expand=true', params: {}, headers: @headers.merge('Authorization' => credentials)
|
||||
assert_response(200)
|
||||
result = JSON.parse(@response.body)
|
||||
assert_equal(Array, result.class)
|
||||
assert(result)
|
||||
assert_equal(0, result.count)
|
||||
|
||||
get '/api/v1/slas?full=true', params: {}, headers: @headers.merge('Authorization' => credentials)
|
||||
assert_response(200)
|
||||
result = JSON.parse(@response.body)
|
||||
assert_equal(Hash, result.class)
|
||||
assert(result)
|
||||
assert(result['record_ids'])
|
||||
assert(result['record_ids'].blank?)
|
||||
assert(result['assets'])
|
||||
assert(result['assets']['Calendar'].present?)
|
||||
assert(result['assets'].present?)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -269,8 +269,10 @@ class ClearbitTest < ActiveSupport::TestCase
|
||||
assert_equal('', customer6.firstname)
|
||||
assert_equal('', customer6.lastname)
|
||||
assert_equal('', customer6.note)
|
||||
assert_equal('http://clearbit.com', customer6.web)
|
||||
assert_equal('3030 16th St, San Francisco, CA 94103, USA', customer6.address)
|
||||
assert_equal('', customer6.web)
|
||||
#assert_equal('http://clearbit.com', customer6.web)
|
||||
assert_equal('San Francisco, CA 94103, USA', customer6.address)
|
||||
#assert_equal('3030 16th St, San Francisco, CA 94103, USA', customer6.address)
|
||||
#assert_equal('San Francisco, CA, USA', customer6.address)
|
||||
|
||||
organization6 = Organization.find_by(name: 'APIHub, Inc')
|
||||
|
||||
@@ -428,7 +428,7 @@ class MonitoringControllerTest < ActionDispatch::IntegrationTest
|
||||
assert(result['message'])
|
||||
assert(result['issues'])
|
||||
assert_equal(false, result['healthy'])
|
||||
assert_equal("Channel: Email::Notification out ;unprocessable mails: 1;scheduler not running;Failed to run import backend 'Import::Ldap'. Cause: Some bad error;Stuck import backend 'Import::Ldap' detected. Last update: #{15.minutes.ago}", result['message'])
|
||||
assert_equal("Channel: Email::Notification out ;unprocessable mails: 1;scheduler not running;Failed to run import backend 'Import::Ldap'. Cause: Some bad error;Stuck import backend 'Import::Ldap' detected. Last update: #{stuck_updated_at_timestamp}", result['message'])
|
||||
|
||||
Setting.set('ldap_integration', false)
|
||||
end
|
||||
|
||||
@@ -41,6 +41,9 @@ class UserDeviceControllerTest < ActionDispatch::IntegrationTest
|
||||
|
||||
ENV['TEST_REMOTE_IP'] = '5.9.62.170' # de
|
||||
ENV['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:46.0) Gecko/20100101 Firefox/46.0'
|
||||
ENV['SWITCHED_FROM_USER_ID'] = nil
|
||||
|
||||
UserDevice.destroy_all
|
||||
end
|
||||
|
||||
test '01 - index with nobody' do
|
||||
@@ -173,6 +176,15 @@ class UserDeviceControllerTest < ActionDispatch::IntegrationTest
|
||||
|
||||
test '04 - login index with admin with fingerprint - II' do
|
||||
|
||||
UserDevice.create!(
|
||||
user_id: @admin.id,
|
||||
name: 'test 1',
|
||||
location: 'some location',
|
||||
user_agent: 'some user agent',
|
||||
ip: '127.0.0.1',
|
||||
fingerprint: 'fingerprintI',
|
||||
)
|
||||
|
||||
params = { fingerprint: 'my_finger_print_II', username: 'user-device-admin', password: 'adminpw' }
|
||||
post '/api/v1/signin', params: params.to_json, headers: @headers
|
||||
assert_response(201)
|
||||
@@ -180,13 +192,13 @@ class UserDeviceControllerTest < ActionDispatch::IntegrationTest
|
||||
|
||||
Scheduler.worker(true)
|
||||
|
||||
assert_equal(3, UserDevice.where(user_id: @admin.id).count)
|
||||
assert_equal(2, UserDevice.where(user_id: @admin.id).count)
|
||||
assert_equal(1, email_notification_count('user_device_new', @admin.email))
|
||||
assert_equal(0, email_notification_count('user_device_new_location', @admin.email))
|
||||
assert_equal(result.class, Hash)
|
||||
assert_not(result['error'])
|
||||
assert(result['config'])
|
||||
assert('my_finger_print_II', controller.session[:user_device_fingerprint])
|
||||
assert('my_finger_print_III', controller.session[:user_device_fingerprint])
|
||||
|
||||
get '/api/v1/users', params: params.to_json, headers: @headers
|
||||
assert_response(200)
|
||||
@@ -195,7 +207,7 @@ class UserDeviceControllerTest < ActionDispatch::IntegrationTest
|
||||
|
||||
Scheduler.worker(true)
|
||||
|
||||
assert_equal(3, UserDevice.where(user_id: @admin.id).count)
|
||||
assert_equal(2, UserDevice.where(user_id: @admin.id).count)
|
||||
assert_equal(1, email_notification_count('user_device_new', @admin.email))
|
||||
assert_equal(0, email_notification_count('user_device_new_location', @admin.email))
|
||||
|
||||
@@ -210,7 +222,7 @@ class UserDeviceControllerTest < ActionDispatch::IntegrationTest
|
||||
|
||||
Scheduler.worker(true)
|
||||
|
||||
assert_equal(3, UserDevice.where(user_id: @admin.id).count)
|
||||
assert_equal(2, UserDevice.where(user_id: @admin.id).count)
|
||||
assert_equal(1, email_notification_count('user_device_new', @admin.email))
|
||||
assert_equal(0, email_notification_count('user_device_new_location', @admin.email))
|
||||
|
||||
@@ -223,7 +235,7 @@ class UserDeviceControllerTest < ActionDispatch::IntegrationTest
|
||||
|
||||
Scheduler.worker(true)
|
||||
|
||||
assert_equal(4, UserDevice.where(user_id: @admin.id).count)
|
||||
assert_equal(3, UserDevice.where(user_id: @admin.id).count)
|
||||
assert_equal(1, email_notification_count('user_device_new', @admin.email))
|
||||
assert_equal(1, email_notification_count('user_device_new_location', @admin.email))
|
||||
|
||||
@@ -234,6 +246,16 @@ class UserDeviceControllerTest < ActionDispatch::IntegrationTest
|
||||
|
||||
test '05 - login index with admin with fingerprint - II' do
|
||||
|
||||
UserDevice.add(
|
||||
ENV['HTTP_USER_AGENT'],
|
||||
ENV['TEST_REMOTE_IP'],
|
||||
@admin.id,
|
||||
'my_finger_print_II',
|
||||
'session', # session|basic_auth|token_auth|sso
|
||||
)
|
||||
|
||||
assert_equal(1, UserDevice.where(user_id: @admin.id).count)
|
||||
|
||||
params = { fingerprint: 'my_finger_print_II', username: 'user-device-admin', password: 'adminpw' }
|
||||
post '/api/v1/signin', params: params.to_json, headers: @headers
|
||||
assert_response(201)
|
||||
@@ -241,7 +263,7 @@ class UserDeviceControllerTest < ActionDispatch::IntegrationTest
|
||||
|
||||
Scheduler.worker(true)
|
||||
|
||||
assert_equal(4, UserDevice.where(user_id: @admin.id).count)
|
||||
assert_equal(1, UserDevice.where(user_id: @admin.id).count)
|
||||
assert_equal(0, email_notification_count('user_device_new', @admin.email))
|
||||
assert_equal(0, email_notification_count('user_device_new_location', @admin.email))
|
||||
assert_equal(result.class, Hash)
|
||||
@@ -252,9 +274,19 @@ class UserDeviceControllerTest < ActionDispatch::IntegrationTest
|
||||
|
||||
test '06 - login index with admin with basic auth' do
|
||||
|
||||
ENV['HTTP_USER_AGENT'] = 'curl 1.2.3'
|
||||
ENV['HTTP_USER_AGENT'] = 'curl 1.0.0'
|
||||
UserDevice.add(
|
||||
ENV['HTTP_USER_AGENT'],
|
||||
'127.0.0.1',
|
||||
@admin.id,
|
||||
'',
|
||||
'basic_auth', # session|basic_auth|token_auth|sso
|
||||
)
|
||||
assert_equal(1, UserDevice.where(user_id: @admin.id).count)
|
||||
|
||||
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('user-device-admin', 'adminpw')
|
||||
|
||||
ENV['HTTP_USER_AGENT'] = 'curl 1.2.3'
|
||||
params = {}
|
||||
get '/api/v1/users', params: params.to_json, headers: @headers.merge('Authorization' => credentials)
|
||||
assert_response(200)
|
||||
@@ -262,7 +294,7 @@ class UserDeviceControllerTest < ActionDispatch::IntegrationTest
|
||||
|
||||
Scheduler.worker(true)
|
||||
|
||||
assert_equal(5, UserDevice.where(user_id: @admin.id).count)
|
||||
assert_equal(2, UserDevice.where(user_id: @admin.id).count)
|
||||
assert_equal(1, email_notification_count('user_device_new', @admin.email))
|
||||
assert_equal(0, email_notification_count('user_device_new_location', @admin.email))
|
||||
assert_equal(result.class, Array)
|
||||
@@ -276,7 +308,7 @@ class UserDeviceControllerTest < ActionDispatch::IntegrationTest
|
||||
|
||||
Scheduler.worker(true)
|
||||
|
||||
assert_equal(5, UserDevice.where(user_id: @admin.id).count)
|
||||
assert_equal(2, UserDevice.where(user_id: @admin.id).count)
|
||||
assert_equal(1, email_notification_count('user_device_new', @admin.email))
|
||||
assert_equal(0, email_notification_count('user_device_new_location', @admin.email))
|
||||
assert_equal(result.class, Array)
|
||||
@@ -294,7 +326,7 @@ class UserDeviceControllerTest < ActionDispatch::IntegrationTest
|
||||
|
||||
Scheduler.worker(true)
|
||||
|
||||
assert_equal(5, UserDevice.where(user_id: @admin.id).count)
|
||||
assert_equal(2, UserDevice.where(user_id: @admin.id).count)
|
||||
assert_equal(1, email_notification_count('user_device_new', @admin.email))
|
||||
assert_equal(0, email_notification_count('user_device_new_location', @admin.email))
|
||||
assert_equal(result.class, Array)
|
||||
@@ -307,6 +339,17 @@ class UserDeviceControllerTest < ActionDispatch::IntegrationTest
|
||||
test '07 - login index with admin with basic auth' do
|
||||
|
||||
ENV['HTTP_USER_AGENT'] = 'curl 1.2.3'
|
||||
|
||||
UserDevice.add(
|
||||
ENV['HTTP_USER_AGENT'],
|
||||
ENV['TEST_REMOTE_IP'],
|
||||
@admin.id,
|
||||
'',
|
||||
'basic_auth', # session|basic_auth|token_auth|sso
|
||||
)
|
||||
|
||||
assert_equal(1, UserDevice.where(user_id: @admin.id).count)
|
||||
|
||||
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('user-device-admin', 'adminpw')
|
||||
|
||||
params = {}
|
||||
@@ -316,7 +359,7 @@ class UserDeviceControllerTest < ActionDispatch::IntegrationTest
|
||||
|
||||
Scheduler.worker(true)
|
||||
|
||||
assert_equal(5, UserDevice.where(user_id: @admin.id).count)
|
||||
assert_equal(1, UserDevice.where(user_id: @admin.id).count)
|
||||
assert_equal(0, email_notification_count('user_device_new', @admin.email))
|
||||
assert_equal(0, email_notification_count('user_device_new_location', @admin.email))
|
||||
assert_equal(result.class, Array)
|
||||
@@ -348,11 +391,20 @@ class UserDeviceControllerTest < ActionDispatch::IntegrationTest
|
||||
|
||||
test '09 - login index with agent with basic auth' do
|
||||
|
||||
ENV['HTTP_USER_AGENT'] = 'curl 1.2.3'
|
||||
|
||||
UserDevice.add(
|
||||
ENV['HTTP_USER_AGENT'],
|
||||
ENV['TEST_REMOTE_IP'],
|
||||
@agent.id,
|
||||
'',
|
||||
'basic_auth', # session|basic_auth|token_auth|sso
|
||||
)
|
||||
|
||||
assert_equal(1, UserDevice.where(user_id: @agent.id).count)
|
||||
assert_equal(0, email_notification_count('user_device_new', @agent.email))
|
||||
assert_equal(0, email_notification_count('user_device_new_location', @agent.email))
|
||||
|
||||
ENV['HTTP_USER_AGENT'] = 'curl 1.2.3'
|
||||
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('user-device-agent', 'agentpw')
|
||||
|
||||
params = {}
|
||||
@@ -371,7 +423,7 @@ class UserDeviceControllerTest < ActionDispatch::IntegrationTest
|
||||
|
||||
test '10 - login with switched_from_user_id' do
|
||||
|
||||
assert_equal(1, UserDevice.where(user_id: @agent.id).count)
|
||||
assert_equal(0, UserDevice.where(user_id: @agent.id).count)
|
||||
assert_equal(0, email_notification_count('user_device_new', @agent.email))
|
||||
assert_equal(0, email_notification_count('user_device_new_location', @agent.email))
|
||||
|
||||
@@ -384,7 +436,7 @@ class UserDeviceControllerTest < ActionDispatch::IntegrationTest
|
||||
|
||||
Scheduler.worker(true)
|
||||
|
||||
assert_equal(1, UserDevice.where(user_id: @agent.id).count)
|
||||
assert_equal(0, UserDevice.where(user_id: @agent.id).count)
|
||||
assert_equal(0, email_notification_count('user_device_new', @agent.email))
|
||||
assert_equal(0, email_notification_count('user_device_new_location', @agent.email))
|
||||
assert_equal(result.class, Hash)
|
||||
@@ -394,7 +446,7 @@ class UserDeviceControllerTest < ActionDispatch::IntegrationTest
|
||||
|
||||
Scheduler.worker(true)
|
||||
|
||||
assert_equal(1, UserDevice.where(user_id: @agent.id).count)
|
||||
assert_equal(0, UserDevice.where(user_id: @agent.id).count)
|
||||
assert_equal(0, email_notification_count('user_device_new', @agent.email))
|
||||
assert_equal(0, email_notification_count('user_device_new_location', @agent.email))
|
||||
|
||||
@@ -408,7 +460,7 @@ class UserDeviceControllerTest < ActionDispatch::IntegrationTest
|
||||
|
||||
Scheduler.worker(true)
|
||||
|
||||
assert_equal(1, UserDevice.where(user_id: @agent.id).count)
|
||||
assert_equal(0, UserDevice.where(user_id: @agent.id).count)
|
||||
assert_equal(0, email_notification_count('user_device_new', @agent.email))
|
||||
assert_equal(0, email_notification_count('user_device_new_location', @agent.email))
|
||||
ENV['USER_DEVICE_UPDATED_AT'] = nil
|
||||
@@ -424,9 +476,85 @@ class UserDeviceControllerTest < ActionDispatch::IntegrationTest
|
||||
# ip reset
|
||||
ENV['TEST_REMOTE_IP'] = '5.9.62.170' # de
|
||||
|
||||
assert_equal(1, UserDevice.where(user_id: @agent.id).count)
|
||||
assert_equal(0, UserDevice.where(user_id: @agent.id).count)
|
||||
assert_equal(0, email_notification_count('user_device_new', @agent.email))
|
||||
assert_equal(0, email_notification_count('user_device_new_location', @agent.email))
|
||||
|
||||
end
|
||||
|
||||
test '11 - login with invalid fingerprint' do
|
||||
|
||||
assert_equal(0, UserDevice.where(user_id: @admin.id).count)
|
||||
assert_equal(0, email_notification_count('user_device_new', @admin.email))
|
||||
assert_equal(0, email_notification_count('user_device_new_location', @admin.email))
|
||||
|
||||
params = { fingerprint: 'to_long_1234567890to_long_1234567890to_long_1234567890to_long_1234567890to_long_1234567890to_long_1234567890to_long_1234567890to_long_1234567890to_long_1234567890to_long_1234567890to_long_1234567890', username: 'user-device-admin', password: 'adminpw' }
|
||||
post '/api/v1/signin', params: params.to_json, headers: @headers
|
||||
assert_response(422)
|
||||
result = JSON.parse(@response.body)
|
||||
|
||||
assert_equal(result.class, Hash)
|
||||
assert_equal('fingerprint is 198 chars but can only be 160 chars!', result['error'])
|
||||
assert_not(result['config'])
|
||||
assert_not(controller.session[:user_device_fingerprint])
|
||||
|
||||
Scheduler.worker(true)
|
||||
|
||||
assert_equal(0, UserDevice.where(user_id: @admin.id).count)
|
||||
assert_equal(0, email_notification_count('user_device_new', @admin.email))
|
||||
assert_equal(0, email_notification_count('user_device_new_location', @admin.email))
|
||||
|
||||
end
|
||||
|
||||
test '12 - login with integer as fingerprint' do
|
||||
|
||||
assert_equal(0, UserDevice.where(user_id: @admin.id).count)
|
||||
assert_equal(0, email_notification_count('user_device_new', @admin.email))
|
||||
assert_equal(0, email_notification_count('user_device_new_location', @admin.email))
|
||||
|
||||
params = { fingerprint: 123_456_789, username: 'user-device-admin', password: 'adminpw' }
|
||||
post '/api/v1/signin', params: params.to_json, headers: @headers
|
||||
assert_response(201)
|
||||
result = JSON.parse(@response.body)
|
||||
assert(123_456_789, controller.session[:user_device_fingerprint])
|
||||
|
||||
Scheduler.worker(true)
|
||||
|
||||
assert_equal(1, UserDevice.where(user_id: @admin.id).count)
|
||||
assert_equal(0, email_notification_count('user_device_new', @admin.email))
|
||||
assert_equal(0, email_notification_count('user_device_new_location', @admin.email))
|
||||
assert_equal(result.class, Hash)
|
||||
assert_nil(result['error'])
|
||||
end
|
||||
|
||||
test '13 - login form controller - check no user device logging' do
|
||||
|
||||
Setting.set('form_ticket_create', true)
|
||||
|
||||
assert_equal(0, UserDevice.where(user_id: @admin.id).count)
|
||||
assert_equal(0, email_notification_count('user_device_new', @admin.email))
|
||||
assert_equal(0, email_notification_count('user_device_new_location', @admin.email))
|
||||
|
||||
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('user-device-admin', 'adminpw')
|
||||
|
||||
params = {
|
||||
fingerprint: 'long_1234567890long_1234567890long_1234567890long_1234567890long_1234567890long_1234567890long_1234567890long_1234567890long_1234567890long_1234567890long_1234567890'
|
||||
}
|
||||
post '/api/v1/form_config', params: params.to_json, headers: @headers.merge('Authorization' => credentials)
|
||||
assert_response(200)
|
||||
|
||||
result = JSON.parse(@response.body)
|
||||
assert_equal(result.class, Hash)
|
||||
assert_not(result['error'])
|
||||
assert(result['endpoint'])
|
||||
assert_not(controller.session[:user_device_fingerprint])
|
||||
|
||||
Scheduler.worker(true)
|
||||
|
||||
assert_equal(0, UserDevice.where(user_id: @admin.id).count)
|
||||
assert_equal(0, email_notification_count('user_device_new', @admin.email))
|
||||
assert_equal(0, email_notification_count('user_device_new_location', @admin.email))
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -49,8 +49,8 @@ class EmailBuildTest < ActiveSupport::TestCase
|
||||
{
|
||||
'Mime-Type' => 'image/png',
|
||||
:content => 'xxx',
|
||||
:filename => 'somename.png',
|
||||
},
|
||||
:filename => 'somename.png'
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
@@ -103,8 +103,8 @@ class EmailBuildTest < ActiveSupport::TestCase
|
||||
{
|
||||
'Mime-Type' => 'image/png',
|
||||
:content => 'xxx',
|
||||
:filename => 'somename.png',
|
||||
},
|
||||
:filename => 'somename.png'
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
@@ -114,6 +114,7 @@ class EmailBuildTest < ActiveSupport::TestCase
|
||||
>'
|
||||
assert_equal(should, mail.text_part.body.to_s)
|
||||
assert_nil(mail.html_part)
|
||||
assert_equal('image/png; filename=somename.png', mail.attachments[0].content_type)
|
||||
|
||||
parser = Channel::EmailParser.new
|
||||
data = parser.parse(mail.to_s)
|
||||
@@ -137,6 +138,88 @@ class EmailBuildTest < ActiveSupport::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
test 'plain email + attachment check 2' do
|
||||
ticket1 = Ticket.create!(
|
||||
title: 'some article helper test1',
|
||||
group: Group.lookup(name: 'Users'),
|
||||
customer_id: 2,
|
||||
state: Ticket::State.lookup(name: 'new'),
|
||||
priority: Ticket::Priority.lookup(name: '2 normal'),
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1,
|
||||
)
|
||||
assert(ticket1, 'ticket created')
|
||||
|
||||
# create inbound article #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',
|
||||
content_type: 'text/html',
|
||||
body: 'some message article helper test1 <div><img style="width: 85.5px; height: 49.5px" src="cid:15.274327094.140938@zammad.example.com">asdasd<img src="cid:15.274327094.140939@zammad.example.com"><br>',
|
||||
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,
|
||||
)
|
||||
|
||||
store1 = Store.add(
|
||||
object: 'Ticket::Article',
|
||||
o_id: article1.id,
|
||||
data: 'content_file1_normally_should_be_an_ics_calendar_file',
|
||||
filename: 'schedule.ics',
|
||||
preferences: {
|
||||
'Mime-Type' => 'text/calendar'
|
||||
},
|
||||
created_by_id: 1,
|
||||
)
|
||||
|
||||
text = '> Welcome!
|
||||
>
|
||||
> Thank you for installing Zammad. äöüß
|
||||
>'
|
||||
mail = Channel::EmailBuild.build(
|
||||
from: 'sender@example.com',
|
||||
to: 'recipient@example.com',
|
||||
body: text,
|
||||
attachments: [
|
||||
store1
|
||||
],
|
||||
)
|
||||
|
||||
should = '> Welcome!
|
||||
>
|
||||
> Thank you for installing Zammad. äöüß
|
||||
>'
|
||||
assert_equal(should, mail.text_part.body.to_s)
|
||||
assert_nil(mail.html_part)
|
||||
assert_equal('text/calendar; filename=schedule.ics', mail.attachments[0].content_type)
|
||||
|
||||
parser = Channel::EmailParser.new
|
||||
data = parser.parse(mail.to_s)
|
||||
|
||||
# check body
|
||||
assert_equal(should, data[:body])
|
||||
|
||||
# check count of attachments, 2
|
||||
assert_equal(1, data[:attachments].length)
|
||||
|
||||
# check attachments
|
||||
data[:attachments]&.each do |attachment|
|
||||
if attachment[:filename] == 'schedule.ics'
|
||||
assert(attachment[:preferences]['Content-ID'])
|
||||
assert_nil(attachment[:preferences]['content-alternative'])
|
||||
assert_equal('text/calendar', attachment[:preferences]['Mime-Type'])
|
||||
assert_equal('UTF-8', attachment[:preferences]['Charset'])
|
||||
else
|
||||
assert(false, "invalid attachment, should not be there, #{attachment.inspect}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test 'plain email + without attachment check' do
|
||||
text = '> Welcome!
|
||||
>
|
||||
|
||||
@@ -663,4 +663,87 @@ Comment: [] =
|
||||
assert_equal(ticket_count, Ticket.count)
|
||||
end
|
||||
|
||||
test 'icinga email autoclose' do
|
||||
Setting.set('icinga_sender', 'zaihan@example.com')
|
||||
email_raw_string = 'Return-Path: <support@example.com>
|
||||
Received: from 04747418efb9 ([175.137.28.47])
|
||||
by smtp.example.com with ESMTPSA id r14sm6448824pfa.163.2018.04.03.10.10.59
|
||||
for <support@example.com>
|
||||
(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);
|
||||
Tue, 03 Apr 2018 10:11:00 -0700 (PDT)
|
||||
From: "zaihan@example.com" <zaihan@example.com>
|
||||
X-Google-Original-From: "zaihan@example.com" <zaihan@example.com>
|
||||
Message-ID: <301139.467253478-sendEmail@04747418efb9>
|
||||
To: "support@example.com" <support@example.com>
|
||||
Subject: PROBLEM - Awesell - is DOWN
|
||||
Date: Tue, 3 Apr 2018 17:11:04 +0000
|
||||
X-Mailer: sendEmail-1.56
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/related; boundary="----MIME delimiter for sendEmail-587258.191387267"
|
||||
|
||||
This is a multi-part message in MIME format. To properly display this message you need a MIME-Version 1.0 compliant Email program.
|
||||
|
||||
------MIME delimiter for sendEmail-587258.191387267
|
||||
Content-Type: text/plain;
|
||||
charset="iso-8859-1"
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
***** Host Monitoring on 04747418efb9 *****
|
||||
|
||||
Awesell is DOWN!
|
||||
|
||||
Info: PING CRITICAL - Packet loss = 100%
|
||||
|
||||
When: 2018-04-03 17:11:04 +0000
|
||||
Host: Awesell
|
||||
IPv4: 192.168.1.8
|
||||
|
||||
------MIME delimiter for sendEmail-587258.191387267--'
|
||||
ticket_0, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
|
||||
assert_equal('new', ticket_0.state.name)
|
||||
assert(ticket_0.preferences)
|
||||
assert(ticket_0.preferences['icinga'])
|
||||
assert_equal('DOWN', ticket_0.preferences['icinga']['state'])
|
||||
|
||||
email_raw_string = 'Return-Path: <support@example.com>
|
||||
Received: from 04747418efb9 ([175.137.28.47])
|
||||
by smtp.example.com with ESMTPSA id b73sm6127782pga.62.2018.04.03.10.31.00
|
||||
for <support@example.com>
|
||||
(version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);
|
||||
Tue, 03 Apr 2018 10:31:01 -0700 (PDT)
|
||||
From: "zaihan@example.com" <zaihan@example.com>
|
||||
X-Google-Original-From: "zaihan@example.com" <zaihan@example.com>
|
||||
Message-ID: <601339.882795827-sendEmail@04747418efb9>
|
||||
To: "support@example.com" <support@example.com>
|
||||
Subject: RECOVERY - Awesell - is UP
|
||||
Date: Tue, 3 Apr 2018 17:31:05 +0000
|
||||
X-Mailer: sendEmail-1.56
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/related; boundary="----MIME delimiter for sendEmail-322998.239033954"
|
||||
|
||||
This is a multi-part message in MIME format. To properly display this message you need a MIME-Version 1.0 compliant Email program.
|
||||
|
||||
------MIME delimiter for sendEmail-322998.239033954
|
||||
Content-Type: text/plain;
|
||||
charset="iso-8859-1"
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
***** Host Monitoring on 04747418efb9 *****
|
||||
|
||||
Awesell is UP!
|
||||
|
||||
Info: PING OK - Packet loss = 68%, RTA = 0.59 ms
|
||||
|
||||
When: 2018-04-03 17:31:05 +0000
|
||||
Host: Awesell
|
||||
IPv4: 192.168.1.8
|
||||
|
||||
------MIME delimiter for sendEmail-322998.239033954--
|
||||
'
|
||||
ticket_1, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
|
||||
assert_equal('closed', ticket_1.state.name)
|
||||
assert(ticket_1.preferences)
|
||||
assert(ticket_1.preferences['icinga'])
|
||||
assert_equal('DOWN', ticket_1.preferences['icinga']['state'])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -35,6 +35,57 @@ class OnlineNotificationTest < ActiveSupport::TestCase
|
||||
created_by_id: 1
|
||||
)
|
||||
@customer_user = User.lookup(email: 'nicole.braun@zammad.org')
|
||||
|
||||
calendar1 = Calendar.create_or_update(
|
||||
name: 'EU 1 - test',
|
||||
timezone: 'Europe/Berlin',
|
||||
business_hours: {
|
||||
mon: {
|
||||
active: true,
|
||||
timeframes: [ ['00:00', '23:59'] ]
|
||||
},
|
||||
tue: {
|
||||
active: true,
|
||||
timeframes: [ ['00:00', '23:59'] ]
|
||||
},
|
||||
wed: {
|
||||
active: true,
|
||||
timeframes: [ ['00:00', '23:59'] ]
|
||||
},
|
||||
thu: {
|
||||
active: true,
|
||||
timeframes: [ ['00:00', '23:59'] ]
|
||||
},
|
||||
fri: {
|
||||
active: true,
|
||||
timeframes: [ ['00:00', '23:59'] ]
|
||||
},
|
||||
sat: {
|
||||
active: true,
|
||||
timeframes: [ ['00:00', '23:59'] ]
|
||||
},
|
||||
sun: {
|
||||
active: true,
|
||||
timeframes: [ ['00:00', '23:59'] ]
|
||||
},
|
||||
},
|
||||
default: true,
|
||||
ical_url: nil,
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1,
|
||||
)
|
||||
|
||||
sla1 = Sla.create_or_update(
|
||||
name: 'test sla 1',
|
||||
condition: {},
|
||||
first_response_time: 20,
|
||||
update_time: 60,
|
||||
solution_time: 120,
|
||||
calendar_id: calendar1.id,
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1,
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
test 'ticket notification' do
|
||||
|
||||
@@ -5,9 +5,49 @@ require 'stats/ticket_waiting_time'
|
||||
class StatsTicketWaitingTimeTest < ActiveSupport::TestCase
|
||||
|
||||
test 'single ticket' do
|
||||
ticket1 = Ticket.create(
|
||||
|
||||
group1 = Group.create!(
|
||||
name: 'Group 1',
|
||||
active: true,
|
||||
email_address: EmailAddress.first,
|
||||
created_by_id: 1,
|
||||
updated_by_id: 1,
|
||||
)
|
||||
roles = Role.where(name: 'Agent')
|
||||
user1 = User.create!(
|
||||
login: 'assets_stats1@example.org',
|
||||
firstname: 'assets_stats1',
|
||||
lastname: 'assets_stats1',
|
||||
email: 'assets_stats1@example.org',
|
||||
password: 'some_pass',
|
||||
active: true,
|
||||
groups: [group1],
|
||||
roles: roles,
|
||||
created_by_id: 1,
|
||||
updated_by_id: 1,
|
||||
)
|
||||
user2 = User.create!(
|
||||
login: 'assets_stats2@example.org',
|
||||
firstname: 'assets_stats2',
|
||||
lastname: 'assets_stats2',
|
||||
email: 'assets_sla2@example.org',
|
||||
password: 'some_pass',
|
||||
active: true,
|
||||
groups: [group1],
|
||||
roles: roles,
|
||||
created_by_id: 1,
|
||||
updated_by_id: 1,
|
||||
)
|
||||
|
||||
result = Stats::TicketWaitingTime.generate(user1)
|
||||
assert_equal(0, result[:handling_time])
|
||||
assert_equal('supergood', result[:state])
|
||||
assert_equal(0, result[:average_per_agent])
|
||||
assert_equal(0, result[:percent])
|
||||
|
||||
ticket1 = Ticket.create!(
|
||||
title: 'com test 1',
|
||||
group: Group.lookup(name: 'Users'),
|
||||
group: group1,
|
||||
customer_id: 2,
|
||||
state: Ticket::State.lookup(name: 'new'),
|
||||
priority: Ticket::Priority.lookup(name: '2 normal'),
|
||||
@@ -16,7 +56,7 @@ class StatsTicketWaitingTimeTest < ActiveSupport::TestCase
|
||||
)
|
||||
|
||||
# communication 1: waiting time 2 hours (BUT too old yesterday)
|
||||
Ticket::Article.create(
|
||||
Ticket::Article.create!(
|
||||
ticket_id: ticket1.id,
|
||||
from: 'a@example.com',
|
||||
to: 'a@example.com',
|
||||
@@ -31,7 +71,7 @@ class StatsTicketWaitingTimeTest < ActiveSupport::TestCase
|
||||
created_at: '2017-04-12 08:00',
|
||||
updated_at: '2017-04-12 08:00',
|
||||
)
|
||||
Ticket::Article.create(
|
||||
Ticket::Article.create!(
|
||||
ticket_id: ticket1.id,
|
||||
from: 'a@example.com',
|
||||
to: 'a@example.com',
|
||||
@@ -48,7 +88,7 @@ class StatsTicketWaitingTimeTest < ActiveSupport::TestCase
|
||||
)
|
||||
|
||||
# communication 2: waiting time 2 hours
|
||||
Ticket::Article.create(
|
||||
Ticket::Article.create!(
|
||||
ticket_id: ticket1.id,
|
||||
from: 'a@example.com',
|
||||
to: 'a@example.com',
|
||||
@@ -63,7 +103,7 @@ class StatsTicketWaitingTimeTest < ActiveSupport::TestCase
|
||||
created_at: '2017-04-13 08:00',
|
||||
updated_at: '2017-04-13 08:00',
|
||||
)
|
||||
Ticket::Article.create(
|
||||
Ticket::Article.create!(
|
||||
ticket_id: ticket1.id,
|
||||
from: 'a@example.com',
|
||||
to: 'a@example.com',
|
||||
@@ -80,7 +120,7 @@ class StatsTicketWaitingTimeTest < ActiveSupport::TestCase
|
||||
)
|
||||
|
||||
# communication 3: waiting time 4 hours
|
||||
Ticket::Article.create(
|
||||
Ticket::Article.create!(
|
||||
ticket_id: ticket1.id,
|
||||
from: 'a@example.com',
|
||||
to: 'a@example.com',
|
||||
@@ -95,7 +135,7 @@ class StatsTicketWaitingTimeTest < ActiveSupport::TestCase
|
||||
created_at: '2017-04-13 11:00',
|
||||
updated_at: '2017-04-13 11:00',
|
||||
)
|
||||
Ticket::Article.create(
|
||||
Ticket::Article.create!(
|
||||
ticket_id: ticket1.id,
|
||||
from: 'a@example.com',
|
||||
to: 'a@example.com',
|
||||
@@ -112,7 +152,7 @@ class StatsTicketWaitingTimeTest < ActiveSupport::TestCase
|
||||
)
|
||||
|
||||
# communication 4: INVALID waiting time 1 hour (because internal)
|
||||
Ticket::Article.create(
|
||||
Ticket::Article.create!(
|
||||
ticket_id: ticket1.id,
|
||||
from: 'a@example.com',
|
||||
to: 'a@example.com',
|
||||
@@ -127,7 +167,7 @@ class StatsTicketWaitingTimeTest < ActiveSupport::TestCase
|
||||
created_at: '2017-04-13 15:00',
|
||||
updated_at: '2017-04-13 15:00',
|
||||
)
|
||||
Ticket::Article.create(
|
||||
Ticket::Article.create!(
|
||||
ticket_id: ticket1.id,
|
||||
from: 'a@example.com',
|
||||
to: 'a@example.com',
|
||||
@@ -143,13 +183,83 @@ class StatsTicketWaitingTimeTest < ActiveSupport::TestCase
|
||||
updated_at: '2017-04-13 15:10',
|
||||
)
|
||||
|
||||
average_time = Stats::TicketWaitingTime.calculate_average([ticket1, ticket1], '2017-04-13 00:00:00')
|
||||
ticket2 = Ticket.create!(
|
||||
title: 'com test 2',
|
||||
group: group1,
|
||||
customer_id: 2,
|
||||
state: Ticket::State.lookup(name: 'new'),
|
||||
priority: Ticket::Priority.lookup(name: '2 normal'),
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1,
|
||||
)
|
||||
|
||||
average_time = Stats::TicketWaitingTime.calculate_average([ticket1.id, ticket2.id], '2017-04-13 00:00:00')
|
||||
|
||||
expected_average_time = 60 * 60 * 2 # for communication 2
|
||||
expected_average_time += 60 * 60 * 4 # for communication 3
|
||||
expected_average_time = expected_average_time / 2 # for average
|
||||
|
||||
travel_to Time.zone.local(2017, 0o4, 13, 23, 0o0, 44)
|
||||
|
||||
assert_equal(expected_average_time, average_time)
|
||||
|
||||
result = Stats::TicketWaitingTime.generate(user1)
|
||||
assert_equal(0, result[:handling_time])
|
||||
assert_equal('supergood', result[:state])
|
||||
assert_equal(180, result[:average_per_agent])
|
||||
assert_equal(0.0, result[:percent])
|
||||
|
||||
ticket3 = Ticket.create!(
|
||||
title: 'com test 3',
|
||||
group: group1,
|
||||
customer_id: 2,
|
||||
owner: user1,
|
||||
state: Ticket::State.lookup(name: 'new'),
|
||||
priority: Ticket::Priority.lookup(name: '2 normal'),
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1,
|
||||
)
|
||||
|
||||
# communication 1: waiting time 2 hours (BUT too old yesterday)
|
||||
Ticket::Article.create!(
|
||||
ticket_id: ticket3.id,
|
||||
from: 'a@example.com',
|
||||
to: 'a@example.com',
|
||||
subject: 'com test 3',
|
||||
message_id: 'some@id_com_1',
|
||||
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,
|
||||
created_at: '2017-04-13 08:00',
|
||||
updated_at: '2017-04-13 08:00',
|
||||
)
|
||||
Ticket::Article.create!(
|
||||
ticket_id: ticket3.id,
|
||||
from: 'a@example.com',
|
||||
to: 'a@example.com',
|
||||
subject: 'com test 3',
|
||||
message_id: 'some@id_com_1',
|
||||
body: 'some message 123',
|
||||
internal: false,
|
||||
sender: Ticket::Article::Sender.find_by(name: 'Agent'),
|
||||
type: Ticket::Article::Type.find_by(name: 'email'),
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1,
|
||||
created_at: '2017-04-13 09:00',
|
||||
updated_at: '2017-04-13 09:00',
|
||||
)
|
||||
|
||||
result = Stats::TicketWaitingTime.generate(user1)
|
||||
assert_equal(60, result[:handling_time])
|
||||
assert_equal('supergood', result[:state])
|
||||
assert_equal(140, result[:average_per_agent])
|
||||
assert_equal(1.0, result[:percent])
|
||||
|
||||
travel_back
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -114,12 +114,24 @@ class TicketEscalationTest < ActiveSupport::TestCase
|
||||
|
||||
ticket.save!
|
||||
assert_not(ticket.has_changes_to_save?)
|
||||
assert(ticket.escalation_at)
|
||||
assert_equal(ticket_escalation_at.to_s, ticket.escalation_at.to_s)
|
||||
|
||||
ticket.title = 'some value 123-1'
|
||||
ticket.save!
|
||||
assert_not(ticket.has_changes_to_save?)
|
||||
|
||||
assert(ticket.escalation_at)
|
||||
assert_not_equal(ticket_escalation_at.to_s, ticket.escalation_at.to_s)
|
||||
|
||||
sla.destroy!
|
||||
calendar.destroy!
|
||||
|
||||
ticket.save!
|
||||
assert_not(ticket.has_changes_to_save?)
|
||||
assert(ticket.escalation_at)
|
||||
|
||||
ticket.title = 'some value 123-2'
|
||||
ticket.save!
|
||||
assert_not(ticket.has_changes_to_save?)
|
||||
assert_not(ticket.escalation_at)
|
||||
|
||||
@@ -315,4 +315,18 @@ class UserDeviceTest < ActiveSupport::TestCase
|
||||
|
||||
end
|
||||
|
||||
test 'invalid fingerprint size' do
|
||||
|
||||
assert_raises(Exceptions::UnprocessableEntity) do
|
||||
UserDevice.add(
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.107 Safari/537.36',
|
||||
'91.115.248.231',
|
||||
@agent.id,
|
||||
'fingerprint1234fingerprint1234fingerprint1234fingerprint1234fingerprint1234fingerprint1234fingerprint1234fingerprint1234fingerprint1234fingerprint1234fingerprint1234',
|
||||
'session',
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user