Compare commits

...
223 Commits
Author SHA1 Message Date
Sojan 2605f24539 Merge branch 'release/2.3.2' 2022-03-23 19:06:45 +05:30
Sojan dd76dd4204 Bump version to 2.3.2 2022-03-23 19:05:34 +05:30
86b4183bde fix: Add multiple file paste support and fix for bugs (#4066)
- Add multiple files paste support.
- Fixes showing file name in the editor field when we paste the file from finder.
- Fixes showing the image in the advance editor when we paste the image as an attachment from the clipboard.

Fixes: #4036

Co-authored-by: Nithin David <1277421+nithindavid@users.noreply.github.com>
2022-03-23 18:25:57 +05:30
Jordan BroughandGitHub 4a21633a2b chore: Use bang version of "find_or_create_by!" in Platform UsersController (#4248) 2022-03-23 16:09:10 +05:30
Sojan JoseandGitHub f8c46341f6 chore: Disable invalid contact resolved activity messages (#4253)
Fixes the bug which triggered "Contact resolved conversation" activity messages when snoozed conversations where reopened
2022-03-23 16:08:42 +05:30
Pranav Raj SandGitHub 771cb5b9fc fix: Update scroll event name to fix auto-scroll (#4251)
Fixes #3622
2022-03-23 13:27:50 +05:30
Sojan JoseandGitHub f43018e017 chore: New translations automation.json (Italian) (#4231) 2022-03-22 12:42:10 +05:30
Sojan JoseandGitHub 467f3b9191 chore: Disable fetching new emails after mailbox error (#4176)
- Disabled email fetch job if credentials for the channel isn't working
- notify customers when the email channel isn't working

fixes: https://github.com/chatwoot/chatwoot/issues/4174
2022-03-22 12:14:17 +05:30
Muhsin KelothandGitHub 715400f7ab fix: Disable pre-chat fields for identified users (#4234) 2022-03-22 01:28:22 +05:30
Tejaswini ChileandGitHub 7b4cac746a feat: enhancement to un-send instagram message (#4120) 2022-03-21 17:10:15 +05:30
Sojan JoseandGitHub 6169d172d8 chore: Update translations from Crowdin (#4217) 2022-03-21 13:15:33 +05:30
Tejaswini ChileandGitHub e06a6a7c55 Automation enhancement (#4087) 2022-03-21 13:12:27 +05:30
c608ca37e0 fix: Contact conversations are showing in the chat list (#4078)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-03-21 11:46:43 +05:30
Pranav Raj SandGitHub 6097f4c122 fix: Update survey locale based on the account (#4207) 2022-03-20 13:07:36 +05:30
Fayaz AhmedandGitHub 8372ae418e System default monospace fonts for code elem (#4222) 2022-03-19 20:56:29 +05:30
Sojan JoseandGitHub 71391d982b chore: filter .xml files for Bandwidth SMS (#4206)
MMS sent from an ATT or Verizon phone number seems to be appending a SMIL file in .xml format for MMS.
2022-03-18 19:00:36 +05:30
b77fad52fb fix: Add missing contact information to fix invalid activity messages (#4195)
Co-authored-by: Tejaswini <tejaswini@chatwoot.com>
2022-03-18 13:43:02 +05:30
Sojan JoseandGitHub 40f31cb0a3 chore: Update translations (#4208) 2022-03-18 11:24:22 +05:30
7f8a199b29 chore: Update translations (#4099)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-03-17 20:02:32 +05:30
Muhsin KelothandGitHub 23fe8847d8 feat: Notification action cable event (#4154) 2022-03-17 18:56:36 +05:30
Sojan 430547e026 Merge branch 'hotfix/2.3.1' into develop 2022-03-17 16:55:00 +05:30
Sojan d0702363f4 Merge branch 'hotfix/2.3.1' 2022-03-17 16:54:49 +05:30
Sojan d6d0c79618 Bump version to 2.3.1 2022-03-17 16:53:36 +05:30
Sojan JoseandGitHub 9f8e442333 chore: Improve n+1 queries (#4202)
Fixes the n+1 queries that get triggered while accessing agents endpoint
2022-03-17 15:57:32 +05:30
Pranav Raj SandGitHub 4a3ddad02f fix: Reduce the height of the unread container while campaign is active (#4185) 2022-03-16 14:45:57 +05:30
Tejaswini ChileandGitHub 207a03155e Feat: authenticate direct upload (#4160) 2022-03-16 13:54:18 +05:30
Sojan 796a7805db Merge branch 'release/2.3.0' into develop 2022-03-15 23:04:09 +05:30
Sojan a50ea9c88a Merge branch 'release/2.3.0' 2022-03-15 23:03:59 +05:30
Sojan 711251560b Bump version to 2.3.0 2022-03-15 22:59:54 +05:30
c4837cd7ac feat: End conversation from widget (#3660)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Fayaz Ahmed <15716057+fayazara@users.noreply.github.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-03-15 22:07:30 +05:30
Pranav Raj SandGitHub 4b748e2c8c fix: Delete content_attributes in message destroy API (#4184) 2022-03-15 22:01:16 +05:30
8c8c5a77c8 feat: Show banner message if Chatwoot update available (#3999)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
Co-authored-by: Fayaz Ahmed <15716057+fayazara@users.noreply.github.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
2022-03-15 21:09:14 +05:30
Sojan JoseandGitHub 02dd5ecfab chore: 360Dialog attachments fix (#4173)
fixes: #3701
2022-03-15 17:42:21 +05:30
Muhsin KelothandGitHub f405572e5c Revert "feat: Custom fields in pre-chat form (#4135)" (#4182)
This reverts commit e730804b48.
2022-03-15 16:54:33 +05:30
Sojan JoseandGitHub 25876993ff fix: NoMethodError: undefined method `id' for nil:NilClass (#4172)
fixes: #4171
2022-03-15 14:30:33 +05:30
b3ba8b9513 fix: Could not able to create label (#4161)
* fixes: Could not able to create label

* review fixes

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2022-03-14 20:43:56 +05:30
Fayaz AhmedandGitHub 4a2452173e feat: Added support for Created At, Last Activity At filters in the UI (#4031) 2022-03-14 20:24:53 +05:30
Aswin Dev P.SandGitHub c62d74a01d feat: Display trends in report metrics (#4144) 2022-03-14 18:15:27 +05:30
Sivin VargheseandGitHub 5edf0f2bbe fix: Fix modal responsiveness (#4149) 2022-03-14 18:14:16 +05:30
Fayaz AhmedandGitHub dd1fe4f93a fix: Handle unsanitized markup in DOM (#4110) 2022-03-14 18:13:21 +05:30
Muhsin KelothandGitHub e730804b48 feat: Custom fields in pre-chat form (#4135)
Fixes: #2744
2022-03-14 15:06:56 +05:30
Sivin VargheseandGitHub 578414d788 feat: Adds ability to delete contact without having to type name (#4155) 2022-03-11 17:00:55 +05:30
Sivin VargheseandGitHub 619441ce1c fixes: Showing vue warning in the console (#4152)
* fixes: Showing vue warn with PR#4100

* Minor fixes
2022-03-10 22:06:59 +05:30
Nithin David ThomasandGitHub 3c3b6f90c7 fix: Add base_url to the audio notification file (#4116) 2022-03-10 20:40:47 +05:30
Tejaswini ChileandGitHub 647efa12e7 fix: Attach instagram images with file type story_mentions (#4100) 2022-03-10 20:27:30 +05:30
b3545f42f1 fixes: Contact search is skipping the '+' sign for email. (#4142)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-03-10 11:15:41 +05:30
Pranav Raj SandGitHub fb03b6f434 chore: Upgrade rails to 6.1.4.7 to fix (#4143)
Fixes CVE-2022-21831
2022-03-10 11:01:40 +05:30
Pranav Raj SandGitHub 1aa7d6f7d8 fix: Update brand name in mailers (#4123) 2022-03-08 09:22:57 +05:30
giquieuandGitHub 96b719017b feat: Ability to send voice message to channel (#4064)
Created the possibility to send audio as an attachment via the dashboard.
The channels that can send audio are the same channels that can send any type of attachment.
Used RecordRTC (https://github.com/muaz-khan/RecordRTC) to capture the audio and Wavesurfer (https://github.com/katspaugh/wavesurfer.js) to display the audio waves.
RecordRTC can be used to record videos if necessary.

Fixes #1973
2022-03-04 19:43:07 +05:30
Aswin Dev P.SandGitHub b94e67f5d7 feat: Add agents filter in CSAT reports (#4106)
* add agents filter in csat reports
2022-03-04 03:49:26 -08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Pranav Raj S
c76b588850 chore(deps): bump marked from 2.0.3 to 4.0.10 (#3771)
* chore(deps): bump marked from 2.0.3 to 4.0.10

Bumps [marked](https://github.com/markedjs/marked) from 2.0.3 to 4.0.10.
- [Release notes](https://github.com/markedjs/marked/releases)
- [Changelog](https://github.com/markedjs/marked/blob/master/.releaserc.json)
- [Commits](https://github.com/markedjs/marked/compare/v2.0.3...v4.0.10)

---
updated-dependencies:
- dependency-name: marked
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* use {marked}

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-03-04 14:53:40 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Muhsin KelothNithin David Thomas
a28b911132 chore(deps): bump url-parse from 1.5.7 to 1.5.10 (#4076)
Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.5.7 to 1.5.10.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.5.7...1.5.10)

---
updated-dependencies:
- dependency-name: url-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
2022-03-04 10:58:05 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
6e4ae1a1bc chore(deps): bump commonmarker from 0.23.2 to 0.23.4 (#4108)
Bumps [commonmarker](https://github.com/gjtorikian/commonmarker) from 0.23.2 to 0.23.4.
- [Release notes](https://github.com/gjtorikian/commonmarker/releases)
- [Changelog](https://github.com/gjtorikian/commonmarker/blob/main/CHANGELOG.md)
- [Commits](https://github.com/gjtorikian/commonmarker/compare/v0.23.2...v0.23.4)

---
updated-dependencies:
- dependency-name: commonmarker
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-04 10:57:22 +05:30
Pranav Raj SandGitHub 9583a2dbad chore: Pass sso_account_id to select the account during SSO Login (#4103) 2022-03-03 20:49:51 +05:30
Sojan JoseandGitHub 11adfd2384 chore: remove unnecessary dependent destroy (#4104) 2022-03-03 13:02:02 +05:30
Pranav Raj SandGitHub 28d102f526 fix: Show new message screen instead of input field (#4102) 2022-03-03 12:36:31 +05:30
3d7ca61481 Show email signature with maximum height 8vh, fixes #4021 (#4063)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Fayaz Ahmed <15716057+fayazara@users.noreply.github.com>
Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
2022-03-02 15:28:56 +05:30
Sojan JoseandGitHub 7b9e4982cf Feat: Support MMS in SMS Channel ( Bandwidth ) (#4046)
Ability to send and receive MMS to bandwidth channel

fixes: #3961
2022-03-02 15:09:56 +05:30
4d458c2184 feat: Support respective input types in custom attributes - filters (#4032)
* Support differnt input types in custom attributes

* Handle list type custom attributes

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Vishnu Narayanan <vishnu@chatwoot.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2022-03-02 14:16:42 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
0582d15eb5 chore(deps): bump image_processing from 1.12.1 to 1.12.2 (#4095)
Bumps [image_processing](https://github.com/janko/image_processing) from 1.12.1 to 1.12.2.
- [Release notes](https://github.com/janko/image_processing/releases)
- [Changelog](https://github.com/janko/image_processing/blob/master/CHANGELOG.md)
- [Commits](https://github.com/janko/image_processing/compare/v1.12.1...v1.12.2)

---
updated-dependencies:
- dependency-name: image_processing
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-02 10:31:37 +05:30
915fe3897c chore: Add Github in contact social profiles (#4086)
* Add github in social links

* Code cleanup

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2022-03-01 14:33:26 +05:30
Muhsin KelothandGitHub f08d1b35d0 feat: Toggle automation status (#3991) 2022-03-01 14:14:23 +05:30
a3cb26a317 fix: AudioContext warning when loading widget on Chrome (#3956)
* fix: AudioContext warning when loading widget on Chrome

* minor fixes

* Minor fixes

* adds event on document

* Play audio from parent window through SDK

* Adds notification to dashboard

Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
Co-authored-by: Vishnu Narayanan <vishnu@chatwoot.com>
2022-02-28 21:43:24 +05:30
eee89bf0d8 feat: Show cc from last email on reply editor (#3983)
* Adds last emails to reply editor

* Fixes bug in reply box

* Adds test cases

* Prevents private notes having cc bcc data

* Prevents private notes having cc bcc data

* Init reply head with values

* fix broken tests

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Fayaz Ahmed <15716057+fayazara@users.noreply.github.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2022-02-28 21:42:50 +05:30
Muhsin KelothandGitHub 1de18391b4 fix: Handle invalid country code in contact details (#4081) 2022-02-28 19:48:13 +05:30
Muhsin KelothandGitHub bb16780278 chore: Upgrade utils to 0.0.5 (#4082) 2022-02-28 18:50:29 +05:30
4260441f8c Chore: clean up Reporting Events (#4044)
Tech debt clean up

Fixes #4057

Co-authored-by: Aswin Dev P S <aswin@chatwoot.com>
2022-02-28 18:16:12 +05:30
12c0be002e feat: Format time in reports (#4072)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-02-28 16:54:43 +05:30
Muhsin KelothandGitHub aff14b697f chore: Add webhook URL validation (#4080) 2022-02-28 15:44:02 +05:30
Pranav Raj SandGitHub 87a6266ddc fix: Add resetTriggered flag to fix reset loop (#4079) 2022-02-28 14:53:11 +05:30
Sojan cebb1bf871 Merge branch 'release/2.2.1' into develop 2022-02-28 14:12:36 +05:30
Sojan ed2772dd59 Merge branch 'release/2.2.1' 2022-02-28 14:12:22 +05:30
Sojan 4f19fa763c Bump version to 2.2.1 2022-02-28 14:04:40 +05:30
Muhsin KelothandGitHub 46c1734ba1 feat: Add additional attributes in setUser method (#3958) 2022-02-28 12:10:55 +05:30
9b615f11f1 fix: Consider timezone in the reports (#4027)
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2022-02-28 10:56:24 +05:30
Sojan JoseandGitHub 4ca66c1195 fix: Automation loop on conversation_update (#4071) 2022-02-25 18:01:21 +05:30
Pranav Raj SandGitHub cc74bebc0f feat: Display Account context in the UI (#4069) 2022-02-25 16:36:36 +05:30
2c8a3ef3b2 fix: Message signature going with other channels message (#4024)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-02-25 16:36:10 +05:30
Pranav Raj SandGitHub 6c94768bdb feat: Add flat design for widget (#4065) 2022-02-25 16:18:18 +05:30
Nithin David ThomasandGitHub dcecbf4b80 Fix: show empty emails in chat (#4030) 2022-02-24 21:33:16 -08:00
Vishnu NarayananandGitHub d590b0ebc4 fix: nokogiri gem build failure in docker (#4060)
tar (child): xz: Cannot exec: No such file or directory¶

Starting in v1.13.2, the source archive used for libxml2 and libxslt is compressed with xz (previous versions were compressed with gzip. As a result, when compiling from source, your system will need to have xz installed in order to extract the source code for these libraries.
2022-02-23 23:27:19 +05:30
Pranav Raj SandGitHub 85f19fa25a chore: Show error messages from server in agent APIs (#4055) 2022-02-23 20:18:20 +05:30
Tejaswini ChileandGitHub 1ca1b4d36b feat: bulk actions to update conversation objects (#3934)
Added the endpoints for bulk updating conversation objects

Fixes: #3845 #3940 #3943
2022-02-23 16:53:36 +05:30
Sivin VargheseandGitHub 9059f5906a fix: Throwing error when click load more conversation (#4049) 2022-02-23 16:48:53 +05:30
Sojan JoseandGitHub eb0da2026f chore: Bundle update (#4038)
- address the nokogiri security issues
2022-02-22 16:06:04 +05:30
Aswin Dev P.SandGitHub e348db1e37 Fix: add option to choose 24 hour working slot (#4018)
* Add option to choose 24 hour working slot

* fix spec

* add check to update open hour and close hour for open all day

* update 24 hour working slot change in widget side

* add validation to check open_all_day and closed_all_day true at the same time
2022-02-22 01:28:49 -08:00
Muhsin KelothandGitHub 7ba24b90c4 feat: Add chatwoot:error sdk event (#3998) 2022-02-21 09:40:11 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
3b015d4dd8 chore(deps): bump url-parse from 1.5.3 to 1.5.7 (#4016)
Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.5.3 to 1.5.7.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.5.3...1.5.7)

---
updated-dependencies:
- dependency-name: url-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-20 11:30:19 +05:30
ea44a32758 feat: Add hCaptcha for public forms (#4017)
- added hCaptcha based verification for chatwoot signups

Co-authored-by: Sojan <sojan@pepalo.com>
2022-02-18 20:02:50 +05:30
Muhsin KelothandGitHub 80d83b401c feat: Show refresh modal on action cable disconnect (#4010) 2022-02-18 11:09:43 +05:30
Sivin VargheseandGitHub ea3e011d3f fix: Auto scroll to bottom when agent sends a message (#3764) 2022-02-17 20:42:13 +05:30
Sivin VargheseandGitHub 2586c8e7e5 fix: Lock icon colour issue in private messages (#4009)
* fix: Lock icon colour issue in private messages

* minor fixes
2022-02-17 18:58:53 +05:30
Sojan b9025a98d3 Merge branch 'release/2.2.0' into develop 2022-02-15 23:41:56 +05:30
Sojan b6c3d61163 Merge branch 'release/2.2.0' 2022-02-15 23:41:48 +05:30
Sojan 991f291c69 Bump version to 2.2.0 2022-02-15 23:39:57 +05:30
Fayaz AhmedandGitHub 5ad6db07b4 fix: Automation Bugs and minor enhancements (#3936) 2022-02-15 23:36:29 +05:30
Sivin VargheseandGitHub e345a4486d fix: Throwing NavigationDuplicated error (#3978) 2022-02-15 22:49:53 +05:30
Sojan JoseandGitHub 5ad7e93059 chore: Discard activities from email notifiable messages (#3989) 2022-02-15 18:15:45 +05:30
Pranav Raj SandGitHub 505c93c467 revert: "fix: html content is available and mail_part empty" (#3986)
Reverts de4e4c6
2022-02-15 17:19:28 +05:30
464e12ceb7 fix: Update auto reply and hide reply time for email inbox (#3985)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-02-15 17:11:28 +05:30
Aswin Dev P.SandGitHub e6f8895c1b feat: Group by filter in reports (#3973) 2022-02-15 17:10:49 +05:30
Sojan JoseandGitHub a703ef2de6 chore: Update translations (#3980) 2022-02-15 16:22:42 +05:30
94a473c9f8 chore: Delayed deploy of direct uploads (#3966)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-02-15 16:16:54 +05:30
Pranav Raj SandGitHub 2591a04c0b chore: Remove message_signature_enabled from users (#3982) 2022-02-15 12:10:53 +05:30
Nithin David ThomasandGitHub 351a3dc372 feat: Adds email signature form to profile settings (#3906) 2022-02-15 10:38:24 +05:30
Tejaswini ChileandGitHub d5c9193d1a feat: Add specs for reply-to changes (#3959) 2022-02-15 10:28:04 +05:30
Tejaswini ChileandGitHub de4e4c6f65 fix: html content is available and mail_part empty (#3964) 2022-02-14 20:22:58 +05:30
dependabot[bot]andGitHub f34a2e6115 chore(deps): bump puma from to 5.6.2, rails to 6.1.4.6 (#3969) 2022-02-14 20:09:21 +05:30
Pranav Raj SandGitHub 9c7044c81b fix: Use default from attribute if undefined (#3977) 2022-02-14 17:37:40 +05:30
Sojan JoseandGitHub 04148b95a6 chore: Update translations (#3937) 2022-02-14 17:11:43 +05:30
Sojan JoseandGitHub 3d73d8935a feat: Add the ability for a super admin to define account limits (#3946)
Fixes: #3773
2022-02-14 15:55:08 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
edd93ca4a3 chore(deps): bump follow-redirects from 1.14.7 to 1.14.8 (#3970)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.7 to 1.14.8.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.7...v1.14.8)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-14 15:53:13 +05:30
Nithin David ThomasandGitHub 2c15d5b4f3 fix: Custom attribute not getting changed on conversations (#3965) 2022-02-14 15:52:15 +05:30
Nithin David ThomasandGitHub 74d9203868 feat: Show the sender email address (#3974) 2022-02-14 15:46:20 +05:30
Tejaswini ChileandGitHub 25001689ec fix: Use to_i in campaign scheduled_at key (#3968) 2022-02-14 11:20:31 +05:30
Muhsin KelothandGitHub 2da618bd5e feat: Add conversation_created event in webhooks (#3949) 2022-02-14 09:44:25 +05:30
ba7f387dde chore: Reduces clutter in inbox settings page (#3960)
* chore: Reduces clutter in inbox settings page

* Fix spacing issues

Co-authored-by: Fayaz Ahmed <15716057+fayazara@users.noreply.github.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2022-02-11 08:38:42 +05:30
Sivin VargheseandGitHub 1115bd18ed fix: Icon colors are not recognizable in bubble (#3957) 2022-02-11 07:15:07 +05:30
e9a162e71a feat: Prefer reply-to over from in email channels (#3837)
Co-authored-by: root <root@support.loomio.com>
2022-02-10 19:20:30 +05:30
Nithin David ThomasandGitHub 4201b57315 fix: Fixes console error with image component in widget (#3955) 2022-02-10 12:48:22 +05:30
Sivin VargheseandGitHub 0ec3a56041 fix: Blank page after deleting contact (#3948) 2022-02-09 14:35:18 +05:30
Aswin Dev P.SandGitHub 4e416b4d51 Add openssl verify mode and email encryption for smtp configuration (#3885)
* Add openssl verify mode and email encryption for smtp configuration
* Add spec for code coverage
2022-02-08 03:26:13 -08:00
Muhsin KelothandGitHub 4ae9ed8f94 chore: Assign the conversation to agent who opens the conversation (#3925) 2022-02-08 16:39:19 +05:30
Pranav Raj SandGitHub 7b2ff2f112 feat: Show assignee name in folders view (#3933) 2022-02-08 10:38:14 +05:30
Sojan JoseandGitHub e2ff1b1a15 chore: Update German translations (#3928) 2022-02-08 10:12:34 +05:30
Sivin VargheseandGitHub d891c1e7b5 Fixes API call failing after opening the saved filter (#3932) 2022-02-08 10:11:56 +05:30
Pranav Raj SandGitHub 2a07eae69f fix: URL encode params on Platform API call (#3929)
Add URL encoding for the params in platform API calls.

Fixes: product#307
Fixes: #2186
2022-02-07 15:17:36 -08:00
375d943a48 fix: Avoid filter payload changing to null on load more conversations (#3926)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
2022-02-07 22:14:21 +05:30
Sojan JoseandGitHub 4c0f6c1333 chore: Update translations (#3927) 2022-02-07 20:17:49 +05:30
97675c61ea chore: Update to the latest translations (#3923)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-02-07 17:19:34 +05:30
Muhsin KelothandGitHub f4c0ce5dfa chore: Assign to agent who create new message from contact panel (#3924) 2022-02-07 13:43:16 +05:30
Muhsin KelothandGitHub 9f37a6e2ba fix: Render valid urls only in custom attributes (#3921) 2022-02-07 13:04:27 +05:30
047070ad87 chore: Fix translations in Swedish language (#3765)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-02-07 12:44:05 +05:30
1a3d39a7f3 feat: Shows an alert while replying if chats are not assigned to them (#3867)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-02-07 12:27:20 +05:30
Muhsin KelothandGitHub a737f89c47 fix: Ongoing campaign URL validation (#3890) 2022-02-04 14:38:18 +05:30
Tejaswini ChileandGitHub a7987d4d1c feat: Add date custom filter (#3892) 2022-02-04 13:14:20 +05:30
SagarandGitHub 32673ea8b4 chore: Allow Self Hosted Instances to use Support Inbox within Dashboard for Agents (#3907) 2022-02-04 09:58:53 +05:30
Sojan JoseandGitHub 9454c6b14f Fix: Conversation filter permissions (#3908)
fixes: chatwoot/product#225
2022-02-03 18:25:28 -08:00
Sojan JoseandGitHub cf10f3d03b chore: Provider APIs for SMS Channel - Bandwidth (#3889)
fixes: #3888
2022-02-03 15:22:13 -08:00
Tejaswini ChileandGitHub fba7f40bee Fix: Custom filter distinct select (#3895) 2022-02-03 14:39:47 +05:30
Nithin David ThomasandGitHub 5c6958482f fix: Shows tooltip and avatar for template messages (#3898) 2022-02-03 12:14:34 +05:30
Pranav Raj SandGitHub 8dcb4a5ed4 chore: Add resolve action in Dialogflow Integration (#3900) 2022-02-03 12:05:39 +05:30
Pranav Raj SandGitHub dbb50e5923 fix: Update font-size/width to fix iOS input zoom (#3894) 2022-02-03 10:11:02 +05:30
Muhsin KelothandGitHub 903072ef14 chore: Add custom attributes in conversation webhook payload (#3839) 2022-02-03 10:05:56 +05:30
fc1f257793 feat: Clone automation rules (#3893)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2022-02-03 09:14:22 +05:30
Tejaswini ChileandGitHub 8821106da9 Fix: Added the backend validation for name (#3878)
- Added the backend validation for name
- Add message size constraint
2022-02-02 14:21:17 -08:00
Tejaswini ChileandGitHub e99ea0b582 feat: Email signature API support (#3874) 2022-02-02 19:37:33 +05:30
193a531e49 feat: Edit automation modal (#3876)
* Add automation modal

* Fix the v-model for automations

* Actions and Condition dropdowns for automations

* Fix merge conflicts

* Handle event change and confirmation

* Appends new action

* Removes actions

* Automation api integration

* Api integration for creating automations

* Registers vuex module to the global store

* Automations table

* Updarted labels and actions

* Integrate automation api

* Fixes the mutation error - removed the data key wrapper

* Fixed the automation condition models to work with respective event types

* Remove temporary fixes added to the api request

* Displa timestamp and automation status values

* Added the clone buton

* Removed uncessary helper method

* Specs for automations

* Handle WIP code

* Remove the payload wrap

* Fix the action query payload

* Removed unnecessary files

* Disabled Automations routes

* Ability to delete automations

* Fix specs

* Edit automation modal

* Edit automation modal and api integration

* Replaced hardcoded values

* Using absolute paths

* Update app/javascript/dashboard/routes/dashboard/settings/automation/EditAutomationRule.vue

Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>

* Update app/javascript/dashboard/routes/dashboard/settings/automation/Index.vue

Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>

* Intendation fix

* Disable automation route

* Minor fix

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2022-02-02 19:15:02 +05:30
91b9168fae feat: Add the ability to add new automation rule (#3459)
* Add automation modal

* Fix the v-model for automations

* Actions and Condition dropdowns for automations

* Fix merge conflicts

* Handle event change and confirmation

* Appends new action

* Removes actions

* Automation api integration

* Api integration for creating automations

* Registers vuex module to the global store

* Automations table

* Updarted labels and actions

* Integrate automation api

* Fixes the mutation error - removed the data key wrapper

* Fixed the automation condition models to work with respective event types

* Remove temporary fixes added to the api request

* Displa timestamp and automation status values

* Added the clone buton

* Removed uncessary helper method

* Specs for automations

* Handle WIP code

* Remove the payload wrap

* Fix the action query payload

* Removed unnecessary files

* Disabled Automations routes

* Ability to delete automations

* Fix specs

* Fixed merge conflicts

Co-authored-by: Fayaz Ahmed <15716057+fayazara@users.noreply.github.com>
Co-authored-by: fayazara <fayazara@gmail.com>
Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2022-02-02 18:46:07 +05:30
1f3c5002b3 feat: Shows agent avatar for message sent by agent (#3884)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2022-02-01 16:34:54 +05:30
Vishnu NarayananandGitHub c0276d252a chore: add aws rds root cert for tls connection (#3812)
* chore: add aws rds cert for tls connection

* add aws rds ca cert

* remove intermediate ca cert
2022-02-01 16:01:25 +05:30
Fayaz AhmedandGitHub 52d1821cd3 feature: Filtering conversations and contacts with custom attributes (#3851) 2022-02-01 13:13:55 +05:30
Pranav Raj SandGitHub e0d24e0a73 chore: Use globalConfig in NetworkNotification (#3871) 2022-02-01 12:25:51 +05:30
Prithvi TharunandGitHub b1e1d7e423 fix: Update the copy to use inclusive language (#3880)
Fixes #3879
2022-02-01 11:12:20 +05:30
Tejaswini ChileandGitHub a95d249ec1 Feat: Custom attribute advanced filter (#3818) 2022-01-31 13:36:44 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
a1034a70c4 chore(deps): bump node-fetch from 2.6.1 to 2.6.7 (#3862)
Bumps [node-fetch](https://github.com/node-fetch/node-fetch) from 2.6.1 to 2.6.7.
- [Release notes](https://github.com/node-fetch/node-fetch/releases)
- [Commits](https://github.com/node-fetch/node-fetch/compare/v2.6.1...v2.6.7)

---
updated-dependencies:
- dependency-name: node-fetch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-28 11:41:12 -08:00
Tejaswini ChileandGitHub 357a374478 Fix: Update yarn (#3866) 2022-01-28 13:19:22 +05:30
Tejaswini ChileandGitHub 1467a8fa33 Fix: parse verification mail (#3864)
Email parsing logic was stripping of HTML tables which was causing the issue in this case.

Fixes: #3731
2022-01-27 15:45:26 -08:00
13eaff156a Added keybindings for commandbar (#3854)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2022-01-27 17:57:33 +05:30
Sivin VargheseandGitHub e7e46590a4 bug: Fixes sidebar does not show an active page for settings page (#3861) 2022-01-27 17:39:21 +05:30
Frederik KriewitzandGitHub 17018d392d chore: update deployment scripts to install sudo (#3841)
* Update setup_20.04.sh to install sudo
* Update setup_18.04.sh to install sudo
2022-01-27 16:55:27 +05:30
Tejaswini ChileandGitHub 69eaf3ff7f feat: Activestorage direct upload (#3768) 2022-01-27 15:57:22 +05:30
Sivin VargheseandGitHub cd6c9a8fe9 enhancement: Custom views (#3838)
* enhancement: Custom views

* Review fixes
2022-01-27 08:26:12 +05:30
Pranav Raj SandGitHub 5710b7559e fix: Fix email template to render the chat bubble properly (#3858) 2022-01-26 17:52:08 -08:00
Shivam ChaharandGitHub 8e6d8e7654 fix: Validate avatar only when it has changed (#3849) 2022-01-26 16:01:41 -08:00
59deffc7e3 feat: Ability to toggle conversation continuity via email (#3817)
Fixes: #3368
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-01-26 15:59:48 -08:00
Sojan JoseandGitHub 34e8ad9dc5 feat: Unify user and super admin credentials (#3830)
Fixes: #3061, #3489
2022-01-25 16:58:49 -08:00
Pranav Raj SandGitHub 23965fbaa3 fix: Return if window.$chatwoot is already defined (#3847) 2022-01-25 16:39:19 -08:00
8cff690640 feat: Support multiple file upload on dashboard (#3748)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2022-01-24 18:46:58 -08:00
Sivin VargheseandGitHub b304f5a826 feat: Adds the ability to delete a segment (#3836)
* feat: Adds the ability to delete a segment

* Minor fixes

* Small changes
2022-01-24 17:37:43 +05:30
Prithvi TharunandGitHub e90d064648 fix: Fixes typo in documentation on sidebar 2022-01-24 16:03:37 +05:30
Sivin VargheseandGitHub 504fc24fb3 feat: Add the ability to save filters for contact (#3791) 2022-01-21 14:11:59 -08:00
Sojan JoseandGitHub 693f2531ab feat: Support 360Dialog Interactive Templates (#3823)
fixes: #3795
2022-01-20 17:09:30 -08:00
Tejaswini ChileandGitHub e997aaceb7 Fix: Email Parsing for html fix (#3659) 2022-01-20 15:55:35 -08:00
Tejaswini ChileandGitHub a7c947aeae feat: Clone and update automation rules (#3782)
- endpoints to clone and update automation rules

fixes: #3740
2022-01-20 15:30:21 -08:00
ab864a86fd fix: File and emoji pickers are disabled by default (#3803)
File and emoji pickers should be enabled by default.

Fixes: #3560

Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
2022-01-19 22:20:04 -08:00
Shivam ChaharandGitHub 9617137688 chore: Account auto resolution time validation (#3651) 2022-01-19 13:16:21 +05:30
1b36143d27 fix: Fixes alignment of widget unread view close button (#3806)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2022-01-19 12:43:04 +05:30
241e7d0c29 Enhancement: Add background color to draggable blocks in the sidebar (#3778)
* add background

* update background color in contact info

Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
2022-01-19 11:26:52 +05:30
Sivin VargheseandGitHub 185f916b2a feat: Adds the ability to delete the saved custom views (#3780)
* feat: Adds the ability to delete the saved custom views

* Removed unused tag

* Review fixes

* Review fixes

* Update DeleteCustomViews.vue
2022-01-19 09:40:32 +05:30
Sojan 4a68d13310 Merge branch 'hotfix/2.1.1' into develop 2022-01-17 23:37:52 -08:00
Sojan ceb2b9673c Merge branch 'hotfix/2.1.1' 2022-01-17 23:37:45 -08:00
Sojan Jose 1749b4eb61 chore: Clean up assignment logic (#3763) 2022-01-17 23:36:54 -08:00
Sojan 346d89ecc0 Bump version to 2.1.1 2022-01-17 23:33:47 -08:00
c64cffb294 fix: Update the hardcoded scroll-width to match with the current table length (#3774)
Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
2022-01-18 07:37:51 +05:30
6deb3764dc Fix: Pagination bug with filters payload (#3747)
* Removes the last operators value

* Moved removing the last operator to the payload generator

* Update app/javascript/dashboard/helper/filterQueryGenerator.js

This makes sure the last operator is never sent to the request

Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>

* Fixed the test specs

* Fixed the intendation and added comments

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
2022-01-17 20:35:53 +05:30
4398734bdf feat: Adds the ability to have custom view for conversations (#3666)
* feat: Adds the ability to save custom filters and display folders on the sidebar

* Minor fixes

* Review fixes

* Review fixes

* i18n fixes

* Shows conversations when the user click on the folder sidebar item

* Spacing fixes

* Review fixes

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2022-01-17 09:18:54 +05:30
Sojan JoseandGitHub 290196d43b chore: Clean up assignment logic (#3763) 2022-01-13 21:38:10 -08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
fcd2b892bf chore(deps): bump follow-redirects from 1.14.5 to 1.14.7 (#3760)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.5 to 1.14.7.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.5...v1.14.7)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-13 12:46:09 -08:00
cab5a3e53b chore: Add a redirect link to the conversation in success toast message (#3711)
When creating a conversation from the contacts tab now we can go directly to the conversation by clicking the link in the success message

Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-01-13 11:15:40 -08:00
Muhsin KelothandGitHub 359caf754c fix: Update contact bio style to solve overflow (#3753) 2022-01-13 10:57:12 -08:00
Sivin VargheseandGitHub 7fcac532df fix: Update timestamp style on the private message from bot (#3758) 2022-01-13 10:54:52 -08:00
Nithin David ThomasandGitHub d57be3ffae fix: Allow private note without selecting a tweet (#3754) 2022-01-13 10:54:00 -08:00
Pranav Raj SandGitHub 5046dcaf61 fix: Update broken CSS Styles in liquid template (#3756) 2022-01-13 10:52:48 -08:00
Tejaswini ChileandGitHub 7df68c6388 Feat: Automations Actions (#3564) 2022-01-13 11:21:06 +05:30
Sojan JoseandGitHub 905fd62974 chore: Support Whatsapp Interactive messages (360Dialog) (#3751)
Render button and list reply clicks as text messages in agent dashboard

fixes: #3684
2022-01-12 19:42:23 -08:00
Sojan JoseandGitHub 1c99294c8c chore: Fix 360Dialog template message breakage (#3750)
Template parsing fails when regexp characters are present in templates.

Fixes: #3587
2022-01-12 17:41:42 -08:00
94209d29cb fix: Fixes widget unread view blocking parent page (#3658)
Co-authored-by: Pranav <pranav@chatwoot.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2022-01-12 12:27:16 -08:00
9c31d7c672 feat: Use vue-router on widget route management (#3415)
* feat: Add vue-router to widget

Co-authored-by: Pranav <pranav@chatwoot.com>

* Move to dynamic imports

* Move to routerMixin

* Fix popup button display

* Remove unnecessary import

* router -> route

* Fix open state

* Fix issues

* Remove used CSS

* Fix specs

* Fix specs

* Fix widgetColor specs

* Fix mutation specs

* Fixes broken lint errors

* Fixes issues with widget flow

Co-authored-by: Nithin <nithin@chatwoot.com>
Co-authored-by: Nithin David <1277421+nithindavid@users.noreply.github.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2022-01-12 16:25:27 +05:30
Sojan 991a42c417 Merge branch 'release/2.1.0' into develop 2022-01-11 19:28:49 -08:00
Sojan 4a902fce6d Merge branch 'release/2.1.0' 2022-01-11 19:28:42 -08:00
Sojan 5475746a87 Bump version to 2.1.0 2022-01-11 19:26:23 -08:00
Vishnu NarayananandGitHub acba07cf6e chore: revert GlobalConfig changes (#3702)
Priority to GlobalConfig changes are reverted and for the time being, env vars will take precedence if set.

Fixes #3699
2022-01-11 19:20:23 -08:00
Sojan JoseandGitHub f44be0b1e6 fix: SuperAdmin Improvements (#3733)
- Fix broken access tokens pages
- Fix broken avatar images in the super admin users tab
- Fix broken pagination links in super admin
- Add agent bot tabs

Fixes: #2021
2022-01-11 19:00:00 -08:00
Prithvi TharunandGitHub bec1266242 fix: Update shortcode -> short code (#3708)
Closes #3672
2022-01-11 15:20:52 -08:00
Sojan JoseandGitHub ebb04487a8 chore: Prevent null in Contact JSONB attributes (#3730) 2022-01-11 15:14:55 -08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
de37975be4 chore(deps): bump google-protobuf from 3.18.1 to 3.19.2 (#3714)
Bumps [google-protobuf](https://github.com/protocolbuffers/protobuf) from 3.18.1 to 3.19.2.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/master/generate_changelog.py)
- [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.18.1...v3.19.2)

---
updated-dependencies:
- dependency-name: google-protobuf
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-11 13:25:44 -08:00
Axel TietjeandGitHub 8a26d78623 chore: fix german translations (#3724) 2022-01-12 00:36:57 +05:30
Aswin Dev P.SandGitHub 7ee7062843 feat: Toggle to disallow users to send messages after a conversation is resolved (#3605) 2022-01-11 00:32:03 -08:00
Tejaswini ChileandGitHub a0884310f4 feat: Save automation rules (#3359) 2022-01-10 12:41:59 +05:30
Anisha-artandGitHub 9a9462f5cb fix: Removed extra space before "." (#3717)
Fixes Removed extra space before "." as mentioned in the issue: #3669
2022-01-09 22:27:45 -08:00
Prithvi TharunandGitHub 9b6f1df871 fix: Update copy in canned responses (#3716)
Fixes #3669
2022-01-09 22:26:35 -08:00
Anisha-artandGitHub ab4d63a006 Error with plurals in Invite members onboarding guide. Issue #3671 (#3718)
Updated "email address" to "email addresses" in the Invite your team members guide.
2022-01-10 10:29:16 +05:30
Fayaz AhmedandGitHub 0745ceb430 Chore: Update ninja keys to 1.1.9 (#3710) 2022-01-07 13:52:08 -08:00
Pranav Raj SandGitHub f7ed7756d9 chore: Use logo thumbnail instead of logo in the sidebar (#3706) 2022-01-06 15:09:01 -08:00
Muhsin KelothandGitHub e1fe4f09bc chore: Add subscription delete API (#3703) 2022-01-06 21:40:43 +05:30
Nayan PatelandGitHub a61dae0bb2 General updates and add CODEOWNERS (#3685) 2022-01-05 12:37:22 +05:30
Nithin David ThomasandGitHub fade94658a chore: Show scrollbar for sidebar only on hover (#3688) 2022-01-05 09:37:43 +05:30
Vishnu NarayananandGitHub e775d22b98 feat: add new view and controller to display app configs together (#3563)
fixes: #3578
2022-01-04 21:50:16 +05:30
Tejaswini ChileandGitHub e304ad27cb Chore: Custom filter api docs updated (#3668) 2022-01-04 13:10:29 +05:30
Prithvi TharunandGitHub 3e8ae3865f chore: Update the copy for new password in profile settings (#3676)
Fixes #3656
2022-01-02 22:43:50 -08:00
Nithin David ThomasandGitHub 223fb2b7e4 chore: Updates design of resolve context menu (#3667) 2021-12-29 22:24:11 +05:30
Nithin David ThomasandGitHub 666028a443 feat: Adds multiple path support for fluent icons (#3665)
* feat: Adds multiple path support for icons

* Changes dashboard icon
2021-12-29 18:01:49 +05:30
1232 changed files with 38927 additions and 6917 deletions
+9
View File
@@ -22,6 +22,9 @@ checks:
enabled: true
config:
threshold: 300
method-lines:
config:
threshold: 50
exclude_patterns:
- 'spec/'
- '**/specs/'
@@ -44,3 +47,9 @@ exclude_patterns:
- 'app/javascript/shared/constants/countries.js'
- 'app/javascript/dashboard/components/widgets/conversation/advancedFilterItems/languages.js'
- 'app/javascript/dashboard/routes/dashboard/contacts/contactFilterItems/index.js'
- 'app/javascript/dashboard/routes/dashboard/settings/automation/constants.js'
- 'app/javascript/dashboard/components/widgets/FilterInput/FilterOperatorTypes.js'
- 'app/javascript/dashboard/routes/dashboard/settings/reports/constants.js'
- 'app/javascript/dashboard/i18n/index.js'
- 'app/javascript/widget/i18n/index.js'
- 'app/javascript/survey/i18n/index.js'
+2 -2
View File
@@ -29,8 +29,8 @@ module.exports = {
'vue/html-self-closing': 'off',
"vue/no-v-html": 'off',
'vue/singleline-html-element-content-newline': 'off',
'import/extensions': ['off']
'import/extensions': ['off'],
'no-console': 'error'
},
settings: {
'import/resolver': {
+13
View File
@@ -11,10 +11,12 @@ Metrics/ClassLength:
Max: 125
Exclude:
- 'app/models/conversation.rb'
- 'app/models/contact.rb'
- 'app/mailers/conversation_reply_mailer.rb'
- 'app/models/message.rb'
- 'app/builders/messages/facebook/message_builder.rb'
- 'app/controllers/api/v1/accounts/contacts_controller.rb'
- 'app/listeners/action_cable_listener.rb'
RSpec/ExampleLength:
Max: 25
Style/Documentation:
@@ -25,6 +27,8 @@ Style/FrozenStringLiteralComment:
Enabled: false
Style/SymbolArray:
Enabled: false
Style/OpenStructUse:
Enabled: false
Style/OptionalBooleanParameter:
Exclude:
- 'app/services/email_templates/db_resolver_service.rb'
@@ -67,14 +71,20 @@ Rails/ApplicationController:
- 'app/controllers/platform_controller.rb'
- 'app/controllers/public_controller.rb'
- 'app/controllers/survey/responses_controller.rb'
Rails/CompactBlank:
Enabled: false
Rails/EnvironmentVariableAccess:
Enabled: false
Rails/TimeZoneAssignment:
Enabled: false
Rails/RedundantPresenceValidationOnBelongsTo:
Enabled: false
Style/ClassAndModuleChildren:
EnforcedStyle: compact
Exclude:
- 'config/application.rb'
Style/MapToHash:
Enabled: false
RSpec/NestedGroups:
Enabled: true
Max: 4
@@ -82,6 +92,8 @@ RSpec/MessageSpies:
Enabled: false
RSpec/StubbedMock:
Enabled: false
RSpec/FactoryBot/SyntaxMethods:
Enabled: false
Naming/VariableNumber:
Enabled: false
Metrics/MethodLength:
@@ -118,6 +130,7 @@ Rails/ReversibleMigration:
- 'db/migrate/20191020085608_rename_old_tables.rb'
- 'db/migrate/20191126185833_update_user_invite_foreign_key.rb'
- 'db/migrate/20191130164019_add_template_type_to_messages.rb'
- 'db/migrate/20210513083044_remove_not_null_from_webhook_url_channel_api.rb'
Rails/BulkChangeTable:
Exclude:
- 'db/migrate/20161025070152_removechannelsfrommodels.rb'
+1 -1
View File
@@ -50,7 +50,7 @@ decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
This Code of Conduct applies within all community spaces and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
+1 -1
View File
@@ -2,4 +2,4 @@
Thanks for taking the time to contribute! :tada::+1:
Please refer to our [Contributing Guide](https://www.chatwoot.com/docs/contributing-guide) for detailed instructions.
Please refer to our [Contributing Guide](https://www.chatwoot.com/docs/contributing-guide) for detailed instructions on how to contribute.
+2 -2
View File
@@ -42,7 +42,7 @@ gem 'down', '~> 5.0'
gem 'aws-sdk-s3', require: false
gem 'azure-storage-blob', require: false
gem 'google-cloud-storage', require: false
gem 'image_processing'
gem 'image_processing', '~> 1.12.2'
##-- gems for database --#
gem 'groupdate'
@@ -102,7 +102,7 @@ gem 'sentry-ruby'
gem 'sentry-sidekiq'
##-- background job processing --##
gem 'sidekiq'
gem 'sidekiq', '~> 6.4.0'
# We want cron jobs
gem 'sidekiq-cron'
+206 -205
View File
@@ -9,73 +9,73 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (6.1.4.3)
actionpack (= 6.1.4.3)
activesupport (= 6.1.4.3)
actioncable (6.1.4.7)
actionpack (= 6.1.4.7)
activesupport (= 6.1.4.7)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (6.1.4.3)
actionpack (= 6.1.4.3)
activejob (= 6.1.4.3)
activerecord (= 6.1.4.3)
activestorage (= 6.1.4.3)
activesupport (= 6.1.4.3)
actionmailbox (6.1.4.7)
actionpack (= 6.1.4.7)
activejob (= 6.1.4.7)
activerecord (= 6.1.4.7)
activestorage (= 6.1.4.7)
activesupport (= 6.1.4.7)
mail (>= 2.7.1)
actionmailer (6.1.4.3)
actionpack (= 6.1.4.3)
actionview (= 6.1.4.3)
activejob (= 6.1.4.3)
activesupport (= 6.1.4.3)
actionmailer (6.1.4.7)
actionpack (= 6.1.4.7)
actionview (= 6.1.4.7)
activejob (= 6.1.4.7)
activesupport (= 6.1.4.7)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (6.1.4.3)
actionview (= 6.1.4.3)
activesupport (= 6.1.4.3)
actionpack (6.1.4.7)
actionview (= 6.1.4.7)
activesupport (= 6.1.4.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.4.3)
actionpack (= 6.1.4.3)
activerecord (= 6.1.4.3)
activestorage (= 6.1.4.3)
activesupport (= 6.1.4.3)
actiontext (6.1.4.7)
actionpack (= 6.1.4.7)
activerecord (= 6.1.4.7)
activestorage (= 6.1.4.7)
activesupport (= 6.1.4.7)
nokogiri (>= 1.8.5)
actionview (6.1.4.3)
activesupport (= 6.1.4.3)
actionview (6.1.4.7)
activesupport (= 6.1.4.7)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
active_record_query_trace (1.8)
activejob (6.1.4.3)
activesupport (= 6.1.4.3)
activejob (6.1.4.7)
activesupport (= 6.1.4.7)
globalid (>= 0.3.6)
activemodel (6.1.4.3)
activesupport (= 6.1.4.3)
activerecord (6.1.4.3)
activemodel (= 6.1.4.3)
activesupport (= 6.1.4.3)
activerecord-import (1.2.0)
activerecord (>= 3.2)
activestorage (6.1.4.3)
actionpack (= 6.1.4.3)
activejob (= 6.1.4.3)
activerecord (= 6.1.4.3)
activesupport (= 6.1.4.3)
activemodel (6.1.4.7)
activesupport (= 6.1.4.7)
activerecord (6.1.4.7)
activemodel (= 6.1.4.7)
activesupport (= 6.1.4.7)
activerecord-import (1.3.0)
activerecord (>= 4.2)
activestorage (6.1.4.7)
actionpack (= 6.1.4.7)
activejob (= 6.1.4.7)
activerecord (= 6.1.4.7)
activesupport (= 6.1.4.7)
marcel (~> 1.0.0)
mini_mime (>= 1.1.0)
activesupport (6.1.4.3)
activesupport (6.1.4.7)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
zeitwerk (~> 2.3)
acts-as-taggable-on (8.1.0)
activerecord (>= 5.0, < 6.2)
acts-as-taggable-on (9.0.1)
activerecord (>= 6.0, < 7.1)
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
administrate (0.16.0)
administrate (0.17.0)
actionpack (>= 5.0)
actionview (>= 5.0)
activerecord (>= 5.0)
@@ -85,46 +85,46 @@ GEM
momentjs-rails (~> 2.8)
sassc-rails (~> 2.1)
selectize-rails (~> 0.6)
annotate (3.1.1)
activerecord (>= 3.2, < 7.0)
annotate (3.2.0)
activerecord (>= 3.2, < 8.0)
rake (>= 10.4, < 14.0)
ast (2.4.2)
attr_extras (6.2.4)
attr_extras (6.2.5)
aws-eventstream (1.2.0)
aws-partitions (1.513.0)
aws-sdk-core (3.121.1)
aws-partitions (1.556.0)
aws-sdk-core (3.126.2)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-partitions (~> 1, >= 1.525.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-kms (1.49.0)
aws-sdk-core (~> 3, >= 3.120.0)
aws-sdk-kms (1.54.0)
aws-sdk-core (~> 3, >= 3.126.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.103.0)
aws-sdk-core (~> 3, >= 3.120.0)
aws-sdk-s3 (1.112.0)
aws-sdk-core (~> 3, >= 3.126.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
aws-sigv4 (1.4.0)
aws-eventstream (~> 1, >= 1.0.2)
azure-storage-blob (2.0.1)
azure-storage-blob (2.0.3)
azure-storage-common (~> 2.0)
nokogiri (~> 1.11.0.rc2)
azure-storage-common (2.0.2)
nokogiri (~> 1, >= 1.10.8)
azure-storage-common (2.0.4)
faraday (~> 1.0)
faraday_middleware (~> 1.0.0.rc1)
faraday_middleware (~> 1.0, >= 1.0.0.rc1)
net-http-persistent (~> 4.0)
nokogiri (~> 1.11.0.rc2)
nokogiri (~> 1, >= 1.10.8)
barnes (0.0.9)
multi_json (~> 1)
statsd-ruby (~> 1.1)
bcrypt (3.1.16)
bindex (0.8.1)
bootsnap (1.9.1)
msgpack (~> 1.0)
brakeman (5.1.1)
bootsnap (1.10.3)
msgpack (~> 1.2)
brakeman (5.2.1)
browser (5.3.1)
builder (3.2.4)
bullet (6.1.5)
bullet (7.0.1)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
bundle-audit (0.1.0)
@@ -135,13 +135,13 @@ GEM
byebug (11.1.3)
climate_control (1.0.1)
coderay (1.1.3)
commonmarker (0.23.2)
commonmarker (0.23.4)
concurrent-ruby (1.1.9)
connection_pool (2.2.5)
crack (0.4.5)
rexml
crass (1.0.6)
cypress-on-rails (1.11.0)
cypress-on-rails (1.12.1)
rack
database_cleaner (2.0.1)
database_cleaner-active_record (~> 2.0.0)
@@ -151,11 +151,12 @@ GEM
database_cleaner-core (2.0.1)
datetime_picker_rails (0.0.7)
momentjs-rails (>= 2.8.1)
ddtrace (0.53.0)
ffi (~> 1.0)
ddtrace (0.54.2)
debase-ruby_core_source (<= 0.10.14)
msgpack
debase-ruby_core_source (0.10.14)
declarative (0.0.20)
devise (4.8.0)
devise (4.8.1)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
@@ -165,7 +166,7 @@ GEM
bcrypt (~> 3.0)
devise (> 3.5.2, < 5)
rails (>= 4.2.0, < 6.2)
diff-lcs (1.4.4)
diff-lcs (1.5.0)
digest-crc (0.6.4)
rake (>= 12.0.0, < 14.0.0)
docile (1.4.0)
@@ -175,14 +176,14 @@ GEM
dotenv-rails (2.7.6)
dotenv (= 2.7.6)
railties (>= 3.2)
down (5.2.4)
down (5.3.0)
addressable (~> 2.8)
ecma-re-validator (0.3.0)
regexp_parser (~> 2.0)
ecma-re-validator (0.4.0)
regexp_parser (~> 2.2)
email_reply_trimmer (0.1.13)
erubi (1.10.0)
erubis (2.7.0)
et-orbi (1.2.5)
et-orbi (1.2.6)
tzinfo
execjs (2.8.1)
facebook-messenger (2.0.1)
@@ -197,11 +198,11 @@ GEM
i18n (>= 1.6, < 2)
faraday (1.0.1)
multipart-post (>= 1.2, < 3)
faraday_middleware (1.0.0)
faraday_middleware (1.2.0)
faraday (~> 1.0)
fcm (1.0.3)
fcm (1.0.5)
faraday (~> 1)
ffi (1.15.4)
ffi (1.15.5)
flag_shih_tzu (0.3.23)
flay (2.12.1)
erubis (~> 2.7.0)
@@ -218,11 +219,11 @@ GEM
googleapis-common-protos-types (>= 1.0.4, < 2.0)
googleauth (~> 0.9)
grpc (~> 1.25)
geocoder (1.7.0)
gli (2.20.1)
geocoder (1.7.3)
gli (2.21.0)
globalid (1.0.0)
activesupport (>= 5.0)
google-apis-core (0.4.1)
google-apis-core (0.4.2)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
@@ -231,9 +232,9 @@ GEM
retriable (>= 2.0, < 4.a)
rexml
webrick
google-apis-iamcredentials_v1 (0.7.0)
google-apis-iamcredentials_v1 (0.10.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-storage_v1 (0.8.0)
google-apis-storage_v1 (0.11.0)
google-apis-core (>= 0.4, < 2.a)
google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
@@ -247,22 +248,22 @@ GEM
google-cloud-env (1.5.0)
faraday (>= 0.17.3, < 2.0)
google-cloud-errors (1.2.0)
google-cloud-storage (1.34.1)
addressable (~> 2.5)
google-cloud-storage (1.36.1)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.1)
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
google-protobuf (3.18.1)
google-protobuf (3.18.1-universal-darwin)
google-protobuf (3.18.1-x86_64-linux)
google-protobuf (3.19.4)
google-protobuf (3.19.4-x86_64-darwin)
google-protobuf (3.19.4-x86_64-linux)
googleapis-common-protos (1.3.12)
google-protobuf (~> 3.14)
googleapis-common-protos-types (~> 1.2)
grpc (~> 1.27)
googleapis-common-protos-types (1.2.0)
googleapis-common-protos-types (1.3.0)
google-protobuf (~> 3.14)
googleauth (0.17.1)
faraday (>= 0.17.3, < 2.0)
@@ -271,25 +272,25 @@ GEM
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (~> 0.15)
groupdate (5.2.2)
activesupport (>= 5)
grpc (1.41.0)
google-protobuf (~> 3.17)
groupdate (6.0.1)
activesupport (>= 5.2)
grpc (1.43.1)
google-protobuf (~> 3.18)
googleapis-common-protos-types (~> 1.0)
grpc (1.41.0-universal-darwin)
google-protobuf (~> 3.17)
grpc (1.43.1-universal-darwin)
google-protobuf (~> 3.18)
googleapis-common-protos-types (~> 1.0)
grpc (1.41.0-x86_64-linux)
google-protobuf (~> 3.17)
grpc (1.43.1-x86_64-linux)
google-protobuf (~> 3.18)
googleapis-common-protos-types (~> 1.0)
haikunator (1.1.1)
hairtrigger (0.2.24)
activerecord (>= 5.0, < 7)
hairtrigger (0.2.25)
activerecord (>= 5.0, < 8)
ruby2ruby (~> 2.4)
ruby_parser (~> 3.10)
hana (1.3.7)
hashdiff (1.0.1)
hashie (4.1.0)
hashie (5.0.0)
hkdf (0.3.0)
html2text (0.2.1)
nokogiri (~> 1.6)
@@ -300,53 +301,55 @@ GEM
mime-types (~> 3.0)
multi_xml (>= 0.5.2)
httpclient (2.8.3)
i18n (1.8.11)
i18n (1.10.0)
concurrent-ruby (~> 1.0)
image_processing (1.12.1)
image_processing (1.12.2)
mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3)
jbuilder (2.11.2)
jbuilder (2.11.5)
actionview (>= 5.0.0)
activesupport (>= 5.0.0)
jmespath (1.4.0)
jmespath (1.6.0)
jquery-rails (4.4.0)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
json (2.5.1)
json_refs (0.1.6)
json (2.6.1)
json_refs (0.1.7)
hana
json_schemer (0.2.18)
json_schemer (0.2.19)
ecma-re-validator (~> 0.3)
hana (~> 1.3)
regexp_parser (~> 2.0)
uri_template (~> 0.7)
jwt (2.3.0)
kaminari (1.2.1)
kaminari (1.2.2)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.2.1)
kaminari-activerecord (= 1.2.1)
kaminari-core (= 1.2.1)
kaminari-actionview (1.2.1)
kaminari-actionview (= 1.2.2)
kaminari-activerecord (= 1.2.2)
kaminari-core (= 1.2.2)
kaminari-actionview (1.2.2)
actionview
kaminari-core (= 1.2.1)
kaminari-activerecord (1.2.1)
kaminari-core (= 1.2.2)
kaminari-activerecord (1.2.2)
activerecord
kaminari-core (= 1.2.1)
kaminari-core (1.2.1)
koala (3.0.0)
kaminari-core (= 1.2.2)
kaminari-core (1.2.2)
koala (3.1.0)
addressable
faraday
faraday (< 2)
json (>= 1.8)
rexml
launchy (2.5.0)
addressable (~> 2.7)
letter_opener (1.7.0)
launchy (~> 2.2)
line-bot-api (1.22.0)
line-bot-api (1.23.0)
liquid (5.1.0)
listen (3.7.0)
listen (3.7.1)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
loofah (2.13.0)
loofah (2.14.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
@@ -355,43 +358,43 @@ GEM
maxminddb (0.1.22)
memoist (0.16.2)
method_source (1.0.0)
mime-types (3.3.1)
mime-types (3.4.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2021.0901)
mime-types-data (3.2022.0105)
mini_magick (4.11.0)
mini_mime (1.1.2)
mini_portile2 (2.5.3)
mini_portile2 (2.8.0)
minitest (5.15.0)
mock_redis (0.29.0)
mock_redis (0.30.0)
ruby2_keywords
momentjs-rails (2.20.1)
momentjs-rails (2.29.1.1)
railties (>= 3.1)
msgpack (1.4.2)
msgpack (1.4.5)
multi_json (1.15.0)
multi_xml (0.6.0)
multipart-post (2.1.1)
net-http-persistent (4.0.1)
connection_pool (~> 2.2)
netrc (0.11.0)
newrelic_rpm (8.0.0)
newrelic_rpm (8.4.0)
nio4r (2.5.8)
nokogiri (1.11.7)
mini_portile2 (~> 2.5.0)
nokogiri (1.13.3)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
nokogiri (1.11.7-arm64-darwin)
nokogiri (1.13.3-arm64-darwin)
racc (~> 1.4)
nokogiri (1.11.7-x86_64-darwin)
nokogiri (1.13.3-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.11.7-x86_64-linux)
nokogiri (1.13.3-x86_64-linux)
racc (~> 1.4)
oauth (0.5.6)
oauth (0.5.8)
orm_adapter (0.5.0)
os (1.1.1)
os (1.1.4)
parallel (1.21.0)
parser (3.0.2.0)
parser (3.1.1.0)
ast (~> 2.4.1)
path_expander (1.1.0)
pg (1.2.3)
pg (1.3.2)
procore-sift (0.16.0)
rails (> 4.2.0)
pry (0.14.1)
@@ -400,57 +403,57 @@ GEM
pry-rails (0.3.9)
pry (>= 0.10.4)
public_suffix (4.0.6)
puma (5.5.2)
puma (5.6.2)
nio4r (~> 2.0)
pundit (2.1.1)
pundit (2.2.0)
activesupport (>= 3.0.0)
raabro (1.4.0)
racc (1.6.0)
rack (2.2.3)
rack-attack (6.5.0)
rack-attack (6.6.0)
rack (>= 1.0, < 3)
rack-cors (1.1.1)
rack (>= 2.0.0)
rack-proxy (0.7.0)
rack-proxy (0.7.2)
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
rack-timeout (0.6.0)
rails (6.1.4.3)
actioncable (= 6.1.4.3)
actionmailbox (= 6.1.4.3)
actionmailer (= 6.1.4.3)
actionpack (= 6.1.4.3)
actiontext (= 6.1.4.3)
actionview (= 6.1.4.3)
activejob (= 6.1.4.3)
activemodel (= 6.1.4.3)
activerecord (= 6.1.4.3)
activestorage (= 6.1.4.3)
activesupport (= 6.1.4.3)
rails (6.1.4.7)
actioncable (= 6.1.4.7)
actionmailbox (= 6.1.4.7)
actionmailer (= 6.1.4.7)
actionpack (= 6.1.4.7)
actiontext (= 6.1.4.7)
actionview (= 6.1.4.7)
activejob (= 6.1.4.7)
activemodel (= 6.1.4.7)
activerecord (= 6.1.4.7)
activestorage (= 6.1.4.7)
activesupport (= 6.1.4.7)
bundler (>= 1.15.0)
railties (= 6.1.4.3)
railties (= 6.1.4.7)
sprockets-rails (>= 2.0.0)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.4.2)
loofah (~> 2.3)
railties (6.1.4.3)
actionpack (= 6.1.4.3)
activesupport (= 6.1.4.3)
railties (6.1.4.7)
actionpack (= 6.1.4.7)
activesupport (= 6.1.4.7)
method_source
rake (>= 0.13)
thor (~> 1.0)
rainbow (3.0.0)
rainbow (3.1.1)
rake (13.0.6)
rb-fsevent (0.11.0)
rb-fsevent (0.11.1)
rb-inotify (0.10.1)
ffi (~> 1.0)
redis (4.4.0)
redis (4.6.0)
redis-namespace (1.8.1)
redis (>= 3.0.4)
regexp_parser (2.1.1)
regexp_parser (2.2.1)
representable (3.1.1)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
@@ -465,19 +468,19 @@ GEM
netrc (~> 0.8)
retriable (3.1.2)
rexml (3.2.5)
rspec (3.10.0)
rspec-core (~> 3.10.0)
rspec-expectations (~> 3.10.0)
rspec-mocks (~> 3.10.0)
rspec-core (3.10.1)
rspec-support (~> 3.10.0)
rspec-expectations (3.10.1)
rspec (3.11.0)
rspec-core (~> 3.11.0)
rspec-expectations (~> 3.11.0)
rspec-mocks (~> 3.11.0)
rspec-core (3.11.0)
rspec-support (~> 3.11.0)
rspec-expectations (3.11.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.10.0)
rspec-mocks (3.10.2)
rspec-support (~> 3.11.0)
rspec-mocks (3.11.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.10.0)
rspec-rails (5.0.2)
rspec-support (~> 3.11.0)
rspec-rails (5.0.3)
actionpack (>= 5.2)
activesupport (>= 5.2)
railties (>= 5.2)
@@ -485,36 +488,36 @@ GEM
rspec-expectations (~> 3.10)
rspec-mocks (~> 3.10)
rspec-support (~> 3.10)
rspec-support (3.10.2)
rubocop (1.22.1)
rspec-support (3.11.0)
rubocop (1.25.1)
parallel (~> 1.10)
parser (>= 3.0.0.0)
parser (>= 3.1.0.0)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml
rubocop-ast (>= 1.12.0, < 2.0)
rubocop-ast (>= 1.15.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.12.0)
parser (>= 3.0.1.1)
rubocop-performance (1.11.5)
rubocop-ast (1.16.0)
parser (>= 3.1.1.0)
rubocop-performance (1.13.2)
rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
rubocop-rails (2.12.3)
rubocop-rails (2.13.2)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.7.0, < 2.0)
rubocop-rspec (2.5.0)
rubocop-rspec (2.8.0)
rubocop (~> 1.19)
ruby-progressbar (1.11.0)
ruby-vips (2.1.3)
ruby-vips (2.1.4)
ffi (~> 1.12)
ruby2_keywords (0.0.5)
ruby2ruby (2.4.4)
ruby_parser (~> 3.1)
sexp_processor (~> 4.6)
ruby_parser (3.17.0)
sexp_processor (~> 4.15, >= 4.15.1)
ruby_parser (3.18.1)
sexp_processor (~> 4.16)
sassc (2.4.0)
ffi (~> 1.9)
sassc-rails (2.1.2)
@@ -523,30 +526,28 @@ GEM
sprockets (> 3.0)
sprockets-rails
tilt
scout_apm (4.1.2)
scout_apm (5.1.1)
parser
seed_dump (3.3.1)
activerecord (>= 4)
activesupport (>= 4)
selectize-rails (0.12.6)
semantic_range (3.0.0)
sentry-rails (4.7.3)
sentry-rails (5.1.0)
railties (>= 5.0)
sentry-ruby-core (~> 4.7.0)
sentry-ruby (4.7.3)
sentry-ruby-core (~> 5.1.0)
sentry-ruby (5.1.0)
concurrent-ruby (~> 1.0, >= 1.0.2)
faraday (>= 1.0)
sentry-ruby-core (= 4.7.3)
sentry-ruby-core (4.7.3)
sentry-ruby-core (= 5.1.0)
sentry-ruby-core (5.1.0)
concurrent-ruby
faraday
sentry-sidekiq (4.7.3)
sentry-ruby-core (~> 4.7.0)
sentry-sidekiq (5.1.0)
sentry-ruby-core (~> 5.1.0)
sidekiq (>= 3.0)
sexp_processor (4.15.3)
shoulda-matchers (5.0.0)
sexp_processor (4.16.0)
shoulda-matchers (5.1.0)
activesupport (>= 5.2.0)
sidekiq (6.2.2)
sidekiq (6.4.1)
connection_pool (>= 2.2.2)
rack (~> 2.0)
redis (>= 4.2.0)
@@ -563,7 +564,7 @@ GEM
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
slack-ruby-client (0.17.0)
slack-ruby-client (1.0.0)
faraday (>= 1.0)
faraday_middleware
gli
@@ -573,7 +574,7 @@ GEM
spring-watcher-listen (2.0.1)
listen (>= 2.7, < 4.0)
spring (>= 1.2, < 3.0)
sprockets (4.0.2)
sprockets (4.0.3)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-rails (3.4.2)
@@ -582,13 +583,13 @@ GEM
sprockets (>= 3.0.0)
squasher (0.6.2)
statsd-ruby (1.5.0)
telephone_number (1.4.12)
thor (1.1.0)
telephone_number (1.4.13)
thor (1.2.1)
tilt (2.0.10)
time_diff (0.3.0)
activesupport
i18n
trailblazer-option (0.1.1)
trailblazer-option (0.1.2)
twilio-ruby (5.32.0)
faraday (~> 1.0.0)
jwt (>= 1.5, <= 2.5)
@@ -597,7 +598,7 @@ GEM
oauth
tzinfo (2.0.4)
concurrent-ruby (~> 1.0)
tzinfo-data (1.2021.3)
tzinfo-data (1.2021.5)
tzinfo (>= 1.0.0)
uber (0.1.0)
uglifier (4.2.0)
@@ -608,12 +609,12 @@ GEM
unicode-display_width (2.1.0)
uniform_notifier (1.14.2)
uri_template (0.7.0)
valid_email2 (4.0.0)
valid_email2 (4.0.3)
activemodel (>= 3.2)
mail (~> 2.5)
warden (1.2.9)
rack (>= 2.0.9)
web-console (4.1.0)
web-console (4.2.0)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
@@ -635,7 +636,7 @@ GEM
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
wisper (2.0.0)
zeitwerk (2.5.1)
zeitwerk (2.5.4)
PLATFORMS
arm64-darwin-20
@@ -687,7 +688,7 @@ DEPENDENCIES
hairtrigger
hashie
html2text
image_processing
image_processing (~> 1.12.2)
jbuilder
json_refs
json_schemer
@@ -726,7 +727,7 @@ DEPENDENCIES
sentry-ruby
sentry-sidekiq
shoulda-matchers
sidekiq
sidekiq (~> 6.4.0)
sidekiq-cron
simplecov (= 0.17.1)
slack-ruby-client
@@ -750,4 +751,4 @@ RUBY VERSION
ruby 3.0.2p107
BUNDLED WITH
2.2.25
2.3.8
+1 -1
View File
@@ -118,4 +118,4 @@ Thanks goes to all these [wonderful people](https://www.chatwoot.com/docs/contri
<a href="https://github.com/chatwoot/chatwoot/graphs/contributors"><img src="https://opencollective.com/chatwoot/contributors.svg?width=890&button=false" /></a>
*Chatwoot* &copy; 2017-2021, Chatwoot Inc - Released under the MIT License.
*Chatwoot* &copy; 2017-2022, Chatwoot Inc - Released under the MIT License.
+13 -2
View File
@@ -52,12 +52,11 @@ class ContactIdentifyAction
end
def update_contact
custom_attributes = params[:custom_attributes] ? @contact.custom_attributes.merge(params[:custom_attributes]) : @contact.custom_attributes
# blank identifier or email will throw unique index error
# TODO: replace reject { |_k, v| v.blank? } with compact_blank when rails is upgraded
@contact.update!(params.slice(:name, :email, :identifier, :phone_number).reject do |_k, v|
v.blank?
end.merge({ custom_attributes: custom_attributes }))
end.merge({ custom_attributes: custom_attributes, additional_attributes: additional_attributes }))
ContactAvatarJob.perform_later(@contact, params[:avatar_url]) if params[:avatar_url].present?
end
@@ -68,4 +67,16 @@ class ContactIdentifyAction
mergee_contact: merge_contact
).perform
end
def custom_attributes
params[:custom_attributes] ? @contact.custom_attributes.deep_merge(params[:custom_attributes].stringify_keys) : @contact.custom_attributes
end
def additional_attributes
if params[:additional_attributes]
@contact.additional_attributes.deep_merge(params[:additional_attributes].stringify_keys)
else
@contact.additional_attributes
end
end
end
+2 -1
View File
@@ -2,7 +2,7 @@
class AccountBuilder
include CustomExceptions::Account
pattr_initialize [:account_name!, :email!, :confirmed, :user, :user_full_name, :user_password]
pattr_initialize [:account_name!, :email!, :confirmed, :user, :user_full_name, :user_password, :super_admin]
def perform
if @user.nil?
@@ -65,6 +65,7 @@ class AccountBuilder
password: user_password,
password_confirmation: user_password,
name: @user_full_name)
@user.type = 'SuperAdmin' if @super_admin
@user.confirm if @confirmed
@user.save!
end
+13 -7
View File
@@ -4,7 +4,7 @@ class ContactInboxBuilder
def perform
@contact = Contact.find(contact_id)
@inbox = @contact.account.inboxes.find(inbox_id)
return unless ['Channel::TwilioSms', 'Channel::Email', 'Channel::Api', 'Channel::Whatsapp'].include? @inbox.channel_type
return unless ['Channel::TwilioSms', 'Channel::Sms', 'Channel::Email', 'Channel::Api', 'Channel::Whatsapp'].include? @inbox.channel_type
source_id = @source_id || generate_source_id
create_contact_inbox(source_id) if source_id.present?
@@ -13,12 +13,18 @@ class ContactInboxBuilder
private
def generate_source_id
return twilio_source_id if @inbox.channel_type == 'Channel::TwilioSms'
return wa_source_id if @inbox.channel_type == 'Channel::Whatsapp'
return @contact.email if @inbox.channel_type == 'Channel::Email'
return SecureRandom.uuid if @inbox.channel_type == 'Channel::Api'
nil
case @inbox.channel_type
when 'Channel::TwilioSms'
twilio_source_id
when 'Channel::Whatsapp'
wa_source_id
when 'Channel::Email'
@contact.email
when 'Channel::Sms'
@contact.phone_number
when 'Channel::Api'
SecureRandom.uuid
end
end
def wa_source_id
+6 -3
View File
@@ -8,9 +8,11 @@ class Messages::MessageBuilder
@conversation = conversation
@user = user
@message_type = params[:message_type] || 'outgoing'
@items = params.to_unsafe_h&.dig(:content_attributes, :items)
@attachments = params[:attachments]
return unless params.instance_of?(ActionController::Parameters)
@in_reply_to = params.to_unsafe_h&.dig(:content_attributes, :in_reply_to)
@items = params.to_unsafe_h&.dig(:content_attributes, :items)
end
def perform
@@ -27,11 +29,12 @@ class Messages::MessageBuilder
return if @attachments.blank?
@attachments.each do |uploaded_attachment|
@message.attachments.build(
attachment = @message.attachments.build(
account_id: @message.account_id,
file_type: file_type(uploaded_attachment&.content_type),
file: uploaded_attachment
)
attachment.file_type = file_type(uploaded_attachment&.content_type) if uploaded_attachment.is_a?(ActionDispatch::Http::UploadedFile)
end
end
@@ -1,10 +1,14 @@
class Messages::Messenger::MessageBuilder
include ::FileTypeHelper
def process_attachment(attachment)
return if attachment['type'].to_sym == :template
attachment_obj = @message.attachments.new(attachment_params(attachment).except(:remote_file_url))
attachment_obj.save!
attach_file(attachment_obj, attachment_params(attachment)[:remote_file_url]) if attachment_params(attachment)[:remote_file_url]
fetch_story_link(attachment_obj) if attachment_obj.file_type == 'story_mention'
update_attachment_file_type(attachment_obj)
end
def attach_file(attachment, file_url)
@@ -22,7 +26,7 @@ class Messages::Messenger::MessageBuilder
file_type = attachment['type'].to_sym
params = { file_type: file_type, account_id: @message.account_id }
if [:image, :file, :audio, :video].include? file_type
if [:image, :file, :audio, :video, :share, :story_mention].include? file_type
params.merge!(file_type_params(attachment))
elsif file_type == :location
params.merge!(location_params(attachment))
@@ -39,4 +43,31 @@ class Messages::Messenger::MessageBuilder
remote_file_url: attachment['payload']['url']
}
end
def update_attachment_file_type(attachment)
return unless attachment.file_type == 'share' || attachment.file_type == 'story_mention'
attachment.file_type = file_type(attachment.file&.content_type)
attachment.save!
end
def fetch_story_link(attachment)
message = attachment.message
begin
k = Koala::Facebook::API.new(@inbox.channel.page_access_token) if @inbox.facebook?
result = k.get_object(message.source_id, fields: %w[story from]) || {}
rescue Koala::Facebook::AuthenticationError
@inbox.channel.authorization_error!
raise
rescue StandardError => e
result = {}
Sentry.capture_exception(e)
end
story_id = result['story']['mention']['id']
story_sender = result['from']['username']
message.content_attributes[:story_sender] = story_sender
message.content_attributes[:story_id] = story_id
message.content = I18n.t('conversations.messages.instagram_story_content', story_sender: story_sender)
message.save!
end
end
+34 -27
View File
@@ -2,9 +2,14 @@ class V2::ReportBuilder
include DateRangeHelper
attr_reader :account, :params
DEFAULT_GROUP_BY = 'day'.freeze
def initialize(account, params)
@account = account
@params = params
timezone_offset = (params[:timezone_offset] || 0).to_f
@timezone = ActiveSupport::TimeZone[timezone_offset]&.name
end
def timeseries
@@ -62,56 +67,58 @@ class V2::ReportBuilder
@team ||= account.teams.find(params[:id])
end
def get_grouped_values(object_scope)
object_scope.group_by_period(
params[:group_by] || DEFAULT_GROUP_BY,
:created_at,
default_value: 0,
range: range,
permit: %w[day week month year],
time_zone: @timezone
)
end
def conversations_count
scope.conversations
.group_by_day(:created_at, range: range, default_value: 0)
.count
(get_grouped_values scope.conversations).count
end
def incoming_messages_count
scope.messages.incoming.unscope(:order)
.group_by_day(:created_at, range: range, default_value: 0)
.count
(get_grouped_values scope.messages.incoming.unscope(:order)).count
end
def outgoing_messages_count
scope.messages.outgoing.unscope(:order)
.group_by_day(:created_at, range: range, default_value: 0)
.count
(get_grouped_values scope.messages.outgoing.unscope(:order)).count
end
def resolutions_count
scope.conversations
.resolved
.group_by_day(:created_at, range: range, default_value: 0)
.count
(get_grouped_values scope.conversations.resolved).count
end
def avg_first_response_time
scope.events
.where(name: 'first_response')
.group_by_day(:created_at, range: range, default_value: 0)
.average(:value)
(get_grouped_values scope.reporting_events.where(name: 'first_response')).average(:value)
end
def avg_resolution_time
scope.events.where(name: 'conversation_resolved')
.group_by_day(:created_at, range: range, default_value: 0)
.average(:value)
(get_grouped_values scope.reporting_events.where(name: 'conversation_resolved')).average(:value)
end
# Taking average of average is not too accurate
# https://en.wikipedia.org/wiki/Simpson's_paradox
# TODO: Will optimize this later
def avg_resolution_time_summary
return 0 if avg_resolution_time.values.empty?
avg_rt = scope.reporting_events
.where(name: 'conversation_resolved', created_at: range)
.average(:value)
(avg_resolution_time.values.sum / avg_resolution_time.values.length)
return 0 if avg_rt.blank?
avg_rt
end
def avg_first_response_time_summary
return 0 if avg_first_response_time.values.empty?
avg_frt = scope.reporting_events
.where(name: 'first_response', created_at: range)
.average(:value)
(avg_first_response_time.values.sum / avg_first_response_time.values.length)
return 0 if avg_frt.blank?
avg_frt
end
end
+7
View File
@@ -1,5 +1,8 @@
class RoomChannel < ApplicationCable::Channel
def subscribed
# TODO: should we only do ensure stream if current account is present?
# for now going ahead with guard clauses in update_subscription and broadcast_presence
ensure_stream
current_user
current_account
@@ -15,6 +18,8 @@ class RoomChannel < ApplicationCable::Channel
private
def broadcast_presence
return if @current_account.blank?
data = { account_id: @current_account.id, users: ::OnlineStatusTracker.get_available_users(@current_account.id) }
data[:contacts] = ::OnlineStatusTracker.get_available_contacts(@current_account.id) if @current_user.is_a? User
ActionCable.server.broadcast(@pubsub_token, { event: 'presence.update', data: data })
@@ -26,6 +31,8 @@ class RoomChannel < ApplicationCable::Channel
end
def update_subscription
return if @current_account.blank?
::OnlineStatusTracker.update_presence(@current_account.id, @current_user.class.name, @current_user.id)
end
@@ -68,7 +68,7 @@ class Api::V1::Accounts::AgentsController < Api::V1::Accounts::BaseController
end
def agents
@agents ||= Current.account.users.order_by_full_name.includes({ avatar_attachment: [:blob] })
@agents ||= Current.account.users.order_by_full_name.includes(:account_users, { avatar_attachment: [:blob] })
end
def validate_limit
@@ -0,0 +1,44 @@
class Api::V1::Accounts::AutomationRulesController < Api::V1::Accounts::BaseController
before_action :check_authorization
before_action :fetch_automation_rule, only: [:show, :update, :destroy, :clone]
def index
@automation_rules = Current.account.automation_rules
end
def create
@automation_rule = Current.account.automation_rules.create(automation_rules_permit)
end
def show; end
def update
@automation_rule.update(automation_rules_permit)
end
def destroy
@automation_rule.destroy!
head :ok
end
def clone
automation_rule = Current.account.automation_rules.find_by(id: params[:automation_rule_id])
new_rule = automation_rule.dup
new_rule.save
@automation_rule = new_rule
end
private
def automation_rules_permit
params.permit(
:name, :description, :event_name, :account_id, :active,
conditions: [:attribute_key, :filter_operator, :query_operator, { values: [] }],
actions: [:action_name, { action_params: [{}] }]
)
end
def fetch_automation_rule
@automation_rule = Current.account.automation_rules.find_by(id: params[:id])
end
end
@@ -1,32 +1,6 @@
class Api::V1::Accounts::BaseController < Api::BaseController
include SwitchLocale
include EnsureCurrentAccountHelper
before_action :current_account
around_action :switch_locale_using_account_locale
private
def current_account
@current_account ||= ensure_current_account
Current.account = @current_account
end
def ensure_current_account
account = Account.find(params[:account_id])
if current_user
account_accessible_for_user?(account)
elsif @resource.is_a?(AgentBot)
account_accessible_for_bot?(account)
end
account
end
def account_accessible_for_user?(account)
@current_account_user = account.account_users.find_by(user_id: current_user.id)
Current.account_user = @current_account_user
render_unauthorized('You are not authorized to access this account') unless @current_account_user
end
def account_accessible_for_bot?(account)
render_unauthorized('You are not authorized to access this account') unless @resource.agent_bot_inboxes.find_by(account_id: account.id)
end
end
@@ -0,0 +1,26 @@
class Api::V1::Accounts::BulkActionsController < Api::V1::Accounts::BaseController
before_action :type_matches?
def create
if type_matches?
::BulkActionsJob.perform_later(
account: @current_account,
user: current_user,
params: permitted_params
)
head :ok
else
render json: { success: false }, status: :unprocessable_entity
end
end
private
def type_matches?
['Conversation'].include?(params[:type])
end
def permitted_params
params.permit(:type, ids: [], fields: [:status, :assignee_id, :team_id], labels: [add: [], remove: []])
end
end
@@ -18,7 +18,7 @@ class Api::V1::Accounts::CampaignsController < Api::V1::Accounts::BaseController
def show; end
def update
@campaign.update(campaign_params)
@campaign.update!(campaign_params)
end
private
@@ -1,4 +1,5 @@
class Api::V1::Accounts::Conversations::BaseController < Api::V1::Accounts::BaseController
include EnsureCurrentAccountHelper
before_action :conversation
private
@@ -0,0 +1,17 @@
class Api::V1::Accounts::Conversations::DirectUploadsController < ActiveStorage::DirectUploadsController
include EnsureCurrentAccountHelper
before_action :current_account
before_action :conversation
def create
return if @conversation.nil? || @current_account.nil?
super
end
private
def conversation
@conversation ||= Current.account.conversations.find_by(display_id: params[:conversation_id])
end
end
@@ -13,7 +13,7 @@ class Api::V1::Accounts::Conversations::MessagesController < Api::V1::Accounts::
def destroy
ActiveRecord::Base.transaction do
message.update!(content: I18n.t('conversations.messages.deleted'), deleted: true)
message.update!(content: I18n.t('conversations.messages.deleted'), content_attributes: { deleted: true })
message.attachments.destroy_all
end
end
@@ -61,6 +61,7 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
else
@status = @conversation.toggle_status
end
assign_conversation if @conversation.status == 'open' && Current.user.is_a?(User) && Current.user&.agent?
end
def toggle_typing_status
@@ -93,6 +94,11 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
@conversation.snoozed_until = parse_date_time(params[:snoozed_until].to_s) if params[:snoozed_until]
end
def assign_conversation
@agent = Current.account.users.find(current_user.id)
@conversation.update_assignee(@agent)
end
def trigger_typing_event(event, is_private)
user = current_user.presence || @resource
Rails.configuration.dispatcher.dispatch(event, Time.zone.now, conversation: @conversation, user: user, is_private: is_private)
@@ -30,8 +30,7 @@ class Api::V1::Accounts::CsatSurveyResponsesController < Api::V1::Accounts::Base
def set_csat_survey_responses
@csat_survey_responses = filtrate(
Current.account.csat_survey_responses.includes([:conversation, :assigned_agent, :contact])
)
@csat_survey_responses = @csat_survey_responses.where(created_at: range) if range.present?
).filter_by_created_at(range).filter_by_assigned_agent_id(params[:user_ids])
end
def set_current_page_surveys
@@ -2,6 +2,7 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
include Api::V1::InboxesHelper
before_action :fetch_inbox, except: [:index, :create]
before_action :fetch_agent_bot, only: [:set_agent_bot]
before_action :validate_limit, only: [:create]
# we are already handling the authorization in fetch inbox
before_action :check_authorization, except: [:show]
@@ -47,7 +48,10 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
# Inbox update doesn't necessarily need channel attributes
return if permitted_params(channel_attributes)[:channel].blank?
validate_email_channel(channel_attributes) if @inbox.inbox_type == 'Email'
if @inbox.inbox_type == 'Email'
validate_email_channel(channel_attributes)
@inbox.channel.reauthorized!
end
@inbox.channel.update!(permitted_params(channel_attributes)[:channel])
update_channel_feature_flags
@@ -91,20 +95,9 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
end
def create_channel
case permitted_params[:channel][:type]
when 'web_widget'
Current.account.web_widgets.create!(permitted_params(Channel::WebWidget::EDITABLE_ATTRS)[:channel].except(:type))
when 'api'
Current.account.api_channels.create!(permitted_params(Channel::Api::EDITABLE_ATTRS)[:channel].except(:type))
when 'email'
Current.account.email_channels.create!(permitted_params(Channel::Email::EDITABLE_ATTRS)[:channel].except(:type))
when 'line'
Current.account.line_channels.create!(permitted_params(Channel::Line::EDITABLE_ATTRS)[:channel].except(:type))
when 'telegram'
Current.account.telegram_channels.create!(permitted_params(Channel::Telegram::EDITABLE_ATTRS)[:channel].except(:type))
when 'whatsapp'
Current.account.whatsapp_channels.create!(permitted_params(Channel::Whatsapp::EDITABLE_ATTRS)[:channel].except(:type))
end
return unless %w[web_widget api email line telegram whatsapp sms].include?(permitted_params[:channel][:type])
account_channels_method.create!(permitted_params(channel_type_from_params::EDITABLE_ATTRS)[:channel].except(:type))
end
def update_channel_feature_flags
@@ -118,16 +111,46 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
def permitted_params(channel_attributes = [])
params.permit(
:name, :avatar, :greeting_enabled, :greeting_message, :enable_email_collect, :csat_survey_enabled,
:enable_auto_assignment, :working_hours_enabled, :out_of_office_message, :timezone,
:enable_auto_assignment, :working_hours_enabled, :out_of_office_message, :timezone, :allow_messages_after_resolved,
channel: [:type, *channel_attributes]
)
end
def channel_type_from_params
{
'web_widget' => Channel::WebWidget,
'api' => Channel::Api,
'email' => Channel::Email,
'line' => Channel::Line,
'telegram' => Channel::Telegram,
'whatsapp' => Channel::Whatsapp,
'sms' => Channel::Sms
}[permitted_params[:channel][:type]]
end
def account_channels_method
{
'web_widget' => Current.account.web_widgets,
'api' => Current.account.api_channels,
'email' => Current.account.email_channels,
'line' => Current.account.line_channels,
'telegram' => Current.account.telegram_channels,
'whatsapp' => Current.account.whatsapp_channels,
'sms' => Current.account.sms_channels
}[permitted_params[:channel][:type]]
end
def get_channel_attributes(channel_type)
if channel_type.constantize.const_defined?('EDITABLE_ATTRS')
if channel_type.constantize.const_defined?(:EDITABLE_ATTRS)
channel_type.constantize::EDITABLE_ATTRS.presence
else
[]
end
end
def validate_limit
return unless Current.account.inboxes.count >= Current.account.usage_limits[:inboxes]
render_payment_required('Account limit exceeded. Upgrade to a higher plan')
end
end
@@ -4,6 +4,7 @@ class Api::V1::AccountsController < Api::BaseController
skip_before_action :authenticate_user!, :set_current_user, :handle_with_exception,
only: [:create], raise: false
before_action :check_signup_enabled, only: [:create]
before_action :validate_captcha, only: [:create]
before_action :fetch_account, except: [:create]
before_action :check_authorization, except: [:create]
@@ -55,7 +56,11 @@ class Api::V1::AccountsController < Api::BaseController
end
def check_signup_enabled
raise ActionController::RoutingError, 'Not Found' if GlobalConfig.get_value('ENABLE_ACCOUNT_SIGNUP') == 'false'
raise ActionController::RoutingError, 'Not Found' if GlobalConfigService.load('ENABLE_ACCOUNT_SIGNUP', 'false') == 'false'
end
def validate_captcha
raise ActionController::InvalidAuthenticityToken, 'Invalid Captcha' unless ChatwootCaptcha.new(params[:h_captcha_client_response]).valid?
end
def pundit_user
@@ -7,6 +7,12 @@ class Api::V1::NotificationSubscriptionsController < Api::BaseController
render json: notification_subscription
end
def destroy
notification_subscription = NotificationSubscription.where(["subscription_attributes->>'push_token' = ?", params[:push_token]]).first
notification_subscription.destroy
head :ok
end
private
def set_user
@@ -38,6 +38,7 @@ class Api::V1::ProfilesController < Api::BaseController
:name,
:display_name,
:avatar,
:message_signature,
ui_settings: {}
)
end
@@ -1,5 +1,6 @@
class Api::V1::Widget::BaseController < ApplicationController
include SwitchLocale
include WebsiteTokenHelper
before_action :set_web_widget
before_action :set_contact
@@ -19,23 +20,6 @@ class Api::V1::Widget::BaseController < ApplicationController
@conversation ||= conversations.last
end
def auth_token_params
@auth_token_params ||= ::Widget::TokenService.new(token: request.headers['X-Auth-Token']).decode_token
end
def set_web_widget
@web_widget = ::Channel::WebWidget.find_by!(website_token: permitted_params[:website_token])
@current_account = @web_widget.account
end
def set_contact
@contact_inbox = @web_widget.inbox.contact_inboxes.find_by(
source_id: auth_token_params[:source_id]
)
@contact = @contact_inbox&.contact
raise ActiveRecord::RecordNotFound unless @contact
end
def create_conversation
::Conversation.create!(conversation_params)
end
@@ -94,10 +78,6 @@ class Api::V1::Widget::BaseController < ApplicationController
{ timestamp: permitted_params[:message][:timestamp] }
end
def permitted_params
params.permit(:website_token)
end
def message_params
{
account_id: conversation.account_id,
@@ -46,6 +46,7 @@ class Api::V1::Widget::ContactsController < Api::V1::Widget::BaseController
end
def permitted_params
params.permit(:website_token, :identifier, :identifier_hash, :email, :name, :avatar_url, :phone_number, custom_attributes: {})
params.permit(:website_token, :identifier, :identifier_hash, :email, :name, :avatar_url, :phone_number, custom_attributes: {},
additional_attributes: {})
end
end
@@ -44,6 +44,15 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
head :ok
end
def toggle_status
head :not_found && return if conversation.nil?
unless conversation.resolved?
conversation.status = :resolved
conversation.save
end
head :ok
end
private
def trigger_typing_event(event)
@@ -0,0 +1,11 @@
class Api::V1::Widget::DirectUploadsController < ActiveStorage::DirectUploadsController
include WebsiteTokenHelper
before_action :set_web_widget
before_action :set_contact
def create
return if @contact.nil? || @current_account.nil?
super
end
end
@@ -29,11 +29,12 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
return if params[:message][:attachments].blank?
params[:message][:attachments].each do |uploaded_attachment|
@message.attachments.new(
attachment = @message.attachments.new(
account_id: @message.account_id,
file_type: helpers.file_type(uploaded_attachment&.content_type),
file: uploaded_attachment
)
attachment.file_type = helpers.file_type(uploaded_attachment&.content_type) if uploaded_attachment.is_a?(ActionDispatch::Http::UploadedFile)
end
end
@@ -41,12 +41,23 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
raise Pundit::NotAuthorizedError unless Current.account_user.administrator?
end
def summary_params
def current_summary_params
{
type: params[:type].to_sym,
since: params[:since],
until: params[:until],
id: params[:id]
id: params[:id],
since: range[:current][:since],
until: range[:current][:until],
group_by: params[:group_by]
}
end
def previous_summary_params
{
type: params[:type].to_sym,
id: params[:id],
since: range[:previous][:since],
until: range[:previous][:until],
group_by: params[:group_by]
}
end
@@ -56,12 +67,28 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
type: params[:type].to_sym,
since: params[:since],
until: params[:until],
id: params[:id]
id: params[:id],
group_by: params[:group_by],
timezone_offset: params[:timezone_offset]
}
end
def range
{
current: {
since: params[:since],
until: params[:until]
},
previous: {
since: (params[:since].to_i - (params[:until].to_i - params[:since].to_i)).to_s,
until: params[:since]
}
}
end
def summary_metrics
builder = V2::ReportBuilder.new(Current.account, summary_params)
builder.summary
summary = V2::ReportBuilder.new(Current.account, current_summary_params).summary
summary[:previous] = V2::ReportBuilder.new(Current.account, previous_summary_params).summary
summary
end
end
@@ -0,0 +1,28 @@
module EnsureCurrentAccountHelper
private
def current_account
@current_account ||= ensure_current_account
Current.account = @current_account
end
def ensure_current_account
account = Account.find(params[:account_id])
if current_user
account_accessible_for_user?(account)
elsif @resource.is_a?(AgentBot)
account_accessible_for_bot?(account)
end
account
end
def account_accessible_for_user?(account)
@current_account_user = account.account_users.find_by(user_id: current_user.id)
Current.account_user = @current_account_user
render_unauthorized('You are not authorized to access this account') unless @current_account_user
end
def account_accessible_for_bot?(account)
render_unauthorized('You are not authorized to access this account') unless @resource.agent_bot_inboxes.find_by(account_id: account.id)
end
end
@@ -0,0 +1,24 @@
module WebsiteTokenHelper
def auth_token_params
@auth_token_params ||= ::Widget::TokenService.new(token: request.headers['X-Auth-Token']).decode_token
end
def set_web_widget
@web_widget = ::Channel::WebWidget.find_by!(website_token: permitted_params[:website_token])
@current_account = @web_widget.account
end
def set_contact
@contact_inbox = @web_widget.inbox.contact_inboxes.find_by(
source_id: auth_token_params[:source_id]
)
@contact = @contact_inbox&.contact
raise ActiveRecord::RecordNotFound unless @contact
Current.contact = @contact
end
def permitted_params
params.permit(:website_token)
end
end
+5 -1
View File
@@ -25,7 +25,11 @@ class DashboardController < ActionController::Base
'API_CHANNEL_NAME',
'API_CHANNEL_THUMBNAIL',
'ANALYTICS_TOKEN',
'ANALYTICS_HOST'
'ANALYTICS_HOST',
'DIRECT_UPLOADS_ENABLED',
'HCAPTCHA_SITE_KEY',
'LOGOUT_REDIRECT_LINK',
'DISABLE_USER_PROFILE_UPDATE'
).merge(app_config)
end
@@ -10,6 +10,7 @@ class Installation::OnboardingController < ApplicationController
user_full_name: onboarding_params.dig(:user, :name),
email: onboarding_params.dig(:user, :email),
user_password: params.dig(:user, :password),
super_admin: true,
confirmed: true
).perform
rescue StandardError => e
@@ -9,11 +9,12 @@ class Platform::Api::V1::UsersController < PlatformController
@resource = (User.find_by(email: user_params[:email]) || User.new(user_params))
@resource.save!
@resource.confirm
@platform_app.platform_app_permissibles.find_or_create_by(permissible: @resource)
@platform_app.platform_app_permissibles.find_or_create_by!(permissible: @resource)
end
def login
render json: { url: "#{ENV['FRONTEND_URL']}/app/login?email=#{@resource.email}&sso_auth_token=#{@resource.generate_sso_auth_token}" }
encoded_email = ERB::Util.url_encode(@resource.email)
render json: { url: "#{ENV['FRONTEND_URL']}/app/login?email=#{encoded_email}&sso_auth_token=#{@resource.generate_sso_auth_token}" }
end
def show; end
@@ -33,11 +33,11 @@ class SuperAdmin::AccountsController < SuperAdmin::ApplicationController
# empty values into nil values. It uses other APIs such as `resource_class`
# and `dashboard`:
#
# def resource_params
# params.require(resource_class.model_name.param_key).
# permit(dashboard.permitted_attributes).
# transform_values { |value| value == "" ? nil : value }
# end
def resource_params
permitted_params = super
permitted_params[:limits] = permitted_params[:limits].to_h.compact
permitted_params
end
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
# for more information
@@ -0,0 +1,21 @@
class SuperAdmin::AppConfigsController < SuperAdmin::ApplicationController
def show
@allowed_configs = %w[FB_APP_ID FB_VERIFY_TOKEN FB_APP_SECRET]
# ref: https://github.com/rubocop/rubocop/issues/7767
# rubocop:disable Style/HashTransformValues
@fb_config = InstallationConfig.where(name: @allowed_configs)
.pluck(:name, :serialized_value)
.map { |name, serialized_value| [name, serialized_value['value']] }
.to_h
# rubocop:enable Style/HashTransformValues
end
def create
params['app_config'].each do |key, value|
i = InstallationConfig.where(name: key).first_or_create(value: value, locked: false)
i.value = value
i.save!
end
redirect_to super_admin_app_config_url
end
end
@@ -8,7 +8,7 @@ class SuperAdmin::Devise::SessionsController < Devise::SessionsController
def create
redirect_to(super_admin_session_path, flash: { error: @error_message }) && return unless valid_credentials?
sign_in(@super_admin, scope: :super_admin)
sign_in(:super_admin, @super_admin)
flash.discard
redirect_to super_admin_users_path
end
@@ -1,44 +0,0 @@
class SuperAdmin::SuperAdminsController < SuperAdmin::ApplicationController
# Overwrite any of the RESTful controller actions to implement custom behavior
# For example, you may want to send an email after a foo is updated.
#
# def update
# super
# send_foo_updated_email(requested_resource)
# end
# Override this method to specify custom lookup behavior.
# This will be used to set the resource for the `show`, `edit`, and `update`
# actions.
#
# def find_resource(param)
# Foo.find_by!(slug: param)
# end
# The result of this lookup will be available as `requested_resource`
# Override this if you have certain roles that require a subset
# this will be used to set the records shown on the `index` action.
#
# def scoped_resource
# if current_user.super_admin?
# resource_class
# else
# resource_class.with_less_stuff
# end
# end
# Override `resource_params` if you want to transform the submitted
# data before it's persisted. For example, the following would turn all
# empty values into nil values. It uses other APIs such as `resource_class`
# and `dashboard`:
#
# def resource_params
# params.require(resource_class.model_name.param_key).
# permit(dashboard.permitted_attributes).
# transform_values { |value| value == "" ? nil : value }
# end
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
# for more information
end
@@ -33,12 +33,15 @@ class SuperAdmin::UsersController < SuperAdmin::ApplicationController
# empty values into nil values. It uses other APIs such as `resource_class`
# and `dashboard`:
#
# def resource_params
# params.require(resource_class.model_name.param_key).
# permit(dashboard.permitted_attributes).
# transform_values { |value| value == "" ? nil : value }
# end
def resource_params
permitted_params = super
permitted_params.delete(:password) if permitted_params[:password].blank?
permitted_params
end
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
# for more information
def find_resource(param)
super.becomes(User)
end
end
@@ -0,0 +1,6 @@
class Webhooks::SmsController < ActionController::API
def process_payload
Webhooks::SmsEventsJob.perform_later(params['_json']&.first&.to_unsafe_hash)
head :ok
end
end
+23 -3
View File
@@ -1,5 +1,8 @@
class WidgetTestsController < ActionController::Base
before_action :set_web_widget
before_action :ensure_web_widget
before_action :ensure_widget_position
before_action :ensure_widget_type
before_action :ensure_widget_style
def index
render
@@ -7,7 +10,24 @@ class WidgetTestsController < ActionController::Base
private
def set_web_widget
@web_widget = Channel::WebWidget.first
def ensure_widget_style
@widget_style = params[:widget_style] || 'standard'
end
def ensure_widget_position
@widget_position = params[:position] || 'left'
end
def ensure_widget_type
@widget_type = params[:type] || 'expanded_bubble'
end
def inbox_id
@inbox_id ||= params[:inbox_id] || Channel::WebWidget.first.inbox.id
end
def ensure_web_widget
@inbox = Inbox.find(inbox_id)
@web_widget = @inbox.channel
end
end
+1 -1
View File
@@ -10,7 +10,7 @@ class WidgetsController < ActionController::Base
private
def set_global_config
@global_config = GlobalConfig.get('LOGO_THUMBNAIL', 'BRAND_NAME', 'WIDGET_BRAND_URL')
@global_config = GlobalConfig.get('LOGO_THUMBNAIL', 'BRAND_NAME', 'WIDGET_BRAND_URL', 'DIRECT_UPLOADS_ENABLED')
end
def set_web_widget
+13 -5
View File
@@ -7,6 +7,8 @@ class AccountDashboard < Administrate::BaseDashboard
# Each different type represents an Administrate::Field object,
# which determines how the attribute is displayed
# on pages throughout the dashboard.
enterprise_attribute_types = ChatwootApp.enterprise? ? { limits: Enterprise::AccountLimitsField } : {}
ATTRIBUTE_TYPES = {
id: Field::Number,
name: Field::String,
@@ -16,7 +18,7 @@ class AccountDashboard < Administrate::BaseDashboard
conversations: CountField,
locale: Field::Select.with_options(collection: LANGUAGES_CONFIG.map { |_x, y| y[:iso_639_1_code] }),
account_users: Field::HasMany
}.freeze
}.merge(enterprise_attribute_types).freeze
# COLLECTION_ATTRIBUTES
# an array of attributes that will be displayed on the model's index page.
@@ -33,7 +35,8 @@ class AccountDashboard < Administrate::BaseDashboard
# SHOW_PAGE_ATTRIBUTES
# an array of attributes that will be displayed on the model's show page.
SHOW_PAGE_ATTRIBUTES = %i[
enterprise_show_page_attributes = ChatwootApp.enterprise? ? %i[limits] : []
SHOW_PAGE_ATTRIBUTES = (%i[
id
name
created_at
@@ -41,15 +44,16 @@ class AccountDashboard < Administrate::BaseDashboard
locale
conversations
account_users
].freeze
] + enterprise_show_page_attributes).freeze
# FORM_ATTRIBUTES
# an array of attributes that will be displayed
# on the model's form (`new` and `edit`) pages.
FORM_ATTRIBUTES = %i[
enterprise_form_attributes = ChatwootApp.enterprise? ? %i[limits] : []
FORM_ATTRIBUTES = (%i[
name
locale
].freeze
] + enterprise_form_attributes).freeze
# COLLECTION_FILTERS
# a hash that defines filters that can be used while searching via the search
@@ -69,4 +73,8 @@ class AccountDashboard < Administrate::BaseDashboard
def display_resource(account)
"##{account.id} #{account.name}"
end
def permitted_attributes
super + [limits: {}]
end
end
+4 -1
View File
@@ -12,6 +12,7 @@ class AgentBotDashboard < Administrate::BaseDashboard
avatar_url: AvatarField,
id: Field::Number,
name: Field::String,
account: Field::BelongsTo.with_options(searchable: true, searchable_field: 'name', order: 'id DESC'),
description: Field::String,
outgoing_url: Field::String,
created_at: Field::DateTime,
@@ -26,6 +27,7 @@ class AgentBotDashboard < Administrate::BaseDashboard
COLLECTION_ATTRIBUTES = %i[
id
avatar_url
account
name
outgoing_url
].freeze
@@ -34,7 +36,7 @@ class AgentBotDashboard < Administrate::BaseDashboard
# an array of attributes that will be displayed on the model's show page.
SHOW_PAGE_ATTRIBUTES = %i[
id
avatar_url
account
name
description
outgoing_url
@@ -45,6 +47,7 @@ class AgentBotDashboard < Administrate::BaseDashboard
# on the model's form (`new` and `edit`) pages.
FORM_ATTRIBUTES = %i[
name
account
description
outgoing_url
].freeze
-75
View File
@@ -1,75 +0,0 @@
require 'administrate/base_dashboard'
class SuperAdminDashboard < Administrate::BaseDashboard
# ATTRIBUTE_TYPES
# a hash that describes the type of each of the model's fields.
#
# Each different type represents an Administrate::Field object,
# which determines how the attribute is displayed
# on pages throughout the dashboard.
ATTRIBUTE_TYPES = {
id: Field::Number,
email: Field::String,
password: Field::Password,
remember_created_at: Field::DateTime,
sign_in_count: Field::Number,
current_sign_in_at: Field::DateTime,
last_sign_in_at: Field::DateTime,
current_sign_in_ip: Field::String.with_options(searchable: false),
last_sign_in_ip: Field::String.with_options(searchable: false),
created_at: Field::DateTime,
updated_at: Field::DateTime
}.freeze
# COLLECTION_ATTRIBUTES
# an array of attributes that will be displayed on the model's index page.
#
# By default, it's limited to four items to reduce clutter on index pages.
# Feel free to add, remove, or rearrange items.
COLLECTION_ATTRIBUTES = %i[
id
email
].freeze
# SHOW_PAGE_ATTRIBUTES
# an array of attributes that will be displayed on the model's show page.
SHOW_PAGE_ATTRIBUTES = %i[
id
email
remember_created_at
sign_in_count
current_sign_in_at
last_sign_in_at
current_sign_in_ip
last_sign_in_ip
created_at
updated_at
].freeze
# FORM_ATTRIBUTES
# an array of attributes that will be displayed
# on the model's form (`new` and `edit`) pages.
FORM_ATTRIBUTES = %i[
email
password
].freeze
# COLLECTION_FILTERS
# a hash that defines filters that can be used while searching via the search
# field of the dashboard.
#
# For example to add an option to search for open resources by typing "open:"
# in the search field:
#
# COLLECTION_FILTERS = {
# open: ->(resources) { resources.where(open: true) }
# }.freeze
COLLECTION_FILTERS = {}.freeze
# Overwrite this method to customize how super admins are displayed
# across all pages of the admin dashboard.
#
# def display_resource(super_admin)
# "SuperAdmin ##{super_admin.id}"
# end
end
+6
View File
@@ -30,6 +30,7 @@ class UserDashboard < Administrate::BaseDashboard
created_at: Field::DateTime,
updated_at: Field::DateTime,
pubsub_token: Field::String,
type: Field::Select.with_options(collection: [nil, 'SuperAdmin']),
accounts: CountField
}.freeze
@@ -44,6 +45,7 @@ class UserDashboard < Administrate::BaseDashboard
name
email
accounts
type
].freeze
# SHOW_PAGE_ATTRIBUTES
@@ -53,10 +55,12 @@ class UserDashboard < Administrate::BaseDashboard
avatar_url
unconfirmed_email
name
type
display_name
email
created_at
updated_at
confirmed_at
account_users
].freeze
@@ -68,6 +72,8 @@ class UserDashboard < Administrate::BaseDashboard
display_name
email
password
confirmed_at
type
].freeze
# COLLECTION_FILTERS
+3 -2
View File
@@ -12,11 +12,12 @@ class AsyncDispatcher < BaseDispatcher
[
CampaignListener.instance,
CsatSurveyListener.instance,
EventListener.instance,
HookListener.instance,
InstallationWebhookListener.instance,
NotificationListener.instance,
WebhookListener.instance
ReportingEventListener.instance,
WebhookListener.instance,
AutomationRuleListener.instance
]
end
end
+1 -1
View File
@@ -2,6 +2,6 @@ require 'administrate/field/base'
class AvatarField < Administrate::Field::Base
def avatar_url
data.presence || '/admin/avatar.png'
data.presence&.gsub('?d=404', '?d=mp')
end
end
@@ -0,0 +1,7 @@
require 'administrate/field/base'
class Enterprise::AccountLimitsField < Administrate::Field::Base
def to_s
data.present? ? data.to_json : { agents: nil, inboxes: nil }.to_json
end
end
+1 -1
View File
@@ -55,7 +55,7 @@ class ConversationFinder
def set_inboxes
@inbox_ids = if params[:inbox_id]
current_account.inboxes.where(id: params[:inbox_id])
@current_user.assigned_inboxes.where(id: params[:inbox_id])
else
@current_user.assigned_inboxes.pluck(:id)
end
+42 -2
View File
@@ -26,8 +26,48 @@ module Api::V1::InboxesHelper
def validate_smtp(channel_data)
return unless channel_data.key?('smtp_enabled') && channel_data[:smtp_enabled]
smtp = Net::SMTP.start(channel_data[:smtp_address], channel_data[:smtp_port], channel_data[:smtp_domain], channel_data[:smtp_email],
channel_data[:smtp_password], :login)
smtp = Net::SMTP.new(channel_data[:smtp_address], channel_data[:smtp_port])
set_smtp_encryption(channel_data, smtp)
smtp.start(channel_data[:smtp_domain], channel_data[:smtp_email], channel_data[:smtp_password], :login)
smtp.finish unless smtp&.nil?
end
def set_smtp_encryption(channel_data, smtp)
if channel_data[:smtp_enable_ssl_tls]
set_enable_tls(channel_data, smtp)
elsif channel_data[:smtp_enable_starttls_auto]
set_enable_starttls_auto(channel_data, smtp)
end
end
def set_enable_starttls_auto(channel_data, smtp)
return unless smtp.respond_to?(:enable_starttls_auto)
if channel_data[:smtp_openssl_verify_mode]
context = enable_openssl_mode(channel_data[:smtp_openssl_verify_mode])
smtp.enable_starttls_auto(context)
else
smtp.enable_starttls_auto
end
end
def set_enable_tls(channel_data, smtp)
return unless smtp.respond_to?(:enable_tls)
if channel_data[:smtp_openssl_verify_mode]
context = enable_openssl_mode(channel_data[:smtp_openssl_verify_mode])
smtp.enable_tls(context)
else
smtp.enable_tls
end
end
def enable_openssl_mode(smtp_openssl_verify_mode)
openssl_verify_mode = "OpenSSL::SSL::VERIFY_#{smtp_openssl_verify_mode.upcase}".constantize if smtp_openssl_verify_mode.is_a?(String)
context = Net::SMTP.default_ssl_context
context.verify_mode = openssl_verify_mode
context
end
end
-2
View File
@@ -1,2 +0,0 @@
module Api::V1::ReportsHelper
end
+5 -3
View File
@@ -3,7 +3,7 @@ module FileTypeHelper
def file_type(content_type)
return :image if image_file?(content_type)
return :video if video_file?(content_type)
return :audio if content_type.include?('audio/')
return :audio if content_type&.include?('audio/')
:file
end
@@ -14,7 +14,8 @@ module FileTypeHelper
'image/png',
'image/gif',
'image/bmp',
'image/webp'
'image/webp',
'image'
].include?(content_type)
end
@@ -23,7 +24,8 @@ module FileTypeHelper
'video/ogg',
'video/mp4',
'video/webm',
'video/quicktime'
'video/quicktime',
'video'
].include?(content_type)
end
end
+18 -4
View File
@@ -1,5 +1,6 @@
<template>
<div id="app" class="app-wrapper app-root">
<update-banner :latest-chatwoot-version="latestChatwootVersion" />
<transition name="fade" mode="out-in">
<router-view></router-view>
</transition>
@@ -13,24 +14,27 @@
</template>
<script>
import { accountIdFromPathname } from './helper/URLHelper';
import { mapGetters } from 'vuex';
import AddAccountModal from '../dashboard/components/layout/sidebarComponents/AddAccountModal';
import WootSnackbarBox from './components/SnackbarContainer';
import NetworkNotification from './components/NetworkNotification';
import { accountIdFromPathname } from './helper/URLHelper';
import UpdateBanner from './components/app/UpdateBanner.vue';
import WootSnackbarBox from './components/SnackbarContainer';
export default {
name: 'App',
components: {
WootSnackbarBox,
AddAccountModal,
NetworkNotification,
UpdateBanner,
WootSnackbarBox,
},
data() {
return {
showAddAccountModal: false,
latestChatwootVersion: null,
};
},
@@ -38,6 +42,7 @@ export default {
...mapGetters({
getAccount: 'accounts/getAccount',
currentUser: 'getCurrentUser',
globalConfig: 'globalConfig/get',
}),
hasAccounts() {
return (
@@ -72,8 +77,12 @@ export default {
if (accountId) {
await this.$store.dispatch('accounts/get');
const { locale } = this.getAccount(accountId);
const {
locale,
latest_chatwoot_version: latestChatwootVersion,
} = this.getAccount(accountId);
this.setLocale(locale);
this.latestChatwootVersion = latestChatwootVersion;
}
},
},
@@ -82,6 +91,11 @@ export default {
<style lang="scss">
@import './assets/scss/app';
.update-banner {
height: var(--space-larger);
align-items: center;
font-size: var(--font-size-small) !important;
}
</style>
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
+9 -4
View File
@@ -13,7 +13,7 @@ export default {
.post('auth/sign_in', creds)
.then(response => {
setAuthCredentials(response);
resolve();
resolve(response.data);
})
.catch(error => {
reject(error.response);
@@ -30,6 +30,7 @@ export default {
user_full_name: creds.fullName.trim(),
email: creds.email,
password: creds.password,
h_captcha_client_response: creds.hCaptchaClientResponse,
})
.then(response => {
setAuthCredentials(response);
@@ -138,13 +139,14 @@ export default {
password,
password_confirmation,
displayName,
avatar,
...profileAttributes
}) {
const formData = new FormData();
Object.keys(profileAttributes).forEach(key => {
const value = profileAttributes[key];
if (value) {
formData.append(`profile[${key}]`, value);
const hasValue = profileAttributes[key] === undefined;
if (!hasValue) {
formData.append(`profile[${key}]`, profileAttributes[key]);
}
});
formData.append('profile[display_name]', displayName || '');
@@ -152,6 +154,9 @@ export default {
formData.append('profile[password]', password);
formData.append('profile[password_confirmation]', password_confirmation);
}
if (avatar) {
formData.append('profile[avatar]', avatar);
}
return axios.put(endPoints('profileUpdate').url, formData);
},
@@ -0,0 +1,14 @@
/* global axios */
import ApiClient from './ApiClient';
class AutomationsAPI extends ApiClient {
constructor() {
super('automation_rules', { accountScoped: true });
}
clone(automationId) {
return axios.post(`${this.url}/${automationId}/clone`);
}
}
export default new AutomationsAPI();
+10 -4
View File
@@ -6,15 +6,21 @@ class CSATReportsAPI extends ApiClient {
super('csat_survey_responses', { accountScoped: true });
}
get({ page, from, to } = {}) {
get({ page, from, to, user_ids } = {}) {
return axios.get(this.url, {
params: { page, since: from, until: to, sort: '-created_at' },
params: {
page,
since: from,
until: to,
sort: '-created_at',
user_ids,
},
});
}
getMetrics({ from, to } = {}) {
getMetrics({ from, to, user_ids } = {}) {
return axios.get(`${this.url}/metrics`, {
params: { since: from, until: to },
params: { since: from, until: to, user_ids },
});
}
}
@@ -0,0 +1,18 @@
/* global axios */
import ApiClient from './ApiClient';
class CustomViewsAPI extends ApiClient {
constructor() {
super('custom_filters', { accountScoped: true });
}
getCustomViewsByFilterType(type) {
return axios.get(`${this.url}?filter_type=${type}`);
}
deleteCustomViews(id, type) {
return axios.delete(`${this.url}/${id}?filter_type=${type}`);
}
}
export default new CustomViewsAPI();
@@ -7,17 +7,19 @@ export const buildCreatePayload = ({
isPrivate,
contentAttributes,
echoId,
file,
files,
ccEmails = '',
bccEmails = '',
}) => {
let payload;
if (file) {
if (files && files.length !== 0) {
payload = new FormData();
payload.append('attachments[]', file, file.name);
if (message) {
payload.append('content', message);
}
files.forEach(file => {
payload.append('attachments[]', file);
});
payload.append('private', isPrivate);
payload.append('echo_id', echoId);
payload.append('cc_emails', ccEmails);
@@ -46,7 +48,7 @@ class MessageApi extends ApiClient {
private: isPrivate,
contentAttributes,
echo_id: echoId,
file,
files,
ccEmails = '',
bccEmails = '',
}) {
@@ -58,7 +60,7 @@ class MessageApi extends ApiClient {
isPrivate,
contentAttributes,
echoId,
file,
files,
ccEmails,
bccEmails,
}),
+20 -4
View File
@@ -1,20 +1,36 @@
/* global axios */
import ApiClient from './ApiClient';
const getTimeOffset = () => -new Date().getTimezoneOffset() / 60;
class ReportsAPI extends ApiClient {
constructor() {
super('reports', { accountScoped: true, apiVersion: 'v2' });
}
getReports(metric, since, until, type = 'account', id) {
getReports(metric, since, until, type = 'account', id, group_by) {
return axios.get(`${this.url}`, {
params: { metric, since, until, type, id },
params: {
metric,
since,
until,
type,
id,
group_by,
timezone_offset: getTimeOffset(),
},
});
}
getSummary(since, until, type = 'account', id) {
getSummary(since, until, type = 'account', id, group_by) {
return axios.get(`${this.url}/summary`, {
params: { since, until, type, id },
params: {
since,
until,
type,
id,
group_by,
},
});
}
@@ -0,0 +1,15 @@
import automations from '../automation';
import ApiClient from '../ApiClient';
describe('#AutomationsAPI', () => {
it('creates correct instance', () => {
expect(automations).toBeInstanceOf(ApiClient);
expect(automations).toHaveProperty('get');
expect(automations).toHaveProperty('show');
expect(automations).toHaveProperty('create');
expect(automations).toHaveProperty('update');
expect(automations).toHaveProperty('delete');
expect(automations).toHaveProperty('clone');
expect(automations.url).toBe('/api/v1/automation_rules');
});
});
@@ -36,7 +36,7 @@ describe('#ConversationAPI', () => {
echoId: 12,
isPrivate: true,
file: new Blob(['test-content'], { type: 'application/pdf' }),
files: [new Blob(['test-content'], { type: 'application/pdf' })],
});
expect(formPayload).toBeInstanceOf(FormData);
expect(formPayload.get('content')).toEqual('test content');
@@ -27,6 +27,7 @@ describe('#Reports API', () => {
since: 1621103400,
until: 1621621800,
type: 'account',
timezone_offset: -0,
},
});
});
@@ -26,7 +26,8 @@
code {
border: 0;
font-family: 'Monaco', Verdana;
font-family: 'ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas',
'"Liberation Mono"', '"Courier New"', 'monospace';
font-size: $font-size-mini;
&.hljs {
@@ -55,7 +56,6 @@ code {
padding-right: var(--space-normal);
}
.badge {
border-radius: var(--border-radius-normal);
}
@@ -10,10 +10,24 @@ body {
.app-wrapper {
@include full-height;
flex-grow: 0;
min-height: 0;
width: 100%;
}
.app-root {
.banner + .app-wrapper {
.button--fixed-right-top {
top: 5.6 * $space-one;
}
.off-canvas-content {
.button--fixed-right-top {
top: $space-small;
}
}
}
is-closed .app-root {
@include flex;
flex-direction: column;
}
@@ -21,6 +35,7 @@ body {
.app-content {
@include flex;
@include full-height;
min-height: 0;
overflow: hidden;
}
@@ -54,3 +54,9 @@
.text-y-800 {
color: var(--y-800);
}
.text-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@@ -228,8 +228,14 @@
@include flex-align(right, null);
.wrap {
align-items: flex-end;
display: flex;
margin-right: $space-normal;
text-align: right;
.sender--info {
padding: var(--space-small) 0 var(--space-smaller) var(--space-small);
}
}
.bubble {
@@ -20,9 +20,28 @@
color: $color-heading;
}
.metric-wrap {
align-items: baseline;
display: flex;
}
.metric {
font-size: $font-size-bigger;
font-size: $font-size-big;
font-weight: $font-weight-feather;
margin-top: $space-smaller;
}
.metric-trend {
font-size: $font-size-small;
margin-left: $space-small;
}
.metric-up {
color: $success-color;
}
.metric-down {
color: $alert-color;
}
.desc {
@@ -15,7 +15,7 @@
@include shadow;
background-color: $woot-snackbar-bg;
border-radius: $space-smaller;
display: inline-block;
display: inline-flex;
margin-bottom: $space-small;
max-width: 40rem;
min-height: 3rem;
+165 -20
View File
@@ -1,26 +1,51 @@
l<template>
<template>
<div class="conversations-list-wrap">
<slot></slot>
<div class="chat-list__top" :class="{ filter__applied: hasAppliedFilters }">
<div
class="chat-list__top"
:class="{ filter__applied: hasAppliedFiltersOrActiveFolders }"
>
<h1 class="page-title text-truncate" :title="pageTitle">
{{ pageTitle }}
</h1>
<div class="filter--actions">
<chat-filter
v-if="!hasAppliedFilters"
v-if="!hasAppliedFiltersOrActiveFolders"
@statusFilterChange="updateStatusType"
/>
<div v-if="hasAppliedFilters && !hasActiveFolders">
<woot-button
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.ADD.SAVE_BUTTON')"
size="tiny"
variant="smooth"
color-scheme="secondary"
icon="save"
@click="onClickOpenAddFoldersModal"
/>
<woot-button
v-tooltip.top-end="$t('FILTER.CLEAR_BUTTON_LABEL')"
size="tiny"
variant="smooth"
color-scheme="alert"
icon="dismiss-circle"
@click="resetAndFetchData"
/>
</div>
<div v-if="hasActiveFolders">
<woot-button
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.DELETE.DELETE_BUTTON')"
size="tiny"
variant="smooth"
color-scheme="alert"
icon="delete"
class="delete-custom-view__button"
@click="onClickOpenDeleteFoldersModal"
/>
</div>
<woot-button
v-else
size="small"
variant="clear"
color-scheme="alert"
@click="resetAndFetchData"
>
{{ $t('FILTER.CLEAR_BUTTON_LABEL') }}
</woot-button>
<woot-button
v-tooltip.top-end="$t('FILTER.TOOLTIP_LABEL')"
variant="clear"
color-scheme="secondary"
@@ -33,8 +58,24 @@ l<template>
</div>
</div>
<add-custom-views
v-if="showAddFoldersModal"
:custom-views-query="foldersQuery"
:open-last-saved-item="openLastSavedItemInFolder"
@close="onCloseAddFoldersModal"
/>
<delete-custom-views
v-if="showDeleteFoldersModal"
:show-delete-popup.sync="showDeleteFoldersModal"
:active-custom-view="activeFolder"
:custom-views-id="foldersId"
:open-last-item-after-delete="openLastItemAfterDeleteInFolder"
@close="onCloseDeleteFoldersModal"
/>
<chat-type-tabs
v-if="!hasAppliedFilters"
v-if="!hasAppliedFiltersOrActiveFolders"
:items="assigneeTabItems"
:active-tab="activeAssigneeTab"
class="tab--chat-type"
@@ -51,6 +92,7 @@ l<template>
:key="chat.id"
:active-label="label"
:team-id="teamId"
:folders-id="foldersId"
:chat="chat"
:conversation-type="conversationType"
:show-assignee="showAssigneeInConversationCard"
@@ -87,7 +129,7 @@ l<template>
>
<conversation-advanced-filter
v-if="showAdvancedFilters"
:filter-types="advancedFilterTypes"
:initial-filter-types="advancedFilterTypes"
:on-close="onToggleAdvanceFiltersModal"
@applyFilter="onApplyFilter"
/>
@@ -108,6 +150,8 @@ import conversationMixin from '../mixins/conversations';
import wootConstants from '../constants';
import advancedFilterTypes from './widgets/conversation/advancedFilterItems';
import filterQueryGenerator from '../helper/filterQueryGenerator.js';
import AddCustomViews from 'dashboard/routes/dashboard/customviews/AddCustomViews';
import DeleteCustomViews from 'dashboard/routes/dashboard/customviews/DeleteCustomViews.vue';
import {
hasPressedAltAndJKey,
@@ -116,10 +160,12 @@ import {
export default {
components: {
AddCustomViews,
ChatTypeTabs,
ConversationCard,
ChatFilter,
ConversationAdvancedFilter,
DeleteCustomViews,
},
mixins: [timeMixin, conversationMixin, eventListenerMixins],
props: {
@@ -139,6 +185,10 @@ export default {
type: String,
default: '',
},
foldersId: {
type: [String, Number],
default: 0,
},
},
data() {
return {
@@ -149,6 +199,9 @@ export default {
...filter,
attributeName: this.$t(`FILTER.ATTRIBUTES.${filter.attributeI18nKey}`),
})),
foldersQuery: {},
showAddFoldersModal: false,
showDeleteFoldersModal: false,
};
},
computed: {
@@ -163,9 +216,23 @@ export default {
activeInbox: 'getSelectedInbox',
conversationStats: 'conversationStats/getStats',
appliedFilters: 'getAppliedConversationFilters',
folders: 'customViews/getCustomViews',
}),
hasAppliedFilters() {
return this.appliedFilters.length;
return this.appliedFilters.length !== 0;
},
hasActiveFolders() {
return this.activeFolder && this.foldersId !== 0;
},
hasAppliedFiltersOrActiveFolders() {
return this.hasAppliedFilters || this.hasActiveFolders;
},
savedFoldersValue() {
if (this.hasActiveFolders) {
const payload = this.activeFolder.query;
this.fetchSavedFilteredConversations(payload);
}
return {};
},
assigneeTabItems() {
return this.$t('CHAT_LIST.ASSIGNEE_TYPE_TABS').map(item => {
@@ -178,7 +245,10 @@ export default {
});
},
showAssigneeInConversationCard() {
return this.activeAssigneeTab === wootConstants.ASSIGNEE_TYPE.ALL;
return (
this.hasAppliedFiltersOrActiveFolders ||
this.activeAssigneeTab === wootConstants.ASSIGNEE_TYPE.ALL
);
},
inbox() {
return this.$store.getters['inboxes/getInbox'](this.activeInbox);
@@ -189,7 +259,9 @@ export default {
);
},
currentPageFilterKey() {
return this.hasAppliedFilters ? 'appliedFilters' : this.activeAssigneeTab;
return this.hasAppliedFiltersOrActiveFolders
? 'appliedFilters'
: this.activeAssigneeTab;
},
currentFiltersPage() {
return this.$store.getters['conversationPage/getCurrentPageFilter'](
@@ -212,6 +284,7 @@ export default {
conversationType: this.conversationType
? this.conversationType
: undefined,
folders: this.hasActiveFolders ? this.savedFoldersValue : undefined,
};
},
pageTitle() {
@@ -227,11 +300,14 @@ export default {
if (this.conversationType === 'mention') {
return this.$t('CHAT_LIST.MENTION_HEADING');
}
if (this.hasActiveFolders) {
return this.activeFolder.name;
}
return this.$t('CHAT_LIST.TAB_HEADING');
},
conversationList() {
let conversationList = [];
if (!this.hasAppliedFilters) {
if (!this.hasAppliedFiltersOrActiveFolders) {
const filters = this.conversationFilters;
if (this.activeAssigneeTab === 'me') {
conversationList = [...this.mineChatsList(filters)];
@@ -246,6 +322,16 @@ export default {
return conversationList;
},
activeFolder() {
if (this.foldersId) {
const activeView = this.folders.filter(
view => view.id === Number(this.foldersId)
);
const [firstValue] = activeView;
return firstValue;
}
return undefined;
},
activeTeam() {
if (this.teamId) {
return this.$store.getters['teams/getTeam'](this.teamId);
@@ -266,6 +352,11 @@ export default {
conversationType() {
this.resetAndFetchData();
},
activeFolder() {
if (!this.hasAppliedFilters) {
this.resetAndFetchData();
}
},
},
mounted() {
this.$store.dispatch('setChatFilter', this.activeStatus);
@@ -280,10 +371,23 @@ export default {
if (this.$route.name !== 'home') {
this.$router.push({ name: 'home' });
}
this.foldersQuery = filterQueryGenerator(payload);
this.$store.dispatch('conversationPage/reset');
this.$store.dispatch('emptyAllConversations');
this.fetchFilteredConversations(payload);
},
onClickOpenAddFoldersModal() {
this.showAddFoldersModal = true;
},
onCloseAddFoldersModal() {
this.showAddFoldersModal = false;
},
onClickOpenDeleteFoldersModal() {
this.showDeleteFoldersModal = true;
},
onCloseDeleteFoldersModal() {
this.showDeleteFoldersModal = false;
},
onToggleAdvanceFiltersModal() {
this.showAdvancedFilters = !this.showAdvancedFilters;
},
@@ -335,6 +439,13 @@ export default {
this.$store.dispatch('conversationPage/reset');
this.$store.dispatch('emptyAllConversations');
this.$store.dispatch('clearConversationFilters');
if (this.hasActiveFolders) {
const payload = this.activeFolder.query;
this.fetchSavedFilteredConversations(payload);
}
if (this.foldersId) {
return;
}
this.fetchConversations();
},
fetchConversations() {
@@ -343,9 +454,14 @@ export default {
.then(() => this.$emit('conversation-load'));
},
loadMoreConversations() {
if (!this.hasAppliedFilters) {
if (!this.hasAppliedFiltersOrActiveFolders) {
this.fetchConversations();
} else {
}
if (this.hasActiveFolders) {
const payload = this.activeFolder.query;
this.fetchSavedFilteredConversations(payload);
}
if (this.hasAppliedFilters) {
this.fetchFilteredConversations(this.appliedFilters);
}
},
@@ -359,6 +475,15 @@ export default {
.then(() => this.$emit('conversation-load'));
this.showAdvancedFilters = false;
},
fetchSavedFilteredConversations(payload) {
let page = this.currentFiltersPage + 1;
this.$store
.dispatch('fetchFilteredConversations', {
queryData: payload,
page,
})
.then(() => this.$emit('conversation-load'));
},
updateAssigneeTab(selectedTab) {
if (this.activeAssigneeTab !== selectedTab) {
bus.$emit('clearSearchInput');
@@ -374,6 +499,22 @@ export default {
this.resetAndFetchData();
}
},
openLastSavedItemInFolder() {
const lastItemOfFolder = this.folders[this.folders.length - 1];
const lastItemId = lastItemOfFolder.id;
this.$router.push({
name: 'folder_conversations',
params: { id: lastItemId },
});
},
openLastItemAfterDeleteInFolder() {
if (this.folders.length > 0) {
this.openLastSavedItemInFolder();
} else {
this.$router.push({ name: 'home' });
this.fetchConversations();
}
},
},
};
</script>
@@ -413,7 +554,11 @@ export default {
}
.filter__applied {
padding: var(--space-slab) 0 !important;
padding: 0 0 var(--space-slab) 0 !important;
border-bottom: 1px solid var(--color-border);
}
.delete-custom-view__button {
margin-right: var(--space-normal);
}
</style>
@@ -63,7 +63,7 @@
rel="noopener noreferrer"
class="value"
>
{{ value || '---' }}
{{ urlValue }}
</a>
<p v-else class="value">
{{ displayValue || '---' }}
@@ -119,7 +119,7 @@ import format from 'date-fns/format';
import { required, url } from 'vuelidate/lib/validators';
import { BUS_EVENTS } from 'shared/constants/busEvents';
import MultiselectDropdown from 'shared/components/ui/MultiselectDropdown.vue';
import { isValidURL } from '../helper/URLHelper';
const DATE_FORMAT = 'yyyy-MM-dd';
export default {
@@ -141,16 +141,6 @@ export default {
editedValue: null,
};
},
validations() {
if (this.isAttributeTypeLink) {
return {
editedValue: { required, url },
};
}
return {
editedValue: { required },
};
},
computed: {
formattedValue() {
@@ -184,6 +174,9 @@ export default {
isAttributeTypeDate() {
return this.attributeType === 'date';
},
urlValue() {
return isValidURL(this.value) ? this.value : '---';
},
notAttributeTypeCheckboxAndList() {
return !this.isAttributeTypeCheckbox && !this.isAttributeTypeList;
},
@@ -206,6 +199,23 @@ export default {
return this.editedValue;
},
},
watch: {
value() {
this.isEditing = false;
this.editedValue = this.value;
},
},
validations() {
if (this.isAttributeTypeLink) {
return {
editedValue: { required, url },
};
}
return {
editedValue: { required },
};
},
mounted() {
this.editedValue = this.formattedValue;
bus.$on(BUS_EVENTS.FOCUS_CUSTOM_ATTRIBUTE, focusAttributeKey => {
@@ -4,7 +4,12 @@
<div class="ui-notification">
<fluent-icon icon="wifi-off" />
<p class="ui-notification-text">
{{ $t('NETWORK.NOTIFICATION.TEXT') }}
{{
useInstallationName(
$t('NETWORK.NOTIFICATION.TEXT'),
globalConfig.installationName
)
}}
</p>
<woot-button variant="clear" size="small" @click="refreshPage">
{{ $t('NETWORK.BUTTON.REFRESH') }}
@@ -23,15 +28,28 @@
</template>
<script>
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
import { mapGetters } from 'vuex';
import { BUS_EVENTS } from 'shared/constants/busEvents';
export default {
mixins: [globalConfigMixin],
data() {
return {
showNotification: !navigator.onLine,
};
},
computed: {
...mapGetters({ globalConfig: 'globalConfig/get' }),
},
mounted() {
window.addEventListener('offline', this.updateOnlineStatus);
window.bus.$on(BUS_EVENTS.WEBSOCKET_DISCONNECT, () => {
this.updateOnlineStatus({ type: 'offline' });
});
},
beforeDestroy() {
@@ -4,6 +4,11 @@
<div class="ui-snackbar-text">
{{ message }}
</div>
<div v-if="action" class="ui-snackbar-action">
<router-link v-if="action.type == 'link'" :to="action.to">
{{ action.message }}
</router-link>
</div>
</div>
</div>
</template>
@@ -12,6 +17,10 @@
export default {
props: {
message: { type: String, default: '' },
action: {
type: Object,
default: () => {},
},
showButton: Boolean,
duration: {
type: [String, Number],
@@ -4,6 +4,7 @@
v-for="snackMessage in snackMessages"
:key="snackMessage.key"
:message="snackMessage.message"
:action="snackMessage.action"
/>
</transition-group>
</template>
@@ -35,8 +36,12 @@ export default {
bus.$off('newToastMessage', this.onNewToastMessage);
},
methods: {
onNewToastMessage(message) {
this.snackMessages.push({ key: new Date().getTime(), message });
onNewToastMessage(message, action) {
this.snackMessages.push({
key: new Date().getTime(),
message,
action,
});
window.setTimeout(() => {
this.snackMessages.splice(0, 1);
}, this.duration);
@@ -0,0 +1,74 @@
<template>
<banner
v-if="shouldShowBanner"
class="update-banner"
color-scheme="primary"
:banner-message="bannerMessage"
href-link="https://github.com/chatwoot/chatwoot/releases"
:href-link-text="$t('GENERAL_SETTINGS.LEARN_MORE')"
has-close-button
@close="dismissUpdateBanner"
/>
</template>
<script>
import Banner from 'dashboard/components/ui/Banner.vue';
import LocalStorage from '../../helper/localStorage';
import { mapGetters } from 'vuex';
import adminMixin from 'dashboard/mixins/isAdmin';
const semver = require('semver');
const dismissedUpdates = new LocalStorage('dismissedUpdates');
export default {
components: {
Banner,
},
mixins: [adminMixin],
props: {
latestChatwootVersion: {
type: String,
default: '',
},
},
computed: {
...mapGetters({ globalConfig: 'globalConfig/get' }),
hasAnUpdateAvailable() {
if (!semver.valid(this.latestChatwootVersion)) {
return false;
}
return semver.lt(
this.globalConfig.appVersion,
this.latestChatwootVersion
);
},
bannerMessage() {
return this.$t('GENERAL_SETTINGS.UPDATE_CHATWOOT', {
latestChatwootVersion: this.latestChatwootVersion,
});
},
shouldShowBanner() {
return (
this.globalConfig.displayManifest &&
this.hasAnUpdateAvailable &&
!this.isVersionNotificationDismissed(this.latestChatwootVersion) &&
this.isAdmin
);
},
},
methods: {
isVersionNotificationDismissed(version) {
return dismissedUpdates.get().includes(version);
},
dismissUpdateBanner() {
let updatedDismissedItems = dismissedUpdates.get();
if (updatedDismissedItems instanceof Array) {
updatedDismissedItems.push(this.latestChatwootVersion);
} else {
updatedDismissedItems = [this.latestChatwootVersion];
}
dismissedUpdates.store(updatedDismissedItems);
this.latestChatwootVersion = this.globalConfig.appVersion;
},
},
};
</script>
@@ -52,12 +52,16 @@
<woot-dropdown-item v-if="!isPending">
<woot-button
variant="clear"
color-scheme="secondary"
size="small"
icon="book-clock"
@click="() => toggleStatus(STATUS_TYPE.PENDING)"
>
{{ this.$t('CONVERSATION.RESOLVE_DROPDOWN.MARK_PENDING') }}
</woot-button>
</woot-dropdown-item>
<woot-dropdown-divider v-if="isOpen" />
<woot-dropdown-sub-menu
v-if="isOpen"
:title="this.$t('CONVERSATION.RESOLVE_DROPDOWN.SNOOZE.TITLE')"
@@ -65,6 +69,9 @@
<woot-dropdown-item>
<woot-button
variant="clear"
color-scheme="secondary"
size="small"
icon="send-clock"
@click="() => toggleStatus(STATUS_TYPE.SNOOZED, null)"
>
{{ this.$t('CONVERSATION.RESOLVE_DROPDOWN.SNOOZE.NEXT_REPLY') }}
@@ -73,6 +80,9 @@
<woot-dropdown-item>
<woot-button
variant="clear"
color-scheme="secondary"
size="small"
icon="dual-screen-clock"
@click="
() => toggleStatus(STATUS_TYPE.SNOOZED, snoozeTimes.tomorrow)
"
@@ -83,6 +93,9 @@
<woot-dropdown-item>
<woot-button
variant="clear"
color-scheme="secondary"
size="small"
icon="calendar-clock"
@click="
() => toggleStatus(STATUS_TYPE.SNOOZED, snoozeTimes.nextWeek)
"
@@ -110,6 +123,8 @@ import {
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
import WootDropdownSubMenu from 'shared/components/ui/dropdown/DropdownSubMenu.vue';
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
import WootDropdownDivider from 'shared/components/ui/dropdown/DropdownDivider';
import wootConstants from '../../constants';
import {
getUnixTime,
@@ -129,6 +144,7 @@ export default {
WootDropdownItem,
WootDropdownMenu,
WootDropdownSubMenu,
WootDropdownDivider,
},
mixins: [clickaway, alertMixin, eventListenerMixins],
props: { conversationId: { type: [String, Number], required: true } },
@@ -269,5 +285,6 @@ export default {
margin-top: var(--space-micro);
right: 0;
max-width: 20rem;
min-width: 15.6rem;
}
</style>
@@ -21,6 +21,7 @@ import SubmitButton from './buttons/FormSubmitButton';
import Tabs from './ui/Tabs/Tabs';
import TabsItem from './ui/Tabs/TabsItem';
import Thumbnail from './widgets/Thumbnail.vue';
import ConfirmModal from './widgets/modal/ConfirmationModal.vue';
const WootUIKit = {
AvatarUploader,
@@ -45,6 +46,7 @@ const WootUIKit = {
Tabs,
TabsItem,
Thumbnail,
ConfirmModal,
install(Vue) {
const keys = Object.keys(this);
keys.pop(); // remove 'install' from keys
@@ -1,7 +1,7 @@
<template>
<aside class="woot-sidebar">
<primary-sidebar
:logo-source="globalConfig.logo"
:logo-source="globalConfig.logoThumbnail"
:installation-name="globalConfig.installationName"
:account-id="accountId"
:menu-items="primaryMenuItems"
@@ -14,27 +14,11 @@
:inboxes="inboxes"
:labels="labels"
:teams="teams"
:custom-views="customViews"
:menu-config="activeSecondaryMenu"
:current-role="currentRole"
@add-label="showAddLabelPopup"
/>
<woot-key-shortcut-modal
v-if="showShortcutModal"
@close="closeKeyShortcutModal"
@clickaway="closeKeyShortcutModal"
/>
<account-selector
:show-account-modal="showAccountModal"
@close-account-modal="toggleAccountModal"
@show-create-account-modal="openCreateAccountModal"
/>
<add-account-modal
:show="showCreateAccountModal"
@close-account-create-modal="closeCreateAccountModal"
/>
<woot-modal :show.sync="showAddLabelModal" :on-close="hideAddLabelPopup">
<add-label-modal @close="hideAddLabelPopup" />
</woot-modal>
</aside>
</template>
@@ -45,12 +29,8 @@ import adminMixin from '../../mixins/isAdmin';
import { getSidebarItems } from './config/default-sidebar';
import alertMixin from 'shared/mixins/alertMixin';
import AccountSelector from './sidebarComponents/AccountSelector.vue';
import AddAccountModal from './sidebarComponents/AddAccountModal.vue';
import AddLabelModal from '../../routes/dashboard/settings/labels/AddLabel';
import PrimarySidebar from './sidebarComponents/Primary';
import SecondarySidebar from './sidebarComponents/Secondary';
import WootKeyShortcutModal from 'components/widgets/modal/WootKeyShortcutModal';
import {
hasPressedAltAndCKey,
hasPressedAltAndRKey,
@@ -64,21 +44,13 @@ import router from '../../routes';
export default {
components: {
AccountSelector,
AddAccountModal,
AddLabelModal,
PrimarySidebar,
SecondarySidebar,
WootKeyShortcutModal,
},
mixins: [adminMixin, alertMixin, eventListenerMixins],
data() {
return {
showOptionsMenu: false,
showAccountModal: false,
showCreateAccountModal: false,
showAddLabelModal: false,
showShortcutModal: false,
};
},
@@ -92,6 +64,26 @@ export default {
labels: 'labels/getLabelsOnSidebar',
teams: 'teams/getMyTeams',
}),
activeCustomView() {
if (this.activePrimaryMenu.key === 'contacts') {
return 'contact';
}
if (this.activePrimaryMenu.key === 'conversations') {
return 'conversation';
}
return '';
},
customViews() {
return this.$store.getters['customViews/getCustomViewsByFilterType'](
this.activeCustomView
);
},
isConversationOrContactActive() {
return (
this.activePrimaryMenu.key === 'contacts' ||
this.activePrimaryMenu.key === 'conversations'
);
},
sideMenuConfig() {
return getSidebarItems(this.accountId);
},
@@ -119,20 +111,32 @@ export default {
return activePrimaryMenu;
},
},
watch: {
activeCustomView() {
this.fetchCustomViews();
},
},
mounted() {
this.$store.dispatch('labels/get');
this.$store.dispatch('inboxes/get');
this.$store.dispatch('notifications/unReadCount');
this.$store.dispatch('teams/get');
this.$store.dispatch('attributes/get');
this.fetchCustomViews();
},
methods: {
fetchCustomViews() {
if (this.isConversationOrContactActive) {
this.$store.dispatch('customViews/get', this.activeCustomView);
}
},
toggleKeyShortcutModal() {
this.showShortcutModal = true;
this.$emit('open-key-shortcut-modal');
},
closeKeyShortcutModal() {
this.showShortcutModal = false;
this.$emit('close-key-shortcut-modal');
},
handleKeyEvents(e) {
if (hasPressedCommandAndForwardSlash(e)) {
@@ -167,20 +171,10 @@ export default {
window.$chatwoot.toggle();
},
toggleAccountModal() {
this.showAccountModal = !this.showAccountModal;
},
openCreateAccountModal() {
this.showAccountModal = false;
this.showCreateAccountModal = true;
},
closeCreateAccountModal() {
this.showCreateAccountModal = false;
this.$emit('toggle-account-modal');
},
showAddLabelPopup() {
this.showAddLabelModal = true;
},
hideAddLabelPopup() {
this.showAddLabelModal = false;
this.$emit('show-add-label-popup');
},
},
};
@@ -190,16 +184,8 @@ export default {
.woot-sidebar {
background: var(--white);
display: flex;
}
.secondary-menu {
background: var(--white);
border-right: 1px solid var(--s-50);
height: 100vh;
width: 19rem;
flex-shrink: 0;
overflow: auto;
padding: var(--space-small);
min-height: 0;
height: 100%;
}
</style>
@@ -5,6 +5,7 @@ const contacts = accountId => ({
routes: [
'contacts_dashboard',
'contact_profile_dashboard',
'contacts_segments_dashboard',
'contacts_labels_dashboard',
],
menuItems: [
@@ -14,6 +14,8 @@ const conversations = accountId => ({
'conversations_through_team',
'conversation_mentions',
'conversation_through_mentions',
'folder_conversations',
'conversations_through_folders',
],
menuItems: [
{
@@ -70,7 +70,7 @@ const settings = accountId => ({
toStateName: 'attributes_list',
},
{
icon: 'autocorrect',
icon: 'automation',
label: 'AUTOMATION',
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/automation/list`),
@@ -0,0 +1,36 @@
<template>
<div v-if="showShowCurrentAccountContext" class="account-context--group">
{{ $t('SIDEBAR.CURRENTLY_VIEWING_ACCOUNT') }}
<p class="account-context--name text-ellipsis">
{{ account.name }}
</p>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters({
account: 'getCurrentAccount',
userAccounts: 'getUserAccounts',
}),
showShowCurrentAccountContext() {
return this.userAccounts.length > 1 && this.account.name;
},
},
};
</script>
<style scoped lang="scss">
.account-context--group {
border-radius: var(--border-radius-normal);
border: 1px solid var(--color-border);
font-size: var(--font-size-mini);
padding: var(--space-small);
margin-bottom: var(--space-small);
.account-context--name {
font-weight: var(--font-weight-medium);
margin-bottom: 0;
}
}
</style>
@@ -54,7 +54,7 @@
:class="{ 'is-active': isActive }"
@click="e => handleProfileSettingClick(e, navigate)"
>
<fluent-icon icon="person" class="icon icon--font" />
<fluent-icon icon="person" size="14" class="icon icon--font" />
<span class="button__content">
{{ $t('SIDEBAR_ITEMS.PROFILE_SETTINGS') }}
</span>
@@ -93,7 +93,7 @@ export default {
width: var(--space-jumbo);
border-right: 1px solid var(--s-50);
box-sizing: content-box;
height: 100vh;
height: 100%;
flex-shrink: 0;
}
@@ -1,5 +1,6 @@
<template>
<div v-if="hasSecondaryMenu" class="main-nav secondary-menu">
<account-context />
<transition-group name="menu-list" tag="ul" class="menu vertical">
<secondary-nav-item
v-for="menuItem in accessibleMenuItems"
@@ -18,9 +19,11 @@
<script>
import { frontendURL } from '../../../helper/URLHelper';
import SecondaryNavItem from './SecondaryNavItem.vue';
import AccountContext from './AccountContext.vue';
export default {
components: {
AccountContext,
SecondaryNavItem,
},
props: {
@@ -40,6 +43,10 @@ export default {
type: Array,
default: () => [],
},
customViews: {
type: Array,
default: () => [],
},
menuConfig: {
type: Object,
default: () => {},
@@ -53,6 +60,9 @@ export default {
hasSecondaryMenu() {
return this.menuConfig.menuItems && this.menuConfig.menuItems.length;
},
contactCustomViews() {
return this.customViews.filter(view => view.filter_type === 'contact');
},
accessibleMenuItems() {
if (!this.currentRole) {
return [];
@@ -150,14 +160,57 @@ export default {
})),
};
},
foldersSection() {
return {
icon: 'folder',
label: 'CUSTOM_VIEWS_FOLDER',
hasSubMenu: true,
key: 'custom_view',
children: this.customViews
.filter(view => view.filter_type === 'conversation')
.map(view => ({
id: view.id,
label: view.name,
truncateLabel: true,
toState: frontendURL(
`accounts/${this.accountId}/custom_view/${view.id}`
),
})),
};
},
contactSegmentsSection() {
return {
icon: 'folder',
label: 'CUSTOM_VIEWS_SEGMENTS',
hasSubMenu: true,
key: 'custom_view',
children: this.customViews
.filter(view => view.filter_type === 'contact')
.map(view => ({
id: view.id,
label: view.name,
truncateLabel: true,
toState: frontendURL(
`accounts/${this.accountId}/contacts/custom_view/${view.id}`
),
})),
};
},
additionalSecondaryMenuItems() {
let conversationMenuItems = [this.inboxSection, this.labelSection];
let contactMenuItems = [this.contactLabelSection];
if (this.teams.length) {
conversationMenuItems = [this.teamSection, ...conversationMenuItems];
}
if (this.customViews.length) {
conversationMenuItems = [this.foldersSection, ...conversationMenuItems];
}
if (this.contactCustomViews.length) {
contactMenuItems = [this.contactSegmentsSection, ...contactMenuItems];
}
return {
conversations: conversationMenuItems,
contacts: [this.contactLabelSection],
contacts: contactMenuItems,
};
},
},
@@ -168,3 +221,18 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.secondary-menu {
background: var(--white);
border-right: 1px solid var(--s-50);
height: 100%;
width: 19rem;
flex-shrink: 0;
overflow: hidden;
padding: var(--space-small);
&:hover {
overflow: auto;
}
}
</style>
@@ -7,7 +7,7 @@
v-else
class="secondary-menu--title secondary-menu--link fs-small"
:class="computedClass"
:to="menuItem.toState"
:to="menuItem && menuItem.toState"
>
<fluent-icon
:icon="menuItem.icon"
@@ -15,6 +15,14 @@
size="14"
/>
{{ $t(`SIDEBAR.${menuItem.label}`) }}
<span
v-if="menuItem.label === 'AUTOMATION'"
data-view-component="true"
label="Beta"
class="beta"
>
{{ $t('SIDEBAR.BETA') }}
</span>
</router-link>
<ul v-if="hasSubMenu" class="nested vertical menu">
@@ -73,14 +81,48 @@ export default {
hasSubMenu() {
return !!this.menuItem.children;
},
isInboxConversation() {
return (
this.$store.state.route.name === 'inbox_conversation' &&
this.menuItem.toStateName === 'home'
);
},
isTeamsSettings() {
return (
this.$store.state.route.name === 'settings_teams_edit' &&
this.menuItem.toStateName === 'settings_teams_list'
);
},
isInboxsSettings() {
return (
this.$store.state.route.name === 'settings_inbox_show' &&
this.menuItem.toStateName === 'settings_inbox_list'
);
},
isIntegrationsSettings() {
return (
this.$store.state.route.name === 'settings_integrations_webhook' &&
this.menuItem.toStateName === 'settings_integrations'
);
},
isApplicationsSettings() {
return (
this.$store.state.route.name === 'settings_applications_integration' &&
this.menuItem.toStateName === 'settings_applications'
);
},
computedClass() {
// If active Inbox is present
// donot highlight conversations
if (this.activeInbox) return ' ';
if (
this.$store.state.route.name === 'inbox_conversation' &&
this.menuItem.toStateName === 'home'
this.isInboxConversation ||
this.isTeamsSettings ||
this.isInboxsSettings ||
this.isIntegrationsSettings ||
this.isApplicationsSettings
) {
return 'is-active';
}
@@ -187,4 +229,17 @@ export default {
color: var(--w-500);
}
}
.beta {
padding-right: var(--space-smaller) !important;
padding-left: var(--space-smaller) !important;
margin-left: var(--space-half) !important;
display: inline-block;
font-size: var(--font-size-micro);
font-weight: var(--font-weight-medium);
line-height: 18px;
border: 1px solid transparent;
border-radius: 2em;
color: var(--g-800);
border-color: var(--g-700);
}
</style>
@@ -0,0 +1,149 @@
<template>
<div class="banner" :class="bannerClasses">
<span class="banner-message">
{{ bannerMessage }}
<a
v-if="hrefLink"
:href="hrefLink"
rel="noopener noreferrer nofollow"
target="_blank"
>
{{ hrefLinkText }}
</a>
</span>
<woot-button
v-if="hasActionButton"
size="small"
variant="link"
icon="arrow-right"
color-scheme="primary"
class-names="banner-action__button"
@click="onClick"
>
{{ actionButtonLabel }}
</woot-button>
<woot-button
v-if="hasCloseButton"
size="small"
variant="link"
color-scheme="secondary"
icon="dismiss-circle"
class-names="banner-action__button"
@click="onClickClose"
>
</woot-button>
</div>
</template>
<script>
export default {
props: {
bannerMessage: {
type: String,
default: '',
},
hrefLink: {
type: String,
default: '',
},
hrefLinkText: {
type: String,
default: '',
},
hasActionButton: {
type: Boolean,
default: false,
},
actionButtonLabel: {
type: String,
default: '',
},
colorScheme: {
type: String,
default: '',
},
hasCloseButton: {
type: Boolean,
default: false,
},
},
computed: {
bannerClasses() {
return [this.colorScheme];
},
},
methods: {
onClick(e) {
this.$emit('click', e);
},
onClickClose(e) {
this.$emit('close', e);
},
},
};
</script>
<style lang="scss" scoped>
.banner {
display: flex;
color: var(--white);
font-size: var(--font-size-mini);
padding: var(--space-slab) var(--space-normal);
justify-content: center;
position: sticky;
&.primary {
background: var(--w-500);
.banner-action__button {
color: var(--white);
}
}
&.secondary {
background: var(--s-200);
color: var(--s-800);
a {
color: var(--s-800);
}
}
&.alert {
background: var(--r-400);
}
&.warning {
background: var(--y-800);
color: var(--s-600);
a {
color: var(--s-600);
}
}
&.gray {
background: var(--b-500);
.banner-action__button {
color: var(--white);
}
}
a {
margin-left: var(--space-smaller);
text-decoration: underline;
color: var(--white);
font-size: var(--font-size-mini);
}
.banner-action__button {
margin: 0 var(--space-smaller);
::v-deep .button__content {
white-space: nowrap;
}
}
.banner-message {
display: flex;
align-items: center;
}
}
</style>
@@ -30,7 +30,7 @@ export default {
},
value: {
type: Date,
default: () => [],
default: [],
},
},
@@ -1,5 +1,5 @@
<template>
<div>
<div class="preview-item__wrap">
<div
v-for="(attachment, index) in attachments"
:key="attachment.id"
@@ -7,7 +7,7 @@
>
<div class="thumb-wrap">
<img
v-if="isTypeImage(attachment.resource.type)"
v-if="isTypeImage(attachment.resource)"
class="image-thumb"
:src="attachment.thumb"
/>
@@ -15,16 +15,17 @@
</div>
<div class="file-name-wrap">
<span class="item">
{{ attachment.resource.name }}
{{ fileName(attachment.resource) }}
</span>
</div>
<div class="file-size-wrap">
<span class="item">
{{ formatFileSize(attachment.resource.size) }}
<span class="item text-truncate">
{{ formatFileSize(attachment.resource) }}
</span>
</div>
<div class="remove-file-wrap">
<woot-button
v-if="!isTypeAudio(attachment.resource)"
class="remove--attachment clear secondary"
icon="dismiss"
@click="() => onRemoveAttachment(index)"
@@ -50,25 +51,42 @@ export default {
onRemoveAttachment(index) {
this.removeAttachment(index);
},
formatFileSize(size) {
formatFileSize(file) {
const size = file.byte_size || file.size;
return formatBytes(size, 0);
},
isTypeImage(type) {
isTypeImage(file) {
const type = file.content_type || file.type;
return type.includes('image');
},
isTypeAudio(file) {
const type = file.content_type || file.type;
return type.includes('audio');
},
fileName(file) {
return file.filename || file.name;
},
},
};
</script>
<style lang="scss" scoped>
.preview-item__wrap {
display: flex;
flex-direction: column;
overflow: auto;
margin-top: var(--space-normal);
max-height: 20rem;
}
.preview-item {
display: flex;
padding: var(--space-slab) 0 0;
background: var(--color-background-light);
background: var(--b-50);
border-radius: var(--border-radius-normal);
width: fit-content;
width: 24rem;
padding: var(--space-smaller);
margin-top: var(--space-normal);
margin-bottom: var(--space-one);
}
.thumb-wrap {
@@ -104,6 +122,7 @@ export default {
> .item {
margin: 0;
overflow: hidden;
font-size: var(--font-size-mini);
font-weight: var(--font-weight-medium);
}
@@ -114,7 +133,8 @@ export default {
}
.file-name-wrap {
max-width: 50%;
max-width: 60%;
min-width: 50%;
overflow: hidden;
text-overflow: ellipsis;
margin-left: var(--space-small);
@@ -0,0 +1,182 @@
<template>
<div
class="filter"
:class="{ error: v.action_params.$dirty && v.action_params.$error }"
>
<div class="filter-inputs">
<select
v-model="action_name"
class="action__question"
@change="resetFilter()"
>
<option
v-for="attribute in actionTypes"
:key="attribute.key"
:value="attribute.key"
>
{{ attribute.label }}
</option>
</select>
<div class="filter__answer--wrap">
<div class="multiselect-wrap--small">
<multiselect
v-model="action_params"
track-by="id"
label="name"
:placeholder="'Select'"
:multiple="true"
selected-label
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
deselect-label=""
:max-height="160"
:options="dropdownValues"
:allow-empty="false"
/>
</div>
</div>
<woot-button
icon="dismiss"
variant="clear"
color-scheme="secondary"
@click="removeAction"
/>
</div>
<p
v-if="v.action_params.$dirty && v.action_params.$error"
class="filter-error"
>
{{ $t('FILTER.EMPTY_VALUE_ERROR') }}
</p>
</div>
</template>
<script>
export default {
props: {
value: {
type: Object,
default: () => null,
},
actionTypes: {
type: Array,
default: () => [],
},
dropdownValues: {
type: Array,
default: () => [],
},
v: {
type: Object,
default: () => null,
},
},
computed: {
action_name: {
get() {
if (!this.value) return null;
return this.value.action_name;
},
set(value) {
const payload = this.value || {};
this.$emit('input', { ...payload, action_name: value });
},
},
action_params: {
get() {
if (!this.value) return null;
return this.value.action_params;
},
set(value) {
const payload = this.value || {};
this.$emit('input', { ...payload, action_params: value });
},
},
},
methods: {
removeAction() {
this.$emit('removeAction');
},
resetFilter() {
this.$emit('resetFilter');
},
},
};
</script>
<style lang="scss" scoped>
.filter {
background: var(--color-background);
padding: var(--space-small);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-medium);
margin-bottom: var(--space-small);
}
.filter.error {
background: var(--r-50);
}
.filter-inputs {
display: flex;
}
.filter-error {
color: var(--r-500);
display: block;
margin: var(--space-smaller) 0;
}
.action__question,
.filter__operator {
margin-bottom: var(--space-zero);
margin-right: var(--space-smaller);
}
.action__question {
max-width: 50%;
}
.filter__answer--wrap {
margin-right: var(--space-smaller);
flex-grow: 1;
input {
margin-bottom: 0;
}
}
.filter__answer {
&.answer--text-input {
margin-bottom: var(--space-zero);
}
}
.filter__join-operator-wrap {
position: relative;
z-index: var(--z-index-twenty);
margin: var(--space-zero);
}
.filter__join-operator {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin: var(--space-one) var(--space-zero);
.operator__line {
position: absolute;
width: 100%;
border-bottom: 1px solid var(--color-border);
}
.operator__select {
position: relative;
width: auto;
margin-bottom: var(--space-zero) !important;
}
}
.multiselect {
margin-bottom: var(--space-zero);
}
</style>
@@ -18,11 +18,11 @@ export default {
},
backgroundColor: {
type: String,
default: 'white',
default: '#c2e1ff',
},
color: {
type: String,
default: '',
default: '#1976cc',
},
customStyle: {
type: Object,
@@ -0,0 +1,90 @@
export const OPERATOR_TYPES_1 = [
{
value: 'equal_to',
label: 'Equal to',
},
{
value: 'not_equal_to',
label: 'Not equal to',
},
];
export const OPERATOR_TYPES_2 = [
{
value: 'equal_to',
label: 'Equal to',
},
{
value: 'not_equal_to',
label: 'Not equal to',
},
{
value: 'is_present',
label: 'Is present',
},
{
value: 'is_not_present',
label: 'Is not present',
},
];
export const OPERATOR_TYPES_3 = [
{
value: 'equal_to',
label: 'Equal to',
},
{
value: 'not_equal_to',
label: 'Not equal to',
},
{
value: 'contains',
label: 'Contains',
},
{
value: 'does_not_contain',
label: 'Does not contain',
},
];
export const OPERATOR_TYPES_4 = [
{
value: 'equal_to',
label: 'Equal to',
},
{
value: 'not_equal_to',
label: 'Not equal to',
},
{
value: 'is_present',
label: 'Is present',
},
{
value: 'is_not_present',
label: 'Is not present',
},
{
value: 'is_greater_than',
label: 'Is greater than',
},
{
value: 'is_less_than',
label: 'Is less than',
},
];
export const OPERATOR_TYPES_5 = [
{
value: 'is_greater_than',
label: 'Is greater than',
},
{
value: 'is_less_than',
label: 'Is less than',
},
{
value: 'days_before',
label: 'Is x days before',
},
];
@@ -0,0 +1,15 @@
import {
OPERATOR_TYPES_1,
OPERATOR_TYPES_2,
OPERATOR_TYPES_3,
OPERATOR_TYPES_4,
} from './FilterOperatorTypes';
describe('#filterOperators', () => {
it('Matches the correct Operators', () => {
expect(OPERATOR_TYPES_1).toMatchObject(OPERATOR_TYPES_1);
expect(OPERATOR_TYPES_2).toMatchObject(OPERATOR_TYPES_2);
expect(OPERATOR_TYPES_3).toMatchObject(OPERATOR_TYPES_3);
expect(OPERATOR_TYPES_4).toMatchObject(OPERATOR_TYPES_4);
});
});
@@ -1,8 +1,29 @@
<template>
<div class="filters">
<div class="filter">
<div class="filter" :class="{ error: v.values.$dirty && v.values.$error }">
<div class="filter-inputs">
<select
v-if="groupedFilters"
v-model="attributeKey"
class="filter__question"
@change="resetFilter()"
>
<optgroup
v-for="(group, i) in filterGroups"
:key="i"
:label="group.name"
>
<option
v-for="attribute in group.attributes"
:key="attribute.key"
:value="attribute.key"
>
{{ attribute.name }}
</option>
</optgroup>
</select>
<select
v-else
v-model="attributeKey"
class="filter__question"
@change="resetFilter()"
@@ -63,6 +84,14 @@
:option-height="104"
/>
</div>
<div v-else-if="inputType === 'date'" class="multiselect-wrap--small">
<input
v-model="values"
type="date"
:editable="false"
class="answer--text-input datepicker"
/>
</div>
<input
v-else
v-model="values"
@@ -112,6 +141,10 @@ export default {
type: String,
default: 'plain_text',
},
dataType: {
type: String,
default: 'plain_text',
},
operators: {
type: Array,
default: () => [],
@@ -132,6 +165,14 @@ export default {
type: Boolean,
default: true,
},
groupedFilters: {
type: Boolean,
default: false,
},
filterGroups: {
type: Array,
default: () => [],
},
},
computed: {
attributeKey: {
@@ -193,6 +234,10 @@ export default {
border-radius: var(--border-radius-medium);
}
.filter.error {
background: var(--r-50);
}
.filter-inputs {
display: flex;
}

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