Compare commits

...

30 Commits

Author SHA1 Message Date
Martin Edenhofer
2153521d32 Updated CHANGES to 2.7.1. 2018-12-03 15:43:57 +01:00
Thorsten Eckel
28404fb8e7 Updated rails gem (CVE-2018-16476). 2018-11-28 14:45:54 +01:00
Martin Edenhofer
2fccbc28e8 Improved performance of utf8_encode!() and html2text() (issue #2374). 2018-11-27 10:33:06 +01:00
Thorsten Eckel
5821a463d3 Improve performance by fetching 'fqdn' Setting in HTML sanitization only once instead for each link found (issue #2374). 2018-11-27 10:33:02 +01:00
Thorsten Eckel
1044d4f000 Improve performance by not checking empty strings or strings not containing ':' for URLs (issue #2374). 2018-11-27 10:32:55 +01:00
Thorsten Eckel
9bf49e5481 Improved performance by checking in memory Cache TTL with integers instead of TimeZone objects (issue #2374). 2018-11-27 10:32:49 +01:00
Ishan Kanade
0203025dfc Fix Zendesk import failure on lowest subscription tier, aka "Essential" (fixes #2262)
This merges PR #2303, adding one small, stylistic change to follow a Rails idiom.
It also includes a regression test for the bugfix supplied by the PR.
2018-11-21 23:37:11 +08:00
Ryan Lue
43a4ee106e Hide autosaved article drafts from server logs (fixes #2363) 2018-11-21 11:37:04 +01:00
Martin Edenhofer
f87b13b076 Fixed issue #2353 - Chat does not work due to errors in WebSocket (Database connection). Credits to @d--j for providing the solution. 2018-11-17 07:40:09 +01:00
Martin Edenhofer
b1b2155a84 Merge branch 'stable' of git.znuny.com:zammad/zammad into stable 2018-11-16 08:30:33 +01:00
Martin Edenhofer
50d8acd797 Followup for issue #2330 - selecting the overviews on the I Pad does not work. 2018-11-16 08:30:11 +01:00
Martin Edenhofer
566cc26f0f Fixed issue #2330 - selecting the overviews on the I Pad does not work.
Dropdown menus should have been on top of their backdrop because when the dropdown is triggered via the button the backdrop is inserted before after the button and before the dropdown menu. So the backdrop is behind the menu in the DOM and they could be on the same z-index but the menu would still be in front of the backdrop because of the order.

When triggering the dropdown via javascript the backdrop gets inserted behind the backdrop. Now the backdrop is overlaying the dropdown menu making its items unclickable. This only happens on touch devices because only there the backdrop gets inserted.

Change: we increase the z-index of the dropdown menu (lvl 5 to lvl 6) to be always on top of the backdrop, no matter how the elements are ordered. At the same time we increased the z-index of the elements laying on z-index lvl 6 to lvl7 which was previousely unused. This applies to the global search, modal clues and popovers.
2018-11-16 08:19:35 +01:00
Thorsten Eckel
fd8584492f Added temporary workaround for issue #2343 - Lost DB connection causes jobs to not be processed anymore. 2018-11-13 15:37:48 +01:00
Billy Zhou
5eb98fd372 Fixed #2332 - Role-Filter shows inactive Roles
(cherry picked from commit 5796f78c4f)
2018-11-13 08:05:53 +01:00
Martin Edenhofer
19da152d18 Follow up #2321 - In certain cases the timeout for IMAP actions isn't sufficient (e. g. expunge for 20k E-Mails takes longer than 1 minute). 2018-11-12 08:48:56 +01:00
Thorsten Eckel
05f1354f19 Fixed issue #2321 - IMAP fetch stops because of request timeout 2018-11-06 15:30:05 +01:00
Thorsten Eckel
6b784ec6af Updated rack gem (CVE-2018-16470, CVE-2018-16471). 2018-11-06 13:16:44 +01:00
Martin Edenhofer
6e0a63a3c2 Fixed issue #2329 - Unable to import (update) users with email addresses als lookup index written in upper letters. 2018-11-06 06:44:23 +01:00
Martin Edenhofer
3411dd5337 Fixed calendar test with timezones. 2018-11-05 08:09:29 +01:00
Martin Edenhofer
40d80e37ff Fixed issue #2327 - Websocket messages are not working correctly via Ajax long polling (e. g. multiple browser tabs can get opened with one session). 2018-11-05 07:48:09 +01:00
Martin Edenhofer
7a8d1d03e2 Stabilised test. 2018-11-01 09:30:31 +01:00
Martin Edenhofer
677a2a8e6c Fixed issue #2324 - Improved error handling of invalid tag for text modules and add current time format (to not use 2018-10-31T08:02:21.917Z format). 2018-10-31 17:43:26 +01:00
Martin Edenhofer
ee66e0a444 Merge branch 'stable' of git.znuny.com:zammad/zammad into stable 2018-10-31 10:58:27 +01:00
Martin Edenhofer
ec6f7e68fd Updated loofah gem (CVE-2018-16468). 2018-10-31 10:57:59 +01:00
Ryan Lue
ff74137471 Show list of customer/org tickets in created_at order (fixes #2296) 2018-10-30 13:31:39 +01:00
Martin Edenhofer
f5a22caae0 Merge branch 'stable' of git.znuny.com:zammad/zammad into stable 2018-10-29 15:50:52 +01:00
Martin Edenhofer
399066bd2d Fixed admin calendar sla test (time zone adjustment). 2018-10-29 15:50:07 +01:00
Ryan Lue
b8ff7cd86d Ignore <img src="cid:..."> in forward-email text field (fixes #2305) 2018-10-29 16:23:26 +08:00
Martin Edenhofer
a3c0d67c8c Added article to placeholder options. 2018-10-25 15:14:03 +02:00
Martin Edenhofer
11487b85fe Addec changes for 2.7.0. 2018-10-25 07:27:35 +02:00
68 changed files with 54428 additions and 245 deletions

View File

@@ -1,16 +1,94 @@
# Change Log
## [2.7.0](https://github.com/zammad/zammad/tree/2.7.0) (2018-xx-xx)
## [2.7.1](https://github.com/zammad/zammad/tree/2.7.1) (2018-12-03)
[Full Changelog](https://github.com/zammad/zammad/compare/2.7.0...2.7.1)
**Implemented enhancements:**
- Logs flooded with article autosave data [2363](https://github.com/zammad/zammad/issues/2363) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
**Fixed bugs:**
- Unable to process html email with 2k links in an acceptable time [2374](https://github.com/zammad/zammad/issues/2374) [[bug](https://github.com/zammad/zammad/labels/bug)] [[needs verification](https://github.com/zammad/zammad/labels/needs verification)]
- Zendesk-Import fails for User & Organizations if Plan is below "Team" [2262](https://github.com/zammad/zammad/issues/2262) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)]
- Chat does not work due to errors in WebSocket (Database connection) [2353](https://github.com/zammad/zammad/issues/2353) [[bug](https://github.com/zammad/zammad/labels/bug)]
- selecting the overviews on the I Pad does not work [2330](https://github.com/zammad/zammad/issues/2330) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Lost DB connection causes jobs to not be processed anymore [2343](https://github.com/zammad/zammad/issues/2343) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Role-Filter shows inactive Roles [2332](https://github.com/zammad/zammad/issues/2332) [[admin area](https://github.com/zammad/zammad/labels/admin area)] [[bug](https://github.com/zammad/zammad/labels/bug)]
- IMAP fetch stops because of request timeout [2321](https://github.com/zammad/zammad/issues/2321) [[bug](https://github.com/zammad/zammad/labels/bug)] [[channel](https://github.com/zammad/zammad/labels/channel)]
- Unable to import (update) users with email addresses als lookup index written in upper letters [2329](https://github.com/zammad/zammad/issues/2329) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Websocket messages are not working correctly via Ajax long polling (e. g. multiple browser tabs can get opened with one session). [2327](https://github.com/zammad/zammad/issues/2327) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Improved error handling of invalid tag for text modules and add current time format (to not use 2018-10-31T08:02:21.917Z format) [2324](https://github.com/zammad/zammad/issues/2324) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Ticket list in overview of customer and organisation random order [2296](https://github.com/zammad/zammad/issues/2296) [[bug](https://github.com/zammad/zammad/labels/bug)] [[frontend](https://github.com/zammad/zammad/labels/frontend)]
- Can't forward E-Mail in special cases within Firefox [2305](https://github.com/zammad/zammad/issues/2305) [[bug](https://github.com/zammad/zammad/labels/bug)] [[frontend](https://github.com/zammad/zammad/labels/frontend)]
## [2.7.0](https://github.com/zammad/zammad/tree/2.7.0) (2018-10-25)
[Full Changelog](https://github.com/zammad/zammad/compare/2.6.0...2.7.0)
**Implemented enhancements:**
- O365/Office365 authentication missing given- and surname [2281](https://github.com/zammad/zammad/issues/2281) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
- Callback issue when Zammad if internal and external FQDN differ [2223](https://github.com/zammad/zammad/issues/2223) [[CTI](https://github.com/zammad/zammad/labels/CTI)] [[admin area](https://github.com/zammad/zammad/labels/admin area)] [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[frontend](https://github.com/zammad/zammad/labels/frontend)]
- Italian and Spanish translations for notifications. [2270](https://github.com/zammad/zammad/issues/2270) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
- Make stats store and cti log searchable via elastichsearch & destroy stats store if user got deleted [2261](https://github.com/zammad/zammad/issues/2261) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
- Global Bcc for all outgoing emails for external archive options. [2228](https://github.com/zammad/zammad/issues/2228) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
- Added custom notification templates to make changes update save [2081](https://github.com/zammad/zammad/pull/2081) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[notification](https://github.com/zammad/zammad/labels/notification)]
- WebApp (monitoring section) claims scheduler not running, if delayed jobs get too many [2188](https://github.com/zammad/zammad/issues/2188) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
**Fixed bugs:**
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
- Reindex elastic search not possible because of <null>/Twitter::NullObject [1394](https://github.com/zammad/zammad/issues/1394) [[bug](https://github.com/zammad/zammad/labels/bug)]
- `rake searchindex:rebuild` is not working after Elasticsearch 2.4 upgrade to Elasticsearch 5.6 "Limit of total fields [1000] in index" [2297](https://github.com/zammad/zammad/issues/2297) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Update bot_add.jst.eco [2295](https://github.com/zammad/zammad/pull/2295)
- CTI Log: duration_talking_time is incorrect and after update initialized_at is a string (not timestamp) [2292](https://github.com/zammad/zammad/issues/2292) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Performance issue in bulk action selection in overview [2279](https://github.com/zammad/zammad/issues/2279) [[bug](https://github.com/zammad/zammad/labels/bug)] [[frontend](https://github.com/zammad/zammad/labels/frontend)] [[overviews](https://github.com/zammad/zammad/labels/overviews)] [[regression](https://github.com/zammad/zammad/labels/regression)]
- Note is not shown for customer / organisations if it's empty. [2277](https://github.com/zammad/zammad/issues/2277) [[bug](https://github.com/zammad/zammad/labels/bug)] [[regression](https://github.com/zammad/zammad/labels/regression)]
- Permission issue: response templates only usable when admin is also agent [2285](https://github.com/zammad/zammad/issues/2285) [[admin area](https://github.com/zammad/zammad/labels/admin area)] [[bug](https://github.com/zammad/zammad/labels/bug)]
- Bulk Form javascript error when checking item [2273](https://github.com/zammad/zammad/issues/2273) [[bug](https://github.com/zammad/zammad/labels/bug)] [[frontend](https://github.com/zammad/zammad/labels/frontend)] [[needs verification](https://github.com/zammad/zammad/labels/needs verification)] [[overviews](https://github.com/zammad/zammad/labels/overviews)] [[regression](https://github.com/zammad/zammad/labels/regression)]
- Records in Reporting not updated when single ActiveRecord can not be found [2246](https://github.com/zammad/zammad/issues/2246) [[bug](https://github.com/zammad/zammad/labels/bug)] [[reporting](https://github.com/zammad/zammad/labels/reporting)]
- Users deactivated by the LDAP sync get updated on every run [2256](https://github.com/zammad/zammad/issues/2256) [[LDAP](https://github.com/zammad/zammad/labels/LDAP)] [[bug](https://github.com/zammad/zammad/labels/bug)] [[integration](https://github.com/zammad/zammad/labels/integration)]
- Search with id:123 does not work anymore [2195](https://github.com/zammad/zammad/issues/2195) [[bug](https://github.com/zammad/zammad/labels/bug)] [[search](https://github.com/zammad/zammad/labels/search)]
- Update zammad_ssl.conf [2269](https://github.com/zammad/zammad/pull/2269)
- Fix failing Travis CI build [2268](https://github.com/zammad/zammad/pull/2268)
- Every session comes from 127.0.0.1 [742](https://github.com/zammad/zammad/issues/742) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Reply does not work as expected [2184](https://github.com/zammad/zammad/issues/2184) [[bug](https://github.com/zammad/zammad/labels/bug)] [[frontend](https://github.com/zammad/zammad/labels/frontend)]
- Invalid date causes errors [2173](https://github.com/zammad/zammad/issues/2173) [[bug](https://github.com/zammad/zammad/labels/bug)] [[frontend](https://github.com/zammad/zammad/labels/frontend)]
- Check the correct hash array if the agent is present [2243](https://github.com/zammad/zammad/pull/2243) [[bug](https://github.com/zammad/zammad/labels/bug)] [[needs verification](https://github.com/zammad/zammad/labels/needs verification)]
- CTI: Limit used caller log states and prevent race conditions (if callback comes later) [2255](https://github.com/zammad/zammad/issues/2255) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Boolean object set to false is not visible [2233](https://github.com/zammad/zammad/issues/2233) [[bug](https://github.com/zammad/zammad/labels/bug)] [[object manager attribute](https://github.com/zammad/zammad/labels/object manager attribute)]
- Unable to process spam email `"ERROR: #<Exceptions::UnprocessableEntity: Invalid email>"` [2254](https://github.com/zammad/zammad/issues/2254) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Unable to index elasticsearch if article.preferences[:delivery_status_message] has no uf8 charset [2253](https://github.com/zammad/zammad/issues/2253) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Unable to process email `"ERROR: #<NoMethodError: undefined method `match' for nil:NilClass>"` [2252](https://github.com/zammad/zammad/issues/2252) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Fixed typo in 404: Ressource -> resource [2251](https://github.com/zammad/zammad/pull/2251)
- Unable to process spam mail with invalid date field [2245](https://github.com/zammad/zammad/issues/2245) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Bulk action should not be shown if user has no change permissions [2026](https://github.com/zammad/zammad/issues/2026) [[bug](https://github.com/zammad/zammad/labels/bug)] [[bulk](https://github.com/zammad/zammad/labels/bulk)] [[frontend](https://github.com/zammad/zammad/labels/frontend)] [[overviews](https://github.com/zammad/zammad/labels/overviews)]
- Naming an attribute "attribute" causes ActiveRecord failure [2236](https://github.com/zammad/zammad/issues/2236) [[bug](https://github.com/zammad/zammad/labels/bug)] [[object manager attribute](https://github.com/zammad/zammad/labels/object manager attribute)]
- Overview setting isn't applied on submit [2235](https://github.com/zammad/zammad/issues/2235) [[bug](https://github.com/zammad/zammad/labels/bug)] [[frontend](https://github.com/zammad/zammad/labels/frontend)]
- Also not active report profiles are shown in #report screen [2232](https://github.com/zammad/zammad/issues/2232) [[bug](https://github.com/zammad/zammad/labels/bug)]
- LDAP/Exchange UTF-8 Status Code 500 [2140](https://github.com/zammad/zammad/issues/2140) [[LDAP](https://github.com/zammad/zammad/labels/LDAP)] [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)]
- In overviews conditions the tag name (not the tag id) is stored. Renaming tags will break overviews condition [1690](https://github.com/zammad/zammad/issues/1690) [[bug](https://github.com/zammad/zammad/labels/bug)] [[overviews](https://github.com/zammad/zammad/labels/overviews)]
- Wrong Recent viewed tickets list [2194](https://github.com/zammad/zammad/issues/2194) [[bug](https://github.com/zammad/zammad/labels/bug)] [[frontend](https://github.com/zammad/zammad/labels/frontend)]
- Unprocessable email ERROR: #<ArgumentError: invalid byte sequence in UTF-8> [2227](https://github.com/zammad/zammad/issues/2227) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Unable to process email (Encoding::ConverterNotFoundError: code converter not found (Windows-1258 to UTF-8)) [2224](https://github.com/zammad/zammad/issues/2224) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Printed documents do not contain the whole ticket content, if it gets too much [2162](https://github.com/zammad/zammad/issues/2162) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Email IMAP fetch stops working if mailserver is once unreachable [1861](https://github.com/zammad/zammad/issues/1861) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Unable to use exchange sync (stack level too deep (SystemStackError)) [2220](https://github.com/zammad/zammad/issues/2220) [[bug](https://github.com/zammad/zammad/labels/bug)] [[integration](https://github.com/zammad/zammad/labels/integration)]
- Exchange import fails if folder names can't be converted to Unicode [2152](https://github.com/zammad/zammad/issues/2152) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)] [[waiting for feedback](https://github.com/zammad/zammad/labels/waiting for feedback)]
- CTI Caller Log blocks user deletion/destroy and fails [2218](https://github.com/zammad/zammad/issues/2218) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Can't process email with space in email [2198](https://github.com/zammad/zammad/issues/2198) [[bug](https://github.com/zammad/zammad/labels/bug)] [[mail processing](https://github.com/zammad/zammad/labels/mail processing)]
- Unprocessable Mail if sender contains http.net@ [2199](https://github.com/zammad/zammad/issues/2199) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Call log shows only orga infos on event, not on site load [2075](https://github.com/zammad/zammad/issues/2075) [[CTI](https://github.com/zammad/zammad/labels/CTI)] [[bug](https://github.com/zammad/zammad/labels/bug)] [[frontend](https://github.com/zammad/zammad/labels/frontend)] [[needs verification](https://github.com/zammad/zammad/labels/needs verification)]
- Chat-Widget disappears after close [2197](https://github.com/zammad/zammad/issues/2197) [[bug](https://github.com/zammad/zammad/labels/bug)] [[chat](https://github.com/zammad/zammad/labels/chat)]
- work around for outlook feature that breaks email address standard [2154](https://github.com/zammad/zammad/issues/2154) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Upgrade from 2.0 to 2.5 db migrations canceled [2159](https://github.com/zammad/zammad/issues/2159) [[bug](https://github.com/zammad/zammad/labels/bug)] [[object manager attribute](https://github.com/zammad/zammad/labels/object manager attribute)] [[update](https://github.com/zammad/zammad/labels/update)] [[waiting for feedback](https://github.com/zammad/zammad/labels/waiting for feedback)]
- Order of relation select fields (Group, State etc.) values is broken [2209](https://github.com/zammad/zammad/issues/2209) [[bug](https://github.com/zammad/zammad/labels/bug)] [[frontend](https://github.com/zammad/zammad/labels/frontend)]
- Unable to modify tree_select attributes with fresh 2.6 [2206](https://github.com/zammad/zammad/issues/2206) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Max size of tabs is not recognised [2204](https://github.com/zammad/zammad/issues/2204) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Unable to process email `EncodingError: could not find a valid input encoding` [2200](https://github.com/zammad/zammad/issues/2200) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Email - full quote time specification [1426](https://github.com/zammad/zammad/issues/1426) [[feature backlog](https://github.com/zammad/zammad/labels/feature backlog)] [[ticket](https://github.com/zammad/zammad/labels/ticket)]
- Bulk-Action: Not possible to change owner [1864](https://github.com/zammad/zammad/issues/1864) [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[bug](https://github.com/zammad/zammad/labels/bug)] [[frontend](https://github.com/zammad/zammad/labels/frontend)]
- Incorrect reset after deleting single object-selection [2020](https://github.com/zammad/zammad/issues/2020) [[bug](https://github.com/zammad/zammad/labels/bug)] [[object manager attribute](https://github.com/zammad/zammad/labels/object manager attribute)]
- Puma performance is going worst if users with phone attribute are updated or new tickets are created. [2193](https://github.com/zammad/zammad/issues/2193) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Sometimes automatisation is not executed [2191](https://github.com/zammad/zammad/issues/2191) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Problem while updating to 2.6: Mysql2::Error: Invalid default value for 'start_at' [2183](https://github.com/zammad/zammad/issues/2183) [[bug](https://github.com/zammad/zammad/labels/bug)] [[update](https://github.com/zammad/zammad/labels/update)]
- LDAP / Exchange update (save!) records unnecessarily [2187](https://github.com/zammad/zammad/issues/2187) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)]
- Overview not showing unassigned tickets "if not defined" (e. g. for owner but also for select or input fields) [2171](https://github.com/zammad/zammad/issues/2171) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Incorrect encoding of email messages in HTML format (Windows-1250) [2167](https://github.com/zammad/zammad/issues/2167) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Call-Log shows inactive users [2096](https://github.com/zammad/zammad/issues/2096) [[CTI](https://github.com/zammad/zammad/labels/CTI)] [[bug](https://github.com/zammad/zammad/labels/bug)] [[frontend](https://github.com/zammad/zammad/labels/frontend)]

View File

@@ -2,7 +2,7 @@ source 'https://rubygems.org'
# core - base
ruby '2.4.4'
gem 'rails', '5.1.5'
gem 'rails', '5.1.6.1'
# core - rails additions
gem 'activerecord-import'

View File

@@ -23,37 +23,37 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (5.1.5)
actionpack (= 5.1.5)
actioncable (5.1.6.1)
actionpack (= 5.1.6.1)
nio4r (~> 2.0)
websocket-driver (~> 0.6.1)
actionmailer (5.1.5)
actionpack (= 5.1.5)
actionview (= 5.1.5)
activejob (= 5.1.5)
actionmailer (5.1.6.1)
actionpack (= 5.1.6.1)
actionview (= 5.1.6.1)
activejob (= 5.1.6.1)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (5.1.5)
actionview (= 5.1.5)
activesupport (= 5.1.5)
actionpack (5.1.6.1)
actionview (= 5.1.6.1)
activesupport (= 5.1.6.1)
rack (~> 2.0)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (5.1.5)
activesupport (= 5.1.5)
actionview (5.1.6.1)
activesupport (= 5.1.6.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
activejob (5.1.5)
activesupport (= 5.1.5)
activejob (5.1.6.1)
activesupport (= 5.1.6.1)
globalid (>= 0.3.6)
activemodel (5.1.5)
activesupport (= 5.1.5)
activerecord (5.1.5)
activemodel (= 5.1.5)
activesupport (= 5.1.5)
activemodel (5.1.6.1)
activesupport (= 5.1.6.1)
activerecord (5.1.6.1)
activemodel (= 5.1.6.1)
activesupport (= 5.1.6.1)
arel (~> 8.0)
activerecord-import (0.25.0)
activerecord (>= 3.2)
@@ -65,9 +65,9 @@ GEM
multi_json (~> 1.11, >= 1.11.2)
rack (>= 1.5.2, < 3)
railties (>= 4.0, < 5.2)
activesupport (5.1.5)
activesupport (5.1.6.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (~> 0.7)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
addressable (2.5.2)
@@ -105,9 +105,9 @@ GEM
coffee-script
execjs
json
composite_primary_keys (10.0.3)
activerecord (~> 5.1.0, >= 5.1.5)
concurrent-ruby (1.0.5)
composite_primary_keys (10.0.5)
activerecord (~> 5.1.0, >= 5.1.6)
concurrent-ruby (1.1.3)
coveralls (0.7.1)
multi_json (~> 1.3)
rest-client
@@ -199,7 +199,7 @@ GEM
http-form_data (2.0.0)
http_parser.rb (0.6.0)
httpclient (2.8.3)
i18n (0.9.5)
i18n (1.1.1)
concurrent-ruby (~> 1.0)
icalendar (2.4.1)
icalendar-recurrence (1.1.2)
@@ -225,7 +225,7 @@ GEM
logging (2.2.2)
little-plugger (~> 1.1)
multi_json (~> 1.10)
loofah (2.2.2)
loofah (2.2.3)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
lumberjack (1.0.12)
@@ -233,10 +233,10 @@ GEM
mime-types (>= 1.16, < 4)
memoizable (0.4.2)
thread_safe (~> 0.3, >= 0.3.1)
method_source (0.9.0)
mime-types (3.1)
method_source (0.9.2)
mime-types (3.2.2)
mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521)
mime-types-data (3.2018.0812)
mini_portile2 (2.3.0)
minitest (5.11.3)
multi_json (1.12.2)
@@ -248,7 +248,7 @@ GEM
nestful (1.1.3)
net-ldap (0.16.1)
netrc (0.11.0)
nio4r (2.3.0)
nio4r (2.3.1)
nokogiri (1.8.5)
mini_portile2 (~> 2.3.0)
nori (2.6.0)
@@ -323,22 +323,22 @@ GEM
pry (>= 0.9.11)
public_suffix (3.0.1)
puma (3.11.0)
rack (2.0.5)
rack (2.0.6)
rack-livereload (0.3.16)
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails (5.1.5)
actioncable (= 5.1.5)
actionmailer (= 5.1.5)
actionpack (= 5.1.5)
actionview (= 5.1.5)
activejob (= 5.1.5)
activemodel (= 5.1.5)
activerecord (= 5.1.5)
activesupport (= 5.1.5)
rails (5.1.6.1)
actioncable (= 5.1.6.1)
actionmailer (= 5.1.6.1)
actionpack (= 5.1.6.1)
actionview (= 5.1.6.1)
activejob (= 5.1.6.1)
activemodel (= 5.1.6.1)
activerecord (= 5.1.6.1)
activesupport (= 5.1.6.1)
bundler (>= 1.3.0)
railties (= 5.1.5)
railties (= 5.1.6.1)
sprockets-rails (>= 2.0.0)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
@@ -347,9 +347,9 @@ GEM
loofah (~> 2.2, >= 2.2.2)
rails-observers (0.1.5)
activemodel (>= 4.0)
railties (5.1.5)
actionpack (= 5.1.5)
activesupport (= 5.1.5)
railties (5.1.6.1)
actionpack (= 5.1.6.1)
activesupport (= 5.1.6.1)
method_source
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
@@ -447,7 +447,7 @@ GEM
therubyracer (0.12.3)
libv8 (~> 3.16.14.15)
ref
thor (0.20.0)
thor (0.20.3)
thread_safe (0.3.6)
tilt (2.0.8)
tins (1.15.1)
@@ -563,7 +563,7 @@ DEPENDENCIES
pry-stack_explorer
puma
rack-livereload
rails (= 5.1.5)
rails (= 5.1.6.1)
rails-observers
rb-fsevent
rchardet (>= 1.8.0)

View File

@@ -1320,6 +1320,7 @@ class App.Import extends App.ControllerModal
params.set('try', true)
if _.isEmpty(params.get('data'))
params.delete('data')
@formDisable(e)
@ajax(
id: 'csv_import'
type: 'POST'
@@ -1341,6 +1342,14 @@ class App.Import extends App.ControllerModal
return
@data = data
@update()
@formEnable(e)
error: (data) =>
details = data.responseJSON || {}
@notify
type: 'error'
msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to import!')
timeout: 6000
@formEnable(e)
)
class App.ImportTryResult extends App.ControllerModal
@@ -1361,10 +1370,23 @@ class App.ImportTryResult extends App.ControllerModal
import_example_url: "#{@baseUrl}/import"
result: @result
))
# check if data is processing...
if @data
result = App.view("#{@templateDirectory}/result")(
@data
)
content.find('.js-error').html(result)
if result
content.find('.js-error').removeClass('hide')
else
content.find('.js-error').addClass('hide')
content
onSubmit: (e) =>
@params.set('try', false)
@formDisable(e)
@ajax(
id: 'csv_import'
type: 'POST'
@@ -1386,6 +1408,14 @@ class App.ImportTryResult extends App.ControllerModal
return
@data = data
@update()
@formEnable(e)
error: (data) =>
details = data.responseJSON || {}
@notify
type: 'error'
msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to import!')
timeout: 6000
@formEnable(e)
)
class App.ImportResult extends App.ControllerModal

View File

@@ -364,6 +364,11 @@ class App.UiElement.ticket_perform_action
object: 'Ticket'
display: 'Ticket'
},
{
prefix: 'article'
object: 'TicketArticle'
display: 'Article'
},
{
prefix: 'user'
object: 'User'

View File

@@ -18,7 +18,7 @@ class Index extends App.ControllerSubContent
{ name: 'Import', 'data-type': 'import', class: 'btn' }
{ name: 'New User', 'data-type': 'new', class: 'btn--success' }
]
roles: App.Role.all()
roles: App.Role.findAllByAttribute('active', true)
)
@$('.tab').on(

View File

@@ -709,16 +709,29 @@ class App.Utils
key = key.replace(/<.+?>/g, '')
levels = key.split(/\./)
dataRef = objects
dataRefLast = undefined
for level in levels
if level of dataRef
if typeof dataRef is 'object' && level of dataRef
dataRefLast = dataRef
dataRef = dataRef[level]
else
dataRef = ''
break
value = undefined
if typeof dataRef is 'function'
value = dataRef()
else if dataRef isnt undefined && dataRef isnt null && dataRef.toString
value = dataRef.toString()
if dataRefLast && dataRefLast.constructor && dataRefLast.constructor.className
localClassRef = App[dataRefLast.constructor.className]
if localClassRef && localClassRef.attributesGet
attributes = localClassRef.attributesGet()
if attributes && attributes[level]
if attributes[level]['tag'] is 'datetime'
value = App.i18n.translateTimestamp(dataRef)
else if attributes[level]['tag'] is 'date'
value = App.i18n.translateDate(dataRef)
if !value
value = dataRef.toString()
else
value = ''
#console.log( "tag replacement #{key}, #{value} env: ", objects)
@@ -1146,7 +1159,7 @@ class App.Utils
html.find('img').each( (index) ->
src = $(@).attr('src')
if !src.match(/^data:/i)
if !src.match(/^(data|cid):/i) # <img src="cid: ..."> may mean broken emails (see issue #2305)
base64 = App.Utils._htmlImage2DataUrl(@)
$(@).attr('src', base64)
)

View File

@@ -35,6 +35,11 @@ class App.WebSocket
_instance ?= new _webSocketSingleton
_instance.support()
@queue: ->
if _instance == undefined
_instance ?= new _webSocketSingleton
_instance.queue
# The actual Singleton class
class _webSocketSingleton extends App.Controller
@include App.LogInclude
@@ -355,10 +360,10 @@ class _webSocketSingleton extends App.Controller
success: (data) =>
if data && data.error
@client_id = undefined
@_ajaxInit( force: true )
@_ajaxInit(force: true)
error: =>
@client_id = undefined
@_ajaxInit( force: true )
@_ajaxInit(force: true)
)
_ajaxReceive: =>

View File

@@ -1,3 +1,3 @@
<div class="batch-dragger zIndex-10">
<div class="batch-dragger">
<div class="batch-dragger-counter js-batch-dragger-count"></div>
</div>

View File

@@ -213,19 +213,6 @@ ol, ul {
cursor: not-allowed;
}
.zIndex-1,
.zIndex-2,
.zIndex-3,
.zIndex-4,
.zIndex-5,
.zIndex-6,
.zIndex-7,
.zIndex-8,
.zIndex-9,
.zIndex-10 {
position: relative;
}
.zIndex-1 {
z-index: 100;
}
@@ -2017,7 +2004,8 @@ input.has-error {
}
.selected-clue {
@extend .zIndex-6;
position: relative;
@extend .zIndex-7;
pointer-events: none;
}
@@ -3535,7 +3523,7 @@ footer {
}
.global-search-menu {
@extend .zIndex-6;
@extend .zIndex-7;
background: #26272e;
position: absolute;
left: 0;
@@ -4031,7 +4019,7 @@ footer {
padding: 10px 20px;
overflow: auto;
position: relative;
&--large-padding {
padding: 30px 40px;
}
@@ -4051,7 +4039,7 @@ footer {
}
.popover {
@extend .zIndex-6;
@extend .zIndex-7;
position: absolute;
font-family: inherit;
width: 372px;
@@ -4173,6 +4161,7 @@ footer {
max-width: 400px;
min-width: 350px;
flex-direction: column;
@extend .zIndex-10;
&.is-visible {
display: flex;
@@ -4682,18 +4671,18 @@ footer {
left: 0;
padding: 0 5px 0 20px;
width: 100%;
.controls-label {
display: none;
}
.form-control {
width: 70px;
line-height: inherit;
position: static;
padding: 0;
height: auto;
&.time {
margin-left: 5px;
width: 38px;
@@ -5410,7 +5399,7 @@ footer {
color: inherit;
align-items: center;
border-bottom: 1px solid hsl(0,0%,96%);
&:last-child {
border-bottom: none;
}
@@ -5422,11 +5411,11 @@ footer {
top: 9px;
width: 38px;
text-align: center;
svg {
vertical-align: bottom;
}
img {
width: 30px;
height: 30px;
@@ -6225,7 +6214,7 @@ footer {
/* allow/show autocomplete in modal dialog */
.ui-autocomplete.ui-widget-content {
@extend .zIndex-9;
@extend .zIndex-8;
position: absolute;
}
@@ -6235,11 +6224,11 @@ footer {
color: white;
font-family: inherit;
font-size: inherit;
.ui-menu-item {
padding: 9px 15px;
list-style-image: none;
&:not(:first-child) {
box-shadow: 0 1px rgba(255,255,255,.13) inset;
}
@@ -6388,8 +6377,7 @@ footer {
}
.modal {
position: fixed;
@extend .zIndex-9;
@extend .zIndex-10;
}
.modal-dialog {
@@ -6397,7 +6385,7 @@ footer {
max-width: 660px;
margin-top: 35px;
margin-bottom: 35px;
&.wizard {
margin: 35px auto;
}
@@ -6484,6 +6472,7 @@ footer {
.modal.modal--local {
display: block;
padding: 0 40px;
@extend .zIndex-9;
.modal-backdrop {
background: hsla(210,17%,93%,.55);
@@ -6503,13 +6492,11 @@ footer {
}
.dropdown-backdrop {
position: fixed;
@extend .zIndex-5; // has to be behind dropdown-menu (dropdown-menu is also zIndex-5 but behind dropdown-backdrop in dom)
@extend .zIndex-5; // has to be behind dropdown-menu
}
.dropdown-menu {
@extend .zIndex-5; // has to be behind modal windows and beneath notifications (popover)
position: absolute;
@extend .zIndex-6; // has to be behind modal windows and beneath notifications (popover)
margin: 0;
padding: 0;
min-width: 100%;
@@ -7383,7 +7370,7 @@ label + .wizard-buttonList {
padding: 0;
@extend %clickable;
opacity: 0;
&:focus + label {
text-decoration: underline;
}
@@ -7683,7 +7670,7 @@ label + .wizard-buttonList {
.horizontal-filter-value {
flex-shrink: 0;
// lower the min-width of url input fields (normally 400px) so that it fits in
input[type=url] {
min-width: 200px;
@@ -8465,7 +8452,7 @@ output {
top: 10px;
left: 0;
right: 0;
@extend .zIndex-10;
@extend .zIndex-9;
pointer-events: none;
.noty_bar {
@@ -9366,6 +9353,7 @@ output {
width: 250px;
height: 40px;
will-change: transform;
@extend .zIndex-10;
&-item {
position: absolute;

View File

@@ -8,6 +8,8 @@ module TicketStats
limit: limit,
condition: condition,
current_user: current_user,
sort_by: 'created_at',
order_by: 'desc',
)
assets_of_tickets(tickets, assets)
end

View File

@@ -2,6 +2,7 @@
class LongPollingController < ApplicationController
skip_before_action :session_update # prevent race conditions
prepend_before_action :authentication_check_only
# GET /api/v1/message_send
def message_send
@@ -14,24 +15,22 @@ class LongPollingController < ApplicationController
client_id = client_id_gen
log 'new client connection', client_id
end
if !params['data']
params['data'] = {}
end
data = params['data'].permit!.to_h
session_data = {}
if current_user&.id
session_data = { 'id' => current_user.id }
end
# spool messages for new connects
if params['data']['spool']
Sessions.spool_create(params['data'])
if data['spool']
Sessions.spool_create(data)
end
if params['data']['event'] == 'login'
if data['event'] == 'login'
Sessions.create(client_id, session_data, { type: 'ajax' })
elsif params['data']['event']
elsif data['event']
message = Sessions::Event.run(
event: params['data']['event'],
payload: params['data'],
event: data['event'],
payload: data,
session: session_data,
client_id: client_id,
clients: {},
@@ -41,15 +40,15 @@ class LongPollingController < ApplicationController
Sessions.send(client_id, message)
end
else
log "unknown message '#{params['data'].inspect}'", client_id
log "unknown message '#{data.inspect}'", client_id
end
if new_connection
result = { client_id: client_id }
render json: result
else
render json: {}
return
end
render json: {}
end
# GET /api/v1/message_receive
@@ -110,7 +109,7 @@ class LongPollingController < ApplicationController
params[:client_id].to_s
end
def log( data, client_id = '-' )
def log(data, client_id = '-')
logger.info "client(#{client_id}) #{data}"
end
end

View File

@@ -349,7 +349,12 @@ curl http://localhost/api/v1/organization/{id} -v -u #{login}:#{password} -H "Co
# @response_message 401 Invalid session.
def import_start
permission_check('admin.user')
string = params[:data] || params[:file].read.force_encoding('utf-8')
string = params[:data]
if string.blank? && params[:file].present?
string = params[:file].read.force_encoding('utf-8')
end
raise Exceptions::UnprocessableEntity, 'No source data submitted!' if string.blank?
result = Organization.csv_import(
string: string,
parse_params: {

View File

@@ -186,7 +186,12 @@ curl http://localhost/api/v1/text_modules.json -v -u #{login}:#{password} -H "Co
# @response_message 401 Invalid session.
def import_start
permission_check('admin.text_module')
string = params[:data] || params[:file].read.force_encoding('utf-8')
string = params[:data]
if string.blank? && params[:file].present?
string = params[:file].read.force_encoding('utf-8')
end
raise Exceptions::UnprocessableEntity, 'No source data submitted!' if string.blank?
result = TextModule.csv_import(
string: string,
parse_params: {

View File

@@ -318,8 +318,14 @@ class TicketArticlesController < ApplicationController
raise 'Only can import tickets if system is in import mode.'
end
string = params[:data]
if string.blank? && params[:file].present?
string = params[:file].read.force_encoding('utf-8')
end
raise Exceptions::UnprocessableEntity, 'No source data submitted!' if string.blank?
result = Ticket::Article.csv_import(
string: params[:file].read.force_encoding('utf-8'),
string: string,
parse_params: {
col_sep: ';',
},

View File

@@ -641,7 +641,12 @@ class TicketsController < ApplicationController
raise 'Only can import tickets if system is in import mode.'
end
string = params[:data] || params[:file].read.force_encoding('utf-8')
string = params[:data]
if string.blank? && params[:file].present?
string = params[:file].read.force_encoding('utf-8')
end
raise Exceptions::UnprocessableEntity, 'No source data submitted!' if string.blank?
result = Ticket.csv_import(
string: string,
parse_params: {

View File

@@ -1043,7 +1043,12 @@ curl http://localhost/api/v1/users/avatar -v -u #{login}:#{password} -H "Content
# @response_message 401 Invalid session.
def import_start
permission_check('admin.user')
string = params[:data] || params[:file].read.force_encoding('utf-8')
string = params[:data]
if string.blank? && params[:file].present?
string = params[:file].read.force_encoding('utf-8')
end
raise Exceptions::UnprocessableEntity, 'No source data submitted!' if string.blank?
result = User.csv_import(
string: string,
parse_params: {

View File

@@ -20,20 +20,33 @@ returns
=end
def lookup(**attr)
raise ArgumentError, "Multiple lookup attributes given (#{attr.keys.join(', ')}), only support (#{lookup_keys.join(', ')})" if attr.many?
attr.transform_keys!(&:to_sym).slice!(*lookup_keys)
raise ArgumentError, "Valid lookup attribute required (#{lookup_keys.join(', ')})" if attr.empty?
raise ArgumentError, "Multiple lookup attributes given (#{attr.keys.join(', ')})" if attr.many?
record = cache_get(attr.values.first)
record ||= find_and_save_to_cache_by(attr)
end
private
=begin
return possible lookup keys for model
result = Model.lookup_keys
returns
[:id, :name] # or fror users [:id, :login, :email]
=end
def lookup_keys
@lookup_keys ||= %i[id name login email number] & attribute_names.map(&:to_sym)
end
private
def find_and_save_to_cache_by(attr)
if !Rails.application.config.db_case_sensitive && string_key?(attr.keys.first)
where(attr).find { |r| r[attr.keys.first] == attr.values.first }

View File

@@ -94,32 +94,40 @@ example
Rails.logger.info "fetching imap (#{options[:host]}/#{options[:user]} port=#{port},ssl=#{ssl},starttls=#{starttls},folder=#{folder},keep_on_server=#{keep_on_server})"
# on check, reduce open_timeout to have faster probing
timeout = 45
check_type_timeout = 45
if check_type == 'check'
timeout = 6
check_type_timeout = 6
end
Timeout.timeout(timeout) do
timeout(check_type_timeout) do
@imap = ::Net::IMAP.new(options[:host], port, ssl, nil, false)
if starttls
@imap.starttls()
end
end
@imap.login(options[:user], options[:password])
timeout(check_type_timeout) do
@imap.login(options[:user], options[:password])
end
# select folder
@imap.select(folder)
timeout(check_type_timeout) do
# select folder
@imap.select(folder)
end
# sort messages by date on server (if not supported), if not fetch messages via search (first in, first out)
filter = ['ALL']
if keep_on_server && check_type != 'check' && check_type != 'verify'
filter = %w[NOT SEEN]
end
begin
message_ids = @imap.sort(['DATE'], filter, 'US-ASCII')
rescue
message_ids = @imap.search(filter)
message_ids = nil
timeout(6.minutes) do
begin
message_ids = @imap.sort(['DATE'], filter, 'US-ASCII')
rescue
message_ids = @imap.search(filter)
end
end
# check mode only
@@ -131,7 +139,10 @@ example
# check messages
message_ids.each do |message_id|
message_meta = @imap.fetch(message_id, ['RFC822.HEADER'])[0].attr
message_meta = nil
timeout(1.minute) do
message_meta = @imap.fetch(message_id, ['RFC822.HEADER'])[0].attr
end
# check how many content messages we have, for notice used
header = message_meta['RFC822.HEADER']
@@ -158,7 +169,10 @@ example
# check for verify message
message_ids.each do |message_id|
message_meta = @imap.fetch(message_id, ['ENVELOPE'])[0].attr
message_meta = nil
timeout(1.minute) do
message_meta = @imap.fetch(message_id, ['ENVELOPE'])[0].attr
end
# check if verify message exists
subject = message_meta['ENVELOPE'].subject
@@ -166,8 +180,10 @@ example
next if subject !~ /#{verify_string}/
Rails.logger.info " - verify email #{verify_string} found"
@imap.store(message_id, '+FLAGS', [:Deleted])
@imap.expunge()
timeout(600) do
@imap.store(message_id, '+FLAGS', [:Deleted])
@imap.expunge()
end
disconnect
return {
result: 'ok',
@@ -189,7 +205,10 @@ example
count += 1
Rails.logger.info " - message #{count}/#{count_all}"
message_meta = @imap.fetch(message_id, ['RFC822.SIZE', 'ENVELOPE', 'FLAGS', 'INTERNALDATE'])[0]
message_meta = nil
timeout(1.minute) do
message_meta = @imap.fetch(message_id, ['RFC822.SIZE', 'ENVELOPE', 'FLAGS', 'INTERNALDATE'])[0]
end
# ignore to big messages
info = too_big?(message_meta, count, count_all)
@@ -205,19 +224,28 @@ example
next if already_imported?(message_id, message_meta, count, count_all, keep_on_server, channel)
# delete email from server after article was created
msg = @imap.fetch(message_id, 'RFC822')[0].attr['RFC822']
msg = nil
timeout(1.minute) do
msg = @imap.fetch(message_id, 'RFC822')[0].attr['RFC822']
end
next if !msg
process(channel, msg, false)
if !keep_on_server
@imap.store(message_id, '+FLAGS', [:Deleted])
else
@imap.store(message_id, '+FLAGS', [:Seen])
timeout(1.minute) do
if !keep_on_server
@imap.store(message_id, '+FLAGS', [:Deleted])
else
@imap.store(message_id, '+FLAGS', [:Seen])
end
end
count_fetched += 1
end
if !keep_on_server
@imap.expunge()
timeout(10.minutes) do
@imap.expunge()
end
end
disconnect
if count.zero?
@@ -234,7 +262,9 @@ example
def disconnect
return if !@imap
@imap.disconnect()
timeout(1.minute) do
@imap.disconnect()
end
end
=begin
@@ -273,7 +303,9 @@ returns
return false if ticket.preferences[:channel_id] != channel[:id]
end
@imap.store(message_id, '+FLAGS', [:Seen])
timeout(1.minute) do
@imap.store(message_id, '+FLAGS', [:Seen])
end
Rails.logger.info " - ignore message #{count}/#{count_all} - because message message id already imported"
true
end
@@ -296,4 +328,10 @@ returns
false
end
def timeout(seconds)
Timeout.timeout(seconds) do
yield
end
end
end

View File

@@ -118,6 +118,17 @@ returns
return result
end
# check if min one lookup key exists
if header.count == (header - lookup_keys.map(&:to_s)).count
errors.push "No lookup column like #{lookup_keys.map(&:to_s).join(',')} for #{new.class} found."
result = {
errors: errors,
try: try,
result: 'failed',
}
return result
end
# get payload based on csv
payload = []
rows.each do |row|
@@ -172,11 +183,15 @@ returns
payload.each do |attributes|
line_count += 1
record = nil
%i[id number name login email].each do |lookup_by|
next if !attributes[lookup_by]
lookup_keys.each do |lookup_by|
next if attributes[lookup_by].blank?
params = {}
params[lookup_by] = attributes[lookup_by]
params[lookup_by] = if %i[email login].include?(lookup_by)
attributes[lookup_by].downcase
else
attributes[lookup_by]
end
record = lookup(params)
break if record
end
@@ -199,6 +214,7 @@ returns
end
# create object
BulkImportInfo.enable
Transaction.execute(disable_notification: true, reset_user_id: true) do
UserInfo.current_user_id = clean_params[:updated_by_id] || clean_params[:created_by_id]
if !record || delete == true
@@ -217,7 +233,7 @@ returns
record.associations_from_param(attributes)
record.save!
rescue => e
errors.push "Line #{line_count}: #{e.message}"
errors.push "Line #{line_count}: Unable to create record - #{e.message}"
next
end
else
@@ -234,14 +250,20 @@ returns
record.with_lock do
record.associations_from_param(attributes)
record.update!(clean_params)
clean_params.each do |key, value|
record[key] = value
end
next if !record.changed?
record.save!
end
rescue => e
errors.push "Line #{line_count}: #{e.message}"
errors.push "Line #{line_count}: Unable to update record - #{e.message}"
next
end
end
end
BulkImportInfo.disable
records.push record
end
@@ -258,7 +280,6 @@ returns
try: try,
result: result,
}
end
=begin

View File

@@ -9,6 +9,9 @@ module Cti
after_commit :update_cti_logs, on: :destroy
after_commit :update_cti_logs_with_fg_optimization, on: :create
skip_callback :commit, :after, :update_cti_logs, if: -> { BulkImportInfo.enabled? }
skip_callback :commit, :after, :update_cti_logs_with_fg_optimization, if: -> { BulkImportInfo.enabled? }
=begin
Cti::CallerId.maybe_add(

View File

@@ -125,7 +125,7 @@ reload config settings
end
@@change_id = Cache.get('Setting::ChangeId') # rubocop:disable Style/ClassVars
@@lookup_at = Time.zone.now # rubocop:disable Style/ClassVars
@@lookup_at = Time.now.to_i # rubocop:disable Style/ClassVars
true
end
private_class_method :load
@@ -147,14 +147,14 @@ reload config settings
# check if cache is still valid
def self.cache_valid?
if @@lookup_at && @@lookup_at > Time.zone.now - @@lookup_timeout
if @@lookup_at && @@lookup_at > Time.now.to_i - @@lookup_timeout
#logger.debug "Setting.cache_valid?: cache_id has been set within last #{@@lookup_timeout} seconds"
return true
end
change_id = Cache.get('Setting::ChangeId')
if @@change_id && change_id == @@change_id
@@lookup_at = Time.zone.now # rubocop:disable Style/ClassVars
@@lookup_at = Time.now.to_i # rubocop:disable Style/ClassVars
#logger.debug "Setting.cache_valid?: cache still valid, #{@@change_id}/#{change_id}"
return true
end

View File

@@ -21,12 +21,15 @@ class User < ApplicationModel
before_validation :check_name, :check_email, :check_login, :ensure_uniq_email, :ensure_password, :ensure_roles, :ensure_identifier
before_validation :check_mail_delivery_failed, on: :update
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
after_commit :update_caller_id
before_destroy :destroy_longer_required_objects
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
after_commit :update_caller_id
before_destroy :destroy_longer_required_objects
skip_callback :create, :after, :avatar_for_email_check, if: -> { BulkImportInfo.enabled? }
skip_callback :update, :after, :avatar_for_email_check, if: -> { BulkImportInfo.enabled? }
store :preferences

View File

@@ -0,0 +1,10 @@
require 'delayed_job'
module Delayed
class Job < ::ActiveRecord::Base
def self.recover_from(_error)
::ActiveRecord::Base.connection.verify!
end
end
end

View File

@@ -1,4 +1,4 @@
# Be sure to restart your server when you modify this file.
# Configure sensitive parameters which will be filtered from the log file.
Rails.application.config.filter_parameters += %i[password bind_pw]
Rails.application.config.filter_parameters += %i[password bind_pw state.body state.article.body]

13
lib/bulk_import_info.rb Normal file
View File

@@ -0,0 +1,13 @@
module BulkImportInfo
def self.enabled?
Thread.current[:bulk_import]
end
def self.enable
Thread.current[:bulk_import] = true
end
def self.disable
Thread.current[:bulk_import] = false
end
end

View File

@@ -125,11 +125,13 @@ class String
link_list = ''
counter = 0
if !string_only
string.gsub!(/<a[[:space:]].*?href=("|')(.+?)("|').*?>/ix) do
link = $2
counter = counter + 1
link_list += "[#{counter}] #{link}\n"
"[#{counter}] "
if string.scan(/<a[[:space:]]/i).count < 5_000
string.gsub!(/<a[[:space:]].*?href=("|')(.+?)("|').*?>/ix) do
link = $2
counter = counter + 1
link_list += "[#{counter}] #{link}\n"
"[#{counter}] "
end
end
else
string.gsub!(%r{<a[[:space:]]+(|\S+[[:space:]]+)href=("|')(.+?)("|')([[:space:]]*|[[:space:]]+[^>]*)>(.+?)<[[:space:]]*/a[[:space:]]*>}mxi) do |_placeholder|
@@ -477,7 +479,21 @@ class String
def utf8_encode!(**options)
return force_encoding('utf-8') if dup.force_encoding('utf-8').valid_encoding?
viable_encodings(try_first: options[:from]).each do |enc|
# convert string to given charset, if valid_encoding? is true
if options[:from].present?
begin
encoding = Encoding.find(options[:from])
if encoding.present? && dup.force_encoding(encoding).valid_encoding?
force_encoding(encoding)
return encode!('utf-8', encoding)
end
rescue ArgumentError, EncodingError => e
Rails.logger.error { e.inspect }
end
end
# try to find valid encodings of string
viable_encodings.each do |enc|
begin
return encode!('utf-8', enc)
rescue EncodingError => e

View File

@@ -10,6 +10,7 @@ satinize html string based on whiltelist
=end
def self.strict(string, external = false)
@fqdn = Setting.get('fqdn')
# config
tags_remove_content = Rails.configuration.html_sanitizer_tags_remove_content
@@ -30,7 +31,7 @@ satinize html string based on whiltelist
scrubber_link = Loofah::Scrubber.new do |node|
# wrap plain-text URLs in <a> tags
if node.is_a?(Nokogiri::XML::Text) && node.ancestors.map(&:name).exclude?('a')
if node.is_a?(Nokogiri::XML::Text) && node.content.present? && node.content.include?(':') && node.ancestors.map(&:name).exclude?('a')
urls = URI.extract(node.content, LINKABLE_URL_SCHEMES)
.map { |u| u.sub(/[,.]$/, '') } # URI::extract captures trailing dots/commas
.reject { |u| u.match?(/^[^:]+:$/) } # URI::extract will match, e.g., 'tel:'
@@ -396,9 +397,10 @@ cleanup html string:
end
def self.sanitize_attachment_disposition(url)
@fqdn ||= Setting.get('fqdn')
uri = URI(url)
if uri.host == Setting.get('fqdn') && uri.query.present?
if uri.host == @fqdn && uri.query.present?
params = CGI.parse(uri.query || '')
.tap { |p| p.merge!('disposition' => 'attachment') if p.include?('disposition') }
uri.query = URI.encode_www_form(params)
@@ -432,6 +434,7 @@ reolace inline images with cid images
=end
def self.replace_inline_images(string, prefix = rand(999_999_999))
fqdn = Setting.get('fqdn')
attachments_inline = []
filename_counter = 0
scrubber = Loofah::Scrubber.new do |node|
@@ -439,7 +442,7 @@ reolace inline images with cid images
if node['src'] && node['src'] =~ %r{^(data:image/(jpeg|png);base64,.+?)$}i
filename_counter += 1
file_attributes = StaticAssets.data_url_attributes($1)
cid = "#{prefix}.#{rand(999_999_999)}@#{Setting.get('fqdn')}"
cid = "#{prefix}.#{rand(999_999_999)}@#{fqdn}"
filename = cid
if file_attributes[:file_extention].present?
filename = "image#{filename_counter}.#{file_attributes[:file_extention]}"

View File

@@ -39,6 +39,10 @@ class Sequencer
def resource_iteration(&block)
resource_collection.public_send(resource_iteration_method, &block)
rescue ZendeskAPI::Error::NetworkError => e
return if e.response.status.to_s == '403' && resource_klass.in?(%w[UserField OrganizationField])
raise
end
def resource_collection

View File

@@ -334,6 +334,15 @@ send message to recipient client
Sessions.send_to(user_id, data)
e. g.
Sessions.send_to(user_id, {
event: 'session:takeover',
data: {
taskbar_id: 12312
},
})
returns
true|false
@@ -476,6 +485,14 @@ remove all session and spool messages
true
end
=begin
create spool messages
Sessions.spool_create(some: 'data')
=end
def self.spool_create(data)
msg = JSON.generate(data)
path = "#{@path}/spool/"
@@ -492,6 +509,14 @@ remove all session and spool messages
end
end
=begin
get spool messages
Sessions.spool_list(junger_then, for_user_id)
=end
def self.spool_list(timestamp, current_user_id)
path = "#{@path}/spool/"
FileUtils.mkpath path
@@ -512,6 +537,7 @@ remove all session and spool messages
file.flock(File::LOCK_SH)
message = file.read
file.flock(File::LOCK_UN)
message_parsed = {}
begin
spool = JSON.parse(message)
message_parsed = JSON.parse(spool['msg'])
@@ -530,7 +556,7 @@ remove all session and spool messages
# add spool attribute to push spool info to clients
message_parsed['spool'] = true
# only send not already now messages
# only send not already older messages
if !timestamp || timestamp < spool['timestamp']
# spool to recipient list
@@ -573,6 +599,19 @@ remove all session and spool messages
data
end
=begin
delete spool messages
Sessions.spool_delete
=end
def self.spool_delete
path = "#{@path}/spool/"
FileUtils.rm_rf path
end
def self.jobs(node_id = nil)
# just make sure that spool path exists

View File

@@ -7,7 +7,8 @@ class Sessions::Event
begin
backend = load_adapter(adapter)
rescue => e
return { event: 'error', data: { error: "No such event #{params[:event]}", payload: params[:payload] } }
Rails.logger.error e
return { event: 'error', data: { error: "No such event #{params[:event]}: #{e.inspect}", payload: params[:payload] } }
end
begin
@@ -16,6 +17,7 @@ class Sessions::Event
instance.destroy
result
rescue => e
Rails.logger.error e
return { event: 'error', data: { error: e.message, payload: params[:payload] } }
end
end

View File

@@ -9,6 +9,14 @@ class Sessions::Event::Base
return if !@clients[@client_id]
@is_web_socket = true
return if !self.class.instance_variable_get(:@database_connection)
ActiveRecord::Base.establish_connection
end
def self.inherited(subclass)
subclass.instance_variable_set(:@database_connection, @database_connection)
end
def websocket_send(recipient_client_id, data)
@@ -120,8 +128,18 @@ class Sessions::Event::Base
puts "#{Time.now.utc.iso8601}:client(#{client_id}) #{data}"
#puts "#{Time.now.utc.iso8601}:#{ level }:client(#{ client_id }) #{ data }"
# rubocop:enable Rails/Output
#Rails.logger.info "#{Time.now.utc.iso8601}:client(#{client_id}) #{data}"
end
def destroy; end
def self.database_connection_required
@database_connection = true
end
def destroy
return if !@is_web_socket
return if !self.class.instance_variable_get(:@database_connection)
ActiveRecord::Base.remove_connection
end
end

View File

@@ -1,5 +1,15 @@
class Sessions::Event::Broadcast < Sessions::Event::Base
=begin
Event module to broadcast messages to all client connections.
To execute this manually, just paste the following into the browser console
App.WebSocket.send({event:'broadcast', recipient: { user_id: [1,2,3]}, data: {some: 'key'}})
=end
def run
# list all current clients

View File

@@ -1,17 +1,5 @@
class Sessions::Event::ChatBase < Sessions::Event::Base
def initialize(params)
super(params)
return if !@is_web_socket
ActiveRecord::Base.establish_connection
end
def destroy
return if !@is_web_socket
ActiveRecord::Base.remove_connection
end
database_connection_required
def run

View File

@@ -1,12 +1,20 @@
class Sessions::Event::Login < Sessions::Event::Base
database_connection_required
=begin
Event module to start websocket session for new client connections.
To execute this manually, just paste the following into the browser console
App.WebSocket.send({event:'login', session_id: '123'})
=end
def run
# get user_id
session = nil
if @is_web_socket
ActiveRecord::Base.establish_connection
end
app_version = AppVersion.event_data
@@ -14,12 +22,7 @@ class Sessions::Event::Login < Sessions::Event::Base
session = ActiveRecord::SessionStore::Session.find_by(session_id: @payload['session_id'])
end
if @is_web_socket
ActiveRecord::Base.remove_connection
end
new_session_data = {}
if session&.data && session.data['user_id']
new_session_data = {
'id' => session.data['user_id'],

View File

@@ -1,17 +1,15 @@
class Sessions::Event::Maintenance < Sessions::Event::Base
database_connection_required
def initialize(params)
super(params)
return if !@is_web_socket
=begin
ActiveRecord::Base.establish_connection
end
Event module to broadcast maintenance messages to all client connections.
def destroy
return if !@is_web_socket
To execute this manually, just paste the following into the browser console
ActiveRecord::Base.remove_connection
end
App.WebSocket.send({event:'maintenance', data: {some: 'key'}})
=end
def run

View File

@@ -1,5 +1,15 @@
class Sessions::Event::Ping < Sessions::Event::Base
=begin
Event module to send pong to client connection.
To execute this manually, just paste the following into the browser console
App.WebSocket.send({event:'ping'})
=end
def run
{
event: 'pong',

View File

@@ -1,6 +1,15 @@
class Sessions::Event::Spool < Sessions::Event::Base
# get spool messages and send them to new client connection
=begin
Event module to serve spool messages and send them to new client connection.
To execute this manually, just paste the following into the browser console
App.WebSocket.send({event:'spool'})
=end
def run
# error handling
@@ -11,8 +20,13 @@ class Sessions::Event::Spool < Sessions::Event::Base
end
if !@session || !@session['id']
log 'error', "can't send spool, session not authenticated"
return
log 'error', "Can't send spool, session not authenticated"
return {
event: 'error',
data: {
error: 'Can\'t send spool, session not authenticated',
},
}
end
spool = Sessions.spool_list(@payload['timestamp'], @session['id'])

View File

@@ -1,4 +1,5 @@
class Sessions::Event::TicketOverviewIndex < Sessions::Event::Base
database_connection_required
def run
return if !valid_session?

View File

@@ -1,4 +1,5 @@
class Sessions::Event::TicketOverviewList < Sessions::Event::Base
database_connection_required
def run
return if !valid_session?

View File

@@ -0,0 +1,46 @@
class Sessions::Event::WhoAmI < Sessions::Event::Base
database_connection_required
=begin
Event module to send `who am i` to client connection.
To execute this manually, just paste the following into the browser console
App.WebSocket.send({event:'who_am_i'})
=end
def run
if !@session || !@session['id']
return {
event: 'who_am_i',
data: {
message: 'session not authenticated',
},
}
end
user = User.find_by(id: @session['id'])
if !user
return {
event: 'who_am_i',
data: {
message: "No such user with id #{@session['id']}",
},
}
end
attributes = user.attributes
attributes.delete('password')
{
event: 'who_am_i',
data: {
message: 'session authenticated',
user: attributes,
},
}
end
end

View File

@@ -1246,7 +1246,7 @@ test("identify signature by HTML", function() {
});
// check attachment references
test("check replace tags", function() {
test("check check attachment reference", function() {
var message = 'some not existing'
var result = false
var verify = App.Utils.checkAttachmentReference(message)
@@ -1392,6 +1392,35 @@ test("check replace tags", function() {
}
verify = App.Utils.replaceTags(message, data)
equal(verify, result)
user = new App.User({
firstname: 'Bob',
lastname: 'Smith',
created_at: '2018-10-31T10:00:00Z',
})
message = "<div>#{user.firstname} #{user.created_at}</div>"
result = '<div>Bob 10/31/2018 10:00</div>'
data = {
user: user
}
verify = App.Utils.replaceTags(message, data)
equal(verify, result)
message = "<div>#{user.firstname} #{user.created_at.date}</div>"
result = '<div>Bob -</div>'
data = {
user: user
}
verify = App.Utils.replaceTags(message, data)
equal(verify, result)
message = "<div>#{user.firstname} #{user.created.date}</div>"
result = '<div>Bob -</div>'
data = {
user: user
}
verify = App.Utils.replaceTags(message, data)
equal(verify, result)
});
// check attibute validation
@@ -3079,6 +3108,11 @@ test("htmlImage2DataUrl", function() {
result = App.Utils.htmlImage2DataUrl(source)
equal(result, should, source)
source = '<img src="cid:1234">some test'
should = '<img src="cid:1234">some test'
result = App.Utils.htmlImage2DataUrl(source)
equal(result, should, source)
});
source = '<img src="/assets/images/avatar-bg.png">some test'

View File

@@ -62,5 +62,42 @@ RSpec.describe String do
end
end
end
context 'perforamnce' do
let(:subject) { original_string.encode(input_encoding) }
context 'with utf8_encode in iso-8859-1' do
let(:original_string) { 'äöü0' * 999_999 }
let(:input_encoding) { Encoding::ISO_8859_1 }
it 'detects the input encoding' do
Timeout.timeout(1) do
expect(subject.utf8_encode(from: 'iso-8859-1')).to eq(original_string)
end
end
end
context 'with utf8_encode in utf-8' do
let(:original_string) { 'äöü0' * 999_999 }
let(:input_encoding) { Encoding::UTF_8 }
it 'detects the input encoding' do
Timeout.timeout(1) do
expect(subject.utf8_encode(from: 'utf-8')).to eq(original_string)
end
end
end
context 'with utf8_encode in iso-8859-1 and charset detection' do
let(:original_string) { 'äöü0' * 199_999 }
let(:input_encoding) { Encoding::ISO_8859_1 }
it 'detects the input encoding' do
Timeout.timeout(8) do
expect(subject.utf8_encode(from: 'utf-8')).to eq(original_string)
end
end
end
end
end
end

View File

@@ -0,0 +1,6 @@
require 'rails_helper'
require 'lib/sequencer/unit/import/zendesk/sub_sequence/base_examples'
RSpec.describe Sequencer::Unit::Import::Zendesk::OrganizationFields, sequencer: :unit do
include_examples 'Sequencer::Unit::Import::Zendesk::SubSequence::Base'
end

View File

@@ -0,0 +1,61 @@
RSpec.shared_examples 'Sequencer::Unit::Import::Zendesk::SubSequence::Base' do
before do
allow(params[:client]).to receive(collection_name).and_return(client_collection)
allow(client_collection).to receive(:all!).and_raise(api_error)
end
let(:params) do
{
dry_run: false,
import_job: instance_double(ImportJob),
client: double('ZendeskAPI'),
group_map: {}, # required by Tickets
organization_map: {}, # required by Tickets
ticket_field_map: {}, # required by Tickets
user_map: {}, # required by Tickets
}
end
let(:collection_name) { described_class.name.demodulize.snakecase.to_sym }
let(:client_collection) { double('ZendeskAPI::Collection') }
let(:api_error) { ZendeskAPI::Error::NetworkError.new('Mock err msg', response_obj) }
let(:response_obj) { double('Faraday::Response') }
# https://github.com/zammad/zammad/issues/2262
context 'for lowest-tier Zendesk subscriptions ("Essential")' do
shared_examples 'Zendesk import data (only available on Team tier and up)' do
context 'when API returns 403 forbidden during sync' do
before { allow(response_obj).to receive(:status).and_return(403) }
it 'rescues the resulting exception' do
expect { process(params) }.not_to raise_error
end
end
context 'when API returns other errors' do
before { allow(response_obj).to receive(:status).and_return(500) }
# https://github.com/zammad/zammad/issues/2262
it 'does not rescue the resulting exception' do
expect { process(params) }.to raise_error(api_error)
end
end
end
shared_examples 'Zendesk import data (available on all tiers)' do
context 'if API returns 403 forbidden during sync' do
before { allow(response_obj).to receive(:status).and_return(403) }
it 'does not rescue the resulting exception' do
expect { process(params) }.to raise_error(api_error)
end
end
end
if described_class.name.demodulize.in?(%w[UserFields OrganizationFields])
include_examples 'Zendesk import data (only available on Team tier and up)'
else
include_examples 'Zendesk import data (available on all tiers)'
end
end
end

View File

@@ -0,0 +1,6 @@
require 'rails_helper'
require 'lib/sequencer/unit/import/zendesk/sub_sequence/base_examples'
RSpec.describe Sequencer::Unit::Import::Zendesk::Tickets, sequencer: :unit do
include_examples 'Sequencer::Unit::Import::Zendesk::SubSequence::Base'
end

View File

@@ -0,0 +1,6 @@
require 'rails_helper'
require 'lib/sequencer/unit/import/zendesk/sub_sequence/base_examples'
RSpec.describe Sequencer::Unit::Import::Zendesk::UserFields, sequencer: :unit do
include_examples 'Sequencer::Unit::Import::Zendesk::SubSequence::Base'
end

View File

@@ -4,8 +4,8 @@ RSpec.shared_examples 'CanLookup' do
describe '::lookup' do
let(:subject) { described_class }
let(:ensure_instance) { create(subject.to_s.downcase) if subject.none? }
let(:string_attributes) { (%i[name login email number] & subject.attribute_names.map(&:to_sym)) }
let(:non_attributes) { (%i[id name login email number] - subject.attribute_names.map(&:to_sym)) }
let(:string_attributes) { subject.lookup_keys }
let(:non_attributes) { (subject.attribute_names.map(&:to_sym) - subject.lookup_keys) }
let(:lookup_id) { 1 }
let(:cache_key) { "#{subject}::#{lookup_id}" }
let(:cache_expiry_param) { { expires_in: 7.days } }
@@ -35,7 +35,7 @@ RSpec.shared_examples 'CanLookup' do
end
it 'accepts exactly one attribute-value pair' do
expect { subject.lookup(:id => lookup_id, string_attributes.sample => 'foo') }
expect { subject.lookup(id: lookup_id, some_attribute: 'foo') }
.to raise_error(ArgumentError)
end
@@ -69,5 +69,10 @@ RSpec.shared_examples 'CanLookup' do
.to have_received(:read)
.with(cache_key)
end
it 'verify lookup keys' do
expect(subject.lookup_keys).to match_array((%i[id name login email number] & subject.attribute_names.map(&:to_sym)))
end
end
end

View File

@@ -55,7 +55,8 @@ RSpec.describe 'Calendars', type: :request do
expect(json_response['ical_feeds']['http://www.google.com/calendar/ical/de.austrian%23holiday%40group.v.calendar.google.com/public/basic.ics']).to eq('Austria')
expect(json_response['timezones']).to be_truthy
expect(json_response['timezones']['Africa/Johannesburg']).to eq(2)
expect(json_response['timezones']['America/Sitka']).to eq(-8)
expect(json_response['timezones']['America/Sitka']).to be_between(-9, -8)
expect(json_response['timezones']['Europe/Berlin']).to be_between(1, 2)
expect(json_response['assets']).to be_truthy
end
end

View File

@@ -182,7 +182,7 @@ RSpec.describe 'Integration CTI', type: :request do
expect(log.initialized_at).to be_truthy
expect(log.start_at).to be_nil
expect(log.end_at).to be_truthy
expect(log.duration_waiting_time).to eq(2)
expect(log.duration_waiting_time).to be_between(2, 3)
expect(log.duration_talking_time).to be_nil
# outbound - II - new call
@@ -226,7 +226,7 @@ RSpec.describe 'Integration CTI', type: :request do
expect(log.initialized_at).to be_truthy
expect(log.start_at).to be_truthy
expect(log.end_at).to be_nil
expect(log.duration_waiting_time).to eq(2)
expect(log.duration_waiting_time).to be_between(2, 3)
expect(log.duration_talking_time).to be_nil
travel 2.seconds
@@ -249,8 +249,8 @@ RSpec.describe 'Integration CTI', type: :request do
expect(log.initialized_at).to be_truthy
expect(log.start_at).to be_truthy
expect(log.end_at).to be_truthy
expect(log.duration_waiting_time).to eq(2)
expect(log.duration_talking_time).to eq(2)
expect(log.duration_waiting_time).to be_between(2, 3)
expect(log.duration_talking_time).to be_between(2, 3)
# inbound - I - new call
params = 'event=newCall&direction=in&to=4930600000000&from=4912347114711&call_id=1234567890-3&user%5B%5D=user+1'

View File

@@ -0,0 +1,124 @@
require 'rails_helper'
RSpec.describe 'LongPolling', type: :request do
let(:agent_user) do
create(:agent_user)
end
before(:each) do
Sessions.sessions.each do |client_id|
Sessions.destroy(client_id)
end
Sessions.spool_delete
end
describe 'request handling' do
it 'receive without client_id - no user login' do
get '/api/v1/message_receive', params: { data: {} }, as: :json
expect(response).to have_http_status(422)
expect(json_response).to be_a_kind_of(Hash)
expect(json_response['error']).to eq('Invalid client_id receive!')
end
it 'send without client_id - no user login' do
get '/api/v1/message_send', params: { data: {} }, as: :json
expect(response).to have_http_status(200)
expect(json_response).to be_a_kind_of(Hash)
expect(json_response['client_id'].to_i).to be_between(1, 9_999_999_999)
client_id = json_response['client_id']
get '/api/v1/message_send', params: { client_id: client_id, data: { event: 'spool' } }, as: :json
expect(response).to have_http_status(200)
expect(json_response).to be_a_kind_of(Hash)
expect(json_response['client_id'].to_i).to be_between(1, 9_999_999_999)
get '/api/v1/message_receive', params: { client_id: client_id, data: {} }, as: :json
expect(response).to have_http_status(422)
expect(json_response).to be_a_kind_of(Hash)
expect(json_response['error']).to eq('Invalid client_id receive!')
end
it 'receive without client_id' do
authenticated_as(agent_user)
get '/api/v1/message_receive', params: { data: {} }, as: :json
expect(response).to have_http_status(422)
expect(json_response).to be_a_kind_of(Hash)
expect(json_response['error']).to eq('Invalid client_id receive!')
end
it 'receive without wrong client_id' do
authenticated_as(agent_user)
get '/api/v1/message_receive', params: { client_id: 'not existing', data: {} }, as: :json
expect(response).to have_http_status(422)
expect(json_response).to be_a_kind_of(Hash)
expect(json_response['error']).to eq('Invalid client_id receive!')
end
it 'send without client_id' do
authenticated_as(agent_user)
get '/api/v1/message_send', params: { data: {} }, as: :json
expect(response).to have_http_status(200)
expect(json_response).to be_a_kind_of(Hash)
expect(json_response['client_id'].to_i).to be_between(1, 9_999_999_999)
end
it 'send with client_id' do
Sessions.create('123456', {}, { type: 'ajax' })
authenticated_as(agent_user)
get '/api/v1/message_send', params: { client_id: '123456', data: {} }, as: :json
expect(response).to have_http_status(200)
expect(json_response).to be_a_kind_of(Hash)
expect(json_response).to eq({})
end
it 'send event spool and receive data' do
authenticated_as(agent_user)
get '/api/v1/message_send', params: { data: { event: 'login' } }, as: :json
expect(response).to have_http_status(200)
expect(json_response['client_id'].to_i).to be_between(1, 9_999_999_999)
client_id = json_response['client_id']
get '/api/v1/message_receive', params: { client_id: client_id, data: {} }, as: :json
expect(response).to have_http_status(200)
expect(json_response).to be_a_kind_of(Array)
expect(json_response).to eq([{ 'data' => { 'success' => true }, 'event' => 'ws:login' }])
get '/api/v1/message_send', params: { client_id: client_id, data: { event: 'spool' } }, as: :json
expect(response).to have_http_status(200)
expect(json_response).to be_a_kind_of(Hash)
expect(json_response).to eq({})
get '/api/v1/message_receive', params: { client_id: client_id, data: {} }, as: :json
expect(response).to have_http_status(200)
expect(json_response).to be_a_kind_of(Array)
expect(json_response[0]['event']).to eq('spool:sent')
expect(json_response[0]['event']).to eq('spool:sent')
expect(json_response.count).to eq(1)
spool_list = Sessions.spool_list(Time.now.utc.to_i, agent_user.id)
expect(spool_list).to eq([])
get '/api/v1/message_send', params: { client_id: client_id, data: { event: 'broadcast', spool: true, recipient: { user_id: [agent_user.id] }, data: { taskbar_id: 9_391_633 } } }, as: :json
expect(response).to have_http_status(200)
expect(json_response).to be_a_kind_of(Hash)
expect(json_response).to eq({})
get '/api/v1/message_receive', params: { client_id: client_id, data: {} }, as: :json
expect(response).to have_http_status(200)
expect(json_response).to be_a_kind_of(Hash)
expect(json_response).to eq({ 'event' => 'pong' })
travel 2.seconds
spool_list = Sessions.spool_list(Time.now.utc.to_i, agent_user.id)
expect(spool_list).to eq([])
spool_list = Sessions.spool_list(nil, agent_user.id)
expect(spool_list).to eq([{ message: { 'taskbar_id' => 9_391_633 }, type: 'direct' }])
end
end
end

View File

@@ -512,8 +512,8 @@ RSpec.describe 'Organization', type: :request, searchindex: true do
expect(json_response['records'].count).to eq(2)
expect(json_response['result']).to eq('failed')
expect(json_response['errors'].count).to eq(2)
expect(json_response['errors'][0]).to eq("Line 1: unknown attribute 'name2' for Organization.")
expect(json_response['errors'][1]).to eq("Line 2: unknown attribute 'name2' for Organization.")
expect(json_response['errors'][0]).to eq("Line 1: Unable to create record - unknown attribute 'name2' for Organization.")
expect(json_response['errors'][1]).to eq("Line 2: Unable to create record - unknown attribute 'name2' for Organization.")
# valid file try
csv_file_path = Rails.root.join('test', 'data', 'csv', 'organization_simple.csv')

View File

@@ -59,8 +59,8 @@ RSpec.describe 'Text Module', type: :request do
expect(json_response['records'].count).to eq(2)
expect(json_response['result']).to eq('failed')
expect(json_response['errors'].count).to eq(2)
expect(json_response['errors'][0]).to eq("Line 1: unknown attribute 'keywords2' for TextModule.")
expect(json_response['errors'][1]).to eq("Line 2: unknown attribute 'keywords2' for TextModule.")
expect(json_response['errors'][0]).to eq("Line 1: Unable to create record - unknown attribute 'keywords2' for TextModule.")
expect(json_response['errors'][1]).to eq("Line 2: Unable to create record - unknown attribute 'keywords2' for TextModule.")
# valid file try
csv_file_path = Rails.root.join('test', 'data', 'csv', 'text_module_simple.csv')

View File

@@ -2079,4 +2079,37 @@ RSpec.describe 'Ticket', type: :request do
expect(json_response['tickets']).to eq([ticket2.id, ticket1.id])
end
end
describe 'stats' do
let(:ticket1) { create(:ticket, customer: customer, organization: organization) }
let(:ticket2) { create(:ticket, customer: customer, organization: organization) }
let(:ticket3) { create(:ticket, customer: customer, organization: organization) }
let(:customer) { create(:customer_user, organization: organization) }
let(:organization) { create(:organization, shared: false) }
before do
authenticated_as(admin_user)
ticket1
travel 2.minutes
ticket2
travel 2.minutes
ticket3
travel 2.minutes
ticket2.touch # rubocop:disable Rails/SkipsModelValidations
end
# https://github.com/zammad/zammad/issues/2296
it 'orders tickets by created_at desc (#2296)' do
get '/api/v1/ticket_stats', params: { organization_id: organization.id, user_id: customer.id }, as: :json
expect(response).to have_http_status(200)
expect(json_response)
.to be_a_kind_of(Hash)
.and include('user' => hash_including('open_ids' => [ticket3.id, ticket2.id, ticket1.id]))
.and include('organization' => hash_including('open_ids' => [ticket3.id, ticket2.id, ticket1.id]))
end
end
end

View File

@@ -872,8 +872,8 @@ RSpec.describe 'User', type: :request, searchindex: true do
expect(json_response['records'].count).to eq(2)
expect(json_response['result']).to eq('failed')
expect(json_response['errors'].count).to eq(2)
expect(json_response['errors'][0]).to eq("Line 1: unknown attribute 'firstname2' for User.")
expect(json_response['errors'][1]).to eq("Line 2: unknown attribute 'firstname2' for User.")
expect(json_response['errors'][0]).to eq("Line 1: Unable to create record - unknown attribute 'firstname2' for User.")
expect(json_response['errors'][1]).to eq("Line 2: Unable to create record - unknown attribute 'firstname2' for User.")
# valid file try
csv_file_path = Rails.root.join('test', 'data', 'csv', 'user_simple.csv')

View File

@@ -13,7 +13,7 @@ class AdminCalendarSlaTest < TestCase
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)'
timezone_verify = "Europe/Berlin\s\\(GMT\\+(2|1)\\)"
calendar_create(
data: {
name: calendar_name,
@@ -66,7 +66,7 @@ class AdminCalendarSlaTest < TestCase
)
watch_for(
css: '.content.active .modal input.js-input',
value: Regexp.quote(timezone_verify),
value: timezone_verify,
timeout: 4,
)
modal_close()

View File

@@ -226,4 +226,34 @@ class AdminRoleTest < TestCase
exists_not(css: '[href="#manage"]')
end
# regression test for issue #2332 - Role-Filter shows inactive Roles
def test_inactive_roles_do_not_show_in_role_filter
name = "some role #{rand(99_999_999)}"
@browser = browser_instance
login(
username: 'master@example.com',
password: 'test',
url: browser_url,
)
tasks_close_all()
role_create(
data: {
name: name,
active: false
}
)
click(
css: '.content.active a[href="#manage/users"]',
)
# an inactive role should not appear in the role filter tabs
match_not(
css: '.content.active .userSearch',
value: name,
)
end
end

View File

@@ -3712,6 +3712,10 @@ wait untill text in selector disabppears
end
end
if data[:active] == false
select(css: 'select[name="active"]', value: 'inactive')
end
instance.find_elements(css: '.modal button.js-submit')[0].click
modal_disappear(browser: instance)
11.times do

View File

@@ -0,0 +1,20 @@
<html>
<title>some title</title>
<body>
<div>hello</div>
<p>some word <a href="http://example.com?domain?example.com">some url</a> and the end.</p>
<p>some word <a href="http://example.com?domain?example.com">some url</a> and the end.</p>
<p>some word <a href="http://example.com?domain?example.com">some url</a> and the end.</p>
<p>some word <a href="http://example.com?domain?example.com">some url</a> and the end.</p>
<p>some word <a href="http://example.com?domain?example.com">some url</a> and the end.</p>
<p>some word <a href="http://example.com?domain?example.com">some url</a> and the end.</p>
<p>some word <a href="http://example.com?domain?example.com">some url</a> and the end.</p>
<p>some word <a href="http://example.com?domain?example.com">some url</a> and the end.</p>
<p>some word <a href="http://example.com?domain?example.com">some url</a> and the end.</p>
<p>some word <a href="http://example.com?domain?example.com">some url</a> and the end.</p>
<p>some word <a href="http://example.com?domain?example.com">some url</a> and the end.</p>
</body>
</html>

View File

@@ -0,0 +1,26 @@
some title
hello
some word [1] some url and the end.
some word [2] some url and the end.
some word [3] some url and the end.
some word [4] some url and the end.
some word [5] some url and the end.
some word [6] some url and the end.
some word [7] some url and the end.
some word [8] some url and the end.
some word [9] some url and the end.
some word [10] some url and the end.
some word [11] some url and the end.
[1] http://example.com?domain?example.com
[2] http://example.com?domain?example.com
[3] http://example.com?domain?example.com
[4] http://example.com?domain?example.com
[5] http://example.com?domain?example.com
[6] http://example.com?domain?example.com
[7] http://example.com?domain?example.com
[8] http://example.com?domain?example.com
[9] http://example.com?domain?example.com
[10] http://example.com?domain?example.com
[11] http://example.com?domain?example.com

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -56,64 +56,64 @@ class AaaStringTest < ActiveSupport::TestCase
modul = 'test'
result = 'test'
modul.to_filename
assert_equal(result, modul)
assert_equal(result, modul)
modul = 'Some::File'
result = 'Some::File'
modul.to_filename
assert_equal(result, modul)
assert_equal(result, modul)
end
test 'to_filename function' do
modul = 'test'
result = 'test'
assert_equal(result, modul.to_filename)
assert_equal(result, modul.to_filename)
modul = 'Some::File'
result = 'some/file'
assert_equal(result, modul.to_filename)
assert_equal(result, modul.to_filename)
end
test 'to_classname ref' do
modul = 'test'
result = 'test'
modul.to_filename
assert_equal(result, modul)
assert_equal(result, modul)
modul = 'some/file'
result = 'some/file'
modul.to_filename
assert_equal(result, modul)
assert_equal(result, modul)
end
test 'to_classname function' do
modul = 'test'
result = 'Test'
assert_equal(result, modul.to_classname)
assert_equal(result, modul.to_classname)
modul = 'some/file'
result = 'Some::File'
assert_equal(result, modul.to_classname)
assert_equal(result, modul.to_classname)
modul = 'some/files'
result = 'Some::Files'
assert_equal(result, modul.to_classname)
assert_equal(result, modul.to_classname)
modul = 'some_test/files'
result = 'SomeTest::Files'
assert_equal(result, modul.to_classname)
assert_equal(result, modul.to_classname)
end
test 'html2text ref' do
html = 'test'
result = 'test'
html.html2text
assert_equal(result, html)
assert_equal(result, html)
html = '<div>test</div>'
result = '<div>test</div>'
html.html2text
assert_equal(result, html)
assert_equal(result, html)
end
test 'html2text function' do
@@ -458,6 +458,17 @@ Well as though adam took out here. Melvin will be more money. Called him into th
Men-----------------------'
assert_equal(result, html.html2text)
Timeout::timeout(2) do
html = File.read(Rails.root.join('test', 'data', 'string', 'html2text1.html'))
result = File.read(Rails.root.join('test', 'data', 'string', 'html2text1.txt'))
assert_equal(result, html.html2text)
end
Timeout::timeout(2) do
html = File.read(Rails.root.join('test', 'data', 'string', 'html2text2.html'))
result = File.read(Rails.root.join('test', 'data', 'string', 'html2text2.txt'))
assert_equal(result, html.html2text)
end
end
test 'html2html_strict function' do

View File

@@ -39,6 +39,91 @@ class ChatTest < ActiveSupport::TestCase
Setting.set('chat', false)
end
test 'instance_variable test' do
assert_nil(Sessions::Event::Base.instance_variable_get(:@database_connection))
assert_equal(Sessions::Event::ChatBase.instance_variable_get(:@database_connection), true)
assert_equal(Sessions::Event::ChatStatusAgent.instance_variable_get(:@database_connection), true)
end
# check if db connection is available for chat events
# see: https://github.com/zammad/zammad/issues/2353
test 'chat event db connection test' do
class DummyWs
def send(msg)
Rails.logger.info "WS send: #{msg}"
end
end
# with websockets
assert(User.first)
message = Sessions::Event.run(
event: 'login',
payload: {},
session: 123,
remote_ip: '127.0.0.1',
client_id: '123',
clients: {
'123' => {
websocket: DummyWs.new # to simulate a ws connection
}
},
options: {},
)
assert_equal(message, false)
assert_raises(ActiveRecord::ConnectionNotEstablished) do
User.first
end
message = Sessions::Event.run(
event: 'chat_status_customer',
payload: {},
session: 123,
remote_ip: '127.0.0.1',
client_id: '123',
clients: {
'123' => DummyWs.new # to simulate a ws connection
},
options: {},
)
assert_equal(message[:event], 'chat_error')
assert_raises(ActiveRecord::ConnectionNotEstablished) do
User.first
end
# re-establish connection
ActiveRecord::Base.establish_connection
# with ajax long polling
assert(User.first)
message = Sessions::Event.run(
event: 'login',
payload: {},
session: 123,
remote_ip: '127.0.0.1',
client_id: '123',
clients: {},
options: {},
)
assert_equal(message, false)
assert(User.first)
message = Sessions::Event.run(
event: 'chat_status_customer',
payload: {},
session: 123,
remote_ip: '127.0.0.1',
client_id: '123',
clients: {},
options: {},
)
assert_equal(message[:event], 'chat_error')
assert(User.first)
end
test 'default test' do
chat = Chat.create_or_update(

View File

@@ -45,6 +45,20 @@ class OrganizationCsvImportTest < ActiveSupport::TestCase
assert_equal('No records found in file/string for Organization.', result[:errors][0])
end
test 'verify required lookup headers' do
csv_string = "firstname;lastname;active;\nfirstname-simple-import1;lastname-simple-import1;;true\nfirstname-simple-import2;lastname-simple-import2;false\n"
result = Organization.csv_import(
string: csv_string,
parse_params: {
col_sep: ';',
},
try: true,
)
assert_equal(true, result[:try])
assert_equal('failed', result[:result])
assert_equal('No lookup column like id,name for Organization found.', result[:errors][0])
end
test 'simple import' do
csv_string = "id;name;shared;domain;domain_assignment;active;note\n;org-simple-import1;true;org-simple-import1.example.com;false;true;some note1\n;org-simple-import2;true;org-simple-import2.example.com;false;false;some note2\n"
@@ -210,8 +224,8 @@ class OrganizationCsvImportTest < ActiveSupport::TestCase
assert_equal(true, result[:try])
assert_equal(2, result[:errors].count)
assert_equal('failed', result[:result])
assert_equal("Line 1: unknown attribute 'not existing' for Organization.", result[:errors][0])
assert_equal("Line 2: unknown attribute 'not existing' for Organization.", result[:errors][1])
assert_equal("Line 1: Unable to create record - unknown attribute 'not existing' for Organization.", result[:errors][0])
assert_equal("Line 2: Unable to create record - unknown attribute 'not existing' for Organization.", result[:errors][1])
assert_nil(Organization.find_by(name: 'organization-invalid-import1'))
assert_nil(Organization.find_by(name: 'organization-invalid-import2'))
@@ -226,8 +240,8 @@ class OrganizationCsvImportTest < ActiveSupport::TestCase
assert_equal(false, result[:try])
assert_equal(2, result[:errors].count)
assert_equal('failed', result[:result])
assert_equal("Line 1: unknown attribute 'not existing' for Organization.", result[:errors][0])
assert_equal("Line 2: unknown attribute 'not existing' for Organization.", result[:errors][1])
assert_equal("Line 1: Unable to create record - unknown attribute 'not existing' for Organization.", result[:errors][0])
assert_equal("Line 2: Unable to create record - unknown attribute 'not existing' for Organization.", result[:errors][1])
assert_nil(Organization.find_by(name: 'organization-invalid-import1'))
assert_nil(Organization.find_by(name: 'organization-invalid-import2'))

View File

@@ -49,6 +49,20 @@ class TextModuleCsvImportTest < ActiveSupport::TestCase
assert_equal('No records found in file/string for TextModule.', result[:errors][0])
end
test 'verify required lookup headers' do
csv_string = "firstname;lastname;active;\nfirstname-simple-import1;lastname-simple-import1;;true\nfirstname-simple-import2;lastname-simple-import2;false\n"
result = TextModule.csv_import(
string: csv_string,
parse_params: {
col_sep: ';',
},
try: true,
)
assert_equal(true, result[:try])
assert_equal('failed', result[:result])
assert_equal('No lookup column like id,name for TextModule found.', result[:errors][0])
end
test 'simple import' do
TextModule.create!(
name: 'nsome name1',

View File

@@ -49,6 +49,20 @@ class TicketCsvImportTest < ActiveSupport::TestCase
assert_equal('No records found in file/string for Ticket.', result[:errors][0])
end
test 'verify required lookup headers' do
csv_string = "firstname;lastname;active;\nfirstname-simple-import1;lastname-simple-import1;;true\nfirstname-simple-import2;lastname-simple-import2;false\n"
result = Ticket.csv_import(
string: csv_string,
parse_params: {
col_sep: ';',
},
try: true,
)
assert_equal(true, result[:try])
assert_equal('failed', result[:result])
assert_equal('No lookup column like id,number for Ticket found.', result[:errors][0])
end
test 'simple import' do
csv_string = "id;number;title;state;priority;owner;customer;group;note\n;123456;some title1;new;2 normal;-;nicole.braun@zammad.org;Users;some note1\n;123457;some title2;closed;1 low;admin@example.com;nicole.braun@zammad.org;Users;some note2\n"
@@ -173,8 +187,8 @@ class TicketCsvImportTest < ActiveSupport::TestCase
assert_equal(true, result[:try])
assert_equal(2, result[:errors].count)
assert_equal('failed', result[:result])
assert_equal("Line 1: unknown attribute 'not_existing' for Ticket.", result[:errors][0])
assert_equal("Line 2: unknown attribute 'not_existing' for Ticket.", result[:errors][1])
assert_equal("Line 1: Unable to create record - unknown attribute 'not_existing' for Ticket.", result[:errors][0])
assert_equal("Line 2: Unable to create record - unknown attribute 'not_existing' for Ticket.", result[:errors][1])
assert_nil(Ticket.find_by(number: '123456'))
assert_nil(Ticket.find_by(number: '123457'))
@@ -189,8 +203,8 @@ class TicketCsvImportTest < ActiveSupport::TestCase
assert_equal(false, result[:try])
assert_equal(2, result[:errors].count)
assert_equal('failed', result[:result])
assert_equal("Line 1: unknown attribute 'not_existing' for Ticket.", result[:errors][0])
assert_equal("Line 2: unknown attribute 'not_existing' for Ticket.", result[:errors][1])
assert_equal("Line 1: Unable to create record - unknown attribute 'not_existing' for Ticket.", result[:errors][0])
assert_equal("Line 2: Unable to create record - unknown attribute 'not_existing' for Ticket.", result[:errors][1])
assert_nil(Ticket.find_by(number: '123456'))
assert_nil(Ticket.find_by(number: '123457'))

View File

@@ -44,9 +44,24 @@ class UserCsvImportTest < ActiveSupport::TestCase
assert_equal('No records found in file/string for User.', result[:errors][0])
end
test 'verify required lookup headers' do
csv_string = "firstname;lastname;active;\nfirstname-simple-import1;lastname-simple-import1;;true\nfirstname-simple-import2;lastname-simple-import2;false\n"
result = User.csv_import(
string: csv_string,
parse_params: {
col_sep: ';',
},
try: true,
)
assert_equal(true, result[:try])
assert_equal('failed', result[:result])
assert_equal('No lookup column like id,login,email for User found.', result[:errors][0])
end
test 'simple import' do
csv_string = "login;firstname;lastname;email;active;\nuser-simple-import1;firstname-simple-import1;lastname-simple-import1;user-simple-import1@example.com;true\nuser-simple-import2;firstname-simple-import2;lastname-simple-import2;user-simple-import2@example.com;false\n"
count = User.count
csv_string = "login;firstname;lastname;email;active;\nuser-simple-IMPORT1;firstname-simple-import1;lastname-simple-import1;user-simple-IMPORT1@example.com ;true\nuser-simple-import2;firstname-simple-import2;lastname-simple-import2;user-simple-import2@example.com;false\n"
result = User.csv_import(
string: csv_string,
parse_params: {
@@ -57,6 +72,9 @@ class UserCsvImportTest < ActiveSupport::TestCase
assert_equal(true, result[:try])
assert_equal(2, result[:records].count)
assert_equal('success', result[:result])
assert_equal(2, result[:stats][:created])
assert_equal(0, result[:stats][:updated])
assert_equal(count, User.count)
assert_nil(User.find_by(login: 'user-simple-import1'))
assert_nil(User.find_by(login: 'user-simple-import2'))
@@ -71,6 +89,9 @@ class UserCsvImportTest < ActiveSupport::TestCase
assert_equal(false, result[:try])
assert_equal(2, result[:records].count)
assert_equal('success', result[:result])
assert_equal(2, result[:stats][:created])
assert_equal(0, result[:stats][:updated])
assert_equal(count + 2, User.count)
user1 = User.find_by(login: 'user-simple-import1')
assert(user1)
@@ -87,6 +108,76 @@ class UserCsvImportTest < ActiveSupport::TestCase
assert_equal(user2.email, 'user-simple-import2@example.com')
assert_equal(user2.active, false)
result = User.csv_import(
string: csv_string,
parse_params: {
col_sep: ';',
},
try: false,
)
assert_equal(false, result[:try])
assert_equal(2, result[:records].count)
assert_equal('success', result[:result])
assert_equal(0, result[:stats][:created])
assert_equal(2, result[:stats][:updated])
assert_equal(count + 2, User.count)
user1_1 = user1
user1 = User.find_by(login: 'user-simple-import1')
assert(user1)
assert_equal(user1.login, 'user-simple-import1')
assert_equal(user1.firstname, 'firstname-simple-import1')
assert_equal(user1.lastname, 'lastname-simple-import1')
assert_equal(user1.email, 'user-simple-import1@example.com')
assert_equal(user1.active, true)
assert_equal(user1.updated_at, user1_1.updated_at)
user2 = user2
user2_1 = User.find_by(login: 'user-simple-import2')
assert(user2)
assert_equal(user2.login, 'user-simple-import2')
assert_equal(user2.firstname, 'firstname-simple-import2')
assert_equal(user2.lastname, 'lastname-simple-import2')
assert_equal(user2.email, 'user-simple-import2@example.com')
assert_equal(user2.active, false)
assert_equal(user2.updated_at, user2_1.updated_at)
travel 2.seconds
csv_string = "login;firstname;lastname;email;active;\n ;firstname-simple-import1;lastname-simple-import1;user-simple-IMPORT1@example.com ;true\n user-simple-import2\t;firstname-simple-import2;lastname-simple-import2;;false\n"
result = User.csv_import(
string: csv_string,
parse_params: {
col_sep: ';',
},
try: false,
)
assert_equal(false, result[:try])
assert_equal(2, result[:records].count)
assert_equal(0, result[:stats][:created])
assert_equal(2, result[:stats][:updated])
assert_equal('success', result[:result])
assert_equal(count + 2, User.count)
user1_1 = user1
user1 = User.find_by(email: 'user-simple-import1@example.com')
assert(user1)
assert_equal(user1.login, 'user-simple-import1@example.com')
assert_equal(user1.firstname, 'firstname-simple-import1')
assert_equal(user1.lastname, 'lastname-simple-import1')
assert_equal(user1.email, 'user-simple-import1@example.com')
assert_equal(user1.active, true)
assert_not_equal(user1.updated_at, user1_1.updated_at)
user2 = user2
user2_1 = User.find_by(login: 'user-simple-import2')
assert(user2)
assert_equal(user2.login, 'user-simple-import2')
assert_equal(user2.firstname, 'firstname-simple-import2')
assert_equal(user2.lastname, 'lastname-simple-import2')
assert_equal(user2.email, 'user-simple-import2@example.com')
assert_equal(user2.active, false)
assert_equal(user2.updated_at, user2_1.updated_at)
user1.destroy!
user2.destroy!
end
@@ -340,8 +431,8 @@ class UserCsvImportTest < ActiveSupport::TestCase
assert_equal(true, result[:try])
assert_equal(2, result[:errors].count)
assert_equal('failed', result[:result])
assert_equal("Line 1: unknown attribute 'firstname2' for User.", result[:errors][0])
assert_equal("Line 2: unknown attribute 'firstname2' for User.", result[:errors][1])
assert_equal("Line 1: Unable to create record - unknown attribute 'firstname2' for User.", result[:errors][0])
assert_equal("Line 2: Unable to create record - unknown attribute 'firstname2' for User.", result[:errors][1])
assert_nil(User.find_by(login: 'user-invalid-import1'))
assert_nil(User.find_by(login: 'user-invalid-import2'))
@@ -356,8 +447,8 @@ class UserCsvImportTest < ActiveSupport::TestCase
assert_equal(false, result[:try])
assert_equal(2, result[:errors].count)
assert_equal('failed', result[:result])
assert_equal("Line 1: unknown attribute 'firstname2' for User.", result[:errors][0])
assert_equal("Line 2: unknown attribute 'firstname2' for User.", result[:errors][1])
assert_equal("Line 1: Unable to create record - unknown attribute 'firstname2' for User.", result[:errors][0])
assert_equal("Line 2: Unable to create record - unknown attribute 'firstname2' for User.", result[:errors][1])
assert_nil(User.find_by(login: 'user-invalid-import1'))
assert_nil(User.find_by(login: 'user-invalid-import2'))