Compare commits

...

107 Commits

Author SHA1 Message Date
Martin Gruner
20df373351 Release Zammad 5.2.3. 🚀 2022-09-30 09:03:08 +02:00
Florian Liebe
d3e3475aec Fixes #3963 - Unable to add Microsoft 365 Account when admin consent for enterprise applications is enabled. 2022-09-30 06:51:52 +00:00
Tobias Schäfer
c469f406cd Fixes #4259 - KB answer linking in ticket zoom fails. 2022-09-30 08:50:40 +02:00
Rolf Schmidt
bca4224393 Fixes #3664 - Add workaround so structure changes in the store will not raise exceptions while locking. 2022-09-29 13:44:27 +02:00
Weblate
eb9780155d Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Carko <darko@augmentech.si>
Co-authored-by: Marcin Miłek <marcin.milek@pwr.edu.pl>
Co-authored-by: Martin Gruner <mg@zammad.com>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/da/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/el/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/es/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fi/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/he_IL/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/hu/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/it/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/nb_NO/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pt_BR/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/sv/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/zh_Hans/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/zh_Hant/
Translation: Zammad/Zammad (stable version)
2022-09-28 05:22:19 +00:00
renovatebot
7dadccdea0 Maintenance: Update dependency rails to v6.1.7 2022-09-27 14:18:58 +00:00
Florian Liebe
7dc7fceeac Fixes #3630 - HTML table in mail preview is misformatted. 2022-09-27 15:16:30 +02:00
Rolf Schmidt
df978093e7 Follow up a6102e77d5 - Fixes #4243 - Instead of adding ticket.agent permission we will remove the groups for roles which do not have the ticket.agent permissions. 2022-09-26 19:23:20 +02:00
Rolf Schmidt
8e7e0e467a Follow up 34ec90bd - Fixes #4243 - Add migration to fix existing roles without ticket.agent permission but group relations. 2022-09-22 17:33:36 +02:00
Rolf Schmidt
9ec1d91366 Follow up 34ec90bd - Fixes #4243 - Zammad suffers from serious performance degrade on incorrect role configurations. 2022-09-22 14:26:55 +02:00
Dusan Vuckovic
ea52d8319c Fixes #4071 - Apply via role granted permission while creating shared draft 2022-09-22 13:09:11 +02:00
Rolf Schmidt
6b66f28818 Follow up 0b14a3e114 - Fixes #4245 - Mandatory field in combination with automatic assignment leads to a divergence in the setting of the ticket owner. 2022-09-21 16:33:39 +02:00
Dominik Klein
fb063580ef Fixes #4249 - Zendesk Import: Link to linked Tickets are not working after migration to Zammad. 2022-09-20 22:53:48 +02:00
Weblate
c5b6308170 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Marcel Herrguth <github@thehomeofanime.de>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: grtnl <geert.noel@aalst.be>
Co-authored-by: salvolp <s.salvo17@gmail.com>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/de/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/es_MX/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/nl/
Translation: Zammad/Zammad (stable version)
2022-09-20 17:25:18 +00:00
Dominik Klein
4206e70c3a Fixes #4246 - Type attribute is being ignored on Zendesk import. 2022-09-20 09:12:01 +02:00
Rolf Schmidt
0b14a3e114 Fixes #4245 - Mandatory field in combination with automatic assignment leads to a divergence in the setting of the ticket owner. 2022-09-19 13:55:43 +02:00
Dusan Vuckovic
317ec97e3d Fixes #3099 - Email address falsely converted on pasting into article body 2022-09-16 14:43:23 +02:00
Dominik Klein
ab473b2f0d Fixes #4244 - Zendesk import does not import comment create date inside of the created articles. 2022-09-15 13:03:58 +02:00
Dominik Klein
2a840a5e5a Fixes #4234 - Zendesk import does not import embedded images. 2022-09-15 09:09:17 +02:00
Dusan Vuckovic
ed4ff7391d Fixes #4215 - Fix width of tags add in trigger 2022-09-15 08:26:13 +02:00
Florian Liebe
0066362558 Fixes #4184 - HTML comments/conditions being rendered from emails. 2022-09-14 22:34:51 +02:00
Rolf Schmidt
f67686003a Maintenance: Update commands for package view to use rake and fix user for package installations. 2022-09-14 16:55:36 +02:00
Bola Ahmed Buari
6e5310ea4f Fixes #3923 - Drag & Drop bulk operations not available in search results 2022-09-13 18:24:16 +01:00
Dusan Vuckovic
3477d67650 Closes #4240 - Fixes #3718 - Dashboard stopped working. 2022-09-13 15:51:56 +02:00
Rolf Schmidt
f71f7a103b Fixes #4156 - The webhook could not be saved by Telegram, seems to be an invalid URL. 2022-09-13 13:40:42 +00:00
Tobias Schäfer
2e162101be Fixes#4207 - Respect selected value within time range selector. 2022-09-13 14:40:45 +02:00
Martin Gruner
80ed32a94d Release Zammad 5.2.2. 🚀 2022-09-13 09:57:23 +02:00
Tobias Schäfer
c94b72cd1f Maintenance: Improving readonly view in agent interface. 2022-09-13 06:27:25 +00:00
Rolf Schmidt
5b4d53653f Maintenance: Improve websocket assets handling. 2022-09-13 06:27:25 +00:00
Florian Liebe
3463862759 Fixes #4221 - Add sorting of text modules. 2022-09-12 13:25:05 +02:00
Weblate
92343c9cea Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Joffre Ayala <ayala.joffre.clinica@gmail.com>
Co-authored-by: LAO <sumonchai@gmail.com>
Co-authored-by: Martin Gruner <mg@zammad.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: grtnl <geert.noel@aalst.be>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/de/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/en_CA/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/nl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/th/
Translation: Zammad/Zammad (stable version)
2022-09-09 17:30:18 +02:00
Rolf Schmidt
36b8fb3499 Fixes #4233 - Core Workflow: Readonly input fields with link template are not clickable. 2022-09-06 11:39:59 +02:00
Rolf Schmidt
fc034bd3d3 Fixes #4198 - Tickets can be closed with the bulk action, even when a mandatory field is empty. 2022-09-05 13:57:07 +02:00
Dominik Klein
bd64be7971 Maintenance: Update dependency omniauth to v1.9.2 2022-09-01 10:43:18 +02:00
Weblate
280a1f7d04 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Mazaud Thomas <fyroeo@gmail.com>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fr/
Translation: Zammad/Zammad (stable version)
2022-08-29 09:05:26 +02:00
Weblate
388354080a Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: MBekspert <michal.bosak@ekspert.biz>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pl/
Translation: Zammad/Zammad (stable version)
2022-08-25 21:53:46 +02:00
Weblate
4f93962ea4 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Artem <aasavchuk@gmail.com>
Co-authored-by: Chris Maurel <christophermaurel@gmail.com>
Co-authored-by: François Victor <fvictor@rms.lu>
Co-authored-by: Joaquin Diego Perez Gonzalez <joaquinperez@ipglobal.es>
Co-authored-by: Magdalena Dulęba <magdalena.duleba@pwr.edu.pl>
Co-authored-by: Marcel Herrguth <github@thehomeofanime.de>
Co-authored-by: Raoul Schmitt <raoul.schmitt@tizoo.com>
Co-authored-by: Svyrydov Semen <pincher2006@gmail.com>
Co-authored-by: Vicent Oron <voron@virtualwareco.com>
Co-authored-by: Vincent Handfield <vincent@accestech.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Zhilin Ruslan <x-th-unicorn@hotmail.com>
Co-authored-by: grtnl <geert.noel@aalst.be>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/de/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/es/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fr_CA/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/nl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/ru/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/uk/
Translation: Zammad/Zammad (stable version)
2022-08-22 20:16:24 +02:00
Bola Ahmed Buari
a64597ff8d Fixes #4202 - Article gets shortened when its text is marked with highlighter 2022-08-18 16:55:29 +01:00
Rolf Schmidt
f62bd08c16 Fixes #4214 - Remove "Create new Customer" in trigger. 2022-08-16 14:44:28 +02:00
Rolf Schmidt
bf0bd12997 Fixes #4206 - Cannot set date for pending close status in postmaster filter. 2022-08-16 10:57:01 +00:00
Rolf Schmidt
b0d6ca7a92 Fixes #4213 - Article update causes missing icons in the UI after switching internal state. 2022-08-16 12:25:36 +02:00
Rolf Schmidt
0a2a35683c Closes #4185 - Fixes #4186 - Multiselect does not show values properly in reports. 2022-08-15 13:07:49 +02:00
Bola Ahmed Buari
1ac660da33 Fixes #4093 - Knowledge base formatting issue 2022-08-15 06:44:50 +01:00
Stubenhocker1399
a084fc41c8 Closes #4191 - Fixes #849 - Allow additional usage of Ticket Number in (Zoom) URL. 2022-08-09 13:46:28 +01:00
Rolf Schmidt
f176371556 Fixes #4175 - Ticket templates do not save the owner attribute. 2022-08-09 12:48:15 +02:00
Mantas
94358429d6 Fixes #4190 - Knowledge base detail page has long loading times when kb has many categories 2022-08-08 09:28:06 +03:00
Weblate
1be88af5c1 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Ales Zabala Alava <shagi@gisa-elkartea.org>
Co-authored-by: Diego <diegowave@gmail.com>
Co-authored-by: Mark Letford <post@bacaloo.com>
Co-authored-by: Ondrej Pokorny <ondrej.pokorny@entry-cz.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: grtnl <geert.noel@aalst.be>
Co-authored-by: ® <rossdaworld@gmail.com>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/cs/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/de/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/es/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/nl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pt_BR/
Translation: Zammad/Zammad (stable version)
2022-08-03 18:05:47 +02:00
Florian Liebe
08c6e3aa01 Fixes #3861 - Ticket hook not represented consistently inside a ticket with some articles. 2022-08-03 10:08:14 +02:00
Rolf Schmidt
c2fa10175a Fixes #4188 - multi_tree_select: field is used, but browser says it is empty. 2022-08-02 17:50:45 +02:00
Rolf Schmidt
83a46adb2d Fixes #4193 - Core Workflows no longer allow multiple values for single selection fields. 2022-08-02 15:54:51 +02:00
Dominik Klein
44ef54ecdd Maintenance: Fix date picker test on month switches (when the first day in the... 2022-08-02 10:02:04 +02:00
Weblate
e6bf4d571c Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Danniel Macedo <djsmacedo@gmail.com>
Co-authored-by: Diego <diegowave@gmail.com>
Co-authored-by: Magdalena Dulęba <magdalena.duleba@pwr.edu.pl>
Co-authored-by: Michael Graciano Martins <mgm1996@hotmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Wim Deprez <wim@tamale.nl>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/nl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pt_BR/
Translation: Zammad/Zammad (stable version)
2022-07-29 04:19:05 +02:00
Rolf Schmidt
f5818ada93 Fixes #4178 - Unable to remove chat topics if chat sessions exist. 2022-07-28 16:21:03 +02:00
Rolf Schmidt
89d4a33d5d Fixes #4174 - Tree select not proper in Report. 2022-07-28 16:20:51 +02:00
Rolf Schmidt
d12552191f Fixes #4171 - Avatar upload fails on cropping image. 2022-07-28 16:20:38 +02:00
Florian Liebe
689a7cb148 Maintenance: Improve FillDb module to be able to create knowledge base data. 2022-07-28 09:52:03 +02:00
Florian Liebe
dec7b134b5 Fixes #4180, Closes #4177 - Unable to scroll ticket image view when using arrow keys. 2022-07-26 10:46:40 +02:00
Weblate
f9db2a4728 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Magdalena Dulęba <magdalena.duleba@pwr.edu.pl>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: notkrisu <damasiewiczkrystian@gmail.com>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pl/
Translation: Zammad/Zammad (stable version)
2022-07-26 08:23:37 +00:00
Florian Liebe
5851f63fbb Fixes #4172 - Secondary organizations are always shown in sidebar even if customer has no secondary organizations assigned. 2022-07-26 09:29:27 +02:00
Weblate
24ec9c959c Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Magdalena Dulęba <magdalena.duleba@pwr.edu.pl>
Co-authored-by: Marcin Miłek <marcin.milek@pwr.edu.pl>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: notkrisu <damasiewiczkrystian@gmail.com>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pl/
Translation: Zammad/Zammad (stable version)
2022-07-24 15:27:06 +02:00
Weblate
1a09e50077 Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Ivan Perovic <ip@zammad.com>
Co-authored-by: LAO <sumonchai@gmail.com>
Co-authored-by: Magdalena Dulęba <magdalena.duleba@pwr.edu.pl>
Co-authored-by: Marco Badan <info@marcobadan.com>
Co-authored-by: Martin Gruner <mg@zammad.com>
Co-authored-by: Virtorgan <dpires1966@yahoo.fr>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Zhilin Ruslan <x-th-unicorn@hotmail.com>
Co-authored-by: jebulol <jebulol@gmail.com>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/de/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fi/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/fr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/hr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/it/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/pl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/ru/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/th/
Translation: Zammad/Zammad (stable version)
2022-07-19 13:51:51 +02:00
Florian Liebe
d6c25a2671 Fixes #4134 - Elasticsearch forgets results from attachments over time. 2022-07-19 08:42:35 +02:00
Dominik Klein
c62a36004c Fixes #4169 - Zendesk migration runs randomly in ssl handshake failure... 2022-07-13 22:27:21 +02:00
Dominik Klein
0a9f022d2e Fixes #4170 - Zendesk migration has currently no retry for attachment fetch. 2022-07-13 14:15:09 +02:00
Mantas Masalskis
93b4cca236 Fixes #4131: Triggers must not allow adding perform settings which are empty / nil 2022-07-13 11:22:38 +00:00
Dominik Klein
00204dded0 Maintenance: Change rubocop config to allow 'allow_any_instance_of' by default. 2022-07-13 13:01:21 +02:00
renovatebot
5f503051fe Maintenance: Update dependency rails to v6.1.6.1 2022-07-13 09:10:55 +02:00
Rolf Schmidt
c28298e71b Fixes #4167 - LDAP tries to sync all the time. 2022-07-12 11:29:04 +02:00
Mantas
979ea9caf0 Fixes #4165 - KB answer image in editor has extra padding 2022-07-07 20:47:21 +03:00
Martin Gruner
c74fc76783 Maintenance: Refactor CI configuration. 2022-07-07 07:36:21 +02:00
Mantas Masalskis
61874e0a23 Fixes #4042 - Signatures are taken over from colleagues via "shared draft" function 2022-07-06 19:11:32 +03:00
Rolf Schmidt
bea0a2de1d Fixes #4148 - MultiSelects do not allow storing values in tickets with MariaDB. 2022-07-06 15:49:53 +02:00
Florian Liebe
ca75506667 Fixes #3991 - Triggers are not executed if article contains a mention and variable article.body_as_html is used. 2022-07-06 14:16:47 +02:00
Mantas
30d94ce61d Fixes #4149 - Attachment from KB answer is still sent if manually removed before update 2022-07-05 20:28:00 +03:00
Florian Liebe
3196d8afee Fixes #3829 - User avatars are missing if authentication is done via Google. 2022-07-05 17:03:51 +02:00
Florian Liebe
ff630ed1cb Fixes #3853 - Displayed ticket counter for overviews with subscribe condition differs from number of shown tickets. 2022-07-05 15:13:30 +02:00
Martin Edenhofer
b8c8a0b5f7 Fixed #4157 - Ticket is not opened, nothing can be viewed if auto assignment selector in admin interface has no value selected. 2022-07-05 13:58:03 +02:00
Florian Liebe
3ee984d0f3 Fixes #3962 - iCal implementation of Thunderbird can not be handled. 2022-07-05 11:30:49 +02:00
Martin Gruner
e91a7cae53 Release Zammad 5.2.1. 🚀 2022-07-05 09:25:50 +02:00
Martin Gruner
22cd7ccf3e Maintenance: Refactored handling of login_failed counter. 2022-07-04 18:20:12 +00:00
Mantas Masalskis
e7b06654fd Maintenance: Refactored attachment handling
(cherry picked from commit 26f7a45e9b6457985bed2157447977801923d29e)
2022-07-04 18:20:12 +00:00
Martin Gruner
ccff1354ba Maintenance: Improve configuration of rack_attack.
(cherry picked from commit a41170308a8ba0c66465c74ca53217a3bbd68537)
2022-07-04 18:20:12 +00:00
Rolf Schmidt
0c1d519c5a Maintenance: Improve search controller for customer organization search. 2022-07-04 18:20:12 +00:00
Mantas
0a0f22ec94 Fixes #4159 - Allow very long email address 2022-07-04 19:51:05 +03:00
Martin Gruner
2628fee273 Fixes: #4158 - Elasticsearch 8 incompatible with Zammad 5.2.
(cherry picked from commit c29751907a)
2022-07-04 13:01:45 +02:00
Weblate
68c3bcad6c Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Ivan Perovic <ip@zammad.com>
Co-authored-by: JPB <jeffreypeerboom@eijsden-margraten.nl>
Co-authored-by: LAO <sumonchai@gmail.com>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/hr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/nl/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/th/
Translation: Zammad/Zammad (stable version)
2022-07-04 10:42:23 +02:00
Martin Gruner
6e4a52b9d2 Maintenance: Improve CI service container configuration.
(cherry picked from commit 488f5c027b)
2022-07-01 10:49:06 +02:00
Rolf Schmidt
6a1a5fc02e Performance: Readd authorizations caching (0b6010c390). 2022-06-30 17:51:17 +02:00
Rolf Schmidt
215d1a42bc Performance: Faster updates because core workflow assets are not needed to be generated on validation. 2022-06-30 17:50:56 +02:00
Florian Liebe
caec9d4328 Fixes #4100 - Selecting group permission "full" behaves differently in user- and role-management. 2022-06-30 16:52:00 +02:00
Weblate
045c5145ad Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: Ivan Perovic <ip@zammad.com>
Co-authored-by: Wim Deprez <wim@tamale.nl>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/hr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/nl/
Translation: Zammad/Zammad (stable version)
2022-06-30 11:21:00 +02:00
Rolf Schmidt
743b98213e Fixes #4153 - Trigger Attribute "action > is > updated" is not working after Zammad 5.2 update. 2022-06-30 10:01:04 +02:00
Rolf Schmidt
960910fef7 Performance: Rollback old attributes_with_association_ids caching logic and... 2022-06-30 08:34:16 +02:00
Mantas Masalskis
fb1ac41210 Fixes #4151 - cannot process emails with special characters in local part 2022-06-29 22:03:59 +03:00
Florian Liebe
b62c81e55a Fixes #4140 - No error message is shown if the mandatory role field is missing during creating/updating overviews. 2022-06-29 15:55:05 +02:00
Martin Gruner
e6eb44170b Maintenance: Bump selenium-webdriver from 4.2.1 to 4.3.0
Bumps [selenium-webdriver](https://github.com/SeleniumHQ/selenium) from 4.2.1 to 4.3.0.
- [Release notes](https://github.com/SeleniumHQ/selenium/releases)
- [Changelog](https://github.com/SeleniumHQ/selenium/blob/trunk/rb/CHANGES)
- [Commits](https://github.com/SeleniumHQ/selenium/commits/selenium-4.3.0)


(cherry picked from commit 3e31d4d654)
2022-06-29 13:17:06 +02:00
Florian Liebe
efab6b06f4 Fixes #4129 - Tooltips are not displayed correctly for all fields while creating a new group. 2022-06-29 10:56:55 +02:00
Mantas
205433de6c Maintenance: Chrome browser in CI uses same timestamp format as Firefox 2022-06-28 22:09:35 +03:00
Weblate
ac194df03c Maintenance: Translations update from translations.zammad.org.
Special thanks to the amazing translation team. You rock! 💪

Co-authored-by: DjMagicFingers <amir@srvit.co.il>
Co-authored-by: Ivan Perovic <ip@zammad.com>
Co-authored-by: LAO <sumonchai@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Zhilin Ruslan <x-th-unicorn@hotmail.com>
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/he_IL/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/hr/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/ru/
Translate-URL: https://translations.zammad.org/projects/zammad/zammad-stable/th/
Translation: Zammad/Zammad (stable version)
2022-06-28 07:33:21 +02:00
Rolf Schmidt
80bf57c1b2 Fixes #4143 - LDAP import job no longer starts automatically. 2022-06-23 21:13:22 +02:00
Mantas Masalskis
ea2eb62872 Fixes #4141 - background_services will stop immediately after starting 2022-06-23 17:38:23 +03:00
Mantas
bf8b71d851 Fixes #4031 - The use of shared drafts for response ends with the saving of an internal note 2022-06-22 19:47:22 +03:00
Rolf Schmidt
446f51f5e3 Maintenance: Ignore exists db strategy check in support dir to exclude custom development support files as well. 2022-06-22 15:14:01 +01:00
Martin Gruner
2f1bf5c5cf Maintenance: Correct the filtering of (non-)integration tests in RSpec. 2022-06-22 12:10:16 +02:00
Florian Liebe
f76a83364f Maintenance: Enable RSpec/AlignLeftLetBrace cop 2022-06-22 07:46:52 +02:00
Rolf Schmidt
56c5f3a0c7 Maintenance: Improve custom gemfile loading to support multiple gem files. 2022-06-21 16:53:38 +02:00
Martin Gruner
29bf395d54 Release Zammad 5.2.0. 🚀 2022-06-21 11:22:21 +02:00
534 changed files with 27591 additions and 24213 deletions

2
.gitattributes vendored
View File

@@ -1,5 +1,4 @@
app/assets/javascripts/app/lib/animations/** linguist-vendored
app/assets/javascripts/app/lib/base/ba-linkify.js linguist-vendored
app/assets/javascripts/app/lib/base/bootstrap-tokenfield.js linguist-vendored
app/assets/javascripts/app/lib/base/clipboard.js linguist-vendored
app/assets/javascripts/app/lib/base/cropper.js linguist-vendored
@@ -15,6 +14,7 @@ app/assets/javascripts/app/lib/base/jquery.fineuploader-3.0.js linguist-vendored
app/assets/javascripts/app/lib/base/jquery.hotkeys.js linguist-vendored
app/assets/javascripts/app/lib/base/jquery.noty.js linguist-vendored
app/assets/javascripts/app/lib/base/jquery.visible.js linguist-vendored
app/assets/javascripts/app/lib/base/linkify.js linguist-vendored
app/assets/javascripts/app/lib/base/marked.js linguist-vendored
app/assets/javascripts/app/lib/base/modernizr.custom.92138.js linguist-vendored
app/assets/javascripts/app/lib/base/seedrandom.js linguist-vendored

View File

@@ -26,3 +26,7 @@
- echo -e "\\e[0Ksection_start:`date +%s`:zammad_db_unseeded[collapsed=true]\\r\\e[0Kbundle exec rake zammad:db:unseeded"
- bundle exec rake zammad:db:unseeded
- echo -e "\\e[0Ksection_end:`date +%s`:zammad_db_unseeded\\r\\e[0K"
zammad_ci_test_prepare:
- echo -e "\\e[0Ksection_start:`date +%s`:zammad_ci_test_prepare[collapsed=true]\\r\\e[0Kbundle exec rake zammad:ci:test:prepare"
- bundle exec rake zammad:ci:test:prepare
- echo -e "\\e[0Ksection_end:`date +%s`:zammad_ci_test_prepare\\r\\e[0K"

View File

@@ -1,15 +1,4 @@
.variables_es: &variables_es
variables:
ES_INDEX_RAND: 'true'
ES_URL: 'http://elasticsearch:9200'
ELASTICSEARCH_TAG: '7.16'
.variables_redis_memcached: &variables_redis_memcached
variables:
REDIS_URL: 'redis://redis:6379'
MEMCACHE_SERVERS: 'memcached'
.variables_selenium_firefox: &variables_selenium_firefox
.variables_selenium_firefox:
variables:
REMOTE_URL: 'http://selenium-firefox:4444/wd/hub'
BROWSER: 'firefox'
@@ -19,7 +8,7 @@
SE_NODE_OVERRIDE_MAX_SESSIONS: 'true'
SE_OPTS: '--log /builds/selenium.log'
.variables_selenium_chrome: &variables_selenium_chrome
.variables_selenium_chrome:
variables:
REMOTE_URL: 'http://selenium-chrome:4444/wd/hub'
BROWSER: chrome
@@ -30,157 +19,40 @@
SE_OPTS: '--log /builds/selenium.log'
CHROME_LOG_FILE: '/builds/chrome.log'
# DB Docker
.docker_mysql: &docker_mysql
name: $CI_REGISTRY/docker/zammad-mysql:stable
alias: mysql
.services:
mysql:
name: $CI_REGISTRY/docker/zammad-mysql:stable
alias: mysql
.docker_postgresql: &docker_postgresql
name: $CI_REGISTRY/docker/zammad-postgresql:stable
alias: postgresql
mariadb:
name: $CI_REGISTRY/docker/zammad-mariadb:stable
alias: mysql
.docker_elasticsearch: &docker_elasticsearch
name: $CI_REGISTRY/docker/zammad-elasticsearch:$ELASTICSEARCH_TAG
alias: elasticsearch
postgresql:
name: $CI_REGISTRY/docker/zammad-postgresql:stable
alias: postgresql
.docker_selenium_chrome: &docker_selenium_chrome
name: $CI_REGISTRY/docker/zammad-selenium-chrome:stable
alias: selenium-chrome
elasticsearch:
name: $CI_REGISTRY/docker/zammad-elasticsearch:$ELASTICSEARCH_TAG
alias: elasticsearch
.docker_selenium_firefox: &docker_selenium_firefox
name: $CI_REGISTRY/docker/zammad-selenium-firefox:stable
alias: selenium-firefox
selenium-chrome:
name: $CI_REGISTRY/docker/zammad-selenium-chrome:stable
alias: selenium-chrome
.docker_imap: &docker_imap
name: $CI_REGISTRY/docker/zammad-imap:stable
alias: mail
selenium-firefox:
name: $CI_REGISTRY/docker/zammad-selenium-firefox:stable
alias: selenium-firefox
.docker_redis: &docker_redis
name: $CI_REGISTRY/docker/zammad-redis:stable
alias: redis
imap:
name: $CI_REGISTRY/docker/zammad-imap:stable
alias: mail
.docker_memcached: &docker_memcached
name: $CI_REGISTRY/docker/zammad-memcached:stable
alias: memcached
command: ['memcached', '-m', '256M']
redis:
name: $CI_REGISTRY/docker/zammad-redis:stable
alias: redis
# service templates
.services_mysql:
services:
- <<: *docker_mysql
.services_mysql_redis_memcached:
extends:
- .variables_redis_memcached
services:
- <<: *docker_mysql
- <<: *docker_redis
- <<: *docker_memcached
.services_postgresql:
services:
- <<: *docker_postgresql
.services_postgresql_redis_memcached:
extends:
- .variables_redis_memcached
services:
- <<: *docker_postgresql
- <<: *docker_redis
- <<: *docker_memcached
.services_mysql_postgresql_redis_memcached:
extends:
- .variables_redis_memcached
services:
- <<: *docker_mysql
- <<: *docker_postgresql
- <<: *docker_redis
- <<: *docker_memcached
.services_postgresql_selenium_chrome:
extends:
- .variables_selenium_chrome
services:
- <<: *docker_postgresql
- <<: *docker_selenium_chrome
.services_postgresql_selenium_firefox:
extends:
- .variables_selenium_firefox
services:
- <<: *docker_postgresql
- <<: *docker_selenium_firefox
.services_mysql_postgresql_imap_redis_memcached:
extends:
- .variables_redis_memcached
services:
- <<: *docker_mysql
- <<: *docker_postgresql
- <<: *docker_imap
- <<: *docker_redis
- <<: *docker_memcached
.services_mysql_postgresql_elasticsearch:
services:
- <<: *docker_mysql
- <<: *docker_postgresql
- <<: *docker_elasticsearch
.services_mysql_postgresql_redis_memcached_elasticsearch:
extends:
- .variables_redis_memcached
services:
- <<: *docker_mysql
- <<: *docker_postgresql
- <<: *docker_redis
- <<: *docker_memcached
- <<: *docker_elasticsearch
.services_mysql_postgresql_elasticsearch_selenium_chrome_imap:
extends:
- .variables_selenium_chrome
services:
- <<: *docker_mysql
- <<: *docker_postgresql
- <<: *docker_elasticsearch
- <<: *docker_selenium_chrome
- <<: *docker_imap
.services_mysql_postgresql_elasticsearch_selenium_firefox_imap:
extends:
- .variables_selenium_firefox
services:
- <<: *docker_mysql
- <<: *docker_postgresql
- <<: *docker_elasticsearch
- <<: *docker_selenium_firefox
- <<: *docker_imap
.services_mysql_postgresql_elasticsearch_selenium_chrome_imap_redis_memcached:
extends:
- .variables_selenium_chrome
- .variables_redis_memcached
services:
- <<: *docker_mysql
- <<: *docker_postgresql
- <<: *docker_elasticsearch
- <<: *docker_selenium_chrome
- <<: *docker_imap
- <<: *docker_redis
- <<: *docker_memcached
.services_mysql_postgresql_elasticsearch_selenium_firefox_imap_redis_memcached:
extends:
- .variables_selenium_firefox
- .variables_redis_memcached
services:
- <<: *docker_mysql
- <<: *docker_postgresql
- <<: *docker_elasticsearch
- <<: *docker_selenium_firefox
- <<: *docker_imap
- <<: *docker_redis
- <<: *docker_memcached
memcached:
name: $CI_REGISTRY/docker/zammad-memcached:stable
alias: memcached
command: ['memcached', '-m', '256M']

View File

@@ -17,6 +17,11 @@ variables:
MAIL_SERVER_EMAIL: "zammad@mail.test.dc.zammad.com"
KEEP_ON_MAIL_SERVER: "mail"
KEEP_ON_MAIL_SERVER_ACCOUNT: "zammad@mail.test.dc.zammad.com:zammad"
# Settings for elasticsearch, if active
ES_INDEX_RAND: 'true'
ELASTICSEARCH_TAG: '7.16'
# Temporary switch to enable the mobile front end for testing.
ENABLE_EXPERIMENTAL_MOBILE_FRONTEND: 'true'
# Compile assets only once.

View File

@@ -1,83 +0,0 @@
.template_browser-core: &template_browser-core
stage: browser
dependencies:
- browser:build
extends:
- .job_rules_default
- .variables_es
- .tags_docker_low_concurrency
variables:
RAILS_ENV: "production"
APP_RESTART_CMD: 'bundle exec rake environment zammad:ci:app:restart'
script:
- script/build/test_slice_tests.sh $TEST_SLICE
- RAILS_ENV=test bundle exec rake db:create
- echo -e "\\e[0Ksection_start:`date +%s`:zammad_ci_test_start[collapsed=true]\\r\\e[0Kbundle exec rake zammad:ci:test:start"
- bundle exec rake zammad:ci:test:start[with_elasticsearch]
- echo -e "\\e[0Ksection_end:`date +%s`:zammad_ci_test_start\\r\\e[0K"
- time bundle exec rails test --fail-fast test/browser
.template_browser-core_chrome: &template_browser-core_chrome
extends:
- .template_browser-core
- .services_mysql_postgresql_elasticsearch_selenium_chrome_imap_redis_memcached
.template_browser-core_firefox: &template_browser-core_firefox
extends:
- .template_browser-core
- .services_mysql_postgresql_elasticsearch_selenium_firefox_imap_redis_memcached
.template_browser-core_autowizard: &template_browser-core_autowizard
stage: browser
dependencies:
- browser:build
extends:
- .job_rules_default
- .tags_docker_low_concurrency
variables:
RAILS_ENV: "production"
APP_RESTART_CMD: 'bundle exec rake environment zammad:ci:app:restart'
AUTO_WIZARD_FILE: "contrib/auto_wizard_example.json"
TEST_FILE: "test/integration/auto_wizard_browser_test.rb"
script:
- RAILS_ENV=test bundle exec rake db:create
- cp $AUTO_WIZARD_FILE auto_wizard.json
- echo -e "\\e[0Ksection_start:`date +%s`:zammad_ci_test_start[collapsed=true]\\r\\e[0Kbundle exec rake zammad:ci:test:start"
- bundle exec rake zammad:ci:test:start
- echo -e "\\e[0Ksection_end:`date +%s`:zammad_ci_test_start\\r\\e[0K"
- bundle exec rails test $TEST_FILE
.template_browser-core_autowizard_chrome: &template_browser-core_autowizard_chrome
extends:
- .template_browser-core_autowizard
- .services_postgresql_selenium_chrome
.template_browser-core_autowizard_firefox: &template_browser-core_autowizard_firefox
extends:
- .template_browser-core_autowizard
- .services_postgresql_selenium_firefox
.template_browser-core_capybara: &template_browser-core_capybara
stage: browser
dependencies:
- browser:build
extends:
- .job_rules_default
- .variables_es
- .tags_docker_low_concurrency
script:
- echo -e "\\e[0Ksection_start:`date +%s`:zammad_ci_test_prepare[collapsed=true]\\r\\e[0Kbundle exec rake zammad:ci:test:prepare"
- bundle exec rake zammad:ci:test:prepare
- echo -e "\\e[0Ksection_end:`date +%s`:zammad_ci_test_prepare\\r\\e[0K"
- echo "RSpec filter:" "$RSPEC_EXAMPLE_MATCHES"
- bundle exec rspec --require ./spec/rspec_extensions.rb --fail-fast -t ~integration --profile 10 spec/system
.template_browser-core_capybara_chrome: &template_browser-core_capybara_chrome
extends:
- .template_browser-core_capybara
- .services_mysql_postgresql_elasticsearch_selenium_chrome_imap_redis_memcached
.template_browser-core_capybara_firefox: &template_browser-core_capybara_firefox
extends:
- .template_browser-core_capybara
- .services_mysql_postgresql_elasticsearch_selenium_firefox_imap_redis_memcached

View File

@@ -1,62 +0,0 @@
.template_browser-integration_capybara: &template_browser-integration_capybara
stage: browser
dependencies:
- browser:build
extends:
- .job_rules_default
- .variables_es
- .tags_docker_low_concurrency
script:
- echo -e "\\e[0Ksection_start:`date +%s`:zammad_ci_test_prepare[collapsed=true]\\r\\e[0Kbundle exec rake zammad:ci:test:prepare"
- bundle exec rake zammad:ci:test:prepare
- echo -e "\\e[0Ksection_end:`date +%s`:zammad_ci_test_prepare\\r\\e[0K"
- bundle exec rspec --fail-fast --pattern "spec/system/**/*_spec.rb" -t integration --profile 10
.template_browser-integration_capybara_chrome: &template_browser-integration_capybara_chrome
extends:
- .template_browser-integration_capybara
- .services_mysql_postgresql_elasticsearch_selenium_chrome_imap_redis_memcached
.template_browser-integration_capybara_firefox: &template_browser-integration_capybara_firefox
extends:
- .template_browser-integration_capybara
- .services_mysql_postgresql_elasticsearch_selenium_firefox_imap_redis_memcached
.template_browser-integration_chrome: &template_browser-integration_chrome
stage: browser
dependencies:
- browser:build
extends:
- .job_rules_default
- .services_postgresql_selenium_chrome
- .tags_docker_low_concurrency
variables:
RAILS_ENV: "production"
APP_RESTART_CMD: 'bundle exec rake environment zammad:ci:app:restart'
AUTO_WIZARD_FILE: "contrib/auto_wizard_test.json"
script:
- RAILS_ENV=test bundle exec rake db:create
- cp $AUTO_WIZARD_FILE auto_wizard.json
- echo -e "\\e[0Ksection_start:`date +%s`:zammad_ci_test_start[collapsed=true]\\r\\e[0Kbundle exec rake zammad:ci:test:start"
- bundle exec rake zammad:ci:test:start
- echo -e "\\e[0Ksection_end:`date +%s`:zammad_ci_test_start\\r\\e[0K"
- bundle exec rails test $TEST_FILE
.template_browser-integration_import_chrome: &template_browser-integration_import_chrome
stage: browser
dependencies:
- browser:build
extends:
- .job_rules_default
- .services_postgresql_selenium_chrome
- .tags_docker_low_concurrency
variables:
RAILS_ENV: "production"
APP_RESTART_CMD: 'bundle exec rake environment zammad:ci:app:restart'
RAILS_SERVE_STATIC_FILES: "true"
script:
- RAILS_ENV=test bundle exec rake db:create
- echo -e "\\e[0Ksection_start:`date +%s`:zammad_ci_test_start[collapsed=true]\\r\\e[0Kbundle exec rake zammad:ci:test:start"
- bundle exec rake zammad:ci:test:start
- echo -e "\\e[0Ksection_end:`date +%s`:zammad_ci_test_start\\r\\e[0K"
- bundle exec rails test $TEST_FILE

View File

@@ -1,7 +0,0 @@
'integration:capybara:chrome':
extends:
- .template_browser-integration_capybara_chrome
'integration:capybara:firefox':
extends:
- .template_browser-integration_capybara_firefox

View File

@@ -1,3 +1,2 @@
include:
- '.gitlab/ci/browser-core/*.yml'
- '.gitlab/ci/browser-integration/*.yml'
- '.gitlab/ci/browser/*.yml'

View File

@@ -0,0 +1,149 @@
.template_browser-core:
stage: browser
dependencies:
- browser:build
extends:
- .job_rules_default
- .tags_docker_low_concurrency
variables:
RAILS_ENV: "production"
APP_RESTART_CMD: 'bundle exec rake environment zammad:ci:app:restart'
script:
- script/build/test_slice_tests.sh $TEST_SLICE
- RAILS_ENV=test bundle exec rake db:create
- echo -e "\\e[0Ksection_start:`date +%s`:zammad_ci_test_start[collapsed=true]\\r\\e[0Kbundle exec rake zammad:ci:test:start"
- bundle exec rake zammad:ci:test:start[with_elasticsearch]
- echo -e "\\e[0Ksection_end:`date +%s`:zammad_ci_test_start\\r\\e[0K"
- time bundle exec rails test --fail-fast test/browser
.template_browser-core_chrome:
extends:
- .template_browser-core
- .variables_selenium_chrome
services:
- !reference [.services, mysql]
- !reference [.services, postgresql]
- !reference [.services, elasticsearch]
- !reference [.services, selenium-chrome]
- !reference [.services, imap]
- !reference [.services, redis]
- !reference [.services, memcached]
.template_browser-core_firefox:
extends:
- .template_browser-core
- .variables_selenium_firefox
services:
- !reference [.services, mysql]
- !reference [.services, postgresql]
- !reference [.services, elasticsearch]
- !reference [.services, selenium-firefox]
- !reference [.services, imap]
- !reference [.services, redis]
- !reference [.services, memcached]
.template_browser-core_autowizard:
stage: browser
dependencies:
- browser:build
extends:
- .job_rules_default
- .tags_docker_low_concurrency
variables:
RAILS_ENV: "production"
APP_RESTART_CMD: 'bundle exec rake environment zammad:ci:app:restart'
AUTO_WIZARD_FILE: "contrib/auto_wizard_example.json"
TEST_FILE: "test/integration/auto_wizard_browser_test.rb"
script:
- RAILS_ENV=test bundle exec rake db:create
- cp $AUTO_WIZARD_FILE auto_wizard.json
- echo -e "\\e[0Ksection_start:`date +%s`:zammad_ci_test_start[collapsed=true]\\r\\e[0Kbundle exec rake zammad:ci:test:start"
- bundle exec rake zammad:ci:test:start
- echo -e "\\e[0Ksection_end:`date +%s`:zammad_ci_test_start\\r\\e[0K"
- bundle exec rails test $TEST_FILE
.template_browser-core_autowizard_chrome:
extends:
- .template_browser-core_autowizard
- .variables_selenium_chrome
services:
- !reference [.services, postgresql]
- !reference [.services, selenium-chrome]
.template_browser-core_autowizard_firefox:
extends:
- .template_browser-core_autowizard
- .variables_selenium_firefox
services:
- !reference [.services, postgresql]
- !reference [.services, selenium-firefox]
.template_browser-core_capybara:
stage: browser
dependencies:
- browser:build
extends:
- .job_rules_default
- .tags_docker_low_concurrency
script:
- !reference [.scripts, zammad_ci_test_prepare]
- bundle exec rspec --require ./spec/rspec_extensions.rb --fail-fast --profile 10 spec/system
.template_browser-core_capybara_chrome:
extends:
- .template_browser-core_capybara
- .variables_selenium_chrome
services:
- !reference [.services, mysql]
- !reference [.services, postgresql]
- !reference [.services, elasticsearch]
- !reference [.services, selenium-chrome]
- !reference [.services, imap]
- !reference [.services, redis]
- !reference [.services, memcached]
.template_browser-core_capybara_firefox:
extends:
- .template_browser-core_capybara
- .variables_selenium_firefox
services:
- !reference [.services, mysql]
- !reference [.services, postgresql]
- !reference [.services, elasticsearch]
- !reference [.services, selenium-firefox]
- !reference [.services, imap]
- !reference [.services, redis]
- !reference [.services, memcached]
.template_browser-integration_chrome:
stage: browser
dependencies:
- browser:build
extends:
- .job_rules_default
- .tags_docker_low_concurrency
- .variables_selenium_chrome
services:
- !reference [.services, postgresql]
- !reference [.services, selenium-chrome]
variables:
RAILS_ENV: "production"
APP_RESTART_CMD: 'bundle exec rake environment zammad:ci:app:restart'
AUTO_WIZARD_FILE: "contrib/auto_wizard_test.json"
script:
- RAILS_ENV=test bundle exec rake db:create
- cp $AUTO_WIZARD_FILE auto_wizard.json
- echo -e "\\e[0Ksection_start:`date +%s`:zammad_ci_test_start[collapsed=true]\\r\\e[0Kbundle exec rake zammad:ci:test:start"
- bundle exec rake zammad:ci:test:start
- echo -e "\\e[0Ksection_end:`date +%s`:zammad_ci_test_start\\r\\e[0K"
- bundle exec rails test $TEST_FILE
.template_browser-integration_import_chrome:
extends:
- .template_browser-integration_chrome
script:
- RAILS_ENV=test bundle exec rake db:create
- echo -e "\\e[0Ksection_start:`date +%s`:zammad_ci_test_start[collapsed=true]\\r\\e[0Kbundle exec rake zammad:ci:test:start"
- bundle exec rake zammad:ci:test:start
- echo -e "\\e[0Ksection_end:`date +%s`:zammad_ci_test_start\\r\\e[0K"
- bundle exec rails test $TEST_FILE

View File

@@ -0,0 +1,14 @@
capybara:mariadb:
extends:
- .template_browser-core_capybara
- .variables_selenium_chrome
services:
- !reference [.services, mariadb]
- !reference [.services, elasticsearch]
- !reference [.services, selenium-chrome]
- !reference [.services, imap]
- !reference [.services, redis]
- !reference [.services, memcached]
script:
- !reference [.scripts, zammad_ci_test_prepare]
- bundle exec rspec --require ./spec/rspec_extensions.rb --fail-fast -t mariadb --profile 10 spec/system

View File

@@ -1,13 +1,14 @@
# Workaround to enable usage of mixed SSH and Docker GitLab CI runners
.template_lint: &template_lint
.template_lint:
stage: lint
extends:
- .job_rules_default
.template_lint_rails: &template_lint_rails
.template_lint_rails:
extends:
- .template_lint
- .services_postgresql
services:
- !reference [.services, postgresql]
before_script:
- !reference [.scripts, source_rvm]
- !reference [.scripts, bundle_install]
@@ -16,7 +17,8 @@
# Must be a separate job because it uses a custom image.
'lint: shellcheck':
<<: *template_lint
extends:
- .template_lint
image: koalaman/shellcheck-alpine:stable
cache: []
before_script: []
@@ -26,7 +28,8 @@
after_script: []
'lint: i18n & rails':
<<: *template_lint_rails
extends:
- .template_lint_rails
cache: !reference [.cache, read_only_full]
artifacts:
expire_in: 1 week
@@ -46,7 +49,8 @@
- .gitlab/check_graphql_api_consistency.sh
'lint: ruby & security':
<<: *template_lint
extends:
- .template_lint
before_script:
- !reference [.scripts, source_rvm]
- !reference [.scripts, bundle_install]
@@ -60,7 +64,8 @@
- bundle-audit --ignore CVE-2015-9284
'lint: js & css':
<<: *template_lint
extends:
- .template_lint
cache: !reference [.cache, read_only_nodejs]
before_script: []
script:
@@ -77,7 +82,8 @@
# Must be a separate job because it may fail and is only executed manually.
'lint: orphaned ruby gems':
<<: *template_lint_rails
extends:
- .template_lint_rails
allow_failure: true
rules:
- if: $CI_MERGE_REQUEST_ID

View File

@@ -32,8 +32,8 @@
- script/build/sync_repo.sh git@github.com:zammad/zammad.git
'refresh global envs':
extends:
- .services_postgresql
services:
- !reference [.services, postgresql]
stage: pre
needs:
- 'write runner cache: ruby'

View File

@@ -4,8 +4,12 @@
- browser:build
extends:
- .job_rules_default
- .services_mysql_postgresql_redis_memcached
- .tags_docker_low_concurrency
services:
- !reference [.services, mysql]
- !reference [.services, postgresql]
- !reference [.services, redis]
- !reference [.services, memcached]
variables:
RAILS_ENV: "production"
APP_RESTART_CMD: 'bundle exec rake environment zammad:ci:app:restart'

View File

@@ -1,7 +1,7 @@
browser:build:
stage: test
extends:
- .services_postgresql
services:
- !reference [.services, postgresql]
artifacts:
expire_in: 1 week
paths:

View File

@@ -1,9 +1,14 @@
include:
- '.gitlab/ci/test/integration/*.yml'
.template_integration: &template_integration
.template_integration:
extends:
- .job_rules_default
- .services_mysql_postgresql_imap_redis_memcached
- .job_rules_integration_manual_start
services:
- !reference [.services, mysql]
- !reference [.services, postgresql]
- !reference [.services, imap]
- !reference [.services, redis]
- !reference [.services, memcached]
stage: test

View File

@@ -1,8 +1,12 @@
.template_integration_es: &template_integration_es
.template_integration_es:
extends:
- .job_rules_default
- .services_mysql_postgresql_redis_memcached_elasticsearch
- .variables_es
services:
- !reference [.services, mysql]
- !reference [.services, postgresql]
- !reference [.services, redis]
- !reference [.services, memcached]
- !reference [.services, elasticsearch]
stage: test
script:
- !reference [.scripts, zammad_db_unseeded]
@@ -12,18 +16,21 @@
- bundle exec rspec --tag searchindex --tag ~type:system --profile 10
# Lowest supported version
'rspec:es:7.8':
<<: *template_integration_es
rspec:es:7.8:
extends:
- .template_integration_es
variables:
ELASTICSEARCH_TAG: '7.8.1'
# Highest supported version (except for newer releases that are not yet in CI)
'rspec:es:7.16':
<<: *template_integration_es
rspec:es:7.16:
extends:
- .template_integration_es
variables:
ELASTICSEARCH_TAG: '7.16'
'rspec:es:8.1':
<<: *template_integration_es
rspec:es:8.1:
extends:
- .template_integration_es
variables:
ELASTICSEARCH_TAG: '8.1'

View File

@@ -1,4 +1,4 @@
.template_integration_otrs: &template_integration_otrs
.template_integration_otrs:
extends:
- .template_integration
script:
@@ -9,8 +9,9 @@
IMPORT_OTRS_ENDPOINT: 'http://zammad-ci-otrsimport-app/otrs/public.pl?Action=ZammadMigrator'
TZ: 'Europe/Berlin' # Required for the zammad-ci-otrsimport-app containers
'minitest:integration:otrs:6':
<<: *template_integration_otrs
minitest:integration:otrs:6:
extends:
- .template_integration_otrs
services:
- name: $CI_REGISTRY/docker/zammad-mysql:stable
alias: mysql
@@ -26,8 +27,9 @@
- name: $CI_REGISTRY/docker/zammad-ci-otrsimport-app:otrs6
alias: zammad-ci-otrsimport-app
'minitest:integration:otrs:5':
<<: *template_integration_otrs
minitest:integration:otrs:5:
extends:
- .template_integration_otrs
services:
- name: $CI_REGISTRY/docker/zammad-mysql:stable
alias: mysql
@@ -43,8 +45,9 @@
- name: $CI_REGISTRY/docker/zammad-ci-otrsimport-app:otrs5
alias: zammad-ci-otrsimport-app
'minitest:integration:otrs:4':
<<: *template_integration_otrs
minitest:integration:otrs:4:
extends:
- .template_integration_otrs
services:
- name: $CI_REGISTRY/docker/zammad-mysql:stable
alias: mysql
@@ -60,8 +63,9 @@
- name: $CI_REGISTRY/docker/zammad-ci-otrsimport-app:otrs4
alias: zammad-ci-otrsimport-app
'minitest:integration:otrs:33':
<<: *template_integration_otrs
minitest:integration:otrs:33:
extends:
- .template_integration_otrs
services:
- name: $CI_REGISTRY/docker/zammad-mysql:stable
alias: mysql
@@ -77,8 +81,9 @@
- name: $CI_REGISTRY/docker/zammad-ci-otrsimport-app:otrs33
alias: zammad-ci-otrsimport-app
'minitest:integration:otrs:32':
<<: *template_integration_otrs
minitest:integration:otrs:32:
extends:
- .template_integration_otrs
services:
- name: $CI_REGISTRY/docker/zammad-mysql:stable
alias: mysql
@@ -94,8 +99,9 @@
- name: $CI_REGISTRY/docker/zammad-ci-otrsimport-app:otrs32
alias: zammad-ci-otrsimport-app
'minitest:integration:otrs:31':
<<: *template_integration_otrs
minitest:integration:otrs:31:
extends:
- .template_integration_otrs
services:
- name: $CI_REGISTRY/docker/zammad-mysql:stable
alias: mysql

View File

@@ -1,15 +1,13 @@
include:
- '.gitlab/ci/test/rspec/*.yml'
.template_rspec: &template_rspec
.template_rspec:
stage: test
extends:
- .job_rules_default
script:
- echo -e "\\e[0Ksection_start:`date +%s`:zammad_db_init[collapsed=true]\\r\\e[0Kbundle exec rake zammad:db:init"
- bundle exec rake zammad:db:init
- echo -e "\\e[0Ksection_end:`date +%s`:zammad_db_init\\r\\e[0K"
- echo "RSpec filter:" "$RSPEC_EXAMPLE_MATCHES"
- bundle exec rspec --require ./spec/rspec_extensions.rb --exclude-pattern "spec/{system,integration}/**/*_spec.rb" -t ~searchindex --profile 10
- !reference [.scripts, zammad_db_init]
- bundle exec rspec --require ./spec/rspec_extensions.rb --exclude-pattern "spec/system/**/*_spec.rb" -t ~searchindex -t ~integration --profile 10
# RSpec 3rd party integration tests are independent of the database
# therefore we need only one job and choose a random database for each run
@@ -19,8 +17,13 @@ rspec:integration:
stage: test
extends:
- .job_rules_default
- .services_mysql_postgresql_imap_redis_memcached
- .job_rules_integration_manual_start
services:
- !reference [.services, mysql]
- !reference [.services, postgresql]
- !reference [.services, imap]
- !reference [.services, redis]
- !reference [.services, memcached]
script:
- !reference [.scripts, zammad_db_init]
- bundle exec rspec -t type:integration --profile 10
- bundle exec rspec --exclude-pattern "spec/system/**/*_spec.rb" -t integration --profile 10

View File

@@ -0,0 +1,11 @@
rspec:mariadb:
extends:
- .template_rspec
services:
- !reference [.services, mariadb]
- !reference [.services, redis]
- !reference [.services, memcached]
script:
- !reference [.scripts, zammad_db_init]
# Execute only tests tagged with :mariadb.
- bundle exec rspec --require ./spec/rspec_extensions.rb --exclude-pattern "spec/system/**/*_spec.rb" -t mariadb --profile 10

View File

@@ -1,53 +1,49 @@
.template_rspec_mysql:
extends:
- .template_rspec
services:
- !reference [.services, mysql]
- !reference [.services, redis]
- !reference [.services, memcached]
rspec:mysql:1:
stage: test
variables:
RSPEC_SLICES: 6
RSPEC_CURRENT_SLICE: 1
extends:
- .services_mysql_redis_memcached
- .template_rspec
- .template_rspec_mysql
rspec:mysql:2:
stage: test
variables:
RSPEC_SLICES: 6
RSPEC_CURRENT_SLICE: 2
extends:
- .services_mysql_redis_memcached
- .template_rspec
- .template_rspec_mysql
rspec:mysql:3:
stage: test
variables:
RSPEC_SLICES: 6
RSPEC_CURRENT_SLICE: 3
extends:
- .services_mysql_redis_memcached
- .template_rspec
- .template_rspec_mysql
rspec:mysql:4:
stage: test
variables:
RSPEC_SLICES: 6
RSPEC_CURRENT_SLICE: 4
extends:
- .services_mysql_redis_memcached
- .template_rspec
- .template_rspec_mysql
rspec:mysql:5:
stage: test
variables:
RSPEC_SLICES: 6
RSPEC_CURRENT_SLICE: 5
extends:
- .services_mysql_redis_memcached
- .template_rspec
- .template_rspec_mysql
rspec:mysql:6:
stage: test
variables:
RSPEC_SLICES: 6
RSPEC_CURRENT_SLICE: 6
extends:
- .services_mysql_redis_memcached
- .template_rspec
- .template_rspec_mysql

View File

@@ -1,53 +1,49 @@
.template_rspec_postgresql:
extends:
- .template_rspec
services:
- !reference [.services, postgresql]
- !reference [.services, redis]
- !reference [.services, memcached]
rspec:postgresql:1:
stage: test
variables:
RSPEC_SLICES: 6
RSPEC_CURRENT_SLICE: 1
extends:
- .services_postgresql_redis_memcached
- .template_rspec
- .template_rspec_postgresql
rspec:postgresql:2:
stage: test
variables:
RSPEC_SLICES: 6
RSPEC_CURRENT_SLICE: 2
extends:
- .services_postgresql_redis_memcached
- .template_rspec
- .template_rspec_postgresql
rspec:postgresql:3:
stage: test
variables:
RSPEC_SLICES: 6
RSPEC_CURRENT_SLICE: 3
extends:
- .services_postgresql_redis_memcached
- .template_rspec
- .template_rspec_postgresql
rspec:postgresql:4:
stage: test
variables:
RSPEC_SLICES: 6
RSPEC_CURRENT_SLICE: 4
extends:
- .services_postgresql_redis_memcached
- .template_rspec
- .template_rspec_postgresql
rspec:postgresql:5:
stage: test
variables:
RSPEC_SLICES: 6
RSPEC_CURRENT_SLICE: 5
extends:
- .services_postgresql_redis_memcached
- .template_rspec
- .template_rspec_postgresql
rspec:postgresql:6:
stage: test
variables:
RSPEC_SLICES: 6
RSPEC_CURRENT_SLICE: 6
extends:
- .services_postgresql_redis_memcached
- .template_rspec
- .template_rspec_postgresql

View File

@@ -1,4 +1,4 @@
.template_unit: &template_unit
.template_unit:
extends:
- .job_rules_default
script:
@@ -10,11 +10,17 @@
unit:mysql:
stage: test
extends:
- .services_mysql_redis_memcached
- .template_unit
services:
- !reference [.services, mysql]
- !reference [.services, redis]
- !reference [.services, memcached]
unit:postgresql:
stage: test
extends:
- .services_postgresql_redis_memcached
- .template_unit
- .template_unit
services:
- !reference [.services, postgresql]
- !reference [.services, redis]
- !reference [.services, memcached]

View File

@@ -7,8 +7,10 @@ require 'fileutils'
#
# Configures the CI system
# - either (randomly) mysql or postgresql, if it is available
# - (randomly) mysql or postgresql, if available
# - (randomly) Redis or File as web socket session back end, if Redis is available
# - (randomly) Memcached or File as Rails cache store, if Memcached is available
# - Elasticsearch support, if available
#
# Database config happens directly in config/database.yml, other settings are written to
# .gitlab/environment.env which must be sourced in the CI configuration.
@@ -22,7 +24,35 @@ class ConfigureEnvironment
true
ENV_FILE_CONTENT
def self.configure_database # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
DB_SETTINGS_MAP = {
'postgresql' => {
'adapter' => 'postgresql',
'username' => 'zammad',
'password' => 'zammad',
'host' => 'postgresql', # db alias from gitlab-ci.yml
},
'mysql' => {
'adapter' => 'mysql2',
'username' => 'root',
'password' => 'zammad',
'host' => 'mysql', # db alias from gitlab-ci.yml
}
}.freeze
# Detect service availability based on host presence in network.
def self.network_host_exists?(hostname)
if %w[1 true].include?(ENV['FF_NETWORK_PER_BUILD'])
begin
!!Resolv::DNS.new.tap { |dns| dns.timeouts = 3 }.getaddress(hostname)
rescue Resolv::ResolvError
false
end
else
File.foreach('/etc/hosts').any? { |l| l[hostname] }
end
end
def self.configure_database # rubocop:disable Metrics/AbcSize
if File.exist? File.join(__dir__, '../config/database.yml')
puts "'config/database.yml' already exists and will not be changed."
@@ -32,51 +62,16 @@ class ConfigureEnvironment
cnf = YAML.load_file(File.join(__dir__, '../config/database/database.yml'))
cnf.delete('default')
database = ENV['ENFORCE_DB_SERVICE']
# Lookup in /etc/hosts first: gitlab uses that if FF_NETWORK_PER_BUILD is not set.
if !database
hostsfile = '/etc/hosts'
database = %w[postgresql mysql].shuffle.find do |possible_database|
File.foreach(hostsfile).any? { |l| l[possible_database] }
end
end
# Lookup via DNS if needed: gitlab uses that if FF_NETWORK_PER_BUILD is enabled.
if !database
dns = Resolv::DNS.new
dns.timeouts = 3
database = %w[postgresql mysql].shuffle.find do |possible_database|
# Perform a lookup of the database host to check if it is configured as a service.
if dns.getaddress possible_database
next possible_database
end
rescue Resolv::ResolvError
# Ignore DNS lookup errors
end
database = ENV['ENFORCE_DB_SERVICE'] || %w[postgresql mysql].shuffle.find do |db|
network_host_exists?(db)
end
raise "Can't find any supported database." if database.nil?
puts "Using #{database} as database service."
db_settings_map = {
'postgresql' => {
'adapter' => 'postgresql',
'username' => 'zammad',
'password' => 'zammad',
'host' => 'postgresql', # db alias from gitlab-ci.yml
},
'mysql' => {
'adapter' => 'mysql2',
'username' => 'root',
'password' => 'zammad',
'host' => 'mysql', # db alias from gitlab-ci.yml
}
}
# fetch DB settings from settings map and fallback to postgresql
db_settings = db_settings_map.fetch(database) { db_settings_map['postgresql'] }
db_settings = DB_SETTINGS_MAP.fetch(database) { DB_SETTINGS_MAP['postgresql'] }
%w[development test production].each do |environment|
cnf[environment].merge!(db_settings)
@@ -85,40 +80,46 @@ class ConfigureEnvironment
File.write(File.join(__dir__, '../config/database.yml'), Psych.dump(cnf))
end
def self.configure_redis # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
puts 'ENABLING THE NEW EXPERIMENTAL MOBILE FRONTEND.' if ENV['ENABLE_EXPERIMENTAL_MOBILE_FRONTEND'] == 'true'
if ENV['ENABLE_EXPERIMENTAL_MOBILE_FRONTEND'] == 'true' && (ENV['REDIS_URL'].nil? || ENV['REDIS_URL'].empty?) # rubocop:disable Rails/Blank
if database_type == 'mysql'
raise 'Redis was not found, but is required for ActionCable on MySQL based systems.'
end
def self.configure_redis
has_redis = network_host_exists?('redis')
needs_redis = database_type == 'mysql' && ENV['ENABLE_EXPERIMENTAL_MOBILE_FRONTEND'] == 'true'
puts 'Redis is not available, using File as web socket session store.'
puts 'Redis is not available, using the PostgreSQL adapter for ActionCable.'
if needs_redis && !has_redis
raise 'Redis was not found, but is required for ActionCable on MySQL based systems.'
end
if has_redis && [true, needs_redis].sample
puts 'Using Redis as web socket session store and as adapter for ActionCable.'
@env_file_content += "export REDIS_URL='redis://redis:6379'\n"
return
end
if database_type == 'mysql' || [true, false].sample
puts 'Using Redis as web socket session store.'
puts 'Using the Redis adapter for ActionCable.'
return
end
puts 'Using File as web socket session store.'
puts 'Using the PostgreSQL adapter for ActionCable.'
puts 'Using File as web socket session store and the PostgreSQL adapter for ActionCable.'
@env_file_content += "unset REDIS_URL\n"
end
def self.configure_memcached
if ENV['MEMCACHE_SERVERS'].nil? || ENV['MEMCACHE_SERVERS'].empty? # rubocop:disable Rails/Blank
puts 'Memcached is not available, using File as Rails cache store.'
return
end
if [true, false].sample
if network_host_exists?('memcached') && [true, false].sample
puts 'Using memcached as Rails cache store.'
@env_file_content += "export MEMCACHE_SERVERS='memcached'\n"
return
end
puts "Using Zammad's file store as Rails cache store."
@env_file_content += "unset MEMCACHE_SERVERS\n"
end
def self.configure_elasticsearch
if network_host_exists?('elasticsearch')
puts 'Activating support for Elasticsearch.'
@env_file_content += "export ES_URL='http://elasticsearch:9200'\n"
return
end
puts 'Not using Elasticsearch.'
@env_file_content += "unset ES_URL\n"
end
# Since configure_database skips if database.yml already exists, check the
# content of that file to reliably determine the database type in all cases.
def self.database_type
@@ -135,9 +136,11 @@ class ConfigureEnvironment
end
def self.run
puts 'ENABLING THE NEW EXPERIMENTAL MOBILE FRONTEND.' if ENV['ENABLE_EXPERIMENTAL_MOBILE_FRONTEND'] == 'true'
configure_database
configure_redis
configure_memcached
configure_elasticsearch
write_env_file
end
end

View File

@@ -339,6 +339,14 @@ RSpec/Rails/AvoidSetupHook:
Exclude:
- "test/**/*"
RSpec/AlignLeftLetBrace:
Description: 'Checks that left braces for adjacent single line lets are aligned.'
Enabled: true
RSpec/AnyInstance:
Description: 'Prefer instance doubles over stubbing any instance of a class.'
Enabled: false
Zammad/PreferNegatedIfOverUnless:
Exclude:
- 'bin/rspec'
@@ -379,4 +387,4 @@ Zammad/ExistsDbStrategy:
Include:
- "spec/**/*.rb"
Exclude:
- spec/support/capybara/common_actions.rb
- "spec/support/capybara/*.rb"

View File

@@ -1,15 +1,3 @@
RSpec/AnyInstance:
Exclude:
- 'spec/lib/import/otrs/article_spec.rb'
- 'spec/lib/import/otrs/customer_spec.rb'
- 'spec/lib/import/otrs/customer_user_spec.rb'
- 'spec/lib/import/otrs/priority_spec.rb'
- 'spec/lib/import/otrs/queue_spec.rb'
- 'spec/lib/import/otrs/state_spec.rb'
- 'spec/lib/import/otrs/ticket_spec.rb'
- 'spec/lib/import/otrs/user_spec.rb'
- 'spec/models/import_job_spec.rb'
RSpec/BeforeAfterAll:
Exclude:
- 'spec/spec_helper.rb'
@@ -191,7 +179,6 @@ RSpec/ContextWording:
- 'spec/system/system/object_manager_spec.rb'
- 'spec/system/system/translations_spec.rb'
- 'spec/system/ticket/create_spec.rb'
- 'spec/system/ticket/inserting_knowledge_base_answer_spec.rb'
- 'spec/system/ticket/shared_draft_start_spec.rb'
- 'spec/system/ticket/shared_draft_zoom_spec.rb'
- 'spec/system/ticket/update/full_quote_header_spec.rb'

View File

@@ -342,7 +342,7 @@ Metrics/AbcSize:
- 'lib/stats/ticket_reopen.rb'
- 'lib/stats/ticket_response_time.rb'
- 'lib/stats/ticket_waiting_time.rb'
- 'lib/telegram.rb'
- 'lib/telegram_helper.rb'
- 'lib/transaction_dispatcher.rb'
- 'lib/twitter_sync.rb'
- 'lib/user_agent.rb'
@@ -641,7 +641,7 @@ Metrics/CyclomaticComplexity:
- 'lib/stats/ticket_load_measure.rb'
- 'lib/stats/ticket_reopen.rb'
- 'lib/stats/ticket_waiting_time.rb'
- 'lib/telegram.rb'
- 'lib/telegram_helper.rb'
- 'lib/transaction_dispatcher.rb'
- 'lib/twitter_sync.rb'
- 'lib/user_agent.rb'
@@ -863,7 +863,7 @@ Metrics/PerceivedComplexity:
- 'lib/stats/ticket_reopen.rb'
- 'lib/stats/ticket_response_time.rb'
- 'lib/stats/ticket_waiting_time.rb'
- 'lib/telegram.rb'
- 'lib/telegram_helper.rb'
- 'lib/transaction_dispatcher.rb'
- 'lib/twitter_sync.rb'
- 'lib/user_agent.rb'

View File

@@ -1,11 +1,187 @@
# Change Log
## [5.2.0](https://github.com/zammad/zammad/tree/5.2.0) (2022-xx-xx)
## [5.2.3](https://github.com/zammad/zammad/tree/5.2.3) (2022-09-30)
[Full Changelog](https://github.com/zammad/zammad/compare/5.2.2...5.2.3)
**Implemented enhancements:**
- Unable to add Microsoft 365 account when admin consent for enterprise applications is enabled [3963](https://github.com/zammad/zammad/issues/3963) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)] [[mail processing](https://github.com/zammad/zammad/labels/mail%20processing)]
- Zendesk Import: Link to linked Tickets are not working after migration to Zammad [4249](https://github.com/zammad/zammad/issues/4249) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[import](https://github.com/zammad/zammad/labels/import)]
**Fixed bugs:**
- KB answer linking in ticket zoom fails: Couldn't find KnowledgeBase::Answer with 'id'=315 [4259](https://github.com/zammad/zammad/issues/4259) [[bug](https://github.com/zammad/zammad/labels/bug)] [[blocker](https://github.com/zammad/zammad/labels/blocker)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)] [[knowledge base](https://github.com/zammad/zammad/labels/knowledge%20base)]
- Locking a record with unpersisted changes is not supported. Use `save` to persist the changes, or `reload` to discard them explicitly [3664](https://github.com/zammad/zammad/issues/3664) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- HTML table in mail preview is misformatted [3630](https://github.com/zammad/zammad/issues/3630) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Zammad suffers from serious performance degrade on incorrect role configurations [4243](https://github.com/zammad/zammad/issues/4243) [[bug](https://github.com/zammad/zammad/labels/bug)] [[blocker](https://github.com/zammad/zammad/labels/blocker)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Apply via role granted permission while creating shared draft [4071](https://github.com/zammad/zammad/issues/4071) [[bug](https://github.com/zammad/zammad/labels/bug)] [[ticket](https://github.com/zammad/zammad/labels/ticket)]
- Mandatory field in combination with automatic assignment leads to a divergence in the setting of the ticket owner [4245](https://github.com/zammad/zammad/issues/4245) [[bug](https://github.com/zammad/zammad/labels/bug)] [[blocker](https://github.com/zammad/zammad/labels/blocker)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)] [[regression](https://github.com/zammad/zammad/labels/regression)]
- Type attribute is being ignored on Zendesk import [4246](https://github.com/zammad/zammad/issues/4246) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Email address falsely converted on pasting into article body [3099](https://github.com/zammad/zammad/issues/3099) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Zendesk import does not import comment create date inside of the created articles [4244](https://github.com/zammad/zammad/issues/4244) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Zendesk import does not import embedded images [4234](https://github.com/zammad/zammad/issues/4234) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Fix width of tags add in trigger [4215](https://github.com/zammad/zammad/issues/4215) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX%2FUI)]
- HTML comments/conditions being rendered from emails [4184](https://github.com/zammad/zammad/issues/4184) [[bug](https://github.com/zammad/zammad/labels/bug)] [[mail processing](https://github.com/zammad/zammad/labels/mail%20processing)]
- Drag & Drop bulk operations not available in search results [3923](https://github.com/zammad/zammad/issues/3923) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Fixes #3718 - Dashboard stopped working. [4240](https://github.com/zammad/zammad/pull/4240)
- Dashboard stopped working [3718](https://github.com/zammad/zammad/issues/3718) [[bug](https://github.com/zammad/zammad/labels/bug)]
- The webhook could not be saved by Telegram, seems to be an invalid URL. [4156](https://github.com/zammad/zammad/issues/4156) [[bug](https://github.com/zammad/zammad/labels/bug)] [[telegram](https://github.com/zammad/zammad/labels/telegram)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Changing time range in "Wait until" picker at trigger action resets time value to "1" [4207](https://github.com/zammad/zammad/issues/4207) [[bug](https://github.com/zammad/zammad/labels/bug)]
## [5.2.2](https://github.com/zammad/zammad/tree/5.2.2) (2022-09-13)
[Full Changelog](https://github.com/zammad/zammad/compare/5.2.1...5.2.2)
**Implemented enhancements:**
- Fixes #849 - Allow additional usage of Ticket Number in (Zoom) URL [4191](https://github.com/zammad/zammad/pull/4191) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[needs verification](https://github.com/zammad/zammad/labels/needs%20verification)]
- Allow additional usage of Ticket Number in (Zoom) URL [849](https://github.com/zammad/zammad/issues/849) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[UX/UI](https://github.com/zammad/zammad/labels/UX%2FUI)] [[frontend / JS app](https://github.com/zammad/zammad/labels/frontend%20%2F%20JS%20app)]
**Fixed bugs:**
- Alphabetical order of text modules [4221](https://github.com/zammad/zammad/issues/4221) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Core Workflow: Readonly input fields with link template are not clickable [4233](https://github.com/zammad/zammad/issues/4233) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX%2FUI)] [[core workflows](https://github.com/zammad/zammad/labels/core%20workflows)]
- Tickets can be closed with the bulk action, even when a mandatory field is empty [4198](https://github.com/zammad/zammad/issues/4198) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Article gets shortened when its text is marked with highlighter [4202](https://github.com/zammad/zammad/issues/4202) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Remove "Create new Customer" in trigger [4214](https://github.com/zammad/zammad/issues/4214) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX%2FUI)]
- Cannot set date for pending close status in postmaster filter [4206](https://github.com/zammad/zammad/issues/4206) [[bug](https://github.com/zammad/zammad/labels/bug)] [[channel](https://github.com/zammad/zammad/labels/channel)]
- Article update causes missing icons in the UI after switching internal state [4213](https://github.com/zammad/zammad/issues/4213) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX%2FUI)] [[ticket](https://github.com/zammad/zammad/labels/ticket)]
- Fixes #4186 - Multiselect does not show values properly in reports. [4185](https://github.com/zammad/zammad/pull/4185) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Multiselect does not show values properly in reports [4186](https://github.com/zammad/zammad/issues/4186) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Knowledge base formatting issue [4093](https://github.com/zammad/zammad/issues/4093) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)] [[knowledge base](https://github.com/zammad/zammad/labels/knowledge%20base)]
- Ticket templates do not save the owner attribute [4175](https://github.com/zammad/zammad/issues/4175) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Knowledge base detail page has long loading times when kb has many categories [4190](https://github.com/zammad/zammad/issues/4190) [[bug](https://github.com/zammad/zammad/labels/bug)] [[knowledge base](https://github.com/zammad/zammad/labels/knowledge%20base)] [[performance](https://github.com/zammad/zammad/labels/performance)]
- Ticket hook not represented consistently inside a ticket with some articles [3861](https://github.com/zammad/zammad/issues/3861) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)] [[good first issue](https://github.com/zammad/zammad/labels/good%20first%20issue)]
- multi_tree_select: field is used, but browser says it is empty [4188](https://github.com/zammad/zammad/issues/4188) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Core Workflows no longer allow multiple values for single selection fields [4193](https://github.com/zammad/zammad/issues/4193) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)] [[core workflows](https://github.com/zammad/zammad/labels/core%20workflows)]
- Unable to remove chat topics if chat sessions exist [4178](https://github.com/zammad/zammad/issues/4178) [[bug](https://github.com/zammad/zammad/labels/bug)] [[chat](https://github.com/zammad/zammad/labels/chat)]
- Tree select not proper in Report [4174](https://github.com/zammad/zammad/issues/4174) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Avatar upload fails on cropping image [4171](https://github.com/zammad/zammad/issues/4171) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Unable to scroll ticket image view when using arrow keys [4180](https://github.com/zammad/zammad/issues/4180) [[bug](https://github.com/zammad/zammad/labels/bug)] [[frontend / JS app](https://github.com/zammad/zammad/labels/frontend%20%2F%20JS%20app)]
- Fix scrolling image view when using arrow keys [4177](https://github.com/zammad/zammad/pull/4177) [[bug](https://github.com/zammad/zammad/labels/bug)] [[frontend / JS app](https://github.com/zammad/zammad/labels/frontend%20%2F%20JS%20app)]
- Secondary organizations are always shown in sidebar even if customer has no secondary organizations assigned [4172](https://github.com/zammad/zammad/issues/4172) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Elasticsearch forgets results from attachments over time [4134](https://github.com/zammad/zammad/issues/4134) [[bug](https://github.com/zammad/zammad/labels/bug)] [[search](https://github.com/zammad/zammad/labels/search)]
- Zendesk migration runs randomly in ssl handshake failure triggered from Zendesk site without a retry [4169](https://github.com/zammad/zammad/issues/4169) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Zendesk migration has currently no retry for attachment fetch [4170](https://github.com/zammad/zammad/issues/4170) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Triggers must not allow adding perform settings which are empty / nil [4131](https://github.com/zammad/zammad/issues/4131) [[bug](https://github.com/zammad/zammad/labels/bug)] [[admin area](https://github.com/zammad/zammad/labels/admin%20area)] [[mail processing](https://github.com/zammad/zammad/labels/mail%20processing)]
- LDAP tries to sync all the time [4167](https://github.com/zammad/zammad/issues/4167) [[bug](https://github.com/zammad/zammad/labels/bug)] [[LDAP](https://github.com/zammad/zammad/labels/LDAP)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- KB answer image in editor has extra padding [4165](https://github.com/zammad/zammad/issues/4165) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Signatures are taken over from colleagues via "shared draft" function [4042](https://github.com/zammad/zammad/issues/4042) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- MultiSelects do not allow storing values in tickets with MariaDB [4148](https://github.com/zammad/zammad/issues/4148) [[bug](https://github.com/zammad/zammad/labels/bug)] [[object manager attribute](https://github.com/zammad/zammad/labels/object%20manager%20attribute)]
- Triggers are not executed if article contains a mention and variable ``article.body_as_html`` is used [3991](https://github.com/zammad/zammad/issues/3991) [[bug](https://github.com/zammad/zammad/labels/bug)] [[trigger](https://github.com/zammad/zammad/labels/trigger)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Attachment from KB answer is still sent if manually removed before update [4149](https://github.com/zammad/zammad/issues/4149) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- User avatars are missing if authentication is done via Google [3829](https://github.com/zammad/zammad/issues/3829) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX%2FUI)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Displayed ticket counter for overviews with subscribe condition differs from number of shown tickets [3853](https://github.com/zammad/zammad/issues/3853) [[bug](https://github.com/zammad/zammad/labels/bug)] [[overviews](https://github.com/zammad/zammad/labels/overviews)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Ticket is not opened, nothing can be viewed if auto assignment selector in admin interface has no value selected [4157](https://github.com/zammad/zammad/issues/4157) [[bug](https://github.com/zammad/zammad/labels/bug)] [[ticket](https://github.com/zammad/zammad/labels/ticket)]
- iCal implementation of Thunderbird can not be handled [3962](https://github.com/zammad/zammad/issues/3962) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
## [5.2.1](https://github.com/zammad/zammad/tree/5.2.1) (2022-07-05)
[Full Changelog](https://github.com/zammad/zammad/compare/5.2.0...5.2.1)
**Fixed bugs:**
- Allow very long email address [4159](https://github.com/zammad/zammad/issues/4159) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Elasticsearch 8 incompatible with Zammad 5.2 [4158](https://github.com/zammad/zammad/issues/4158)
- Selecting group permission "full" behaves differently in user- and role-management [4100](https://github.com/zammad/zammad/issues/4100) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Trigger Attribute "action > is > updated" is not working after Zammad 5.2 update [4153](https://github.com/zammad/zammad/issues/4153) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- cannot process emails with special characters in local part [4151](https://github.com/zammad/zammad/issues/4151) [[bug](https://github.com/zammad/zammad/labels/bug)]
- No error message is shown if the mandatory role field is missing during creating/updating overviews [4140](https://github.com/zammad/zammad/issues/4140) [[bug](https://github.com/zammad/zammad/labels/bug)] [[admin area](https://github.com/zammad/zammad/labels/admin%20area)] [[overviews](https://github.com/zammad/zammad/labels/overviews)]
- Tooltips are not displayed correctly for all fields while creating a new group [4129](https://github.com/zammad/zammad/issues/4129) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX%2FUI)] [[admin area](https://github.com/zammad/zammad/labels/admin%20area)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- LDAP import job no longer starts automatically [4143](https://github.com/zammad/zammad/issues/4143) [[bug](https://github.com/zammad/zammad/labels/bug)] [[blocker](https://github.com/zammad/zammad/labels/blocker)] [[LDAP](https://github.com/zammad/zammad/labels/LDAP)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- background_services will stop immediately after starting [4141](https://github.com/zammad/zammad/issues/4141) [[bug](https://github.com/zammad/zammad/labels/bug)]
- The use of shared drafts for response ends with the saving of an internal note [4031](https://github.com/zammad/zammad/issues/4031) [[bug](https://github.com/zammad/zammad/labels/bug)]
## [5.2.0](https://github.com/zammad/zammad/tree/5.2.0) (2022-06-21)
[Full Changelog](https://github.com/zammad/zammad/compare/5.1.0...5.2.0)
**Implemented enhancements:**
- Time accounting "Overviews" is misleading [4119](https://github.com/zammad/zammad/issues/4119) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[UX/UI](https://github.com/zammad/zammad/labels/UX%2FUI)] [[admin area](https://github.com/zammad/zammad/labels/admin%20area)] [[time accounting](https://github.com/zammad/zammad/labels/time%20accounting)]
- Translate Slack templates in French [4101](https://github.com/zammad/zammad/pull/4101) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[translation](https://github.com/zammad/zammad/labels/translation)] [[integration](https://github.com/zammad/zammad/labels/integration)]
- Translate ticket merge email templates in French [4099](https://github.com/zammad/zammad/pull/4099) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[translation](https://github.com/zammad/zammad/labels/translation)]
- Multi LDAP: Httplog should include source where data was synced from [4106](https://github.com/zammad/zammad/issues/4106) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[UX/UI](https://github.com/zammad/zammad/labels/UX%2FUI)] [[admin area](https://github.com/zammad/zammad/labels/admin%20area)] [[LDAP](https://github.com/zammad/zammad/labels/LDAP)]
- Multiple LDAP server configurations [3141](https://github.com/zammad/zammad/issues/3141) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[admin area](https://github.com/zammad/zammad/labels/admin%20area)] [[LDAP](https://github.com/zammad/zammad/labels/LDAP)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- implemented time accounting export by activity [3976](https://github.com/zammad/zammad/pull/3976) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[admin area](https://github.com/zammad/zammad/labels/admin%20area)] [[time accounting](https://github.com/zammad/zammad/labels/time%20accounting)]
- no test e-mail [1471](https://github.com/zammad/zammad/issues/1471) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
- Multi tree select object manager attribute [4062](https://github.com/zammad/zammad/issues/4062) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- parsing incoming mails breaks on this line "Reply-To: <>" [3867](https://github.com/zammad/zammad/issues/3867) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)] [[mail processing](https://github.com/zammad/zammad/labels/mail%20processing)]
- Prefix the contact form css with 'zammad' for a better selection [4048](https://github.com/zammad/zammad/issues/4048) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[theming / skinning](https://github.com/zammad/zammad/labels/theming%20%2F%20skinning)]
- Show age (indicator) for public KB Answers [2848](https://github.com/zammad/zammad/issues/2848) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[knowledge base](https://github.com/zammad/zammad/labels/knowledge%20base)]
- Allow reauthentication of Google / Microsoft 365 accounts [3240](https://github.com/zammad/zammad/issues/3240) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[channel](https://github.com/zammad/zammad/labels/channel)]
- Avatar lookup during user creation can cause significant delays [3542](https://github.com/zammad/zammad/issues/3542) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
**Fixed bugs:**
- Zendesk import ignores active flag for user during the import [4135](https://github.com/zammad/zammad/issues/4135) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Zammad shows "<![if !supportLists]>- <![endif]>" instead of list icon [4112](https://github.com/zammad/zammad/issues/4112) [[bug](https://github.com/zammad/zammad/labels/bug)] [[mail processing](https://github.com/zammad/zammad/labels/mail%20processing)]
- Zendesk import for users and organizations works not correctly when custom fields are created with a sanitized name [4128](https://github.com/zammad/zammad/issues/4128) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Popovers will not disappear [4122](https://github.com/zammad/zammad/issues/4122) [[bug](https://github.com/zammad/zammad/labels/bug)] [[regression](https://github.com/zammad/zammad/labels/regression)]
- Pop-Up Warning for missing attachments with text containing "attached" or related only appears on english terms (not translated) [4096](https://github.com/zammad/zammad/issues/4096) [[bug](https://github.com/zammad/zammad/labels/bug)]
- 'Sender Format' set to 'Agent Name' causes empty display names in triggers [3890](https://github.com/zammad/zammad/issues/3890) [[bug](https://github.com/zammad/zammad/labels/bug)] [[trigger](https://github.com/zammad/zammad/labels/trigger)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Changing the category of a Knowledge Base answer leads to alternative translation loop [3931](https://github.com/zammad/zammad/issues/3931) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)] [[knowledge base](https://github.com/zammad/zammad/labels/knowledge%20base)]
- Switching the photos displayed in the preview does not work with the arrow keys [4030](https://github.com/zammad/zammad/issues/4030) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Webhhok transfers "accounted_time" of an article as integer instead of float/double [4127](https://github.com/zammad/zammad/issues/4127) [[bug](https://github.com/zammad/zammad/labels/bug)]
- User popover shows secondary organizations label if user has no organizations [4125](https://github.com/zammad/zammad/issues/4125) [[bug](https://github.com/zammad/zammad/labels/bug)]
- OS detection crashes if OS name cannot be parsed [4124](https://github.com/zammad/zammad/issues/4124) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Upgrade to Zammad 5.2 not possible (undefined method `view' for nil:NilClass) [4126](https://github.com/zammad/zammad/issues/4126) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Notifications are sent for all tickets when an organization of a customer is updated [3952](https://github.com/zammad/zammad/issues/3952) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Single tree select no longer allows displaying child values [4118](https://github.com/zammad/zammad/issues/4118) [[bug](https://github.com/zammad/zammad/labels/bug)] [[blocker](https://github.com/zammad/zammad/labels/blocker)] [[UX/UI](https://github.com/zammad/zammad/labels/UX%2FUI)] [[ticket](https://github.com/zammad/zammad/labels/ticket)]
- users customer name is displayed as id [4040](https://github.com/zammad/zammad/issues/4040) [[bug](https://github.com/zammad/zammad/labels/bug)] [[overviews](https://github.com/zammad/zammad/labels/overviews)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Follow-up detection does not work for the body of plain text mails [3894](https://github.com/zammad/zammad/issues/3894) [[bug](https://github.com/zammad/zammad/labels/bug)] [[mail processing](https://github.com/zammad/zammad/labels/mail%20processing)]
- DataPrivacy Tasks fail if Zammad has to solve references on users with invalid email addresses [3935](https://github.com/zammad/zammad/issues/3935) [[bug](https://github.com/zammad/zammad/labels/bug)] [[data privacy](https://github.com/zammad/zammad/labels/data%20privacy)]
- Parents will always be selected when going through the tree in multiple treeselect field [4080](https://github.com/zammad/zammad/issues/4080) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX%2FUI)] [[frontend / JS app](https://github.com/zammad/zammad/labels/frontend%20%2F%20JS%20app)] [[object manager attribute](https://github.com/zammad/zammad/labels/object%20manager%20attribute)] [[form](https://github.com/zammad/zammad/labels/form)]
- Title column is missing if ticket number is disabled in overview options [3943](https://github.com/zammad/zammad/issues/3943) [[bug](https://github.com/zammad/zammad/labels/bug)]
- script/background-services.rb crashes on startup [4113](https://github.com/zammad/zammad/issues/4113) [[bug](https://github.com/zammad/zammad/labels/bug)] [[blocker](https://github.com/zammad/zammad/labels/blocker)]
- Monitoring endpoint is complaing about not executed scheduler job "CacheClearJob.perform_now" [4079](https://github.com/zammad/zammad/issues/4079) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Searches display all groups and owners on bulk selections [4054](https://github.com/zammad/zammad/issues/4054) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)] [[core workflows](https://github.com/zammad/zammad/labels/core%20workflows)]
- Wrong default values in ticket create when creating from user profile [4088](https://github.com/zammad/zammad/issues/4088) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX%2FUI)] [[ticket](https://github.com/zammad/zammad/labels/ticket)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Reporting export file shows empty fields in selected columns when "custom sort option" is enabled [4103](https://github.com/zammad/zammad/issues/4103) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Wrongly creation of "session started" activity stream log entry for session check [4105](https://github.com/zammad/zammad/issues/4105) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Latest upgrade to 5.1 is not possible [4097](https://github.com/zammad/zammad/issues/4097) [[bug](https://github.com/zammad/zammad/labels/bug)] [[regression](https://github.com/zammad/zammad/labels/regression)]
- Trigger autocomplete when clicking the "Tags" field also without a search term [4095](https://github.com/zammad/zammad/issues/4095) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX%2FUI)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)] [[regression](https://github.com/zammad/zammad/labels/regression)]
- Shared draft attribute in object manage not disabled for onclick [4089](https://github.com/zammad/zammad/issues/4089) [[bug](https://github.com/zammad/zammad/labels/bug)] [[object manager attribute](https://github.com/zammad/zammad/labels/object%20manager%20attribute)]
- Assign user to multiple organizations [1573](https://github.com/zammad/zammad/issues/1573) [[feature backlog](https://github.com/zammad/zammad/labels/feature%20backlog)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Preview of *.ics files in ticket zoom attachments [4091](https://github.com/zammad/zammad/issues/4091) [[feature backlog](https://github.com/zammad/zammad/labels/feature%20backlog)] [[ticket](https://github.com/zammad/zammad/labels/ticket)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)] [[productivity](https://github.com/zammad/zammad/labels/productivity)]
- Tab behaviour does not show properly when doing a ticket update at the same time [4094](https://github.com/zammad/zammad/issues/4094) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Custom sort on select returns "undefined method `merge' " for older objects [4086](https://github.com/zammad/zammad/issues/4086) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)] [[object manager attribute](https://github.com/zammad/zammad/labels/object%20manager%20attribute)]
- Tree Select Trigger changes the conditions on its own [4090](https://github.com/zammad/zammad/issues/4090) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Zammad email display broken [4053](https://github.com/zammad/zammad/issues/4053) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Linked ticket state won't be updated [476](https://github.com/zammad/zammad/issues/476) [[bug](https://github.com/zammad/zammad/labels/bug)] [[frontend / JS app](https://github.com/zammad/zammad/labels/frontend%20%2F%20JS%20app)]
- OTRS migration currently do not support custom dropdown multi-select fields [4057](https://github.com/zammad/zammad/issues/4057) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)]
- OTRS import fails for DynamicFields of Type Dropdown without possible values [2375](https://github.com/zammad/zammad/issues/2375) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)]
- Boolean shows wrong default value if not set to shown by default [4065](https://github.com/zammad/zammad/issues/4065) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)] [[core workflows](https://github.com/zammad/zammad/labels/core%20workflows)]
- Zammad notification emails have a broken 'reason' footer line [4066](https://github.com/zammad/zammad/issues/4066) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Javascript error undefined multiple while opening core workflow with tree selects [4067](https://github.com/zammad/zammad/issues/4067) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Popovers will not disappear anymore [4058](https://github.com/zammad/zammad/issues/4058) [[bug](https://github.com/zammad/zammad/labels/bug)] [[regression](https://github.com/zammad/zammad/labels/regression)]
- Freshdesk import currently doesn't handle the ticket type field [4064](https://github.com/zammad/zammad/issues/4064) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)]
- Core Workflow: Does show wrong field list if you only have admin permissions and not ticket.agent permissions [4035](https://github.com/zammad/zammad/issues/4035) [[bug](https://github.com/zammad/zammad/labels/bug)] [[admin area](https://github.com/zammad/zammad/labels/admin%20area)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Migrations (Zendesk, Kayako) currently do not support custom dropdown multi-select fields [4055](https://github.com/zammad/zammad/issues/4055) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Ticket in the ticket creation screen tags are shown after 2 characters, in the ticket zoom after 1 character [3748](https://github.com/zammad/zammad/issues/3748) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Clicking the button in a mail opens the link as a photo [4044](https://github.com/zammad/zammad/issues/4044) [[bug](https://github.com/zammad/zammad/labels/bug)] [[ticket](https://github.com/zammad/zammad/labels/ticket)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Tree select widget closes unexpectedly [4041](https://github.com/zammad/zammad/issues/4041) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- pre-code tags cause Zammad to swallow content [4039](https://github.com/zammad/zammad/issues/4039) [[bug](https://github.com/zammad/zammad/labels/bug)] [[mail processing](https://github.com/zammad/zammad/labels/mail%20processing)]
- Error on second migration of an microsoft365 channel undefined method `options' for nil:NilClass [4060](https://github.com/zammad/zammad/issues/4060) [[bug](https://github.com/zammad/zammad/labels/bug)] [[channel](https://github.com/zammad/zammad/labels/channel)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Freshdesk migration which doesn't support time entries runs in unwanted behaviour [4052](https://github.com/zammad/zammad/issues/4052) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)]
- Freshdesk-Migration does not like no Companies / Organisations within the source [4051](https://github.com/zammad/zammad/issues/4051) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)]
- Cannot encrypt if multiple S/MIME certificates exist and one is expired [4029](https://github.com/zammad/zammad/issues/4029) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Can't delete smime certificate - ERROR: duplicate key value violates unique constraint [4049](https://github.com/zammad/zammad/issues/4049) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Core Workflow - Unable to select "has changed" logic when creating new workflow. It is also replaced with "is" in previously created workflows. [4046](https://github.com/zammad/zammad/issues/4046) [[bug](https://github.com/zammad/zammad/labels/bug)]
- undefined method `to_hash` on editing select fields in the admin interface after migration to 5.1 [4027](https://github.com/zammad/zammad/issues/4027) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Knowledge Base on-the-fly updating does not work [4026](https://github.com/zammad/zammad/issues/4026) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Default time diff for date and datetime objects don't function [3763](https://github.com/zammad/zammad/issues/3763) [[bug](https://github.com/zammad/zammad/labels/bug)] [[object manager attribute](https://github.com/zammad/zammad/labels/object%20manager%20attribute)]
- Problems with duplicate records in Translations table [4020](https://github.com/zammad/zammad/issues/4020) [[bug](https://github.com/zammad/zammad/labels/bug)] [[translation](https://github.com/zammad/zammad/labels/translation)]
- Tree select value cannot be set to "-" (empty) with Trigger/Scheduler/Core workflow [4024](https://github.com/zammad/zammad/issues/4024) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Admin macros are not usable with admin permissions only [4032](https://github.com/zammad/zammad/issues/4032) [[bug](https://github.com/zammad/zammad/labels/bug)] [[admin area](https://github.com/zammad/zammad/labels/admin%20area)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Admin SLA index view does not show update time if "for agent respond" is selected [4033](https://github.com/zammad/zammad/issues/4033) [[bug](https://github.com/zammad/zammad/labels/bug)] [[admin area](https://github.com/zammad/zammad/labels/admin%20area)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Core Workflow: Stop after match is not saved [4034](https://github.com/zammad/zammad/issues/4034) [[bug](https://github.com/zammad/zammad/labels/bug)] [[admin area](https://github.com/zammad/zammad/labels/admin%20area)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)] [[core workflows](https://github.com/zammad/zammad/labels/core%20workflows)]
- KB Public menus scroll bar positioning incorrect [4008](https://github.com/zammad/zammad/issues/4008) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX%2FUI)] [[knowledge base](https://github.com/zammad/zammad/labels/knowledge%20base)]
- Freshdesk import error when a inline image source attribute contains no clean url [4028](https://github.com/zammad/zammad/issues/4028) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- Mention history does not show properly who got added [3984](https://github.com/zammad/zammad/issues/3984) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX%2FUI)] [[history](https://github.com/zammad/zammad/labels/history)]
- Translation introduction text shows wrong keyboard shortcut [2885](https://github.com/zammad/zammad/issues/2885) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX%2FUI)] [[translation](https://github.com/zammad/zammad/labels/translation)]
- Translation introduction text shows wrong keyboard shortcut [3990](https://github.com/zammad/zammad/pull/3990) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Obsolete notice field in admin calendar view [4023](https://github.com/zammad/zammad/issues/4023) [[bug](https://github.com/zammad/zammad/labels/bug)] [[admin area](https://github.com/zammad/zammad/labels/admin%20area)]
- Error “customer_id required” on Macro execution [4022](https://github.com/zammad/zammad/issues/4022) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX%2FUI)] [[macros](https://github.com/zammad/zammad/labels/macros)]
- Translations are not available in API after syncing [4016](https://github.com/zammad/zammad/issues/4016) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Upgraded to 5.1.0: pending time doesn’t show a date [4018](https://github.com/zammad/zammad/issues/4018) [[bug](https://github.com/zammad/zammad/labels/bug)] [[translation](https://github.com/zammad/zammad/labels/translation)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]
- UI is not updated right after importing users csv file [3919](https://github.com/zammad/zammad/issues/3919) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Pending closed/reminder date selector shows current date black on dark gray [2316](https://github.com/zammad/zammad/issues/2316) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX%2FUI)] [[regression](https://github.com/zammad/zammad/labels/regression)]
- LDAP with many groups (<5k) and group role relation (>50) will crash in frontend [3994](https://github.com/zammad/zammad/issues/3994) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX%2FUI)] [[LDAP](https://github.com/zammad/zammad/labels/LDAP)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised%20by%20payment)]

View File

@@ -103,8 +103,7 @@ gem 'rack-attack'
# channels
gem 'gmail_xoauth'
gem 'koala'
# TODO: remove git information after https://github.com/bennesp/telegramAPI/pull/8 is merged
gem 'telegramAPI', git: 'https://github.com/zammad-deps/telegramAPI', branch: 'uri-escape-warning'
gem 'telegram-bot-ruby'
gem 'twitter'
# channels - email additions
@@ -233,4 +232,6 @@ end
# ZAMMAD DEVS: Consult the internal wiki
# (or else risk pushing unwanted changes to Gemfile.lock!)
# https://git.zammad.com/zammad/zammad/wikis/Tips#user-content-customizing-the-gemfile
eval_gemfile 'Gemfile.local' if File.exist?('Gemfile.local')
Dir['Gemfile.local*'].each do |file|
eval_gemfile file
end

View File

@@ -16,14 +16,6 @@ GIT
mail (2.7.2.edge)
mini_mime (>= 0.1.1)
GIT
remote: https://github.com/zammad-deps/telegramAPI
revision: fb62dd5b48b0c41169ab69fc89069969ab4705b2
branch: uri-escape-warning
specs:
telegramAPI (1.4)
rest-client (~> 2.0, >= 2.0.2)
GEM
remote: https://rubygems.org/
specs:
@@ -31,52 +23,52 @@ GEM
simple_po_parser (~> 1.1.6)
aasm (5.2.0)
concurrent-ruby (~> 1.0)
actioncable (6.1.6)
actionpack (= 6.1.6)
activesupport (= 6.1.6)
actioncable (6.1.7)
actionpack (= 6.1.7)
activesupport (= 6.1.7)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (6.1.6)
actionpack (= 6.1.6)
activejob (= 6.1.6)
activerecord (= 6.1.6)
activestorage (= 6.1.6)
activesupport (= 6.1.6)
actionmailbox (6.1.7)
actionpack (= 6.1.7)
activejob (= 6.1.7)
activerecord (= 6.1.7)
activestorage (= 6.1.7)
activesupport (= 6.1.7)
mail (>= 2.7.1)
actionmailer (6.1.6)
actionpack (= 6.1.6)
actionview (= 6.1.6)
activejob (= 6.1.6)
activesupport (= 6.1.6)
actionmailer (6.1.7)
actionpack (= 6.1.7)
actionview (= 6.1.7)
activejob (= 6.1.7)
activesupport (= 6.1.7)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (6.1.6)
actionview (= 6.1.6)
activesupport (= 6.1.6)
actionpack (6.1.7)
actionview (= 6.1.7)
activesupport (= 6.1.7)
rack (~> 2.0, >= 2.0.9)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.1.6)
actionpack (= 6.1.6)
activerecord (= 6.1.6)
activestorage (= 6.1.6)
activesupport (= 6.1.6)
actiontext (6.1.7)
actionpack (= 6.1.7)
activerecord (= 6.1.7)
activestorage (= 6.1.7)
activesupport (= 6.1.7)
nokogiri (>= 1.8.5)
actionview (6.1.6)
activesupport (= 6.1.6)
actionview (6.1.7)
activesupport (= 6.1.7)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
activejob (6.1.6)
activesupport (= 6.1.6)
activejob (6.1.7)
activesupport (= 6.1.7)
globalid (>= 0.3.6)
activemodel (6.1.6)
activesupport (= 6.1.6)
activerecord (6.1.6)
activemodel (= 6.1.6)
activesupport (= 6.1.6)
activemodel (6.1.7)
activesupport (= 6.1.7)
activerecord (6.1.7)
activemodel (= 6.1.7)
activesupport (= 6.1.7)
activerecord-import (1.4.0)
activerecord (>= 4.2)
activerecord-nulldb-adapter (0.8.0)
@@ -87,14 +79,14 @@ GEM
multi_json (~> 1.11, >= 1.11.2)
rack (>= 2.0.8, < 3)
railties (>= 5.2.4.1)
activestorage (6.1.6)
actionpack (= 6.1.6)
activejob (= 6.1.6)
activerecord (= 6.1.6)
activesupport (= 6.1.6)
activestorage (6.1.7)
actionpack (= 6.1.7)
activejob (= 6.1.7)
activerecord (= 6.1.7)
activesupport (= 6.1.7)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (6.1.6)
activesupport (6.1.7)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@@ -110,6 +102,10 @@ GEM
ast (2.4.2)
autoprefixer-rails (10.4.7.0)
execjs (~> 2)
axiom-types (0.1.1)
descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1)
binding_of_caller (1.0.0)
debug_inspector (>= 0.0.1)
biz (1.8.2)
@@ -137,6 +133,8 @@ GEM
clearbit (0.3.3)
nestful (~> 1.1.0)
coderay (1.1.3)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
coffee-rails (5.0.0)
coffee-script (>= 2.2.0)
railties (>= 5.2.0)
@@ -161,6 +159,8 @@ GEM
delayed_job (>= 3.0, < 5)
deprecation_toolkit (2.0.0)
activesupport (>= 5.2)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
diff-lcs (1.5.0)
diffy (3.4.1)
domain_name (0.5.20190701)
@@ -169,6 +169,7 @@ GEM
railties (>= 5)
dotenv (2.7.6)
dry-cli (0.7.0)
dry-inflector (0.3.0)
eco (1.0.0)
coffee-script
eco-source
@@ -180,7 +181,7 @@ GEM
email_address (0.2.3)
simpleidn
equalizer (0.0.11)
erubi (1.10.0)
erubi (1.11.0)
eventmachine (1.2.7)
execjs (2.8.1)
factory_bot (6.2.1)
@@ -237,7 +238,6 @@ GEM
http-cookie (~> 1.0)
http-form_data (~> 2.2)
http-parser (~> 1.2.0)
http-accept (1.7.0)
http-cookie (1.0.5)
domain_name (~> 0.5)
http-form_data (2.3.0)
@@ -245,7 +245,7 @@ GEM
ffi-compiler (>= 1.0, < 2.0)
http_parser.rb (0.6.0)
httpclient (2.8.3)
i18n (1.10.0)
i18n (1.12.0)
concurrent-ruby (~> 1.0)
icalendar (2.7.1)
ice_cube (~> 0.16)
@@ -253,6 +253,7 @@ GEM
icalendar (~> 2.0)
ice_cube (~> 0.16)
ice_cube (0.16.4)
ice_nine (0.11.2)
inflection (1.0.0)
iniparse (1.5.0)
interception (0.5)
@@ -271,7 +272,7 @@ GEM
logging (2.3.1)
little-plugger (~> 1.1)
multi_json (~> 1.14)
loofah (2.18.0)
loofah (2.19.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
marcel (1.0.2)
@@ -286,7 +287,7 @@ GEM
mime-types-data (3.2022.0105)
mini_mime (1.1.2)
mini_portile2 (2.8.0)
minitest (5.16.0)
minitest (5.16.3)
msgpack (1.5.2)
multi_json (1.15.0)
multi_xml (0.6.0)
@@ -295,9 +296,8 @@ GEM
naught (1.1.0)
nestful (1.1.4)
net-ldap (0.17.1)
netrc (0.11.0)
nio4r (2.5.8)
nokogiri (1.13.6)
nokogiri (1.13.8)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
nori (2.6.0)
@@ -308,7 +308,7 @@ GEM
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
omniauth (1.9.1)
omniauth (1.9.2)
hashie (>= 3.4.6)
rack (>= 1.6.2, < 3)
omniauth-facebook (9.0.0)
@@ -380,27 +380,27 @@ GEM
pundit-matchers (1.7.0)
rspec-rails (>= 3.0.0)
racc (1.6.0)
rack (2.2.3.1)
rack (2.2.4)
rack-attack (6.6.1)
rack (>= 1.0, < 3)
rack-proxy (0.7.2)
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails (6.1.6)
actioncable (= 6.1.6)
actionmailbox (= 6.1.6)
actionmailer (= 6.1.6)
actionpack (= 6.1.6)
actiontext (= 6.1.6)
actionview (= 6.1.6)
activejob (= 6.1.6)
activemodel (= 6.1.6)
activerecord (= 6.1.6)
activestorage (= 6.1.6)
activesupport (= 6.1.6)
rack-test (2.0.2)
rack (>= 1.3)
rails (6.1.7)
actioncable (= 6.1.7)
actionmailbox (= 6.1.7)
actionmailer (= 6.1.7)
actionpack (= 6.1.7)
actiontext (= 6.1.7)
actionview (= 6.1.7)
activejob (= 6.1.7)
activemodel (= 6.1.7)
activerecord (= 6.1.7)
activestorage (= 6.1.7)
activesupport (= 6.1.7)
bundler (>= 1.15.0)
railties (= 6.1.6)
railties (= 6.1.7)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
@@ -411,9 +411,9 @@ GEM
nokogiri (>= 1.6)
rails-html-sanitizer (1.4.3)
loofah (~> 2.3)
railties (6.1.6)
actionpack (= 6.1.6)
activesupport (= 6.1.6)
railties (6.1.7)
actionpack (= 6.1.7)
activesupport (= 6.1.7)
method_source
rake (>= 12.2)
thor (~> 1.0)
@@ -426,11 +426,6 @@ GEM
rchardet (1.8.0)
redis (4.6.0)
regexp_parser (2.5.0)
rest-client (2.1.0)
http-accept (>= 1.7.0, < 2.0)
http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 4.0)
netrc (~> 0.8)
rexml (3.2.5)
rspec-core (3.11.0)
rspec-support (~> 3.11.0)
@@ -496,7 +491,7 @@ GEM
sprockets (> 3.0)
sprockets-rails
tilt
selenium-webdriver (4.2.1)
selenium-webdriver (4.3.0)
childprocess (>= 0.5, < 5.0)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
@@ -524,6 +519,10 @@ GEM
activesupport (>= 5.2)
sprockets (>= 3.0.0)
tcr (0.2.2)
telegram-bot-ruby (0.19.2)
dry-inflector
faraday (~> 1.0)
virtus (~> 2.0)
telephone_number (1.4.16)
test-unit (3.5.3)
power_assert
@@ -545,7 +544,7 @@ GEM
multipart-post (~> 2.0)
naught (~> 1.0)
simple_oauth (~> 0.3.0)
tzinfo (2.0.4)
tzinfo (2.0.5)
concurrent-ruby (~> 1.0)
uglifier (4.2.0)
execjs (>= 0.3.0, < 3)
@@ -562,6 +561,10 @@ GEM
logging
nokogiri
rubyntlm
virtus (2.0.0)
axiom-types (~> 0.1)
coercible (~> 1.0)
descendants_tracker (~> 0.0, >= 0.0.3)
vite_rails (3.0.10)
railties (>= 5.1, < 8)
vite_ruby (~> 3.0)
@@ -685,7 +688,7 @@ DEPENDENCIES
spring
sprockets (~> 3.7.2)
tcr
telegramAPI!
telegram-bot-ruby
telephone_number
test-unit
thor

View File

@@ -1,9 +1,4 @@
-----------------------------------------------------------------------------
ba-linkify.js
Source: http://benalman.com/code/test/js-linkify/
Copyright: 2009 "Cowboy" Ben Alman
License: MIT license
-----------------------------------------------------------------------------
bootstrap
Source: https://github.com/twbs/bootstrap
Copyright: 2011-2015 Twitter, Inc.
@@ -107,6 +102,11 @@ Source: https://github.com/customd/jquery-visible
Copyright: 2012, Digital Fusion
License: MIT license
-----------------------------------------------------------------------------
linkify.js
Source: https://github.com/Hypercontext/linkifyjs
Copyright: Copyright (c) 2021 SoapBox Innovations Inc.
License: MIT license
-----------------------------------------------------------------------------
marked.js
Source: https://github.com/chjj/marked
Copyright: 2011-2014, Christopher Jeffrey

View File

@@ -784,7 +784,8 @@ class App.ControllerForm extends App.Controller
# set autofocus by delay to make validation testable
App.Delay.set(
->
lookupForm.find('.has-error').find('input, textarea, select').first().trigger('focus')
# make sure to work for all field types (e.g. column_select which is an hidden select)
lookupForm.find('.has-error').get(0)?.scrollIntoView(true)
200
'validate'
)

View File

@@ -36,6 +36,7 @@ class ChannelAccountOverview extends App.ControllerSubContent
requiredPermission: 'admin.channel_microsoft365'
events:
'click .js-new': 'new'
'click .js-admin-consent': 'adminConsent'
'click .js-delete': 'delete'
'click .js-reauthenticate': 'reauthenticate'
'click .js-configApp': 'configApp'
@@ -126,6 +127,10 @@ class ChannelAccountOverview extends App.ControllerSubContent
@editInbound(undefined, @channel_id, true)
@channel_id = undefined
if @error_code is 'AADSTS65004'
@error_code = undefined
new AdminConsentInfo(container: @container)
show: (params) =>
for key, value of params
if key isnt 'el' && key isnt 'shown' && key isnt 'match'
@@ -141,6 +146,9 @@ class ChannelAccountOverview extends App.ControllerSubContent
new: (e) ->
window.location.href = "#{@apiPath}/external_credentials/microsoft365/link_account"
adminConsent: (e) ->
window.location.href = "#{@apiPath}/external_credentials/microsoft365/link_account?prompt=consent"
delete: (e) =>
e.preventDefault()
id = $(e.target).closest('.action').data('id')
@@ -428,4 +436,16 @@ class AppConfig extends App.ControllerModal
@el.find('.alert').removeClass('hidden').text(data.error || __('App could not be verified.'))
)
class AdminConsentInfo extends App.ControllerModal
buttonClose: true
small: true
buttonSubmit: __('Close')
head: __('Admin Consent')
content: ->
App.view('microsoft365/admin_consent')()
onSubmit: =>
@close()
App.Config.set('microsoft365', { prio: 5000, name: __('Microsoft 365'), parent: '#channels', target: '#channels/microsoft365', controller: App.ChannelMicrosoft365, permission: ['admin.channel_microsoft365'] }, 'NavBarAdmin')

View File

@@ -0,0 +1,598 @@
# coffeelint: disable=camel_case_classes
###
UI Element options:
**attribute.notification**
- Allows to send notifications (default: false)
**attribute.ticket_delete**
- Allows to delete the ticket (default: false)
**attribute.user_action**
- Allows pre conditions like current_user.id or user session specific values (default: true)
###
class App.UiElement.ApplicationAction
@defaults: (attribute) ->
defaults = ['ticket.state_id']
groups =
ticket:
name: __('Ticket')
model: 'Ticket'
article:
name: __('Article')
model: 'Article'
if attribute.notification
groups.notification =
name: __('Notification')
model: 'Notification'
# merge config
elements = {}
for groupKey, groupMeta of groups
if !groupMeta.model || !App[groupMeta.model]
if groupKey is 'notification'
elements["#{groupKey}.email"] = { name: 'email', display: __('Email') }
elements["#{groupKey}.sms"] = { name: 'sms', display: __('SMS') }
elements["#{groupKey}.webhook"] = { name: 'webhook', display: __('Webhook') }
else if groupKey is 'article'
elements["#{groupKey}.note"] = { name: 'note', display: __('Note') }
else
for row in App[groupMeta.model].configure_attributes
# ignore passwords and relations
if row.type isnt 'password' && row.name.substr(row.name.length-4,4) isnt '_ids'
# ignore readonly attributes
if !row.readonly
config = _.clone(row)
switch config.tag
when 'datetime'
config.operator = ['static', 'relative']
when 'tag'
config.operator = ['add', 'remove']
elements["#{groupKey}.#{config.name}"] = config
# add ticket deletion action
if attribute.ticket_delete
elements['ticket.action'] =
name: 'action'
display: __('Action')
tag: 'select'
null: false
translate: true
options:
delete: 'Delete'
[defaults, groups, elements]
@placeholder: (elementFull, attribute, params, groups, elements) ->
item = $( App.view('generic/ticket_perform_action/row')( attribute: attribute ) )
selector = @buildAttributeSelector(elementFull, groups, elements)
item.find('.js-attributeSelector').prepend(selector)
item
@render: (attribute, params = {}) ->
[defaults, groups, elements] = @defaults(attribute)
# return item
item = $( App.view('generic/ticket_perform_action/index')( attribute: attribute ) )
# add filter
item.on('click', '.js-rowActions .js-add', (e) =>
element = $(e.target).closest('.js-filterElement')
placeholder = @placeholder(item, attribute, params, groups, elements)
if element.get(0)
element.after(placeholder)
else
item.append(placeholder)
placeholder.find('.js-attributeSelector select').trigger('change')
@updateAttributeSelectors(item)
)
# remove filter
item.on('click', '.js-rowActions .js-remove', (e) =>
return if $(e.currentTarget).hasClass('is-disabled')
$(e.target).closest('.js-filterElement').remove()
@updateAttributeSelectors(item)
)
# change attribute selector
item.on('change', '.js-attributeSelector select', (e) =>
elementRow = $(e.target).closest('.js-filterElement')
groupAndAttribute = elementRow.find('.js-attributeSelector option:selected').attr('value')
@rebuildAttributeSelectors(item, elementRow, groupAndAttribute, elements, {}, attribute)
@updateAttributeSelectors(item)
)
# change operator selector
item.on('change', '.js-operator select', (e) =>
elementRow = $(e.target).closest('.js-filterElement')
groupAndAttribute = elementRow.find('.js-attributeSelector option:selected').attr('value')
@buildOperator(item, elementRow, groupAndAttribute, elements, {}, attribute)
)
# build initial params
if _.isEmpty(params[attribute.name])
for groupAndAttribute in defaults
# build and append
element = @placeholder(item, attribute, params, groups, elements)
item.append(element)
@rebuildAttributeSelectors(item, element, groupAndAttribute, elements, {}, attribute)
else
for groupAndAttribute, meta of params[attribute.name]
# build and append
element = @placeholder(item, attribute, params, groups, elements)
@rebuildAttributeSelectors(item, element, groupAndAttribute, elements, meta, attribute)
item.append(element)
@disableRemoveForOneAttribute(item)
item
@elementKeyGroup: (elementKey) ->
elementKey.split(/\./)[0]
@buildAttributeSelector: (elementFull, groups, elements) ->
# find first possible attribute
selectedValue = ''
elementFull.find('.js-attributeSelector select option').each(->
if !selectedValue && !$(@).prop('disabled')
selectedValue = $(@).val()
)
selection = $('<select class="form-control"></select>')
for groupKey, groupMeta of groups
displayName = App.i18n.translateInline(groupMeta.name)
selection.closest('select').append("<optgroup label=\"#{displayName}\" class=\"js-#{groupKey}\"></optgroup>")
optgroup = selection.find("optgroup.js-#{groupKey}")
for elementKey, elementGroup of elements
elementGroup = @elementKeyGroup(elementKey)
if elementGroup is groupKey
attributeConfig = elements[elementKey]
displayName = App.i18n.translateInline(attributeConfig.display)
selected = ''
if elementKey is selectedValue
selected = 'selected="selected"'
optgroup.append("<option value=\"#{elementKey}\" #{selected}>#{displayName}</option>")
selection
# disable - if we only have one attribute
@disableRemoveForOneAttribute: (elementFull) ->
if elementFull.find('.js-attributeSelector select').length > 1
elementFull.find('.js-remove').removeClass('is-disabled')
else
elementFull.find('.js-remove').addClass('is-disabled')
@updateAttributeSelectors: (elementFull) ->
# enable all
elementFull.find('.js-attributeSelector select option').prop('disabled', false)
# disable all used attributes
elementFull.find('.js-attributeSelector select').each(->
keyLocal = $(@).val()
elementFull.find('.js-attributeSelector select option[value="' + keyLocal + '"]').attr('disabled', true)
)
# disable - if we only have one attribute
@disableRemoveForOneAttribute(elementFull)
@rebuildAttributeSelectors: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
# set attribute
if groupAndAttribute
elementRow.find('.js-attributeSelector select').val(groupAndAttribute)
notificationTypeMatch = groupAndAttribute.match(/^notification.([\w]+)$/)
articleTypeMatch = groupAndAttribute.match(/^article.([\w]+)$/)
if _.isArray(notificationTypeMatch) && notificationType = notificationTypeMatch[1]
elementRow.find('.js-setAttribute').html('').addClass('hide')
elementRow.find('.js-setArticle').html('').addClass('hide')
@buildNotificationArea(notificationType, elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
else if _.isArray(articleTypeMatch) && articleType = articleTypeMatch[1]
elementRow.find('.js-setAttribute').html('').addClass('hide')
elementRow.find('.js-setNotification').html('').addClass('hide')
@buildArticleArea(articleType, elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
else
elementRow.find('.js-setNotification').html('').addClass('hide')
elementRow.find('.js-setArticle').html('').addClass('hide')
if !elementRow.find('.js-setAttribute div').get(0)
attributeSelectorElement = $( App.view('generic/ticket_perform_action/attribute_selector')(
attribute: attribute
name: name
meta: meta || {}
))
elementRow.find('.js-setAttribute').html(attributeSelectorElement).removeClass('hide')
@buildOperator(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
@buildOperator: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
currentOperator = elementRow.find('.js-operator option:selected').attr('value')
if !meta.operator
meta.operator = currentOperator
name = "#{attribute.name}::#{groupAndAttribute}::operator"
selection = $("<select class=\"form-control\" name=\"#{name}\"></select>")
attributeConfig = elements[groupAndAttribute]
if !attributeConfig || !attributeConfig.operator
elementRow.find('.js-operator').parent().addClass('hide')
else
elementRow.find('.js-operator').parent().removeClass('hide')
if attributeConfig && attributeConfig.operator
for operator in attributeConfig.operator
operatorName = App.i18n.translateInline(operator)
selected = ''
if meta.operator is operator
selected = 'selected="selected"'
selection.append("<option value=\"#{operator}\" #{selected}>#{operatorName}</option>")
selection
elementRow.find('.js-operator select').replaceWith(selection)
@buildPreCondition(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
@buildPreCondition: (elementFull, elementRow, groupAndAttribute, elements, meta, attributeConfig) ->
currentOperator = elementRow.find('.js-operator option:selected').attr('value')
currentPreCondition = elementRow.find('.js-preCondition option:selected').attr('value')
if !meta.pre_condition
meta.pre_condition = currentPreCondition
toggleValue = =>
preCondition = elementRow.find('.js-preCondition option:selected').attr('value')
if preCondition isnt 'specific'
elementRow.find('.js-value select').html('')
elementRow.find('.js-value').addClass('hide')
else
elementRow.find('.js-value').removeClass('hide')
@buildValue(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
# force to use auto complition on user lookup
attribute = clone(attributeConfig, true)
name = "#{attribute.name}::#{groupAndAttribute}::value"
attributeSelected = elements[groupAndAttribute]
preCondition = false
if attributeSelected.relation is 'User'
preCondition = 'user'
attribute.tag = 'user_autocompletion'
if attributeSelected.relation is 'Organization'
preCondition = 'org'
attribute.tag = 'autocompletion_ajax'
if !preCondition || attribute.user_action is false
elementRow.find('.js-preCondition select').html('')
elementRow.find('.js-preCondition').closest('.controls').addClass('hide')
toggleValue()
@buildValue(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
return
elementRow.find('.js-preCondition').closest('.controls').removeClass('hide')
name = "#{attribute.name}::#{groupAndAttribute}::pre_condition"
selection = $("<select class=\"form-control\" name=\"#{name}\" ></select>")
options = {}
if preCondition is 'user'
options =
'current_user.id': App.i18n.translateInline('current user')
'specific': App.i18n.translateInline('specific user')
if attributeSelected.null is true
options['not_set'] = App.i18n.translateInline('unassign user')
else if preCondition is 'org'
options =
'current_user.organization_id': App.i18n.translateInline('current user organization')
'specific': App.i18n.translateInline('specific organization')
for key, value of options
selected = ''
if key is meta.pre_condition
selected = 'selected="selected"'
selection.append("<option value=\"#{key}\" #{selected}>#{App.i18n.translateInline(value)}</option>")
elementRow.find('.js-preCondition').closest('.controls').removeClass('hide')
elementRow.find('.js-preCondition select').replaceWith(selection)
elementRow.find('.js-preCondition select').on('change', (e) ->
toggleValue()
)
@buildValue(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
toggleValue()
@buildValue: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
name = "#{attribute.name}::#{groupAndAttribute}::value"
# build new item
attributeConfig = elements[groupAndAttribute]
config = clone(attributeConfig, true)
if config.relation is 'User'
config.tag = 'user_autocompletion'
config.disableCreateObject = true
if config.relation is 'Organization'
config.tag = 'autocompletion_ajax'
# render ui element
item = ''
if config && App.UiElement[config.tag]
config['name'] = name
if attribute.value && attribute.value[groupAndAttribute]
config['value'] = _.clone(attribute.value[groupAndAttribute]['value'])
config.multiple = false
config.default = undefined
config.nulloption = config.null
if config.tag is 'multiselect' || config.tag is 'multi_tree_select'
config.multiple = true
if config.tag is 'checkbox'
config.tag = 'select'
if config.tag is 'datetime'
config.validationContainer = 'self'
item = App.UiElement[config.tag].render(config, {})
relative_operators = [
__('before (relative)'),
__('within next (relative)'),
__('within last (relative)'),
__('after (relative)'),
__('till (relative)'),
__('from (relative)'),
__('relative'),
]
upcoming_operator = meta.operator
if !_.include(config.operator, upcoming_operator)
if Array.isArray(config.operator)
upcoming_operator = config.operator[0]
else
upcoming_operator = null
if _.include(relative_operators, upcoming_operator)
config['name'] = "#{attribute.name}::#{groupAndAttribute}"
if attribute.value && attribute.value[groupAndAttribute]
config['value'] = _.clone(attribute.value[groupAndAttribute])
item = App.UiElement['time_range'].render(config, {})
elementRow.find('.js-setAttribute > .flex > .js-value').removeClass('hide').html(item)
@buildNotificationArea: (notificationType, elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
return if elementRow.find(".js-setNotification .js-body-#{notificationType}").get(0)
elementRow.find('.js-setNotification').empty()
options =
'article_last_sender': __('Sender of last article')
'ticket_owner': __('Owner')
'ticket_customer': __('Customer')
'ticket_agents': __('All agents')
name = "#{attribute.name}::notification.#{notificationType}"
messageLength = switch notificationType
when 'sms' then 160
else 200000
# meta.recipient was a string in the past (single-select) so we convert it to array if needed
if !_.isArray(meta.recipient)
meta.recipient = [meta.recipient]
columnSelectOptions = []
for key, value of options
selected = undefined
for recipient in meta.recipient
if key is recipient
selected = true
columnSelectOptions.push({ value: key, name: App.i18n.translatePlain(value), selected: selected })
columnSelectRecipientUserOptions = []
for user in App.User.all()
key = "userid_#{user.id}"
selected = undefined
for recipient in meta.recipient
if key is recipient
selected = true
columnSelectRecipientUserOptions.push({ value: key, name: "#{user.firstname} #{user.lastname}", selected: selected })
columnSelectRecipient = new App.ColumnSelect
attribute:
name: "#{name}::recipient"
options: [
{
label: __('Variables'),
group: columnSelectOptions
},
{
label: __('User'),
group: columnSelectRecipientUserOptions
},
]
selectionRecipient = columnSelectRecipient.element()
if notificationType is 'webhook'
notificationElement = $( App.view('generic/ticket_perform_action/webhook')(
attribute: attribute
name: name
notificationType: notificationType
meta: meta || {}
))
notificationElement.find('.js-recipient select').replaceWith(selectionRecipient)
if App.Webhook.search(filter: { active: true }).length isnt 0 || !_.isEmpty(meta.webhook_id)
webhookSelection = App.UiElement.select.render(
name: "#{name}::webhook_id"
multiple: false
null: false
relation: 'Webhook'
value: meta.webhook_id
translate: false
nulloption: true
)
else
webhookSelection = App.view('generic/ticket_perform_action/webhook_not_available')( attribute: attribute )
notificationElement.find('.js-webhooks').html(webhookSelection)
else
notificationElement = $( App.view('generic/ticket_perform_action/notification')(
attribute: attribute
name: name
notificationType: notificationType
meta: meta || {}
))
notificationElement.find('.js-recipient select').replaceWith(selectionRecipient)
visibilitySelection = App.UiElement.select.render(
name: "#{name}::internal"
multiple: false
null: false
options: { true: __('internal'), false: __('public') }
value: meta.internal || 'false'
translate: true
)
includeAttachmentsCheckbox = App.UiElement.select.render(
name: "#{name}::include_attachments"
multiple: false
null: false
options: { true: __('Yes'), false: __('No') }
value: meta.include_attachments || 'false'
translate: true
)
notificationElement.find('.js-internal').html(visibilitySelection)
notificationElement.find('.js-include_attachments').html(includeAttachmentsCheckbox)
notificationElement.find('.js-body div[contenteditable="true"]').ce(
mode: 'richtext'
placeholder: __('message')
maxlength: messageLength
)
new App.WidgetPlaceholder(
el: notificationElement.find('.js-body div[contenteditable="true"]').parent()
objects: [
{
prefix: 'ticket'
object: 'Ticket'
display: __('Ticket')
},
{
prefix: 'article'
object: 'TicketArticle'
display: __('Article')
},
{
prefix: 'user'
object: 'User'
display: __('Current User')
},
]
)
elementRow.find('.js-setNotification').html(notificationElement).removeClass('hide')
if App.Config.get('smime_integration') == true
selection = App.UiElement.select.render(
name: "#{name}::sign"
multiple: false
options: {
'no': __('Do not sign email')
'discard': __('Sign email (if not possible, discard notification)')
'always': __('Sign email (if not possible, send notification anyway)')
}
value: meta.sign
translate: true
)
elementRow.find('.js-sign').html(selection)
selection = App.UiElement.select.render(
name: "#{name}::encryption"
multiple: false
options: {
'no': __('Do not encrypt email')
'discard': __('Encrypt email (if not possible, discard notification)')
'always': __('Encrypt email (if not possible, send notification anyway)')
}
value: meta.encryption
translate: true
)
elementRow.find('.js-encryption').html(selection)
@buildArticleArea: (articleType, elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
return if elementRow.find(".js-setArticle .js-body-#{articleType}").get(0)
elementRow.find('.js-setArticle').empty()
name = "#{attribute.name}::article.#{articleType}"
selection = App.UiElement.select.render(
name: "#{name}::internal"
multiple: false
null: false
label: __('Visibility')
options: { true: 'internal', false: 'public' }
value: meta.internal
translate: true
)
articleElement = $( App.view('generic/ticket_perform_action/article')(
attribute: attribute
name: name
articleType: articleType
meta: meta || {}
))
articleElement.find('.js-internal').html(selection)
articleElement.find('.js-body div[contenteditable="true"]').ce(
mode: 'richtext'
placeholder: __('message')
maxlength: 200000
)
new App.WidgetPlaceholder(
el: articleElement.find('.js-body div[contenteditable="true"]').parent()
objects: [
{
prefix: 'ticket'
object: 'Ticket'
display: __('Ticket')
},
{
prefix: 'article'
object: 'TicketArticle'
display: __('Article')
},
{
prefix: 'user'
object: 'User'
display: __('Current User')
},
]
)
elementRow.find('.js-setArticle').html(articleElement).removeClass('hide')

View File

@@ -106,6 +106,8 @@ class App.UiElement.ApplicationSelector
config.expanding = false
if config.type is 'email' || config.type is 'tel'
config.type = 'text'
if config.tag is 'select'
config.multiple = true
for operatorRegEx, operator of operators_type
myRegExp = new RegExp(operatorRegEx, 'i')
if config.tag && config.tag.match(myRegExp)
@@ -505,11 +507,7 @@ class App.UiElement.ApplicationSelector
config.guess = false
if config.tag is 'checkbox'
config.tag = 'select'
tagSearch = "#{config.tag}_search"
if App.UiElement[tagSearch]
item = App.UiElement[tagSearch].render(config, {})
else
item = App.UiElement[config.tag].render(config, {})
item = @renderConfig(config, meta)
if meta.operator is 'before (relative)' || meta.operator is 'within next (relative)' || meta.operator is 'within last (relative)' || meta.operator is 'after (relative)' || meta.operator is 'from (relative)' || meta.operator is 'till (relative)'
config['name'] = "#{attribute.name}::#{groupAndAttribute}"
if attribute.value && attribute.value[groupAndAttribute]
@@ -523,6 +521,11 @@ class App.UiElement.ApplicationSelector
else
elementRow.find('.js-value').removeClass('hide')
@renderConfig: (config, meta) ->
tagSearch = "#{config.tag}_search"
return App.UiElement[tagSearch].render(config, {}) if App.UiElement[tagSearch]
return App.UiElement[config.tag].render(config, {})
@buildValueConfigMultiple: (config, meta) ->
config.multiple = true
config.nulloption = false

View File

@@ -161,6 +161,8 @@ class App.UiElement.core_workflow_condition extends App.UiElement.ApplicationSel
config.expanding = false
if config.type is 'email' || config.type is 'tel'
config.type = 'text'
if config.tag.match(/^(tree_)?select$/)
config.multiple = true
for operatorRegEx, operator of operatorsType
myRegExp = new RegExp(operatorRegEx, 'i')
if config.tag && config.tag.match(myRegExp)

View File

@@ -75,6 +75,8 @@ class App.UiElement.core_workflow_perform extends App.UiElement.ApplicationSelec
config.default = undefined
if config.tag is 'boolean'
config.tag = 'select'
if config.tag.match(/^(tree_)?select$/)
config.multiple = true
if config.type is 'email' || config.type is 'tel'
config.type = 'text'
for operatorRegEx, operator of operatorsType
@@ -129,12 +131,22 @@ class App.UiElement.core_workflow_perform extends App.UiElement.ApplicationSelec
@buildValueConfigMultiple: (config, meta) ->
if _.contains(['add_option', 'remove_option', 'set_fixed_to', 'select'], meta.operator)
config.multiple = true
if config.data_type.match(/^(tree_)?select$/) && meta.operator is 'select'
config.multiple = false
config.nulloption = true
else
config.multiple = false
config.nulloption = false
return config
@renderConfig: (config, meta) ->
if _.contains(['add_option', 'remove_option', 'set_fixed_to'], meta.operator)
tagSearch = "#{config.tag}_search"
return App.UiElement[tagSearch].render(config, {}) if App.UiElement[tagSearch]
return App.UiElement[config.tag].render(config, {})
@mapOperatorDisplayName: (operator) ->
names =
'show': __('show')

View File

@@ -25,11 +25,29 @@ class App.UiElement.permission extends App.UiElement.ApplicationUiElement
groupAccesses: App.Group.accesses()
) )
item.find('div.js-groupList input[type="checkbox"]').on('change', (e) ->
checked = item.find('div.js-groupList input[type="checkbox"]:checked').length > 0 ? true : false
parentCheckbox = item.find('div.js-groupList input[type="checkbox"]').parents('.js-subPermissionList').find('input[type="checkbox"][data-permission-name]')
parentCheckbox.prop('checked', checked)
)
item.find('div.js-groupList input[type="checkbox"]').parents('.js-subPermissionList').find('input[type="checkbox"][data-permission-name]').on('change', (e) ->
return if $(e.currentTarget).prop('checked')
item.find('div.js-groupList input[type="checkbox"]:checked').prop('checked', false)
)
# show/hide trees
item.find('[name=permission_ids]').on('change', (e) =>
@checkUncheck($(e.currentTarget), permissions, item)
)
item.find('[name=permission_ids]').trigger('change')
# check/uncheck group permissions (particular permissions vs. full)
item.on('click', '.checkbox-replacement', (event) ->
App.PermissionHelper.switchGroupPermission(event)
)
item
@checkUncheck: (element, permissions, item) ->
@@ -54,4 +72,3 @@ class App.UiElement.permission extends App.UiElement.ApplicationUiElement
lookupPermission = App.Permission.findByAttribute('name', localPermission)
if lookupPermission
item.find("[name=permission_ids][value=#{lookupPermission.id}]").prop('checked', false)

View File

@@ -1,78 +1,20 @@
# coffeelint: disable=camel_case_classes
class App.UiElement.postmaster_set
@defaults: ->
class App.UiElement.postmaster_set extends App.UiElement.ApplicationAction
@defaults: (attribute) ->
defaults = ['x-zammad-ticket-state_id']
groups =
ticket:
name: 'Ticket'
name: __('Ticket')
model: 'Ticket'
options: [
{
value: 'priority_id'
name: __('Priority')
relation: 'TicketPriority'
}
{
value: 'state_id'
name: __('State')
relation: 'TicketState'
}
{
value: 'tags'
name: __('Tag')
tag: 'tag'
}
{
value: 'customer_id'
name: __('Customer')
relation: 'User'
tag: 'user_autocompletion'
disableCreateObject: true
}
{
value: 'group_id'
name: __('Group')
relation: 'Group'
}
{
value: 'owner_id'
name: __('Owner')
relation: 'User'
tag: 'user_autocompletion'
disableCreateObject: true
}
]
article:
name: 'Article'
options: [
{
value: 'x-zammad-article-internal'
name: __('Internal')
options: { true: 'yes', false: 'no'}
}
{
value: 'x-zammad-article-type_id'
name: __('Type')
relation: 'TicketArticleType'
}
{
value: 'x-zammad-article-sender_id'
name: __('Sender')
relation: 'TicketArticleSender'
}
]
expert:
name: 'Expert'
options: [
{
value: 'x-zammad-ignore'
name: __('Ignore Message')
options: { true: 'yes', false: 'no'}
}
]
elements = {}
for groupKey, groupMeta of groups
if groupMeta.model && App[groupMeta.model]
if groupMeta.model
for row in App[groupMeta.model].configure_attributes
# ignore passwords and relations
@@ -81,195 +23,24 @@ class App.UiElement.postmaster_set
# ignore readonly attributes
if !row.readonly
config = _.clone(row)
if config.tag is 'tag'
config.operator = ['add', 'remove']
elements["x-zammad-ticket-#{config.name}"] = config
# add additional ticket attributes
for row in App.Ticket.configure_attributes
exists = false
for item in groups.ticket.options
if item.value is row.name
exists = true
switch config.tag
when 'datetime'
config.operator = ['static', 'relative']
when 'tag'
config.operator = ['add', 'remove']
# do not support this types
else if row.tag is 'datetime' || row.tag is 'date' || row.tag is 'tag'
exists = true
elements["x-zammad-#{groupKey}-#{config.name}"] = config
# ignore passwords and relations
if !exists && row.type isnt 'password' && row.name.substr(row.name.length-4,4) isnt '_ids'
elements['x-zammad-article-internal'] = _.clone(App.TicketArticle.attributesGet()['internal'])
elements['x-zammad-article-internal'].null = false
# ignore readonly attributes
if !row.readonly
item =
value: row.name
name: row.display
relation: row.relation
tag: row.tag
options: row.options
groups.ticket.options.push item
elements['x-zammad-article-type_id'] = _.clone(App.TicketArticle.attributesGet()['type_id'])
elements['x-zammad-article-sender_id'] = _.clone(App.TicketArticle.attributesGet()['sender_id'])
elements['x-zammad-ignore'] = { name: 'x-zammad-ignore', display: __('Ignore Message'), tag: 'boolean', type: 'boolean', null: false }
for item in groups.ticket.options
item.value = "x-zammad-ticket-#{item.value}"
[defaults, groups, elements]
[elements, groups]
@placeholder: (elementFull, attribute, params = {}, groups) ->
item = $( App.view('generic/postmaster_set_row')(attribute: attribute) )
selector = @buildAttributeSelector(elementFull, groups, attribute, item)
item.find('.js-attributeSelector').prepend(selector)
item
@render: (attribute, params = {}) ->
[elements, groups] = @defaults()
# scaffold of match elements
item = $( App.view('generic/postmaster_set')(attribute: attribute) )
# add filter
item.on('click', '.js-add', (e) =>
element = $(e.target).closest('.js-filterElement')
placeholder = @placeholder(item, attribute, params, groups)
if element.get(0)
element.after(placeholder)
else
item.append(placeholder)
placeholder.find('.js-attributeSelector select').trigger('change')
)
# remove filter
item.on('click', '.js-remove', (e) =>
return if $(e.currentTarget).hasClass('is-disabled')
$(e.target).closest('.js-filterElement').remove()
@rebuildAttributeSelectors(item)
)
# change attribute selector
item.on('change', '.js-attributeSelector select', (e) =>
elementRow = $(e.target).closest('.js-filterElement')
groupAndAttribute = elementRow.find('.js-attributeSelector option:selected').attr('value')
@rebuildAttributeSelectors(item, elementRow, groupAndAttribute, attribute)
@buildOperator(item, elementRow, groupAndAttribute, elements, {}, attribute)
@buildValue(item, elementRow, groupAndAttribute, groups, undefined, undefined, attribute)
)
# build initial params
if _.isEmpty(params[attribute.name])
element = @placeholder(item, attribute, params, groups)
item.append(element)
groupAndAttribute = element.find('.js-attributeSelector option:selected').attr('value')
@rebuildAttributeSelectors(item, element, groupAndAttribute, attribute)
@buildOperator(item, element, groupAndAttribute, elements, {}, attribute)
@buildValue(item, element, groupAndAttribute, groups, undefined, undefined, attribute)
return item
else
for key, meta of params[attribute.name]
operator = meta.operator
value = meta.value
# build and append
element = @placeholder(item, attribute, params, groups)
groupAndAttribute = element.find('.js-attributeSelector option:selected').attr('value')
@rebuildAttributeSelectors(item, element, key, attribute)
@buildOperator(item, element, key, elements, {}, attribute)
@buildValue(item, element, key, groups, value, operator, attribute)
item.append(element)
item.find('.js-attributeSelector select').trigger('change')
item
@buildValue: (elementFull, elementRow, key, groups, value, operator, attribute) ->
# do nothing if item already exists
name = "#{attribute.name}::#{key}::value"
return if elementRow.find("[name=\"#{name}\"]").get(0)
config = {}
for groupName, meta of groups
for entry in meta.options
if entry.value is key
config = clone(entry)
if !config.tag
if config.relation || config.options
config['tag'] = 'select'
else
config['tag'] = 'input'
config['type'] = 'text'
config['name'] = name
if !value && attribute.value && attribute.value[key]
config['value'] = attribute.value[key].value
else
config['value'] = value
item = App.UiElement[config.tag].render(config, {})
elementRow.find('.js-value').html(item)
@buildAttributeSelector: (elementFull, groups, attribute) ->
# find first possible attribute
selectedValue = ''
elementFull.find('.js-attributeSelector select option').each(->
if !selectedValue && !$(@).prop('disabled')
selectedValue = $(@).val()
)
selection = $('<select class="form-control"></select>')
for groupKey, groupMeta of groups
displayName = App.i18n.translateInline(groupMeta.name)
selection.closest('select').append("<optgroup label=\"#{displayName}\" class=\"js-#{groupKey}\"></optgroup>")
optgroup = selection.find("optgroup.js-#{groupKey}")
for entry in groupMeta.options
displayName = App.i18n.translateInline(entry.name)
selected = ''
if entry.value is selectedValue
selected = 'selected="selected"'
optgroup.append("<option value=\"#{entry.value}\" #{selected}>#{displayName}</option>")
selection
@rebuildAttributeSelectors: (elementFull, elementRow, key, attribute) ->
# enable all
elementFull.find('.js-attributeSelector select option').prop('disabled', false)
# disable all used attributes
elementFull.find('.js-attributeSelector select').each(->
keyLocal = $(@).val()
elementFull.find('.js-attributeSelector select option[value="' + keyLocal + '"]').attr('disabled', true)
)
# disable - if we only have one attribute
if elementFull.find('.js-attributeSelector select').length > 1
elementFull.find('.js-remove').removeClass('is-disabled')
else
elementFull.find('.js-remove').addClass('is-disabled')
# set attribute
if key
elementRow.find('.js-attributeSelector select').val(key)
@buildOperator: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
currentOperator = elementRow.find('.js-operator option:selected').attr('value')
if !meta.operator
meta.operator = currentOperator
name = "#{attribute.name}::#{groupAndAttribute}::operator"
selection = $("<select class=\"form-control\" name=\"#{name}\"></select>")
attributeConfig = elements[groupAndAttribute]
if !attributeConfig || !attributeConfig.operator
elementRow.find('.js-operator').parent().addClass('hide')
else
elementRow.find('.js-operator').parent().removeClass('hide')
if attributeConfig && attributeConfig.operator
for operator in attributeConfig.operator
operatorName = App.i18n.translateInline(operator)
selected = ''
if meta.operator is operator
selected = 'selected="selected"'
selection.append("<option value=\"#{operator}\" #{selected}>#{operatorName}</option>")
selection
elementRow.find('.js-operator select').replaceWith(selection)
@elementKeyGroup: (elementKey) ->
return 'expert' if elementKey is 'x-zammad-ignore'
elementKey.replace('x-zammad-', '').split(/-/)[0]

View File

@@ -4,7 +4,10 @@ class App.UiElement.select_search extends App.UiElement.ApplicationUiElement
attribute = $.extend(true, {}, attributeConfig)
# set multiple option
attribute.multiple = 'multiple'
if attribute.multiple
attribute.multiple = 'multiple'
else
attribute.multiple = ''
delete attribute.filter

View File

@@ -1,575 +1,2 @@
# coffeelint: disable=camel_case_classes
class App.UiElement.ticket_perform_action
@defaults: (attribute) ->
defaults = ['ticket.state_id']
groups =
ticket:
name: __('Ticket')
model: 'Ticket'
article:
name: __('Article')
model: 'Article'
if attribute.notification
groups.notification =
name: __('Notification')
model: 'Notification'
# merge config
elements = {}
for groupKey, groupMeta of groups
if !groupMeta.model || !App[groupMeta.model]
if groupKey is 'notification'
elements["#{groupKey}.email"] = { name: 'email', display: __('Email') }
elements["#{groupKey}.sms"] = { name: 'sms', display: __('SMS') }
elements["#{groupKey}.webhook"] = { name: 'webhook', display: __('Webhook') }
else if groupKey is 'article'
elements["#{groupKey}.note"] = { name: 'note', display: __('Note') }
else
for row in App[groupMeta.model].configure_attributes
# ignore passwords and relations
if row.type isnt 'password' && row.name.substr(row.name.length-4,4) isnt '_ids'
# ignore readonly attributes
if !row.readonly
config = _.clone(row)
switch config.tag
when 'datetime'
config.operator = ['static', 'relative']
when 'tag'
config.operator = ['add', 'remove']
elements["#{groupKey}.#{config.name}"] = config
# add ticket deletion action
if attribute.ticket_delete
elements['ticket.action'] =
name: 'action'
display: __('Action')
tag: 'select'
null: false
translate: true
options:
delete: 'Delete'
[defaults, groups, elements]
@placeholder: (elementFull, attribute, params, groups, elements) ->
item = $( App.view('generic/ticket_perform_action/row')( attribute: attribute ) )
selector = @buildAttributeSelector(elementFull, groups, elements)
item.find('.js-attributeSelector').prepend(selector)
item
@render: (attribute, params = {}) ->
[defaults, groups, elements] = @defaults(attribute)
# return item
item = $( App.view('generic/ticket_perform_action/index')( attribute: attribute ) )
# add filter
item.on('click', '.js-rowActions .js-add', (e) =>
element = $(e.target).closest('.js-filterElement')
placeholder = @placeholder(item, attribute, params, groups, elements)
if element.get(0)
element.after(placeholder)
else
item.append(placeholder)
placeholder.find('.js-attributeSelector select').trigger('change')
@updateAttributeSelectors(item)
)
# remove filter
item.on('click', '.js-rowActions .js-remove', (e) =>
return if $(e.currentTarget).hasClass('is-disabled')
$(e.target).closest('.js-filterElement').remove()
@updateAttributeSelectors(item)
)
# change attribute selector
item.on('change', '.js-attributeSelector select', (e) =>
elementRow = $(e.target).closest('.js-filterElement')
groupAndAttribute = elementRow.find('.js-attributeSelector option:selected').attr('value')
@rebuildAttributeSelectors(item, elementRow, groupAndAttribute, elements, {}, attribute)
@updateAttributeSelectors(item)
)
# change operator selector
item.on('change', '.js-operator select', (e) =>
elementRow = $(e.target).closest('.js-filterElement')
groupAndAttribute = elementRow.find('.js-attributeSelector option:selected').attr('value')
@buildOperator(item, elementRow, groupAndAttribute, elements, {}, attribute)
)
# build initial params
if _.isEmpty(params[attribute.name])
for groupAndAttribute in defaults
# build and append
element = @placeholder(item, attribute, params, groups, elements)
item.append(element)
@rebuildAttributeSelectors(item, element, groupAndAttribute, elements, {}, attribute)
else
for groupAndAttribute, meta of params[attribute.name]
# build and append
element = @placeholder(item, attribute, params, groups, elements)
@rebuildAttributeSelectors(item, element, groupAndAttribute, elements, meta, attribute)
item.append(element)
@disableRemoveForOneAttribute(item)
item
@buildAttributeSelector: (elementFull, groups, elements) ->
# find first possible attribute
selectedValue = ''
elementFull.find('.js-attributeSelector select option').each(->
if !selectedValue && !$(@).prop('disabled')
selectedValue = $(@).val()
)
selection = $('<select class="form-control"></select>')
for groupKey, groupMeta of groups
displayName = App.i18n.translateInline(groupMeta.name)
selection.closest('select').append("<optgroup label=\"#{displayName}\" class=\"js-#{groupKey}\"></optgroup>")
optgroup = selection.find("optgroup.js-#{groupKey}")
for elementKey, elementGroup of elements
spacer = elementKey.split(/\./)
if spacer[0] is groupKey
attributeConfig = elements[elementKey]
displayName = App.i18n.translateInline(attributeConfig.display)
selected = ''
if elementKey is selectedValue
selected = 'selected="selected"'
optgroup.append("<option value=\"#{elementKey}\" #{selected}>#{displayName}</option>")
selection
# disable - if we only have one attribute
@disableRemoveForOneAttribute: (elementFull) ->
if elementFull.find('.js-attributeSelector select').length > 1
elementFull.find('.js-remove').removeClass('is-disabled')
else
elementFull.find('.js-remove').addClass('is-disabled')
@updateAttributeSelectors: (elementFull) ->
# enable all
elementFull.find('.js-attributeSelector select option').prop('disabled', false)
# disable all used attributes
elementFull.find('.js-attributeSelector select').each(->
keyLocal = $(@).val()
elementFull.find('.js-attributeSelector select option[value="' + keyLocal + '"]').attr('disabled', true)
)
# disable - if we only have one attribute
@disableRemoveForOneAttribute(elementFull)
@rebuildAttributeSelectors: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
# set attribute
if groupAndAttribute
elementRow.find('.js-attributeSelector select').val(groupAndAttribute)
notificationTypeMatch = groupAndAttribute.match(/^notification.([\w]+)$/)
articleTypeMatch = groupAndAttribute.match(/^article.([\w]+)$/)
if _.isArray(notificationTypeMatch) && notificationType = notificationTypeMatch[1]
elementRow.find('.js-setAttribute').html('').addClass('hide')
elementRow.find('.js-setArticle').html('').addClass('hide')
@buildNotificationArea(notificationType, elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
else if _.isArray(articleTypeMatch) && articleType = articleTypeMatch[1]
elementRow.find('.js-setAttribute').html('').addClass('hide')
elementRow.find('.js-setNotification').html('').addClass('hide')
@buildArticleArea(articleType, elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
else
elementRow.find('.js-setNotification').html('').addClass('hide')
elementRow.find('.js-setArticle').html('').addClass('hide')
if !elementRow.find('.js-setAttribute div').get(0)
attributeSelectorElement = $( App.view('generic/ticket_perform_action/attribute_selector')(
attribute: attribute
name: name
meta: meta || {}
))
elementRow.find('.js-setAttribute').html(attributeSelectorElement).removeClass('hide')
@buildOperator(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
@buildOperator: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
currentOperator = elementRow.find('.js-operator option:selected').attr('value')
if !meta.operator
meta.operator = currentOperator
name = "#{attribute.name}::#{groupAndAttribute}::operator"
selection = $("<select class=\"form-control\" name=\"#{name}\"></select>")
attributeConfig = elements[groupAndAttribute]
if !attributeConfig || !attributeConfig.operator
elementRow.find('.js-operator').parent().addClass('hide')
else
elementRow.find('.js-operator').parent().removeClass('hide')
if attributeConfig && attributeConfig.operator
for operator in attributeConfig.operator
operatorName = App.i18n.translateInline(operator)
selected = ''
if meta.operator is operator
selected = 'selected="selected"'
selection.append("<option value=\"#{operator}\" #{selected}>#{operatorName}</option>")
selection
elementRow.find('.js-operator select').replaceWith(selection)
@buildPreCondition(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
@buildPreCondition: (elementFull, elementRow, groupAndAttribute, elements, meta, attributeConfig) ->
currentOperator = elementRow.find('.js-operator option:selected').attr('value')
currentPreCondition = elementRow.find('.js-preCondition option:selected').attr('value')
if !meta.pre_condition
meta.pre_condition = currentPreCondition
toggleValue = =>
preCondition = elementRow.find('.js-preCondition option:selected').attr('value')
if preCondition isnt 'specific'
elementRow.find('.js-value select').html('')
elementRow.find('.js-value').addClass('hide')
else
elementRow.find('.js-value').removeClass('hide')
@buildValue(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
# force to use auto complition on user lookup
attribute = clone(attributeConfig, true)
name = "#{attribute.name}::#{groupAndAttribute}::value"
attributeSelected = elements[groupAndAttribute]
preCondition = false
if attributeSelected.relation is 'User'
preCondition = 'user'
attribute.tag = 'user_autocompletion'
if attributeSelected.relation is 'Organization'
preCondition = 'org'
attribute.tag = 'autocompletion_ajax'
if !preCondition
elementRow.find('.js-preCondition select').html('')
elementRow.find('.js-preCondition').closest('.controls').addClass('hide')
toggleValue()
@buildValue(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
return
elementRow.find('.js-preCondition').closest('.controls').removeClass('hide')
name = "#{attribute.name}::#{groupAndAttribute}::pre_condition"
selection = $("<select class=\"form-control\" name=\"#{name}\" ></select>")
options = {}
if preCondition is 'user'
options =
'current_user.id': App.i18n.translateInline('current user')
'specific': App.i18n.translateInline('specific user')
if attributeSelected.null is true
options['not_set'] = App.i18n.translateInline('unassign user')
else if preCondition is 'org'
options =
'current_user.organization_id': App.i18n.translateInline('current user organization')
'specific': App.i18n.translateInline('specific organization')
for key, value of options
selected = ''
if key is meta.pre_condition
selected = 'selected="selected"'
selection.append("<option value=\"#{key}\" #{selected}>#{App.i18n.translateInline(value)}</option>")
elementRow.find('.js-preCondition').closest('.controls').removeClass('hide')
elementRow.find('.js-preCondition select').replaceWith(selection)
elementRow.find('.js-preCondition select').on('change', (e) ->
toggleValue()
)
@buildValue(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
toggleValue()
@buildValue: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
name = "#{attribute.name}::#{groupAndAttribute}::value"
# build new item
attributeConfig = elements[groupAndAttribute]
config = clone(attributeConfig, true)
if config.relation is 'User'
config.tag = 'user_autocompletion'
if config.relation is 'Organization'
config.tag = 'autocompletion_ajax'
# render ui element
item = ''
if config && App.UiElement[config.tag]
config['name'] = name
if attribute.value && attribute.value[groupAndAttribute]
config['value'] = _.clone(attribute.value[groupAndAttribute]['value'])
config.multiple = false
config.default = undefined
config.nulloption = config.null
if config.tag is 'multiselect' || config.tag is 'multi_tree_select'
config.multiple = true
if config.tag is 'checkbox'
config.tag = 'select'
if config.tag is 'datetime'
config.validationContainer = 'self'
item = App.UiElement[config.tag].render(config, {})
relative_operators = [
'before (relative)',
'within next (relative)',
'within last (relative)',
'after (relative)',
'till (relative)',
'from (relative)',
'relative'
]
upcoming_operator = meta.operator
if !_.include(config.operator, upcoming_operator)
if Array.isArray(config.operator)
upcoming_operator = config.operator[0]
else
upcoming_operator = null
if _.include(relative_operators, upcoming_operator)
config['name'] = "#{attribute.name}::#{groupAndAttribute}"
if attribute.value && attribute.value[groupAndAttribute]
config['value'] = _.clone(attribute.value[groupAndAttribute])
item = App.UiElement['time_range'].render(config, {})
elementRow.find('.js-setAttribute > .flex > .js-value').removeClass('hide').html(item)
@buildNotificationArea: (notificationType, elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
return if elementRow.find(".js-setNotification .js-body-#{notificationType}").get(0)
elementRow.find('.js-setNotification').empty()
options =
'article_last_sender': __('Sender of last article')
'ticket_owner': __('Owner')
'ticket_customer': __('Customer')
'ticket_agents': __('All agents')
name = "#{attribute.name}::notification.#{notificationType}"
messageLength = switch notificationType
when 'sms' then 160
else 200000
# meta.recipient was a string in the past (single-select) so we convert it to array if needed
if !_.isArray(meta.recipient)
meta.recipient = [meta.recipient]
columnSelectOptions = []
for key, value of options
selected = undefined
for recipient in meta.recipient
if key is recipient
selected = true
columnSelectOptions.push({ value: key, name: App.i18n.translatePlain(value), selected: selected })
columnSelectRecipientUserOptions = []
for user in App.User.all()
key = "userid_#{user.id}"
selected = undefined
for recipient in meta.recipient
if key is recipient
selected = true
columnSelectRecipientUserOptions.push({ value: key, name: "#{user.firstname} #{user.lastname}", selected: selected })
columnSelectRecipient = new App.ColumnSelect
attribute:
name: "#{name}::recipient"
options: [
{
label: __('Variables'),
group: columnSelectOptions
},
{
label: __('User'),
group: columnSelectRecipientUserOptions
},
]
selectionRecipient = columnSelectRecipient.element()
if notificationType is 'webhook'
notificationElement = $( App.view('generic/ticket_perform_action/webhook')(
attribute: attribute
name: name
notificationType: notificationType
meta: meta || {}
))
notificationElement.find('.js-recipient select').replaceWith(selectionRecipient)
if App.Webhook.search(filter: { active: true }).length isnt 0 || !_.isEmpty(meta.webhook_id)
webhookSelection = App.UiElement.select.render(
name: "#{name}::webhook_id"
multiple: false
null: false
relation: 'Webhook'
value: meta.webhook_id
translate: false
nulloption: true
)
else
webhookSelection = App.view('generic/ticket_perform_action/webhook_not_available')( attribute: attribute )
notificationElement.find('.js-webhooks').html(webhookSelection)
else
notificationElement = $( App.view('generic/ticket_perform_action/notification')(
attribute: attribute
name: name
notificationType: notificationType
meta: meta || {}
))
notificationElement.find('.js-recipient select').replaceWith(selectionRecipient)
visibilitySelection = App.UiElement.select.render(
name: "#{name}::internal"
multiple: false
null: false
options: { true: __('internal'), false: __('public') }
value: meta.internal || 'false'
translate: true
)
includeAttachmentsCheckbox = App.UiElement.select.render(
name: "#{name}::include_attachments"
multiple: false
null: false
options: { true: __('Yes'), false: __('No') }
value: meta.include_attachments || 'false'
translate: true
)
notificationElement.find('.js-internal').html(visibilitySelection)
notificationElement.find('.js-include_attachments').html(includeAttachmentsCheckbox)
notificationElement.find('.js-body div[contenteditable="true"]').ce(
mode: 'richtext'
placeholder: __('message')
maxlength: messageLength
)
new App.WidgetPlaceholder(
el: notificationElement.find('.js-body div[contenteditable="true"]').parent()
objects: [
{
prefix: 'ticket'
object: 'Ticket'
display: __('Ticket')
},
{
prefix: 'article'
object: 'TicketArticle'
display: __('Article')
},
{
prefix: 'user'
object: 'User'
display: __('Current User')
},
]
)
elementRow.find('.js-setNotification').html(notificationElement).removeClass('hide')
if App.Config.get('smime_integration') == true
selection = App.UiElement.select.render(
name: "#{name}::sign"
multiple: false
options: {
'no': __('Do not sign email')
'discard': __('Sign email (if not possible, discard notification)')
'always': __('Sign email (if not possible, send notification anyway)')
}
value: meta.sign
translate: true
)
elementRow.find('.js-sign').html(selection)
selection = App.UiElement.select.render(
name: "#{name}::encryption"
multiple: false
options: {
'no': __('Do not encrypt email')
'discard': __('Encrypt email (if not possible, discard notification)')
'always': __('Encrypt email (if not possible, send notification anyway)')
}
value: meta.encryption
translate: true
)
elementRow.find('.js-encryption').html(selection)
@buildArticleArea: (articleType, elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
return if elementRow.find(".js-setArticle .js-body-#{articleType}").get(0)
elementRow.find('.js-setArticle').empty()
name = "#{attribute.name}::article.#{articleType}"
selection = App.UiElement.select.render(
name: "#{name}::internal"
multiple: false
null: false
label: __('Visibility')
options: { true: 'internal', false: 'public' }
value: meta.internal
translate: true
)
articleElement = $( App.view('generic/ticket_perform_action/article')(
attribute: attribute
name: name
articleType: articleType
meta: meta || {}
))
articleElement.find('.js-internal').html(selection)
articleElement.find('.js-body div[contenteditable="true"]').ce(
mode: 'richtext'
placeholder: __('message')
maxlength: 200000
)
new App.WidgetPlaceholder(
el: articleElement.find('.js-body div[contenteditable="true"]').parent()
objects: [
{
prefix: 'ticket'
object: 'Ticket'
display: __('Ticket')
},
{
prefix: 'article'
object: 'TicketArticle'
display: __('Article')
},
{
prefix: 'user'
object: 'User'
display: __('Current User')
},
]
)
elementRow.find('.js-setArticle').html(articleElement).removeClass('hide')
class App.UiElement.ticket_perform_action extends App.UiElement.ApplicationAction

View File

@@ -27,14 +27,21 @@ class App.UiElement.time_range
@localRenderPulldown(element.filter('.js-valueRangeSelector'), values[range], attribute)
element.find('select.form-control.js-range').on('change', (e) =>
range = $(e.currentTarget).val()
@localRenderPulldown($(e.currentTarget).closest('.js-filterElement').find('.js-valueRangeSelector'), values[range], attribute)
selected_value_name = $(e.currentTarget).prop('name').replace(/::\w+$/, '::value')
selected_value = $("select[name='#{selected_value_name}']").val() if selected_value_name
@localRenderPulldown($(e.currentTarget).closest('.js-filterElement').find('.js-valueRangeSelector'), values[range], attribute, selected_value)
)
element
@localRenderPulldown: (el, range, attribute) ->
@localRenderPulldown: (el, range, attribute, selected_value) ->
return if !range
values = {}
for count in range
values[count.toString()] = count.toString()
select = App.view('generic/time_range_value_selector')(attribute: attribute, values: values)
if !selected_value
if attribute.value
selected_value = attribute.value.value
else
selected_value = 1
select = App.view('generic/time_range_value_selector')(attribute: attribute, values: values, selected_value: selected_value)
el.html(select)

View File

@@ -74,18 +74,10 @@ class App.UiElement.user_permission
groupAccesses: App.Group.accesses()
) )
throttled = _.throttle( (e) ->
input = $(@).find('input')
upcoming_state = if $(e.target).is(':checkbox') then input.prop('checked') else !input.prop('checked')
value = input.val()
if value is 'full' and upcoming_state is true
$(@).closest('tr').find('input:not([value=full])').prop('checked', false)
else if value isnt 'full' and upcoming_state is true
$(@).closest('tr').find('input[value=full]').prop('checked', false)
, 300, { trailing: false })
item.on('click', '.checkbox-replacement', throttled)
# check/uncheck group permissions (particular permissions vs. full)
item.on('click', '.checkbox-replacement', (event) ->
App.PermissionHelper.switchGroupPermission(event)
)
# if customer, remove admin and agent
item.find('[name=role_ids]').on('change', (e) =>

View File

@@ -181,7 +181,7 @@ class App.TicketCreate extends App.Controller
show: =>
@navupdate("#ticket/create/id/#{@id}#{@split}", type: 'menu')
@autosaveStart()
@controllerBind('ticket_create_rerender', (template) => @renderQueue(template))
@controllerBind('ticket_create_rerender', (template) => @renderQueue(template))
@controllerBind('ticket_create_shared_draft_saved', @sharedDraftSaved)
@controllerBind('ticket_create_import_draft_attachments', @importDraftAttachments)
@@ -192,7 +192,7 @@ class App.TicketCreate extends App.Controller
hide: =>
@autosaveStop()
@controllerUnbind('ticket_create_rerender', (template) => @renderQueue(template))
@controllerUnbind('ticket_create_rerender')
@controllerUnbind('ticket_create_shared_draft_saved')
@controllerUnbind('ticket_create_import_draft_attachments')
@@ -292,7 +292,7 @@ class App.TicketCreate extends App.Controller
localeRender = =>
@render(template)
App.QueueManager.add(@queueKey, localeRender)
return if !@formMeta
return if !@formMeta && !@controllerFormCreateMiddle
App.QueueManager.run(@queueKey)
importDraftAttachments: (options = {}) =>
@@ -332,6 +332,10 @@ class App.TicketCreate extends App.Controller
if !_.isEmpty(params['form_id'])
@formId = params['form_id']
if !_.isEmpty(params)
# only use form meta once so it will not get used on templates
@formMeta = undefined
params.priority_id ||= App.TicketPriority.findByAttribute( 'default_create', true )?.id
@html(App.view('agent_ticket_create')(

View File

@@ -20,6 +20,7 @@ class App.KnowledgeBaseSidebarLinkedTickets extends App.Controller
render: ->
@html App.view('knowledge_base/sidebar/linked_tickets')(
tickets: @object.translation(@kb_locale.id)?.linked_tickets() || []
editable: true
)
@renderPopovers()

View File

@@ -10,6 +10,7 @@ class App.KnowledgeBaseSidebarTags extends App.Controller
object_type: 'KnowledgeBaseAnswer'
object: @object
tags: @object.tags
editable: true
)
updateIfNeeded: ->

View File

@@ -24,6 +24,7 @@ App.Config.set('manage/:target', ManageRouter, 'Routes')
App.Config.set('manage/:target/:page', ManageRouter, 'Routes')
App.Config.set('settings/:target', ManageRouter, 'Routes')
App.Config.set('channels/:target', ManageRouter, 'Routes')
App.Config.set('channels/:target/error/:error_code', ManageRouter, 'Routes')
App.Config.set('channels/:target/:channel_id', ManageRouter, 'Routes')
App.Config.set('system/:target', ManageRouter, 'Routes')
App.Config.set('system/:target/:integration', ManageRouter, 'Routes')

View File

@@ -15,8 +15,9 @@ class Package extends App.ControllerSubContent
url: "#{@apiPath}/packages",
processData: true,
success: (data) =>
@packages = data.packages
@commands = data.commands
@packages = data.packages
@package_installation = data.package_installation
@local_gemfiles = data.local_gemfiles
@render()
)
@@ -35,7 +36,8 @@ class Package extends App.ControllerSubContent
@html App.view('package')(
head: __('Dashboard')
packages: @packages
commands: @commands
package_installation: @package_installation
local_gemfiles: @local_gemfiles
)
action: (e) ->

View File

@@ -1,5 +1,6 @@
class App.Search extends App.Controller
@extend App.PopoverProvidable
@extend App.TicketMassUpdatable
elements:
'.js-search': 'searchInput'
@@ -11,6 +12,8 @@ class App.Search extends App.Controller
'click .js-tab': 'showTab'
'input .js-search': 'updateFilledClass'
@include App.ValidUsersForTicketSelectionMethods
constructor: ->
super
@@ -36,6 +39,11 @@ class App.Search extends App.Controller
@render()
)
load = (data) =>
App.Collection.loadAssets(data.assets)
@formMeta = data.form_meta
@bindId = App.TicketOverviewCollection.bind(load)
meta: =>
title = @query || App.i18n.translateInline('Extended Search')
@@ -54,9 +62,13 @@ class App.Search extends App.Controller
if @table
@table.show()
@navupdate(url: '#search', type: 'menu')
return if _.isEmpty(params.query)
@$('.js-search').val(params.query).trigger('keyup')
if !_.isEmpty(params.query)
@$('.js-search').val(params.query).trigger('keyup')
return
if @query
@search(500, true)
hide: ->
if @table
@@ -90,11 +102,22 @@ class App.Search extends App.Controller
@tabs.push tab
# build view
@html App.view('search/index')(
elLocal = $(App.view('search/index')(
query: @query
tabs: @tabs
))
@controllerTicketBatch.releaseController() if @controllerTicketBatch
@controllerTicketBatch = new App.TicketBatch(
el: elLocal.filter('.js-batch-overlay')
parent: @
parentEl: elLocal
appEl: @appEl
batchSuccess: =>
@search(0, true)
)
@html elLocal
if @query
@search(500, true)
@@ -133,6 +156,7 @@ class App.Search extends App.Controller
@globalSearch.search(
delay: delay
query: @query
force: force
)
renderResult: (result = []) =>
@@ -229,16 +253,16 @@ class App.Search extends App.Controller
updateSearch = =>
callback = =>
@search(true)
@search(0, true)
@delay(callback, 100)
@bulkForm.releaseController() if @bulkForm
@bulkForm = new App.TicketBulkForm(
el: @el.find('.bulkAction')
holder: localeEl
view: @view
callback: updateSearch
noSidebar: true
el: @el.find('.bulkAction')
holder: localeEl
view: @view
batchSuccess: updateSearch
noSidebar: true
)
# start bulk action observ

View File

@@ -1,522 +1,22 @@
class App.TicketOverview extends App.Controller
@extend App.TicketMassUpdatable
@include App.ValidUsersForTicketSelectionMethods
className: 'overviews'
activeFocus: 'nav'
mouse:
x: null
y: null
batchAnimationPaused: false
elements:
'.js-batch-overlay': 'batchOverlay'
'.js-batch-overlay-backdrop': 'batchOverlayBackdrop'
'.js-batch-cancel': 'batchCancel'
'.js-batch-macro-circle': 'batchMacroCircle'
'.js-batch-assign-circle': 'batchAssignCircle'
'.js-batch-assign': 'batchAssign'
'.js-batch-assign-inner': 'batchAssignInner'
'.js-batch-assign-group': 'batchAssignGroup'
'.js-batch-assign-group-name': 'batchAssignGroupName'
'.js-batch-assign-group-inner': 'batchAssignGroupInner'
'.js-batch-macro': 'batchMacro'
'.main': 'mainContent'
events:
'mousedown .item': 'startDragItem'
'mouseenter .js-batch-hover-target': 'highlightBatchEntry'
'mouseleave .js-batch-hover-target': 'unhighlightBatchEntry'
@include App.ValidUsersForTicketSelectionMethods
'.main': 'mainContent'
constructor: ->
super
@batchSupport = @permissionCheck('ticket.agent')
@render()
# rerender view, e. g. on language change
@controllerBind('ui:rerender', =>
@renderBatchOverlay()
)
load = (data) =>
App.Collection.loadAssets(data.assets)
@formMeta = data.form_meta
@bindId = App.TicketOverviewCollection.bind(load)
startDragItem: (event) =>
return if !@batchSupport
@grabbedItem = $(event.currentTarget)
offset = @grabbedItem.offset()
@batchDragger = $(App.view('ticket_overview/batch_dragger')())
@grabbedItemClone = @grabbedItem.clone()
@grabbedItemClone.data('offset', @grabbedItem.offset())
@grabbedItemClone.addClass('batch-dragger-item js-main-item')
@batchDragger.append @grabbedItemClone
@batchDragger.data
startX: event.pageX
startY: event.pageY
dx: Math.min(event.pageX - offset.left, 180)
dy: event.pageY - offset.top
moved: false
$(document).on 'mousemove.item', @dragItem
$(document).one 'mouseup.item', @endDragItem
# TODO: fire @cancelDrag on ESC
dragItem: (event) =>
pos = @batchDragger.data()
threshold = 3
x = event.pageX - pos.dx
y = event.pageY - pos.dy
dir = if event.pageY > pos.startY then 1 else -1
if !pos.moved
if Math.abs(event.pageY - pos.startY) > threshold || Math.abs(event.pageX - pos.startX) > threshold
@batchDragger.data 'moved', true
@el.addClass('u-no-userselect')
# check grabbed items batch checkbox to make sure its checked
# (could be grabbed without checking the checkbox it)
@grabbedItemWasntChecked = !@grabbedItem.find('[name="bulk"]').prop('checked')
@grabbedItem.find('[name="bulk"]').prop('checked', true)
@grabbedItemClone.find('[name="bulk"]').prop('checked', true)
additionalItems = @el.find('[name="bulk"]:checked').parents('.item').not(@grabbedItem)
additionalItemsClones = additionalItems.clone()
@draggedItems = @grabbedItemClone.add(additionalItemsClones)
# store offsets for later use
additionalItemsClones.each (i, item) -> $(@).data('offset', additionalItems.eq(i).offset())
@batchDragger.prepend additionalItemsClones.addClass('batch-dragger-item').get().reverse()
if(additionalItemsClones.length)
@batchDragger.find('.js-batch-dragger-count').text(@draggedItems.length)
@renderOptions()
@appEl.append(@batchDragger)
@draggedItems.each (i, item) ->
dx = $(item).data('offset').left - $(item).offset().left - x
dy = $(item).data('offset').top - $(item).offset().top - y
$.Velocity.hook item, 'translateX', "#{dx}px"
$.Velocity.hook item, 'translateY', "#{dy}px"
@alignDraggedItems(-dir)
@mouseY = event.pageY
@showBatchOverlay()
else
return
event.preventDefault()
$.Velocity.hook @batchDragger, 'translateX', "#{x}px"
$.Velocity.hook @batchDragger, 'translateY', "#{y}px"
endDragItem: (event) =>
$(document).off 'mousemove.item'
$(document).off 'mouseup.item'
pos = @batchDragger.data()
@clearDelay('clear-hovered-batch-entry')
if !@hoveredBatchEntry
@cleanUpDrag()
return
$.Velocity.hook @batchDragger, 'transformOriginX', "#{pos.dx}px"
$.Velocity.hook @batchDragger, 'transformOriginY', "#{pos.dy}px"
@hoveredBatchEntry.velocity
properties:
scale: 1.1
options:
duration: 200
complete: =>
if !@hoveredBatchEntry
@cleanUpDrag()
return
@hoveredBatchEntry.velocity 'reverse',
duration: 200
complete: =>
if !@hoveredBatchEntry
@cleanUpDrag()
return
# clean scale
action = @hoveredBatchEntry.attr('data-action')
id = @hoveredBatchEntry.attr('data-id')
groupId = @hoveredBatchEntry.attr('data-group-id')
items = @el.find('[name="bulk"]:checked')
@hoveredBatchEntry.removeAttr('style')
@cleanUpDrag(true)
@performBatchAction items, action, id, groupId
@batchDragger.velocity
properties:
scale: 0
options:
duration: 200
cancelDrag: ->
$(document).off 'mousemove.item'
$(document).off 'mouseup.item'
@cleanUpDrag()
cleanUpDrag: (success) ->
@hideBatchOverlay()
@el.removeClass('u-no-userselect')
$('.batch-dragger').remove()
@hoveredBatchEntry = null
if @grabbedItemWasntChecked
@grabbedItem.find('[name="bulk"]').prop('checked', false)
if success
# uncheck all checked items
@el.find('[name="bulk"]:checked').prop('checked', false)
@el.find('[name="bulk_all"]').prop('checked', false)
alignDraggedItems: (dir) ->
@draggedItems.velocity
properties:
translateX: 0
translateY: (i) => dir * i * @batchDragger.height()/2
options:
easing: 'ease-in-out'
duration: 300
@batchDragger.find('.js-batch-dragger-count').velocity
properties:
translateY: if dir < 0 then 0 else -@batchDragger.height()+8
options:
easing: 'ease-in-out'
duration: 300
performBatchAction: (items, action, id, groupId) ->
ticket_ids = items.toArray().map (item) -> $(item).val()
switch action
when 'macro'
path = 'macro'
data =
ticket_ids: ticket_ids
macro_id: id
when 'user_assign'
path = 'update'
data =
ticket_ids: ticket_ids
attributes:
owner_id: id
if !_.isEmpty(groupId)
data.attributes.group_id = groupId
when 'group_assign'
path = 'update'
data =
ticket_ids: ticket_ids
attributes:
group_id: id
@ajax_mass(path, data)
showBatchOverlay: ->
@batchOverlay.addClass('is-visible')
$('html').css('overflow', 'hidden')
@batchOverlayBackdrop.velocity { opacity: [1, 0] }, { duration: 500 }
@batchMacroOffset = @batchMacro.offset().top + @batchMacro.outerHeight()
@batchAssignOffset = @batchAssign.offset().top
@batchOverlayShown = true
$(document).on 'mousemove.batchoverlay', @controlBatchOverlay
hideBatchOverlay: ->
$(document).off 'mousemove.batchoverlay'
@batchOverlayShown = false
@batchOverlayBackdrop.velocity { opacity: [0, 1] }, { duration: 300, queue: false }
@hideBatchCircles =>
@batchOverlay.removeClass('is-visible')
$('html').css('overflow', '')
if @batchAssignShown
@hideBatchAssign()
if @batchMacroShown
@hideBatchMacro()
if @batchAssignGroupShown
@hideBatchAssignGroup()
controlBatchOverlay: (event) =>
return if @batchAnimationPaused
# store to detect if the mouse is hovering a drag-action entry
# after an animation ended -> @highlightBatchEntryAtMousePosition
@mouse.x = event.pageX
@mouse.y = event.pageY
if @batchAssignGroupShown && @batchAssignGroupOffset != undefined
if @mouse.y < @batchAssignGroupOffset
@hideBatchAssignGroup()
@batchAnimationPaused = true
return
if @mouse.y <= @batchMacroOffset
mouseInArea = 'top'
else if @mouse.y > @batchMacroOffset && @mouse.y <= @batchAssignOffset
mouseInArea = 'middle'
else
mouseInArea = 'bottom'
switch mouseInArea
when 'top'
if !@batchMacroShown
@hideBatchCircles()
@showBatchMacro()
@alignDraggedItems(1)
when 'middle'
if @batchAssignShown
@hideBatchAssign()
if @batchMacroShown
@hideBatchMacro()
if !@batchCirclesShown
@showBatchCircles()
when 'bottom'
if !@batchAssignShown
@hideBatchCircles()
@showBatchAssign()
@alignDraggedItems(-1)
showBatchCircles: ->
@batchCirclesShown = true
@batchMacroCircle.velocity
properties:
translateY: [0, '-150%']
opacity: [1, 0]
options:
easing: [1,-.55,.2,1.37]
duration: 500
visibility: 'visible'
delay: 200
@batchAssignCircle.velocity
properties:
translateY: [0, '150%']
opacity: [1, 0]
options:
easing: [1,-.55,.2,1.37]
duration: 500
visibility: 'visible'
delay: 200
hideBatchCircles: (callback) ->
@batchMacroCircle.velocity
properties:
translateY: ['-150%', 0]
opacity: [0, 1]
options:
duration: 300
visibility: 'hidden'
queue: false
@batchAssignCircle.velocity
properties:
translateY: ['150%', 0]
opacity: [0, 1]
options:
duration: 300
complete: callback
visibility: 'hidden'
queue: false
@batchCirclesShown = false
showBatchAssign: ->
return if !@batchOverlayShown # user might have dropped the item already
@batchAssignShown = true
@batchCancel.css
top: 0
bottom: @batchAssign.height()
@batchAssign.velocity
properties:
translateY: [0, '100%']
opacity: [1, 0]
options:
easing: [1,-.55,.2,1.37]
duration: 500
visibility: 'visible'
complete: @highlightBatchEntryAtMousePosition
@batchCancel.velocity
properties:
translateY: [0, '100%']
opacity: [1, 0]
options:
easing: [1,-.55,.2,1.37]
duration: 500
visibility: 'visible'
hideBatchAssign: ->
@batchAssign.velocity
properties:
translateY: ['100%', 0]
opacity: [0, 1]
options:
duration: 300
visibility: 'hidden'
queue: false
complete: =>
$.Velocity.hook @batchAssign, 'translateY', '0%'
@batchCancel.velocity
properties:
translateY: ['100%', 0]
opacity: [0, 1]
options:
duration: 300
visibility: 'hidden'
queue: false
@batchAssignShown = false
showBatchAssignGroup: =>
return if !@batchOverlayShown # user might have dropped the item already
@batchAssignGroupShown = true
groupId = @hoveredBatchEntry.attr('data-id')
group = App.Group.find(groupId)
@batchAssignGroupName.text group.displayName()
@batchAssignGroupInner.html $(App.view('ticket_overview/batch_overlay_user_group')(
users: @usersInGroups([groupId])
groups: []
groupId: groupId
))
# then adjust the size of the group that it almost overlaps the batch-assign box
@batchAssignGroupInner.height(@batchAssignInner.height())
@batchAssignGroup.velocity
properties:
translateY: [0, '100%']
opacity: [1, 0]
options:
easing: [1,-.55,.2,1.37]
duration: 700
visibility: 'visible'
complete: =>
@highlightBatchEntryAtMousePosition()
@batchAssignGroupOffset = @batchAssignGroup.offset().top
hideBatchAssignGroup: ->
@batchAssignGroup.velocity
properties:
translateY: ['100%', 0]
opacity: [0, 1]
options:
duration: 300
visibility: 'hidden'
queue: false
complete: =>
@batchAssignGroupShown = false
@batchAssignGroupHovered = false
setTimeout (=> @batchAnimationPaused = false), 1000
@batchAssignGroupOffset = undefined
showBatchMacro: ->
return if !@batchOverlayShown # user might have dropped the item already
@batchMacroShown = true
@batchCancel.css
bottom: 0
top: @batchMacro.height()
@batchMacro.velocity
properties:
translateY: [0, '-100%']
opacity: [1, 0]
options:
easing: [1,-.55,.2,1.37]
duration: 500
visibility: 'visible'
complete: @highlightBatchEntryAtMousePosition
@batchCancel.velocity
properties:
translateY: [0, '-100%']
opacity: [1, 0]
options:
easing: [1,-.55,.2,1.37]
duration: 500
visibility: 'visible'
hideBatchMacro: ->
@batchMacro.velocity
properties:
translateY: ['-100%', 0]
opacity: [0, 1]
options:
duration: 300
visibility: 'hidden'
queue: false
complete: =>
$.Velocity.hook @batchMacro, 'translateY', '0%'
@batchCancel.velocity
properties:
translateY: ['-100%', 0]
opacity: [0, 1]
options:
duration: 300
visibility: 'hidden'
queue: false
@batchMacroShown = false
highlightBatchEntryAtMousePosition: =>
entryAtPoint = $(document.elementFromPoint(@mouse.x, @mouse.y)).closest('.js-batch-overlay-entry .avatar')
if(entryAtPoint.length)
@hoveredBatchEntry = entryAtPoint.closest('.js-batch-overlay-entry').addClass('is-hovered')
highlightBatchEntry: (event) ->
@clearDelay('clear-hovered-batch-entry')
@hoveredBatchEntry = $(event.currentTarget).closest('.js-batch-overlay-entry').addClass('is-hovered')
if @hoveredBatchEntry.attr('data-action') is 'group_assign'
@batchAssignGroupHintTimeout = setTimeout @blinkBatchEntry, 800
@batchAssignGroupTimeout = setTimeout @showBatchAssignGroup, 900
unhighlightBatchEntry: (event) ->
return if !@hoveredBatchEntry
if @hoveredBatchEntry.attr('data-action') is 'group_assign'
if @batchAssignGroupTimeout
clearTimeout @batchAssignGroupTimeout
if @batchAssignGroupHintTimeout
clearTimeout @batchAssignGroupHintTimeout
@hoveredBatchEntry.removeClass('is-hovered')
delay = =>
@hoveredBatchEntry = null
@delay(delay, 800, 'clear-hovered-batch-entry')
blinkBatchEntry: =>
@hoveredBatchEntry
.velocity({ opacity: [0.5, 1] }, { duration: 120 })
.velocity({ opacity: [1, 0.5] }, { duration: 60, delay: 40 })
.velocity({ opacity: [0.5, 1] }, { duration: 120 })
.velocity({ opacity: [1, 0.5] }, { duration: 60, delay: 40 })
render: ->
elLocal = $(App.view('ticket_overview/index')())
@@ -533,6 +33,16 @@ class App.TicketOverview extends App.Controller
view: @view
)
@controllerTicketBatch.releaseController() if @controllerTicketBatch
@controllerTicketBatch = new App.TicketBatch(
el: elLocal.filter('.js-batch-overlay')
parent: @
parentEl: elLocal
appEl: @appEl
batchSuccess: =>
@render()
)
@contentController.releaseController() if @contentController
@contentController = new Table(
el: elLocal.find('.overview-table')
@@ -541,8 +51,6 @@ class App.TicketOverview extends App.Controller
keyboardOff: @keyboardOff
)
@renderBatchOverlay(elLocal.filter('.js-batch-overlay'))
@html elLocal
@$('.main').on('click', =>
@@ -559,54 +67,6 @@ class App.TicketOverview extends App.Controller
@delay(update, 2800, 'overview:fetch')
)
renderBatchOverlay: (elLocal) =>
if elLocal
elLocal.html( App.view('ticket_overview/batch_overlay')() )
return
@batchOverlay.html( App.view('ticket_overview/batch_overlay')() )
@refreshElements()
renderOptions: =>
@renderOptionsGroups()
@renderOptionsMacros()
renderOptionsGroups: =>
@batchAssignInner.html $(App.view('ticket_overview/batch_overlay_user_group')(
@validUsersForTicketSelection()
))
renderOptionsMacros: =>
@possibleMacros = []
macros = App.Macro.getList()
items = @el.find('[name="bulk"]:checked')
group_ids =[]
for item in items
ticket = App.Ticket.find($(item).val())
group_ids.push ticket.group_id
group_ids = _.uniq(group_ids)
for macro in macros
# push if no group_ids exists
if _.isEmpty(macro.group_ids) && !_.includes(@possibleMacros, macro)
@possibleMacros.push macro
# push if group_ids are equal
if _.isEqual(macro.group_ids, group_ids) && !_.includes(@possibleMacros, macro)
@possibleMacros.push macro
# push if all group_ids of tickets are in macro.group_ids
if !_.isEmpty(macro.group_ids) && _.isEmpty(_.difference(group_ids,macro.group_ids)) && !_.includes(@possibleMacros, macro)
@possibleMacros.push macro
@batchMacro.html $(App.view('ticket_overview/batch_overlay_macro')(
macros: @possibleMacros
))
active: (state) =>
return @shown if state is undefined
@shown = state
@@ -1047,6 +507,8 @@ class Table extends App.Controller
# open ticket via task manager to provide task with overview info
ticket = App.Ticket.findNative(id)
return if !ticket
App.TaskManager.execute(
key: "Ticket-#{ticket.id}"
controller: 'TicketZoom'
@@ -1217,9 +679,11 @@ class Table extends App.Controller
@bulkForm.releaseController() if @bulkForm
@bulkForm = new App.TicketBulkForm(
el: @el.find('.bulkAction')
holder: @el
view: @view
el: @el.find('.bulkAction')
holder: @el
view: @view
batchSuccess: =>
@render()
)
# start bulk action observ

View File

@@ -93,7 +93,7 @@ class App.TicketZoom extends App.Controller
@ajax(
id: "ticket_zoom_#{@ticket_id}"
type: 'GET'
url: "#{@apiPath}/tickets/#{@ticket_id}?all=true"
url: "#{@apiPath}/tickets/#{@ticket_id}?all=true&auto_assign=true"
processData: true
queue: queue
success: (data, status, xhr) =>
@@ -335,7 +335,7 @@ class App.TicketZoom extends App.Controller
hide: =>
@activeState = false
$('body > .modal').modal('hide')
$('body > .modal').modal('hide') if @shown
@positionPageHeaderStop()
@autosaveStop()
@shortcutNavigationstop()
@@ -557,26 +557,6 @@ class App.TicketZoom extends App.Controller
links: @links
)
# check if autolock is needed
if @Config.get('ticket_auto_assignment') is true
if @ticket.owner_id is 1 && @permissionCheck('ticket.agent') && @ticket.editable('full')
userIdsIgnore = @Config.get('ticket_auto_assignment_user_ids_ignore') || []
if !_.isArray(userIdsIgnore)
userIdsIgnore = [userIdsIgnore]
userIgnored = false
currentUserId = App.Session.get('id')
for userIdIgnore in userIdsIgnore
if userIdIgnore.toString() is currentUserId.toString()
userIgnored = true
break
if userIgnored is false
ticket_auto_assignment_selector = @Config.get('ticket_auto_assignment_selector')
if App.Ticket.selector(@ticket, ticket_auto_assignment_selector['condition'])
assign = =>
@ticket.owner_id = App.Session.get('id')
@ticket.save()
@delay(assign, 800, "ticket-auto-assign-#{@ticket.id}")
# render init content
if elLocal
@html elLocal
@@ -979,6 +959,8 @@ class App.TicketZoom extends App.Controller
new_article: @articleNew.params()
ticket_attributes: @ticketParams()
params.new_article.body = App.Utils.signatureRemoveByHtml(params.new_article.body)
loaded_draft_id = params.new_article.shared_draft_id
params.form_id = params.new_article['form_id']
@@ -1226,6 +1208,41 @@ class TicketZoomRouter extends App.ControllerPermanent
constructor: (params) ->
super
return @byNumber(params) if params.ticket_number
@byTicketId(params)
byNumber: (params) ->
return @byTicketId(params) if !params.ticket_number
return @byTicketId(params) if params.ticket_id
number = params.ticket_number
delete params.ticket_number
ticket = App.Ticket.findByAttribute('number', number)
return @navigate("ticket/zoom/#{ticket.id}") if ticket
App.Ajax.request(
type: 'POST'
url: "#{@apiPath}/tickets/search"
processData: true
data: JSON.stringify(
condition: {
'ticket.number': {
operator: 'is',
value: number
}
}
limit: 1
)
success: (data, status, xhr) =>
return @byTicketId(params) if _.isEmpty(data.tickets)
@navigate("ticket/zoom/#{data.tickets[0]}")
error: =>
@byTicketId(params)
)
byTicketId: (params) ->
# cleanup params
clean_params =
ticket_id: params.ticket_id
@@ -1240,6 +1257,7 @@ class TicketZoomRouter extends App.ControllerPermanent
show: true
)
App.Config.set('ticket/zoom/number/:ticket_number', TicketZoomRouter, 'Routes')
App.Config.set('ticket/zoom/:ticket_id', TicketZoomRouter, 'Routes')
App.Config.set('ticket/zoom/:ticket_id/nav/:nav', TicketZoomRouter, 'Routes')
App.Config.set('ticket/zoom/:ticket_id/:article_id', TicketZoomRouter, 'Routes')

View File

@@ -18,9 +18,12 @@ class App.TicketZoomArticleActions extends App.Controller
@html ''
actionRow: (ticket, article) ->
ticket = App.Ticket.fullLocal(ticket.id)
article = App.TicketArticle.fullLocal(article.id)
actionConfig = App.Config.get('TicketZoomArticleAction')
keys = _.keys(actionConfig).sort()
actions = []
keys = _.keys(actionConfig).sort()
actions = []
for key in keys
config = actionConfig[key]
if config

View File

@@ -5,6 +5,7 @@ class App.TicketZoomArticleImageView extends App.ControllerModal
buttonClass: 'btn--success'
head: ''
veryLarge: true
nextElement: null
events:
'submit form': 'submit'
@@ -20,16 +21,14 @@ class App.TicketZoomArticleImageView extends App.ControllerModal
)
nextRight: =>
nextElement = @parentElement.closest('.attachment').next('.attachment.attachment--preview')
return if nextElement.length is 0
@nextElement = @parentElement.closest('.attachment').next('.attachment.attachment--preview')
return if @nextElement.length is 0
@close()
nextElement.find('img').trigger('click')
nextLeft: =>
prevElement = @parentElement.closest('.attachment').prev('.attachment.attachment--preview')
return if prevElement.length is 0
@nextElement = @parentElement.closest('.attachment').prev('.attachment.attachment--preview')
return if @nextElement.length is 0
@close()
prevElement.find('img').trigger('click')
content: ->
@image = @image.replace(/view=preview/, 'view=inline')
@@ -40,8 +39,15 @@ class App.TicketZoomArticleImageView extends App.ControllerModal
url = "#{$(@image).attr('src')}?disposition=attachment"
window.open(url, '_blank')
onShow: =>
@el.attr('tabindex', '-1')
$('.modal').focus()
onClose: =>
@unbindAll()
onClosed: =>
@nextElement.find('img').trigger('click') if @nextElement
unbindAll: ->
$(document).off('keydown.image_preview')

View File

@@ -55,11 +55,13 @@ class App.TicketZoomArticleNew extends App.Controller
@openTextarea(null, true, !data.nofocus)
for key, value of data.article
if key is 'body'
@$("[data-name=\"#{key}\"]").html(value)
else
@$("[name=\"#{key}\"]").val(value).trigger('change')
switch key
when 'body'
@$("[data-name=\"#{key}\"]").html(value)
when 'internal'
@setArticleInternal(value)
else
@$("[name=\"#{key}\"]").val(value).trigger('change')
@$('[name=shared_draft_id]').val(data.shared_draft_id)
@@ -363,19 +365,13 @@ class App.TicketZoomArticleNew extends App.Controller
@el.find('.js-articleTypes').addClass('is-hidden')
setArticleInternal: (internal) =>
if internal is true
@articleNewEdit
.removeClass('is-public')
.addClass('is-internal')
@$('[name=internal]').val('true')
return
@articleNewEdit
.addClass('is-public')
.removeClass('is-internal')
.toggleClass('is-public', !internal)
.toggleClass('is-internal', internal)
@$('[name=internal]').val('')
value = if internal then 'true' else ''
@$('[name=internal]').val(value)
setArticleTypePre: (type, signaturePosition = 'bottom') =>
wasScrolledToBottom = @isScrolledToBottom()

View File

@@ -94,7 +94,7 @@ class ArticleViewItem extends App.ControllerObserver
constructor: ->
super
@seeMore = false
@seeMoreOpen = false
# set expand of text area only once
@controllerBind('ui::ticket::shown', (data) =>
@@ -254,12 +254,11 @@ class ArticleViewItem extends App.ControllerObserver
bubbleOverflowContainer = @textBubbleOverflowContainer
# expand if see more is already clicked
if @seeMore
if @seeMoreOpen
bubbleContent.css('height', 'auto')
return
# reset bubble height and "see more" opacity
bubbleContent.css('height', '')
else
# reset bubble height and "see more" opacity
bubbleContent.css('height', '')
bubbleOverflowContainer.css('opacity', '')
# remember offset of "see more"
@@ -296,7 +295,9 @@ class ArticleViewItem extends App.ControllerObserver
else if bubbleContentHeight > maxHeight
bubbleContent.attr('data-height', bubbleContentHeight + 30)
bubbleContent.attr('data-height-origin', maxHeight)
bubbleContent.css('height', "#{maxHeight}px")
newHeight = if @seeMoreOpen then 'auto' else "#{maxHeight}px"
bubbleContent.css('height', newHeight)
bubbleOverflowContainer.toggleClass('is-open', @seeMoreOpen).find('.js-toggleFold').html(@label)
bubbleOverflowContainer.removeClass('hide')
else
bubbleOverflowContainer.addClass('hide')
@@ -448,15 +449,15 @@ class ArticleViewItem extends App.ControllerObserver
bubbleOverflowContainer = @textBubbleOverflowContainer
if @seeMoreOpen
label = App.i18n.translateContent('See more')
@label = App.i18n.translateContent('See more')
height = bubbleContent.attr('data-height-origin')
@seeMoreOpen = false
else
label = App.i18n.translateContent('See less')
@label = App.i18n.translateContent('See less')
height = bubbleContent.attr('data-height')
@seeMoreOpen = true
bubbleOverflowContainer.toggleClass('is-open', @seeMoreOpen).find('.js-toggleFold').html(label)
bubbleOverflowContainer.toggleClass('is-open', @seeMoreOpen).find('.js-toggleFold').html(@label)
bubbleContent.velocity
properties:

View File

@@ -175,6 +175,7 @@ class App.FormHandlerCoreWorkflow
)
form.find('[name="' + field + '"]').closest('.form-group').replaceWith(newElement)
form.find('[name="' + field + '"]').closest('.form-group').find('.js-helpMessage').tooltip()
if shown
ui.show(field, form)

View File

@@ -152,12 +152,14 @@ class SidebarTicket extends App.Controller
object_type: 'Ticket'
object: @ticket
tags: @tags
editable: @ticket.editable()
)
@linkWidget = new App.WidgetLink.Ticket(
el: localEl.filter('.js-links')
object_type: 'Ticket'
object: @ticket
links: @links
editable: @ticket.editable()
)
if @permissionCheck('knowledge_base.*') and App.Config.get('kb_active')
@@ -166,6 +168,7 @@ class SidebarTicket extends App.Controller
object_type: 'Ticket'
object: @ticket
links: @links
editable: @ticket.editable()
)
@timeUnitWidget = new App.TicketZoomTimeUnit(

View File

@@ -43,6 +43,7 @@ class App.WidgetLinkKbAnswer extends App.WidgetLink
render: ->
@html App.view('link/kb_answer')(
list: @linksForRendering()
editable: @editable
)
@renderPopovers()

View File

@@ -47,11 +47,13 @@ class App.WidgetLink.Ticket extends App.WidgetLink
tickets: list[type].tickets
object: 'Ticket'
linkType: type
editable: @editable
) unless list[type].tickets.length == 0
# insert data
@html App.view('link/ticket/list')(
links: list
editable: @editable
)
@renderPopovers()

View File

@@ -57,6 +57,8 @@ class App.WidgetSharedDraft extends App.Controller
return false if meta.name.trim() == ''
form.body = App.Utils.signatureRemoveByHtml(form.body)
JSON.stringify({
name: meta.name
group_id: form.group_id

View File

@@ -51,8 +51,10 @@ class App.WidgetTag extends App.Controller
render: =>
return if @lastLocalTags && _.isEqual(@lastLocalTags, @localTags)
@lastLocalTags = _.clone(@localTags)
@html App.view(@templateName)(
tags: @localTags || [],
editable: @editable
)
source = "#{App.Config.get('api_path')}/tag_search"
@$('.js-newTagInput').autocomplete(

View File

@@ -0,0 +1,548 @@
class App.TicketBatch extends App.Controller
requiredPermission: 'ticket.agent'
mouse:
x: null
y: null
batchAnimationPaused: false
elements:
'.js-batch-overlay-backdrop': 'batchOverlayBackdrop'
'.js-batch-cancel': 'batchCancel'
'.js-batch-macro-circle': 'batchMacroCircle'
'.js-batch-assign-circle': 'batchAssignCircle'
'.js-batch-assign': 'batchAssign'
'.js-batch-assign-inner': 'batchAssignInner'
'.js-batch-assign-group': 'batchAssignGroup'
'.js-batch-assign-group-name': 'batchAssignGroupName'
'.js-batch-assign-group-inner': 'batchAssignGroupInner'
'.js-batch-macro': 'batchMacro'
events:
'mouseenter .js-batch-hover-target': 'highlightBatchEntry'
'mouseleave .js-batch-hover-target': 'unhighlightBatchEntry'
constructor: ->
super
# rerender view, e. g. on language change
@controllerBind('ui:rerender', @render)
@render()
render: =>
@html App.view('ticket_overview/batch_overlay')()
@parentEl.off('mousedown.TicketBatch').on('mousedown.TicketBatch', '.item', @startDragItem)
renderOptions: =>
@renderOptionsGroups()
@renderOptionsMacros()
renderOptionsGroups: =>
@batchAssignInner.html $(App.view('ticket_overview/batch_overlay_user_group')(
@parent.validUsersForTicketSelection()
))
renderOptionsMacros: =>
@possibleMacros = []
macros = App.Macro.getList()
items = @parentEl.find('[name="bulk"]:checked')
group_ids =[]
for item in items
ticket = App.Ticket.find($(item).val())
group_ids.push ticket.group_id
group_ids = _.uniq(group_ids)
for macro in macros
# push if no group_ids exists
if _.isEmpty(macro.group_ids) && !_.includes(@possibleMacros, macro)
@possibleMacros.push macro
# push if group_ids are equal
if _.isEqual(macro.group_ids, group_ids) && !_.includes(@possibleMacros, macro)
@possibleMacros.push macro
# push if all group_ids of tickets are in macro.group_ids
if !_.isEmpty(macro.group_ids) && _.isEmpty(_.difference(group_ids,macro.group_ids)) && !_.includes(@possibleMacros, macro)
@possibleMacros.push macro
@batchMacro.html $(App.view('ticket_overview/batch_overlay_macro')(
macros: @possibleMacros
))
startDragItem: (event) =>
@grabbedItem = $(event.currentTarget)
offset = @grabbedItem.offset()
@batchDragger = $(App.view('ticket_overview/batch_dragger')())
@grabbedItemClone = @grabbedItem.clone()
@grabbedItemClone.data('offset', @grabbedItem.offset())
@grabbedItemClone.addClass('batch-dragger-item js-main-item')
@batchDragger.append @grabbedItemClone
@batchDragger.data
startX: event.pageX
startY: event.pageY
dx: Math.min(event.pageX - offset.left, 180)
dy: event.pageY - offset.top
moved: false
$(document).on 'mousemove.item', @dragItem
$(document).one 'mouseup.item', @endDragItem
# TODO: fire @cancelDrag on ESC
dragItem: (event) =>
pos = @batchDragger.data()
threshold = 3
x = event.pageX - pos.dx
y = event.pageY - pos.dy
dir = if event.pageY > pos.startY then 1 else -1
if !pos.moved
if Math.abs(event.pageY - pos.startY) > threshold || Math.abs(event.pageX - pos.startX) > threshold
@batchDragger.data 'moved', true
@el.addClass('u-no-userselect')
# check grabbed items batch checkbox to make sure its checked
# (could be grabbed without checking the checkbox it)
@grabbedItemWasntChecked = !@grabbedItem.find('[name="bulk"]').prop('checked')
@grabbedItem.find('[name="bulk"]').prop('checked', true)
@grabbedItemClone.find('[name="bulk"]').prop('checked', true)
additionalItems = @el.find('[name="bulk"]:checked').parents('.item').not(@grabbedItem)
additionalItemsClones = additionalItems.clone()
@draggedItems = @grabbedItemClone.add(additionalItemsClones)
# store offsets for later use
additionalItemsClones.each (i, item) -> $(@).data('offset', additionalItems.eq(i).offset())
@batchDragger.prepend additionalItemsClones.addClass('batch-dragger-item').get().reverse()
if(additionalItemsClones.length)
@batchDragger.find('.js-batch-dragger-count').text(@draggedItems.length)
@renderOptions()
@appEl.append(@batchDragger)
@draggedItems.each (i, item) ->
dx = $(item).data('offset').left - $(item).offset().left - x
dy = $(item).data('offset').top - $(item).offset().top - y
$.Velocity.hook item, 'translateX', "#{dx}px"
$.Velocity.hook item, 'translateY', "#{dy}px"
@alignDraggedItems(-dir)
@mouseY = event.pageY
@showBatchOverlay()
else
return
event.preventDefault()
$.Velocity.hook @batchDragger, 'translateX', "#{x}px"
$.Velocity.hook @batchDragger, 'translateY', "#{y}px"
endDragItem: (event) =>
$(document).off 'mousemove.item'
$(document).off 'mouseup.item'
pos = @batchDragger.data()
@clearDelay('clear-hovered-batch-entry')
if !@hoveredBatchEntry
@cleanUpDrag()
return
$.Velocity.hook @batchDragger, 'transformOriginX', "#{pos.dx}px"
$.Velocity.hook @batchDragger, 'transformOriginY', "#{pos.dy}px"
@hoveredBatchEntry.velocity
properties:
scale: 1.1
options:
duration: 200
complete: =>
if !@hoveredBatchEntry
@cleanUpDrag()
return
@hoveredBatchEntry.velocity 'reverse',
duration: 200
complete: =>
if !@hoveredBatchEntry
@cleanUpDrag()
return
# clean scale
action = @hoveredBatchEntry.attr('data-action')
id = @hoveredBatchEntry.attr('data-id')
groupId = @hoveredBatchEntry.attr('data-group-id')
items = @parentEl.find('[name="bulk"]:checked')
@hoveredBatchEntry.removeAttr('style')
@cleanUpDrag(true)
@performBatchAction items, action, id, groupId
@batchDragger.velocity
properties:
scale: 0
options:
duration: 200
cancelDrag: ->
$(document).off 'mousemove.item'
$(document).off 'mouseup.item'
@cleanUpDrag()
cleanUpDrag: (success) ->
@hideBatchOverlay()
@el.removeClass('u-no-userselect')
$('.batch-dragger').remove()
@hoveredBatchEntry = null
if @grabbedItemWasntChecked
@grabbedItem.find('[name="bulk"]').prop('checked', false)
if success
# uncheck all checked items
@el.find('[name="bulk"]:checked').prop('checked', false)
@el.find('[name="bulk_all"]').prop('checked', false)
alignDraggedItems: (dir) ->
@draggedItems.velocity
properties:
translateX: 0
translateY: (i) => dir * i * @batchDragger.height()/2
options:
easing: 'ease-in-out'
duration: 300
@batchDragger.find('.js-batch-dragger-count').velocity
properties:
translateY: if dir < 0 then 0 else -@batchDragger.height()+8
options:
easing: 'ease-in-out'
duration: 300
performBatchAction: (items, action, id, groupId) ->
ticket_ids = items.toArray().map (item) -> $(item).val()
switch action
when 'macro'
path = 'macro'
data =
ticket_ids: ticket_ids
macro_id: id
when 'user_assign'
path = 'update'
data =
ticket_ids: ticket_ids
attributes:
owner_id: id
if !_.isEmpty(groupId)
data.attributes.group_id = groupId
when 'group_assign'
path = 'update'
data =
ticket_ids: ticket_ids
attributes:
group_id: id
@parent.ajax_mass(path, data, @batchSuccess)
showBatchOverlay: ->
@el.addClass('is-visible')
$('html').css('overflow', 'hidden')
@batchOverlayBackdrop.velocity { opacity: [1, 0] }, { duration: 500 }
@batchMacroOffset = @batchMacro.offset().top + @batchMacro.outerHeight()
@batchAssignOffset = @batchAssign.offset().top
@batchOverlayShown = true
$(document).on 'mousemove.batchoverlay', @controlBatchOverlay
hideBatchOverlay: ->
$(document).off 'mousemove.batchoverlay'
@batchOverlayShown = false
@batchOverlayBackdrop.velocity { opacity: [0, 1] }, { duration: 300, queue: false }
@hideBatchCircles =>
@el.removeClass('is-visible')
$('html').css('overflow', '')
if @batchAssignShown
@hideBatchAssign()
if @batchMacroShown
@hideBatchMacro()
if @batchAssignGroupShown
@hideBatchAssignGroup()
controlBatchOverlay: (event) =>
return if @batchAnimationPaused
# store to detect if the mouse is hovering a drag-action entry
# after an animation ended -> @highlightBatchEntryAtMousePosition
@mouse.x = event.pageX
@mouse.y = event.pageY
if @batchAssignGroupShown && @batchAssignGroupOffset != undefined
if @mouse.y < @batchAssignGroupOffset
@hideBatchAssignGroup()
@batchAnimationPaused = true
return
if @mouse.y <= @batchMacroOffset
mouseInArea = 'top'
else if @mouse.y > @batchMacroOffset && @mouse.y <= @batchAssignOffset
mouseInArea = 'middle'
else
mouseInArea = 'bottom'
switch mouseInArea
when 'top'
if !@batchMacroShown
@hideBatchCircles()
@showBatchMacro()
@alignDraggedItems(1)
when 'middle'
if @batchAssignShown
@hideBatchAssign()
if @batchMacroShown
@hideBatchMacro()
if !@batchCirclesShown
@showBatchCircles()
when 'bottom'
if !@batchAssignShown
@hideBatchCircles()
@showBatchAssign()
@alignDraggedItems(-1)
showBatchCircles: ->
@batchCirclesShown = true
@batchMacroCircle.velocity
properties:
translateY: [0, '-150%']
opacity: [1, 0]
options:
easing: [1,-.55,.2,1.37]
duration: 500
visibility: 'visible'
delay: 200
@batchAssignCircle.velocity
properties:
translateY: [0, '150%']
opacity: [1, 0]
options:
easing: [1,-.55,.2,1.37]
duration: 500
visibility: 'visible'
delay: 200
hideBatchCircles: (callback) ->
@batchMacroCircle.velocity
properties:
translateY: ['-150%', 0]
opacity: [0, 1]
options:
duration: 300
visibility: 'hidden'
queue: false
@batchAssignCircle.velocity
properties:
translateY: ['150%', 0]
opacity: [0, 1]
options:
duration: 300
complete: callback
visibility: 'hidden'
queue: false
@batchCirclesShown = false
showBatchAssign: ->
return if !@batchOverlayShown # user might have dropped the item already
@batchAssignShown = true
@batchCancel.css
top: 0
bottom: @batchAssign.height()
@batchAssign.velocity
properties:
translateY: [0, '100%']
opacity: [1, 0]
options:
easing: [1,-.55,.2,1.37]
duration: 500
visibility: 'visible'
complete: @highlightBatchEntryAtMousePosition
@batchCancel.velocity
properties:
translateY: [0, '100%']
opacity: [1, 0]
options:
easing: [1,-.55,.2,1.37]
duration: 500
visibility: 'visible'
hideBatchAssign: ->
@batchAssign.velocity
properties:
translateY: ['100%', 0]
opacity: [0, 1]
options:
duration: 300
visibility: 'hidden'
queue: false
complete: =>
$.Velocity.hook @batchAssign, 'translateY', '0%'
@batchCancel.velocity
properties:
translateY: ['100%', 0]
opacity: [0, 1]
options:
duration: 300
visibility: 'hidden'
queue: false
@batchAssignShown = false
showBatchAssignGroup: =>
return if !@batchOverlayShown # user might have dropped the item already
@batchAssignGroupShown = true
groupId = @hoveredBatchEntry.attr('data-id')
group = App.Group.find(groupId)
@batchAssignGroupName.text group.displayName()
@batchAssignGroupInner.html $(App.view('ticket_overview/batch_overlay_user_group')(
users: @parent.usersInGroups([groupId])
groups: []
groupId: groupId
))
# then adjust the size of the group that it almost overlaps the batch-assign box
@batchAssignGroupInner.height(@batchAssignInner.height())
@batchAssignGroup.velocity
properties:
translateY: [0, '100%']
opacity: [1, 0]
options:
easing: [1,-.55,.2,1.37]
duration: 700
visibility: 'visible'
complete: =>
@highlightBatchEntryAtMousePosition()
@batchAssignGroupOffset = @batchAssignGroup.offset().top
hideBatchAssignGroup: ->
@batchAssignGroup.velocity
properties:
translateY: ['100%', 0]
opacity: [0, 1]
options:
duration: 300
visibility: 'hidden'
queue: false
complete: =>
@batchAssignGroupShown = false
@batchAssignGroupHovered = false
setTimeout (=> @batchAnimationPaused = false), 1000
@batchAssignGroupOffset = undefined
showBatchMacro: ->
return if !@batchOverlayShown # user might have dropped the item already
@batchMacroShown = true
@batchCancel.css
bottom: 0
top: @batchMacro.height()
@batchMacro.velocity
properties:
translateY: [0, '-100%']
opacity: [1, 0]
options:
easing: [1,-.55,.2,1.37]
duration: 500
visibility: 'visible'
complete: @highlightBatchEntryAtMousePosition
@batchCancel.velocity
properties:
translateY: [0, '-100%']
opacity: [1, 0]
options:
easing: [1,-.55,.2,1.37]
duration: 500
visibility: 'visible'
hideBatchMacro: ->
@batchMacro.velocity
properties:
translateY: ['-100%', 0]
opacity: [0, 1]
options:
duration: 300
visibility: 'hidden'
queue: false
complete: =>
$.Velocity.hook @batchMacro, 'translateY', '0%'
@batchCancel.velocity
properties:
translateY: ['-100%', 0]
opacity: [0, 1]
options:
duration: 300
visibility: 'hidden'
queue: false
@batchMacroShown = false
highlightBatchEntryAtMousePosition: =>
entryAtPoint = $(document.elementFromPoint(@mouse.x, @mouse.y)).closest('.js-batch-overlay-entry .avatar')
if(entryAtPoint.length)
@hoveredBatchEntry = entryAtPoint.closest('.js-batch-overlay-entry').addClass('is-hovered')
highlightBatchEntry: (event) =>
@clearDelay('clear-hovered-batch-entry')
@hoveredBatchEntry = $(event.currentTarget).closest('.js-batch-overlay-entry').addClass('is-hovered')
if @hoveredBatchEntry.attr('data-action') is 'group_assign'
@batchAssignGroupHintTimeout = setTimeout @blinkBatchEntry, 800
@batchAssignGroupTimeout = setTimeout @showBatchAssignGroup, 900
unhighlightBatchEntry: (event) ->
return if !@hoveredBatchEntry
if @hoveredBatchEntry.attr('data-action') is 'group_assign'
if @batchAssignGroupTimeout
clearTimeout @batchAssignGroupTimeout
if @batchAssignGroupHintTimeout
clearTimeout @batchAssignGroupHintTimeout
@hoveredBatchEntry.removeClass('is-hovered')
delay = =>
@hoveredBatchEntry = null
@delay(delay, 800, 'clear-hovered-batch-entry')
blinkBatchEntry: =>
@hoveredBatchEntry
.velocity({ opacity: [0.5, 1] }, { duration: 120 })
.velocity({ opacity: [1, 0.5] }, { duration: 60, delay: 40 })
.velocity({ opacity: [0.5, 1] }, { duration: 120 })
.velocity({ opacity: [1, 0.5] }, { duration: 60, delay: 40 })

View File

@@ -240,7 +240,7 @@ class App.TicketBulkForm extends App.Controller
@ajax_mass_update(data, =>
@holder.find('.table').find('[name="bulk"]:checked').prop('checked', false)
@render()
@batchSuccess()
@hide()
)

View File

@@ -130,7 +130,7 @@ class App.WidgetUser extends App.Controller
elLocal.find('.js-organizationList').prepend(organizations)
)
if @user.organization_ids.length < @organizationLimit
if @user.organization_ids && @user.organization_ids.length < @organizationLimit
@el.find('.js-showMoreOrganizations').addClass('hidden')
else
@el.find('.js-showMoreOrganizations').removeClass('hidden')

View File

@@ -85,7 +85,7 @@ class App.GlobalSearch extends App.Controller
params.callbackMatch()
# if result hasn't changed, do not rerender
if @lastQuery is query && @searchResultCache[query]
if !params.force && @lastQuery is query && @searchResultCache[query]
diff = difference(@searchResultCache[query].result, result)
if _.isEmpty(diff)
return

View File

@@ -459,12 +459,12 @@ class App.SearchableSelect extends Spine.Controller
content = {}
if @attribute.relation
content =
name: name
name: String(name)
value: value
object: @attribute.relation
else
content =
name: value
name: String(value)
value: value
@input.before App.view('generic/token')(content)

View File

@@ -224,7 +224,16 @@ class App.Utils
# htmlEscapedAndLinkified = App.Utils.linkify(rawText)
@linkify: (string) ->
window.linkify(string)
window.linkifyStr(string, {
attributes: @_getLinkifyAttributes,
})
@_getLinkifyAttributes: (href) ->
attributes = {
title: href,
}
if /^https?:/.test(href) then attributes.target = '_blank'
return attributes
# htmlEscapedAndPhoneified = App.Utils.phoneify(rawText)
@phoneify: (string) ->
@@ -780,6 +789,26 @@ class App.Utils
@signatureIdentifyByHtmlHelper(message)
@signatureRemoveByHtml: (message) ->
container = document.createElement('container-element')
container.innerHTML = message
brsToRemove = []
signatures = container
.querySelectorAll('div[data-signature]')
.forEach (elem) ->
node = elem
while(node?.previousSibling?.nodeName == 'BR')
brsToRemove.push node.previousSibling
node = node.previousSibling
elem.remove()
brsToRemove.forEach (elem) -> elem.remove()
container.innerHTML
@signatureIdentifyByHtmlHelper: (message, internal = false) ->
# blockquotes and signature blocks are considered "dismiss nodes" and their indice will be stored
dismissNodes = []

View File

@@ -1,186 +0,0 @@
/*!
* linkify - v0.3 - 6/27/2009
* http://benalman.com/code/test/js-linkify/
*
* Copyright (c) 2009 "Cowboy" Ben Alman
* Licensed under the MIT license
* http://benalman.com/about/license/
*
* Some regexps adapted from http://userscripts.org/scripts/review/7122
*/
// Turn text into linkified html.
//
// var html = linkify( text, options );
//
// options:
//
// callback (Function) - default: undefined - if defined, this will be called
// for each link- or non-link-chunk with two arguments, text and href. If the
// chunk is non-link, href will be omitted.
//
// punct_regexp (RegExp | Boolean) - a RegExp that can be used to trim trailing
// punctuation from links, instead of the default.
//
// This is a work in progress, please let me know if (and how) it fails!
window.linkify = (function(){
var
SCHEME = "[a-z\\d.-]+://",
IPV4 = "(?:(?:[0-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.){3}(?:[0-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])",
HOSTNAME = "(?:(?:[^\\s!@#$%^&*()_=+[\\]{}\\\\|;:'\",.<>/?]+)\\.)+",
TLD = "(?:ac|ad|aero|ae|af|ag|ai|al|am|an|ao|aq|arpa|ar|asia|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|biz|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|cat|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|coop|com|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|edu|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|info|int|in|io|iq|ir|is|it|je|jm|jobs|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mil|mk|ml|mm|mn|mobi|mo|mp|mq|mr|ms|mt|museum|mu|mv|mw|mx|my|mz|name|na|nc|net|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|org|pa|pe|pf|pg|ph|pk|pl|pm|pn|pro|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tel|tf|tg|th|tj|tk|tl|tm|tn|to|tp|travel|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|xn--0zwm56d|xn--11b5bs3a9aj6g|xn--80akhbyknj4f|xn--9t4b11yi5a|xn--deba0ad|xn--g6w251d|xn--hgbk6aj7f53bba|xn--hlcj6aya9esc7a|xn--jxalpdlp|xn--kgbechtv|xn--zckzah|ye|yt|yu|za|zm|zw)",
HOST_OR_IP = "(?:" + HOSTNAME + TLD + "|" + IPV4 + ")",
PATH = "(?:[;/][^#?<>\\s]*)?",
QUERY_FRAG = "(?:\\?[^#<>\\s]*)?(?:#[^<>\\s]*)?",
URI1 = "\\b" + SCHEME + "[^<>\\s]+",
URI2 = "\\b" + HOST_OR_IP + PATH + QUERY_FRAG + "(?!\\w)",
MAILTO = "mailto:",
EMAIL = "(?:" + MAILTO + ")?[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@" + HOST_OR_IP + QUERY_FRAG + "(?!\\w)",
URI_RE = new RegExp( "(?:" + URI1 + "|" + URI2 + "|" + EMAIL + ")", "ig" ),
SCHEME_RE = new RegExp( "^" + SCHEME, "i" ),
quotes = {
"'": "`",
'>': '<',
')': '(',
']': '[',
'}': '{',
'»': '«',
'›': '‹'
},
default_options = {
callback: function( text, href ) {
// return href ? '<a href="' + href + '" title="' + href + '">' + text + '<\/a>' : text;
return href ? '<a href="' + href + '" title="' + href + '" target="_blank">' + text + '<\/a>' : text;
},
punct_regexp: /(?:[!?.,:;'"]|(?:&|&amp;)(?:lt|gt|quot|apos|raquo|laquo|rsaquo|lsaquo);)$/
};
return function( txt, options ) {
options = options || {};
// me
txt = txt
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
// me
// Temp variables.
var arr,
i,
link,
href,
// Output HTML.
html = '',
// Store text / link parts, in order, for re-combination.
parts = [],
// Used for keeping track of indices in the text.
idx_prev,
idx_last,
idx,
link_last,
// Used for trimming trailing punctuation and quotes from links.
matches_begin,
matches_end,
quote_begin,
quote_end;
// Initialize options.
for ( i in default_options ) {
if ( options[ i ] === undefined ) {
options[ i ] = default_options[ i ];
}
}
// Find links.
while ( arr = URI_RE.exec( txt ) ) {
link = arr[0];
idx_last = URI_RE.lastIndex;
idx = idx_last - link.length;
// Not a link if preceded by certain characters.
if ( /[\/:]/.test( txt.charAt( idx - 1 ) ) ) {
continue;
}
// Trim trailing punctuation.
do {
// If no changes are made, we don't want to loop forever!
link_last = link;
quote_end = link.substr( -1 )
quote_begin = quotes[ quote_end ];
// Ending quote character?
if ( quote_begin ) {
matches_begin = link.match( new RegExp( '\\' + quote_begin + '(?!$)', 'g' ) );
matches_end = link.match( new RegExp( '\\' + quote_end, 'g' ) );
// If quotes are unbalanced, remove trailing quote character.
if ( ( matches_begin ? matches_begin.length : 0 ) < ( matches_end ? matches_end.length : 0 ) ) {
link = link.substr( 0, link.length - 1 );
idx_last--;
}
}
// Ending non-quote punctuation character?
if ( options.punct_regexp ) {
link = link.replace( options.punct_regexp, function(a){
idx_last -= a.length;
return '';
});
}
} while ( link.length && link !== link_last );
href = link;
// Add appropriate protocol to naked links.
if ( !SCHEME_RE.test( href ) ) {
href = ( href.indexOf( '@' ) !== -1 ? ( !href.indexOf( MAILTO ) ? '' : MAILTO )
: !href.indexOf( 'irc.' ) ? 'irc://'
: !href.indexOf( 'ftp.' ) ? 'ftp://'
: 'http://' )
+ href;
}
// Push preceding non-link text onto the array.
if ( idx_prev != idx ) {
parts.push([ txt.slice( idx_prev, idx ) ]);
idx_prev = idx_last;
}
// Push massaged link onto the array
// 2018-10-30: me only link urls, not mailto link
//parts.push([ link, href ]);
if ( href && href.substr && href.substr(0,7) != 'mailto:') {
parts.push([ link, href ]);
}
else {
parts.push([ link, undefined ]);
}
};
// Push remaining non-link text onto the array.
parts.push([ txt.substr( idx_prev ) ]);
// Process the array items.
for ( i = 0; i < parts.length; i++ ) {
html += options.callback.apply( window, parts[i] );
}
// In case of catastrophic failure, return the original text;
return html || txt;
};
})();

View File

@@ -490,7 +490,7 @@
// Clear `cropBox` is necessary when replace
this.cropBox = null;
this.off();
this.unbind();
this.resetPreview();
this.$preview = null;

View File

@@ -518,12 +518,30 @@
callback(item.content, [])
}
function matchItemWithRegExp(item, regex) {
return (item.name && item.name.match(regex)) || (item.keywords && item.keywords.match(regex))
}
function compareItem(a, b) {
if (a.name < b.name) return 1;
if (a.name > b.name) return -1;
return 0;
}
Collection.renderResults = function(textmodule, term) {
var reg = new RegExp(term, 'i')
var result = textmodule.collection.filter(function(item) {
return (item.name && item.name.match(reg)) || (item.keywords && item.keywords.match(reg))
var reg = new RegExp(term, 'i')
var regFull = new RegExp('\\b' + term + '\\b', 'i')
var result = textmodule.collection.filter(function(item) {
return matchItemWithRegExp(item, reg) && !matchItemWithRegExp(item, regFull)
})
result.reverse()
var resultFull = textmodule.collection.filter(function(item) {
return matchItemWithRegExp(item, regFull)
})
result.sort(compareItem)
if (resultFull.length) {
resultFull.sort(compareItem)
result = result.concat(resultFull)
}
textmodule.emptyResultsContainer()
@@ -578,10 +596,8 @@
}),
url: translation.parent().generateURL('/attachments/clone_to_form'),
success: function(data, status, xhr) {
translation.parent().attachments += data.attachments
App.Utils.htmlImage2DataUrlAsync(body, function(output){
callback(output, translation.parent().attachments)
callback(output, data.attachments)
})
},
error: function(xhr) {

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,17 @@
App.PermissionHelper =
switchGroupPermission: (event) ->
throttled = _.throttle( (e) ->
input = $(e.target)
upcoming_state = input.prop('checked')
if !$(e.target).is(':checkbox')
upcoming_state = !upcoming_state
selector = 'input[value=full]'
if input.val() is 'full' and upcoming_state is true
selector = 'input[value!=full]'
$(e.target).closest('tr').find(selector).prop('checked', false)
, 300, { trailing: false })
throttled(event)

View File

@@ -1,6 +1,6 @@
App.ValidUsersForTicketSelectionMethods =
validUsersForTicketSelection: ->
items = $('.content.active .table-overview .table').find('[name="bulk"]:checked')
items = $('.content.active .main .table').find('[name="bulk"]:checked')
# we want to display all users for which we can assign the tickets directly
# for this we need to get the groups of all selected tickets
@@ -17,7 +17,6 @@ App.ValidUsersForTicketSelectionMethods =
group_ids = _.keys(@formMeta?.dependencies?.group_id)
groups = App.Group.findAll(group_ids)
groups_sorted = _.sortBy(groups, (group) -> group.name)
# get the number of visible users per group
# from the TicketOverviewCollection
# (filled for e.g. the TicketCreation or TicketZoom assignment)

View File

@@ -1,4 +1,4 @@
class App.Permission extends App.Model
@configure 'Permission', 'name', 'note', 'active'
@configure 'Permission', 'name', 'note', 'active', 'preferences'
@extend Spine.Model.Ajax
@url: @apiPath + '/permissions'

View File

@@ -7,7 +7,7 @@ class App.PostmasterFilter extends App.Model
{ name: 'name', display: __('Name'), tag: 'input', type: 'text', limit: 250, 'null': false },
{ name: 'channel', display: __('Channel'), type: 'input', readonly: 1 },
{ name: 'match', display: __('Match all of the following'), tag: 'postmaster_match', note: __('You can use regular expression by using "regex:your_reg_exp".') },
{ name: 'perform', display: __('Perform action of the following'), tag: 'postmaster_set' },
{ name: 'perform', display: __('Perform action of the following'), tag: 'postmaster_set', user_action: false },
{ name: 'note', display: __('Note'), tag: 'textarea', limit: 250, null: true },
{ name: 'updated_at', display: __('Updated'), tag: 'datetime', readonly: 1 },
{ name: 'active', display: __('Active'), tag: 'active', default: true },
@@ -23,4 +23,4 @@ class App.PostmasterFilter extends App.Model
@configure_clone = true
@on 'create', (newRecord) ->
newRecord.channel = 'email'
newRecord.channel = 'email'

View File

@@ -258,8 +258,10 @@ class App.Ticket extends App.Model
@_selectorMatch: (object, objectName, attributeName, condition) ->
conditionValue = condition.value
conditionValue = '' if conditionValue == null
conditionValue = '' if conditionValue == undefined
objectValue = object[attributeName]
objectValue = '' if objectValue == null
objectValue = '' if objectValue == undefined
# take care about pre conditions

View File

@@ -378,8 +378,11 @@ class App.User extends App.Model
_.uniq(result)
secondaryOrganizations: (offset, limit, callback) ->
organization_ids = @organization_ids.slice(offset, limit)
missing_organization_ids = _.filter(organization_ids, (id) -> !App.Organization.findNative(id))
organization_ids = []
missing_organization_ids = []
if _.isArray(@organization_ids)
organization_ids = @organization_ids.slice(offset, limit)
missing_organization_ids = _.filter(organization_ids, (id) -> !App.Organization.findNative(id))
organizationResult = ->
organizations = []

View File

@@ -38,7 +38,9 @@
<% end %>
<% if _.isEmpty(@account_channels): %>
<p><%- @T('You have no configured account right now.') %></p>
<table class="table table--placeholder">
<thead><tr><th><%- @T('No Entries') %>
</table>
<% else: %>
<% for channel in @account_channels: %>
<div class="action <% if channel.active isnt true: %>is-inactive<% end %>" data-id="<%- channel.id %>">

View File

@@ -1,7 +1,9 @@
<h2><%- @T('SMS Accounts') %></h2>
<% if _.isEmpty(@account_channels): %>
<p><%- @T('You have no configured account right now.') %></p>
<table class="table table--placeholder">
<thead><tr><th><%- @T('No Entries') %>
</table>
<% else: %>
<% for channel in @account_channels: %>
<div class="action <% if channel.active isnt true: %>is-inactive<% end %>" data-id="<%- channel.id %>">
@@ -85,7 +87,9 @@
<h2><%- @T('SMS Notification') %></h2>
<% if _.isEmpty(@notification_channels): %>
<p><%- @T('You have no configured account right now.') %></p>
<table class="table table--placeholder">
<thead><tr><th><%- @T('No Entries') %>
</table>
<a class="btn btn--success js-editNotification"><%- @T('New') %></a>
<% else: %>
<% for channel in @notification_channels: %>

View File

@@ -6,7 +6,7 @@
<% if @item.objectNative && @item.objectNative.activityMessage: %>
<%- @item.objectNative.activityMessage(@item) %>
<% else: %>
Need objectNative in item <%= item.object %>.find(<%= item.o_id %>)
Need objectNative in item <%= @item.object %>.find(<%= @item.o_id %>)
<% end %>
</span>
<%- @humanTime(@item.created_at, false, 'activity-time') %>

View File

@@ -20,7 +20,7 @@
</h2>
<p class="help-text"><% if @attribute.help: %><%- @T(@attribute.help) %><% end %><%- @attribute.helpLink %></p>
<% end %>
<div class="controls <% if ( @attribute.type is 'url' || !_.isEmpty(@attribute.linktemplate) ) && !_.isEmpty(@placeholderObjects): %>controls-button<% end %>">
<div class="controls <% if ( @attribute.type is 'url' || !_.isEmpty(@attribute.linktemplate) ) && !_.isEmpty(@placeholderObjects): %>controls-button ignore-readonly<% end %>">
<% if ( @attribute.type is 'url' || !_.isEmpty(@attribute.linktemplate) ) && !_.isEmpty(@placeholderObjects): %>
<a href="<% if @attribute.type is 'url': %><%= @placeholderObjects[@className.toLowerCase()][@attribute.name] %><% else: %><%- @replacePlaceholder(@attribute.linktemplate, @placeholderObjects, true) %><% end %>" class="controls-button" target="_blank" rel="nofollow">
<span class="controls-button-inner"><%- @Icon('external') %></span>

View File

@@ -9,6 +9,7 @@
<%= @attribute.autofocus %>
value="<%= @attribute.value %>"
multiple
disabled
>
<% for option in @allOptions: %>
<option value="<%= option.value %>" <%= ' selected' if option.selected %>><%= option.name %></option>

View File

@@ -14,7 +14,6 @@
value
name="<%- @attribute.name %>_completion"
autocomplete="off"
<%= @attribute.required %>
<% if @attribute.disabled: %> disabled<% end %>
>
<% else: %>
@@ -22,7 +21,6 @@
class="searchableSelect-shadow form-control js-shadow"
<% if @attribute.id: %>id="<%= @attribute.id %>"<% end %>
name="<%= @attribute.name %>"
<%= @attribute.required %>
<%= @attribute.autofocus %>
value="<%= @attribute.value %>"
tabindex="-1"
@@ -33,7 +31,6 @@
placeholder="<%= @attribute.placeholder %>"
value="<%= @attribute.valueName %>"
autocomplete="off"
<%= @attribute.required %>
<% if @attribute.disabled: %> disabled<% end %>
>
<% end %>

View File

@@ -17,7 +17,7 @@
</a>
<%- @humanTime(ticket.created_at, false, 'time') %>
</div>
<% if @object: %>
<% if @object && @editable: %>
<a class="list-item-delete js-delete" data-object="<%= @object %>" data-object-id="<%= ticket.id %>" data-link-type="<%= @linkType || 'normal' %>" data-type="remove">
<%- @Icon('diagonal-cross') %>
</a>
@@ -31,4 +31,4 @@
<div data-show="collapsed"><%- @Icon('arrow-up') %> <span><%- @T('Show less') %></span></div>
</div>
<% end %>
</div>
</div>

View File

@@ -1,5 +1,5 @@
<div class="flex horizontal">
<div class="controls half-space-right">
<div class="controls half-space-right flex-no-shrink">
<div class="u-positionOrigin js-operator">
<select></select>
<%- @Icon('arrow-down', 'dropdown-arrow') %>

View File

@@ -5,7 +5,7 @@
<%- @Icon('arrow-down', 'dropdown-arrow') %>
</div>
</div>
<div class="controls js-setAttribute horizontal"></div>
<div class="controls js-setAttribute horizontal span-width"></div>
<div class="horizontal-filter-group js-setNotification flex"></div>
<div class="horizontal-filter-group js-setArticle flex"></div>
</div>

View File

@@ -1,6 +1,6 @@
<select class="form-control js-value" name="<%= @attribute.name %>::value">
<% for key, value of @values: %>
<option value="<%= key %>" <% if @attribute.value && @attribute.value.value.toString() is key.toString(): %>selected<% end %>><%- value %></option>
<option value="<%= key %>" <% if @selected_value && @selected_value.toString() is key.toString(): %>selected<% end %>><%- value %></option>
<% end %>
</select>
<%- @Icon('arrow-down', 'dropdown-arrow') %>
<%- @Icon('arrow-down', 'dropdown-arrow') %>

View File

@@ -31,6 +31,11 @@
<% end %>
<div class="page-content">
<% if _.isEmpty(@channels): %>
<table class="table table--placeholder">
<thead><tr><th><%- @T('No Entries') %>
</table>
<% end %>
<% for channel in @channels: %>
<div class="action <% if channel.active isnt true: %>is-inactive<% end %>" data-id="<%= channel.id %>">
<div class="action-flow" style="width: 100%;">

View File

@@ -2,7 +2,7 @@
<h2><%- @T('Linked Tickets') %></h2>
</div>
<%- @V('generic/ticket_list', show_max: 5, tickets: @tickets, object: 'Ticket') %>
<%- @V('generic/ticket_list', show_max: 5, tickets: @tickets, object: 'Ticket', editable: @editable) %>
<a class="btn btn--action btn--small js-add" href="#">
<%- @Icon('plus-small') %> <span><%- @T('Add') %></span>

View File

@@ -8,9 +8,13 @@
<div class="task-text">
<a class="name kb-answer-popover" data-id="<%- item.id %>" href="<%- item.url %>"><%= item.title %></a>
</div>
<a class="list-item-delete js-delete" data-object="KnowledgeBase::Answer::Translation" data-object-id="<%= item.id %>" data-link-type="normal">
<%- @Icon('diagonal-cross') %>
</a>
<% if @editable: %>
<a class="list-item-delete js-delete" data-object="KnowledgeBase::Answer::Translation" data-object-id="<%= item.id %>" data-link-type="normal">
<%- @Icon('diagonal-cross') %>
</a>
<% end %>
<% end %>
</ol>
<div class="text-muted js-add u-clickable">+ <%- @T('Link Related Answer') %></div>
<% if @editable: %>
<div class="text-muted js-add u-clickable">+ <%- @T('Link Related Answer') %></div>
<% end %>

View File

@@ -5,4 +5,6 @@
<%- @links[type].ticketList %>
</div>
<% end %>
<div class="text-muted u-clickable js-add">+ <%- @T('Add Link') %></div>
<% if @editable: %>
<div class="text-muted u-clickable js-add">+ <%- @T('Add Link') %></div>
<% end %>

View File

@@ -0,0 +1,5 @@
<p>
<%- @T('The admin consent request was sent to your administrator.') %><br>
<%- @T('Please wait until you received the notification email of the approval.') %><br><br>
<%- @T('After approval, please re-connect your Microsoft365 mailbox.') %><br>
</p>

View File

@@ -6,6 +6,7 @@
<div class="page-header-meta">
<a class="btn js-configApp"><%- @T('Configure App') %></a>
<a class="btn btn--success js-new"><%- @T('Add Account') %></a>
<a class="btn btn--success js-admin-consent"><%- @T('Request Admin Consent') %></a>
</div>
</div>
@@ -31,6 +32,11 @@
<% end %>
<div class="page-content">
<% if _.isEmpty(@channels): %>
<table class="table table--placeholder">
<thead><tr><th><%- @T('No Entries') %>
</table>
<% end %>
<% for channel in @channels: %>
<div class="action <% if channel.active isnt true: %>is-inactive<% end %>" data-id="<%= channel.id %>">
<div class="action-flow" style="width: 100%;">

View File

@@ -11,8 +11,19 @@
<p>
<%- @T('After installing, updating, or uninstalling packages the following commands need to be executed on the server:') %>
<ul>
<% for command in @commands: %>
<li><code>zammad> <%- command %></code></li>
<% if @package_installation: %>
<% if @local_gemfiles: %>
<li><code>root> zammad config:set BUNDLE_DEPLOYMENT=0</code></li>
<li><code>root> zammad run bundle install --no-deployment</code></li>
<% end %>
<li><code>root> zammad run rake zammad:package:migrate</code></strong></li>
<li><code>root> zammad run rake assets:precompile</code></strong></li>
<% else: %>
<% if @local_gemfiles: %>
<li><code>zammad> bundle install</code></li>
<% end %>
<li><code>zammad> rake zammad:package:migrate</code></strong></li>
<li><code>zammad> rake assets:precompile</code></strong></li>
<% end %>
<li><code>root> systemctl restart zammad</code></li>
</ul>

Some files were not shown because too many files have changed in this diff Show More