Compare commits

..

256 commits

Author SHA1 Message Date
Sivin Varghese
98c289dc3e
chore: Fixes issue showing the CSAT error message (#6136)
Approved by Muhsin
2022-12-28 12:49:11 +05:30
Nithin David Thomas
3e91765472
fix: Expand title height of textarea on load (#6103) 2022-12-22 14:14:30 -08:00
Sojan Jose
1bf23055df
chore: Update translations (#6113) 2022-12-22 14:08:08 -08:00
Sivin Varghese
2af337be10
feat: Add the ability to toggle the secondary sidebar in all display breakpoints (#6118)
Co-authored-by: Nithin David <1277421+nithindavid@users.noreply.github.com>
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-12-22 14:07:11 -08:00
Muhsin Keloth
dbb6c0a074
chore: Increase the max concurrent number of devices (#6121) 2022-12-22 19:13:54 +05:30
Sivin Varghese
8c88344170
chore: Helpcenter improvements (#6098) 2022-12-22 18:51:24 +05:30
giquieu
6a78254701
feat: Send audio longer than 10 seconds and Add Prop audio-record-format (#6108)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2022-12-22 13:36:03 +05:30
Pranav Raj S
26ada8b342
fix: Update the logic to show read status for web (#6107) 2022-12-21 09:58:56 -08:00
Sivin Varghese
3c6bd2c8fd
fix: Use canned response menu from the editor in contact messages (#6109) 2022-12-21 16:01:50 +05:30
Sivin Varghese
2c2c47d7fd
chore: Hide inbox name only has one inbox (#6115) 2022-12-21 13:31:53 +05:30
Sojan
34f7405689 Merge branch 'release/2.12.0' into develop 2022-12-19 22:48:01 +05:30
Sojan
3ebfb3a140 Bump version to 2.12.0 2022-12-19 22:44:43 +05:30
Pranav Raj S
2dfe38ae4d
chore: Cleanup feature flags (#6096)
- Add more feature flags for CRM, auto_resolution, and reports
- Add a SuperAdmin link in the sidebar if the user is a super-admin
- SuperAdmin could view all the features on an account irrespective of whether the feature is enabled.
2022-12-19 22:38:30 +05:30
Sojan Jose
ca88eb55f4
chore: Update translations from Crowdin 2022-12-19 22:34:49 +05:30
Nithin David Thomas
d1a26e80f4
fix: Hide show more labels button when there's no overflow (#6097) 2022-12-19 08:54:20 -08:00
Nithin David Thomas
022d0b0ea3
chore: Enable prototyping classes for foundation (#5945)
* chore: Enable prototyping classes for foundation

* Marcros css clean up

* Imports utilities separately

* Fix macro position

* Changes global margin

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2022-12-19 14:11:11 +05:30
Tejaswini Chile
5541d9e00b
Fix: automation email improvement (#6061) 2022-12-19 13:21:33 +05:30
Pranav Raj S
38587b3aa1
fix: Update Slack integration to fix message delivery issues (#6093) 2022-12-17 16:41:11 -08:00
Pranav Raj S
4d2b7c37a0
feat: Display labels in the conversation card (#6088)
Co-authored-by: Nithin David Thomas <webofnithin@gmail.com>
2022-12-17 13:11:28 -08:00
Pranav Raj S
aaacf9d4d2
feat: Allow users to disable marking offline automatically (#6079)
Co-authored-by: Nithin David <1277421+nithindavid@users.noreply.github.com>
2022-12-16 11:59:27 -08:00
Sivin Varghese
82d3398932
fix: Add improvements to the Help Center module (#6081) 2022-12-16 11:41:55 -08:00
Muhsin Keloth
9106f6278d
fix: Allow editing label and placeholder for standard attributes in pre chat form (#6067)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2022-12-15 09:36:18 -08:00
Sojan Jose
f8e6308caf
chore: [Snyk] Fix for 7 vulnerabilities (#6075)
* fix: Gemfile to reduce vulnerabilities

The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-RUBY-LOOFAH-3168317
- https://snyk.io/vuln/SNYK-RUBY-LOOFAH-3168318
- https://snyk.io/vuln/SNYK-RUBY-LOOFAH-3168649
- https://snyk.io/vuln/SNYK-RUBY-RAILSHTMLSANITIZER-3168316
- https://snyk.io/vuln/SNYK-RUBY-RAILSHTMLSANITIZER-3168646
- https://snyk.io/vuln/SNYK-RUBY-RAILSHTMLSANITIZER-3168647
- https://snyk.io/vuln/SNYK-RUBY-RAILSHTMLSANITIZER-3168648

* chore: update gemlock

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2022-12-15 16:40:50 +05:30
Sojan Jose
72fcaa739c
chore: Update translations from Crowdin 2022-12-15 14:11:15 +05:30
smartdev58
9292653bf9
fix: Update enabled_features logic to fix superadmin edit action (#5959)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-12-14 18:24:02 -08:00
smartdev58
2a1a38f986
chore: Add feature flags for campaigns and website channel (#5778)
Co-authored-by: Tejaswini Chile <tejaswini@chatwoot.com>
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-12-14 16:06:26 -08:00
Sivin Varghese
2972319026
fix: Update colors in widget buttons to fix invalid colors (#6033)
Co-authored-by: nithindavid <1277421+nithindavid@users.noreply.github.com>
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-12-14 15:21:20 -08:00
dependabot[bot]
26e05de642
chore(deps): bump loofah from 2.18.0 to 2.19.1 (#6072)
Bumps [loofah](https://github.com/flavorjones/loofah) from 2.18.0 to 2.19.1.
- [Release notes](https://github.com/flavorjones/loofah/releases)
- [Changelog](https://github.com/flavorjones/loofah/blob/main/CHANGELOG.md)
- [Commits](https://github.com/flavorjones/loofah/compare/v2.18.0...v2.19.1)

---
updated-dependencies:
- dependency-name: loofah
  dependency-type: indirect
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-14 12:51:36 -08:00
dependabot[bot]
8222a47154
chore(deps): bump rails-html-sanitizer from 1.4.3 to 1.4.4 (#6074)
Bumps [rails-html-sanitizer](https://github.com/rails/rails-html-sanitizer) from 1.4.3 to 1.4.4.
- [Release notes](https://github.com/rails/rails-html-sanitizer/releases)
- [Changelog](https://github.com/rails/rails-html-sanitizer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rails/rails-html-sanitizer/compare/v1.4.3...v1.4.4)

---
updated-dependencies:
- dependency-name: rails-html-sanitizer
  dependency-type: indirect
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-14 12:51:12 -08:00
Sivin Varghese
9d78f0d6c6
feat: Adds number validation for WhatsApp inbox at the creation step (#6043)
Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
2022-12-12 19:52:37 -08:00
Sivin Varghese
86958278cd
fix: Unable to save automation "send email to team" (#6052)
* fix: Unable to save automation "send email to team"

* chore: Minor fixes
2022-12-12 20:10:33 +05:30
Pranav Raj S
823c836906
feat: Allow wildcard URL in the campaigns (#6056) 2022-12-09 16:43:09 -08:00
Pranav Raj S
6200559123
chore: Update analytics events (#6050) 2022-12-08 20:53:13 -08:00
Tejaswini Chile
7dc790a7e0
fix: Automatically remove expired story mention (#5300)
When a user mentions the connected Instagram page in a story, the story's content is downloaded in Chatwoot, then if the user deletes the story, the content persists in the platform.

fixes: #5258
2022-12-08 15:55:24 +03:00
dependabot[bot]
431e2931c4
chore(deps): bump nokogiri from 1.13.9 to 1.13.10 (#6040)
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.13.9 to 1.13.10.
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.13.9...v1.13.10)

---
updated-dependencies:
- dependency-name: nokogiri
  dependency-type: indirect
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-08 14:01:52 +03:00
Pranav Raj S
52ea201070
fix: Remove duplicate submit action (#6039) 2022-12-07 16:28:45 -08:00
Pranav Raj S
779bcf5e0d
feat: Update Signup screen (#6002)
* feat: Update Signup page designs

* feat: Update the signup page with dynamic testimonials

* Remove the images

* chore: Minor UI fixes

* chore: Form aligned to centre

* Update app/javascript/dashboard/routes/auth/components/Signup/Form.vue

* Design improvements

* Update company name key

* Revert "chore: Minor UI fixes"

This reverts commit 1556f4ca835d9aa0d9620fd6a3d52d259f0d7d65.

* Revert "Design improvements

This reverts commit dfb2364cf2f0cc93123698fde92e5f9e00536cc2.

* Remove footer

* Fix spacing

* Update app/views/installation/onboarding/index.html.erb

Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
2022-12-07 15:55:03 -08:00
Pranav Raj S
6064aad38f
chore: Add business email validation on signup (#6037) 2022-12-07 13:03:51 -08:00
Nithin David Thomas
caa45d1d92
feat: Pass logged in agent context to dashboard app (#6034)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-12-07 12:02:27 -08:00
Sivin Varghese
f1d1bb84fd
fix: Filters are not applied unless I'm on the All Conversations screen (#6006)
* fix: Filters are not applied unless I'm on the All Conversations screen

* chore: Review fixes

* chore: Minor sidebar fixes

* chore: Review fixes

Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-12-07 12:00:51 +05:30
dependabot[bot]
01cc3d7c9c
chore(deps): bump qs from 6.5.2 to 6.5.3 (#6028)
Bumps [qs](https://github.com/ljharb/qs) from 6.5.2 to 6.5.3.
- [Release notes](https://github.com/ljharb/qs/releases)
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.5.2...v6.5.3)

---
updated-dependencies:
- dependency-name: qs
  dependency-type: indirect
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-06 12:49:00 -08:00
Sivin Varghese
89cfc5bbf3
fix: Send message with "enter" also do new line (#5961)
* fix: Send message with "enter" also do new line

* chore: Review fixes

* chore: Naming fixes

* chore: Minor fixes

* chore: Fix line break issue when cmd plus enter enabled
2022-12-06 11:25:49 +05:30
OMAR.A
a82b9991b3
fix: Update the link used for email address change in the confirmation mail (#5937) 2022-12-05 16:17:27 -08:00
Sojan Jose
06434bc655
chore: Update translations from Crowdin (#5952) 2022-12-05 16:04:49 -08:00
Jordan Brough
b9fd1d88ea
Escape search term before building regular expression (#5994)
When doing a conversation search, if the search term includes any regular
expression characters and the search returns results, then this function would
throw an exception.

For example, if a conversation includes the text "+15555550111" and you search
for "+15555550111" then you get an exception like:

> Invalid regular expression: /(+15555550111)/: Nothing to repeat

Because the "+" is not escaped.
2022-12-05 16:02:43 -08:00
dependabot[bot]
8004f67efe
chore(deps): bump decode-uri-component from 0.2.0 to 0.2.2 (#6016)
Bumps [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component) from 0.2.0 to 0.2.2.
- [Release notes](https://github.com/SamVerschueren/decode-uri-component/releases)
- [Commits](https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.2)

---
updated-dependencies:
- dependency-name: decode-uri-component
  dependency-type: indirect
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-05 16:01:27 -08:00
Sivin Varghese
87ef39ad9c
feat: Add the ability to search emojis (#5928) 2022-12-05 16:00:42 -08:00
Vishnu Narayanan
c3b6e1a732
fix: update heroku app.json to use premium plans (#5349)
* fix: update heroku app.json to use premium plans

Use premium/paid dynos and addons as Heroku is set to deprecate free dynos/addons.

* fix: set default stack to heroku-20

* chore: update heroku app.json to use new dyno types

web and worker to use basic dynos
redis and postgres to use mini
2022-12-05 21:15:44 +05:30
Muhsin Keloth
c9cae01cb4
fix: Support audio in safari browser (#5943) 2022-12-05 12:30:56 +05:30
Sivin Varghese
613fb0b064
fix: Unable to add emoji exactly where the cursor is at (#5865)
* fix: Unable to add emoji exactly where the cursor is at

* chore: Minor fixes

* chore: Review fixes

* chore: Code clean up

* chore: Review fixes

* chore: Minor fixes

* chore: Review fixes
2022-12-05 11:16:00 +05:30
Tejaswini Chile
0b5c82ad5f
fix: Save hostname for the custom domain in the portal (#5984)
* fix: Save hostname for the custom domain in the portal
2022-12-03 20:37:56 +05:30
Sivin Varghese
c8ec397c79
fix: Update missing features in unattended / mentions view (#6009)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-12-02 15:07:38 -08:00
Tejaswini Chile
a08099bbcc
fix: Custom attr filter on conversations (#5978) 2022-12-02 10:37:32 +05:30
Pranav Raj S
e35638588a
fix: Avoid conversationId getting undefined in unattended view (#6001) 2022-11-30 20:37:58 -08:00
Tejaswini Chile
3083f74d45
fix: Update inbox json, removing password (#5981)
- Filter restricted inbox attributes in APIs for agents 

Fixes chatwoot/product#668

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2022-11-30 13:04:46 +03:00
Nithin David Thomas
85b52a1d3f
feat: Add a view for unattended conversations (#5890)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-11-29 08:18:00 -08:00
Nithin David Thomas
c94ba16565
fix: Updates logic to insert canned response into editor (#5880)
* fix: Updates logic to insert canned response into editor

* Removes commented code

* Parse incoming canned text as markdown
2022-11-29 19:46:55 +05:30
giquieu
0cad3bed71
fix: Files in Whatsapp arrives with a different Name (#5907)
- Add file name parameter to the WhatsApp attachment payload

fixes: #4481

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2022-11-29 16:54:49 +03:00
Clairton Rodrigo Heinzen
edcbd53425
feat: Read/Delivery status for Whatsapp Cloud API (#5157)
Process field statuses received in webhook WhatsApp cloud API

ref: #1021

Co-authored-by: Sojan <sojan@pepalo.com>
Co-authored-by: Nithin David <1277421+nithindavid@users.noreply.github.com>
2022-11-29 15:51:37 +03:00
Tejaswini Chile
a397f01692
fix: unassign team activity message (#5969) 2022-11-29 13:35:08 +05:30
Vishnu Narayanan
4755031e1d
feat: use sendmail for email as default (#5899)
* feat: use sendmail for the email if SMTP_ADDRESS is empty
2022-11-29 09:13:27 +05:30
Tejaswini Chile
fc9fc5a661
fix: destroy bulk service (#5921) 2022-11-25 22:46:50 +05:30
Sojan Jose
b05d06a28a
feat: Ability to lock to single conversation (#5881)
Adds the ability to lock conversation to a single thread for Whatsapp and Sms Inboxes when using outbound messages.

demo: https://www.loom.com/share/c9e1e563c8914837a4139dfdd2503fef

fixes: #4975

Co-authored-by: Nithin David <1277421+nithindavid@users.noreply.github.com>
2022-11-25 13:01:04 +03:00
Vishnu Narayanan
8813c77907
chore: add ph-no-capture css class to messages (#5932)
This ensure posthog replaces the corresponding elements with an empty
block in recordings.

ref: https://posthog.com/manual/recordings#ignoring-sensitive-elements
2022-11-24 10:42:28 -08:00
Sojan Jose
8ea0660862
chore: Add reauthorization prompt for Whatsapp Channel (#5929)
- Add reauthorization prompt for Whatsapp Channel

fixes: #5782
2022-11-24 14:50:32 +03:00
Sojan Jose
606fc9046a
feat: Allow users to mark a conversation as unread (#5924)
Allow users to mark conversations as unread.
Loom video: https://www.loom.com/share/ab70552d3c9c48b685da7dfa64be8bb3

fixes: #5552

Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-11-24 10:55:45 +03:00
Pranav Raj S
e593e516b8
chore: Enable Latvian (lv) language (#5920) 2022-11-22 12:54:13 -08:00
Sojan Jose
b5f8524167
chore: Update translations from Crowdin (#5883) 2022-11-22 11:42:29 -08:00
Sivin Varghese
b765e17457
fix: Sidebar missing at 1200px width (#5917) 2022-11-22 10:48:15 -08:00
Fayaz Ahmed
db37bfea06
fix: Add word-break to canned response list table content 2022-11-22 23:53:15 +05:30
Pranav Raj S
16bfd68d95
chore: Allow admins to choose the agent bot from the UI (#5895) 2022-11-18 08:54:32 -08:00
Fayaz Ahmed
33aacb3401
Add team option in bulk actions (#5885) 2022-11-18 14:44:36 +05:30
Fayaz Ahmed
47676c3cce
feat: Allow agent-bots to be created from the UI (#4153)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-11-17 22:15:58 -08:00
Tejaswini Chile
9bfbd528ef
feat: Assign team with teams none option (#5871) 2022-11-17 14:15:16 +05:30
Tejaswini Chile
e85f998a08
feat: Remove labels in macro (#5875) 2022-11-17 12:30:47 +05:30
Pranav Raj S
66044a0dc3
feat: Show last non-activity messages in the chat list (#5864) 2022-11-16 15:43:55 -08:00
Pranav Raj S
9b9c019de0
feat: Add support for after param in messages API (#5861) 2022-11-16 08:11:48 -08:00
dependabot[bot]
86e0ff76c5
chore(deps): bump loader-utils from 1.4.1 to 1.4.2 (#5859)
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 1.4.1 to 1.4.2.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v1.4.2/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v1.4.1...v1.4.2)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-15 18:56:59 -08:00
dependabot[bot]
abbb6ac676
chore(deps): bump minimatch from 3.0.4 to 3.1.2 (#5860)
Bumps [minimatch](https://github.com/isaacs/minimatch) from 3.0.4 to 3.1.2.
- [Release notes](https://github.com/isaacs/minimatch/releases)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.0.4...v3.1.2)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-type: indirect
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-15 18:56:50 -08:00
Pranav Raj S
42b466bda2
chore: Cleanup the design in widget builder (#5852) 2022-11-15 18:56:24 -08:00
Sojan
956837ded5 Merge branch 'release/2.11.0' into develop 2022-11-16 00:45:43 +00:00
Sojan
f0ef497005 Bump version to 2.11.0 2022-11-16 00:40:38 +00:00
Sojan Jose
8e2da837d4
chore: Update translations from Crowdin 2022-11-16 00:37:29 +00:00
Sojan Jose
e7f1a9ab4d
chore: Enable Macros for all accounts (#5858)
- migrations to enable macros for all accounts
2022-11-16 00:33:09 +00:00
Tejaswini Chile
826a735cdb
fix: Assign agent action changes (#5827) 2022-11-15 13:15:27 +05:30
Tejaswini Chile
38ab3c36db
fix: send label list not object in event data presenter (#5853) 2022-11-15 12:20:06 +05:30
Muhsin Keloth
b5f7be0cd2
fix: Full name update when creating a conversation without an email id (#5832)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-11-14 19:39:46 -08:00
Tejaswini Chile
efceaec950
fix: Activity message generation for team assignemnt automation (#5846) 2022-11-11 19:07:24 +05:30
Fayaz Ahmed
6aba352e0d
fix: Single select for agents and teams in Macros actions (#5837)
* Ignore settings.json

* Single select dropdown for teams and agents
2022-11-10 20:09:33 +05:30
Fayaz Ahmed
9eb861a3b7
feat: Custom attributes in automations and refactor (#4548)
* Custom attributes

* Custom Attrs Manifest

* Fix dropdown values for custom attributes

* Handle edit mode for custom attributes

* Ported duplicate logic to a mixin

* fix Code climate issue

* Fix Codeclimate complexity warning

* Bug fix - Custom attributes getting duplicated

* Bug fixes and Code Climate issue fix

* Code Climate Issues Breakdown

* Fix test spec

* Add labels for Custom attributes in dropdown

* Refactor

* Refactor Automion mixin

* Refactor Mixin

* Refactor getOperator

* Fix getOperatorType

* File name method refactor

* Refactor appendNewCondition

* spec update

* Refactor methods

* Mixin Spec update

* Automation Mixins Test Specs

* Mixin Spec Rerun

* Automation validations mixin spec

* Automation helper test spec

* Send custom_attr key

* Fix spec fixtures

* fix: Changes for custom attribute type and lower case search

* fix: Specs

* fix: Specs

* fix: Ruby version change

* fix: Ruby version change

* Removes Lowercased values and fix label value in api payload

* Fix specs

* Fixed Query Spec

* Removed disabled labels if no attributes are present

* Code Climate Fixes

* fix: custom attribute with indifferent access

* fix: custom attribute with indifferent access

* Fix specs

* Minor label fix

* REtrigger circle ci build

* Update app/javascript/shared/mixins/specs/automationMixin.spec.js

* Update app/javascript/shared/mixins/specs/automationMixin.spec.js

* fix: Custom attribute case insensitivity search

* Add missing reset action method to input

* Set team_input to single select instead of multiple

* fix: remove value case check for date,boolean and number data type

* fix: cognitive complexity

* fix: cognitive complexity

* fix: Fixed activity message for automation system

* fix: Fixed activity message for automation system

* fix: Fixed activity message for automation system

* fix: codeclimate

* fix: codeclimate

* fix: action cable events for label update

* fix: codeclimate, conversation modela number of methods

* fix: codeclimate, conversation modela number of methods

* fix: codeclimate, conversation modela number of methods

* fix: codeclimate, conversation modela number of methods

* Fix margin bottom for attachment button

* Remove margin bottom to avoid conflict from macros

* Fix automation action query generator using the right key

* fix: not running message created event for activity message

* fix: not running message created event for activity message

* codeclimate fix

* codeclimate fix

* codeclimate fix

* Update app/javascript/dashboard/mixins/automations/methodsMixin.js

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>

* Update app/javascript/shared/mixins/specs/automationHelper.spec.js

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>

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

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>

* Update app/javascript/dashboard/mixins/automations/methodsMixin.js

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>

Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
Co-authored-by: Tejaswini <tejaswini@chatwoot.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2022-11-10 10:53:29 +05:30
Sojan Jose
3184c8964d
chore: Update translations from Crowdin (#5831) 2022-11-09 20:26:30 -08:00
Nithin David Thomas
865346223b
fix: Add missing dropdown method (#5830)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-11-09 17:27:04 -08:00
Nithin David Thomas
4f82859bba
chore: Design improvements for thumbnail and dropdown (#5822) 2022-11-09 16:52:30 -08:00
Fayaz Ahmed
47c90e2085
feat: Add a loader till the dashboard app is loaded (#5814)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2022-11-09 10:06:09 -08:00
Tejaswini Chile
7352b928da
feat: Add assign agent option in macro (#5821)
Co-authored-by: fayazara <fayazara@gmail.com>
2022-11-09 09:52:48 -08:00
Muhsin Keloth
5febdde938
chore: Add feature flag for mobile v2 (#5797) 2022-11-08 21:56:02 -08:00
Nithin David Thomas
d39ace5a6b
feat: Improve image loading for thumbnails (#5823) 2022-11-08 21:05:13 -08:00
Sojan Jose
e2059cfc5b
fix: SocketError: getaddrinfo: for imap channels (#5824)
fixes: #5431
2022-11-08 20:23:46 -08:00
Sojan Jose
2e42821c48
chore: Update translations from Crowdin (#5810) 2022-11-08 09:36:24 -08:00
dependabot[bot]
16d59f4bb0
chore(deps): bump loader-utils from 1.4.0 to 1.4.1 (#5816)
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 1.4.0 to 1.4.1.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v1.4.1/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v1.4.0...v1.4.1)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-08 09:33:35 -08:00
Nithin David Thomas
0d9ed0674b
feat: Add store for conversation watchers (#5808)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-11-08 09:33:14 -08:00
salman652
6ff0c93659
feat: Ability to receive location on whatsapp inbox (#5742)
- Ability to  receive location messages on WhatsApp Inbox

ref: https://github.com/chatwoot/chatwoot/issues/3398

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2022-11-07 21:36:47 -08:00
Nithin David Thomas
20406dce01
feat: Adds component to show location based messages (#5809)
- Adds a component to show location-type messages in the dashboard
2022-11-07 20:50:07 -08:00
smartdev58
b50890d1b5
chore: Update data format for platform API feature management (#5718)
Updated JSON data format for Platform API for account creation update API endpoints features flags to accept false values on each feature. 

fixes: #5717
2022-11-07 17:49:45 -08:00
Tejaswini Chile
48373628a1
fix: Macros authorizations (#5779)
Macros policy update.

ref: #5730
2022-11-07 17:46:00 -08:00
CristianDuta
479d88a480
fix: Identifier not persisted on customer created via Inbox API Channel (#5804)
- Fixes the identifier not being used to identify the contact, this results in having a new contact created every time the email or phone is not supplied.

Fixes: #5704
2022-11-07 16:02:45 -08:00
Pranav Raj S
526722dffa
fix: Update invalid payload for template messages (#5802) 2022-11-07 15:21:47 -08:00
Tejaswini Chile
a23974d8b9
Feat/5733 Add private note action in macros (#5805) 2022-11-07 22:12:10 +05:30
matenauta
894234e777
chore: Remove quotes from SMTP_USERNAME (#5800)
Removing quotes related to https://github.com/chatwoot/chatwoot/issues/4787
2022-11-06 16:06:18 -08:00
Nithin David Thomas
dc3ee3464b
feat: Add component for grouped thumbnails (#5796) 2022-11-03 22:38:45 -07:00
Pranav Raj S
4bb5e812ba
fix: Allow ending the conversation if snoozed or pending (#5793) 2022-11-03 13:47:24 -07:00
Nithin David Thomas
8bd5ba187a
fix: Fix widget thumbnail not rendering background colors (#5791) 2022-11-02 22:13:12 -07:00
mjattiot
c121b44df4
fix: GCP Redis managed don't support redis client setname command (#4422)
If using Redis on GCP, actionable will attempt to use the Redis client setname command. However, the command is not supported.
So Introducing `REDIS_DISABLE_CLIENT_COMMAND` environment variable will let you solve this issue. 

ref: https://github.com/rails/rails/issues/38244#issuecomment-575454444
2022-11-02 19:14:51 -07:00
CristianDuta
4c43330b15
feat: Add inbox details endpoint (#5549)
This change targets the public API and is related to the Inbox with channel type API.
Exposes public inbox details under /public/api/v1/inboxes/{inbox_identifier}. This allows access to feature flags and business hours configured for the inbox.

ref: #5514
2022-11-02 19:05:03 -07:00
Sojan Jose
8b659de73d
chore: Use connection_pool for redis (#5790)
fixes: #3199
2022-11-02 17:31:20 -07:00
Tejaswini Chile
936c2ec7e2
fix: Ensure Automation rule executions are account scoped (#5768)
This is one possible fix to tackle with cached automation rules when automation gets called on two different accounts with the same event name.

Fixes: https://github.com/chatwoot/product/issues/605
2022-11-02 17:24:35 -07:00
smartdev58
9c0cce0392
chore: Mark conversations as pending instead of reopen when bot is present (#5751)
With this change, conversations are marked as pending instead of reopening when a bot is connected to that Inbox. 

Fixes: #5668
2022-11-02 13:54:56 -07:00
Arkadiy Ayvazyan
86ca7f4a8d
fix: Autoloading during initialization deprecation warning (#5628)
Autoloading during initialization deprecation warning fix
fixes: #4012
2022-11-01 20:19:42 -07:00
Sojan Jose
6cfd594d85
fix: flaky test whatsapp_cloud_service_spec.rb:17 (#5786)
Fix flaky test: flaky /spec/services/whatsapp/providers/whatsapp_cloud_service_spec.rb:17

example build: https://app.circleci.com/pipelines/github/chatwoot/chatwoot/48955/workflows/a2959d25-19ea-4812-ba15-5aac69c43265/jobs/49523

Cause: factory bot can create phone numbers of length 15 digits, which is valid e164, while our regex only handled up to 14 digits
2022-11-01 19:51:42 -07:00
Sivin Varghese
e1190fd9bf
fix: Text colour is getting merged with the light background colour (#5773) 2022-11-02 05:52:00 +05:30
Stephen Paul Weber
f2753df8df
chore: Use multiple connections in Redis connection pool (#5574)
- The initializer set up a connection pool, but both pools created namespace wrappers around a single global connection. Splitting them up.
2022-11-01 16:49:26 -07:00
Tejaswini Chile
00e06e5139
fix: Unsupported message type (#5783)
- fix handling unsupported message types
2022-11-01 15:21:45 -07:00
Fayaz Ahmed
be516a5ea6
fix: Filename issue while editing a macro with attachment action (#5775) 2022-11-01 16:20:09 +05:30
givetimetolife
f8d9a27d7a
fix: db/seeds.rb field contact_inbox is undefined (#5743)
fixes rails db:chatwoot_prepare error: field contact_inbox is undefined
2022-11-01 00:52:07 -07:00
Muhsin Keloth
92724576af
fix: Update conversation read status indicator logic (#5777) 2022-10-31 09:35:48 -07:00
Tejaswini Chile
a0606d36f6
fix: Set message sender to macros excution (#5769) 2022-10-31 19:33:12 +05:30
Muhsin Keloth
2073a23d5c
chore: Fix Thumbnail component specs (#5776) 2022-10-28 14:19:12 +05:30
Sivin Varghese
b20f5e5cef
feat: Adds URL validation for domain and home page links (#5761) 2022-10-28 06:21:38 +05:30
Nithin David Thomas
3b09840d39
chore: Fix import paths in storybook (#5772) 2022-10-28 01:42:24 +05:30
David Kubeš
2aa99ee137
fix: Fix timestamp auto-update in conversations chat list (#5640)
* fix: Fix timestamp auto-update

* fix: rewrite `setInterval` to `setTimeout`

* fix: change refresh time logic

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-10-28 01:05:39 +05:30
David Kubeš
89d7e4ead6
chore: refactor thumbnail (#5682) 2022-10-28 01:02:23 +05:30
Sojan Jose
12cd15b6ad
chore: Disable email processing for suspended accounts (#5762)
- Disable email processing for suspended accounts
2022-10-26 03:40:47 -07:00
Sivin Varghese
352558dd11
fix: Update color for the action button used in card messages (#5740) 2022-10-25 16:17:05 -07:00
Pranav Raj S
bedb2cab63
Use @page-change instead of on-page-change (#5749) 2022-10-26 03:46:09 +05:30
Nithin David Thomas
abe439594e
fix: Add preference to choose browser lang for widget settings page (#5726)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-10-25 15:04:29 -07:00
David Kubeš
bcde84b5b5
feat: Add ability to paste file/image from clipboard (#5627)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2022-10-25 13:35:11 +05:30
Fayaz Ahmed
06e2219110
chore: Improve macros stability (#5700)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-10-24 23:49:19 -07:00
Fayaz Ahmed
c3ec1d4f8a
feat: Add the ability for the agents to execute a macro (#5698)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-10-24 20:33:59 -07:00
Nithin David Thomas
d54392cb53
fix: Update max-width to fix overflow for emoji selector (#5727) 2022-10-24 19:59:36 -07:00
Sojan Jose
3a71fe3260
chore: Stop duplicate websocket updates for conversation updates (#5711)
- remove extra dispatch for label updates
2022-10-21 18:06:02 -07:00
jacsonsantospht
af020f446e
fix: check the content type for the file when uploading from cloud storage (#5378)
When sending the message with audio, only the signed id of the file is sent.
In the back end check only the UploadedFile type.
The attachment has the default file type image, now it gets the content type from the signed id

Fixes: #5375

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2022-10-21 18:05:36 -07:00
Pranav Raj S
6823b04e5b
fix: Update public partials to fix help center search (#5713) 2022-10-21 17:32:01 -07:00
Nithin David Thomas
8d5a9a9daa
fix: Show account switch modal from portal dashboard (#5712) 2022-10-21 17:24:52 -07:00
Nithin David Thomas
c3426929d7
chore: Refactor sidebar components used in help center (#5695)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-10-21 16:43:43 -07:00
Tejaswini Chile
4a299a9441
fix: Add an action cable events for label updates (#5694)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-10-21 16:12:35 -07:00
Tejaswini Chile
782165478b
fix: Update article count in portal admin dashboard (#5647)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-10-21 13:43:15 -07:00
Pranav Raj S
95cc55d043
fix: Update pagination logic in the help center (#5693) 2022-10-20 20:05:17 -07:00
Tejaswini Chile
a274a1702a
chore: Macros enhancement (#5609)
- Fixed send_attachment and send_email_transcript
- Fixed duplicate activity messages
- Fixed Order of execution

Fixes: #5584
2022-10-20 19:41:48 -07:00
Chad Burggraf
4d0b302802
Avoid crashing when AudioContext is not available. (#5641)
#4942. We've also had a number of crash reports show up in our logs related to this. Typically we see "undefined is not a constructor" because of the `window.AudioContext || window.webkitAudioContext` returns `undefined`.
2022-10-20 19:13:20 -07:00
Pranav Raj S
a8561cd798
fix: Add portal pack to tailwind config (#5686) 2022-10-19 21:49:34 -07:00
smartdev58
fa73b5290c
chore: Allow feature flag parameters in platform API for account creation (#5589)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-10-19 21:16:36 -07:00
DanielGannage
a3277b45af
feat: Update conversation attributes in realtime when they changed (#5542)
Co-authored-by: daniel gannage <daniel@pxdel.com>
Co-authored-by: Tejaswini Chile <tejaswini@chatwoot.com>
2022-10-19 17:26:10 -07:00
Nusret Ozates
10d86fbb35
chore: Ability to Remove password info from sentinel config (#4550)
Introduce the REDIS_SENTINEL_PASSWORD environment variable to customize the behaviour of sentinel passwords. 

Co-authored-by: EXT02D22861 <nusret.ozates@consultant.turkcell.com.tr>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2022-10-19 17:25:16 -07:00
Fayaz Ahmed
22d5703b92
feat: Macros listing and Editor (#5606)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-10-19 17:13:13 -07:00
Nithin David Thomas
1fb1be3ddc
feat: Add search functionality for public portal (#5683)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-10-19 17:09:32 -07:00
Vishnu Narayanan
bce0bb8acb
chore: Improve pr and bug report templates (#5556)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-10-19 13:45:32 -07:00
Tejaswini Chile
ceaffe862a
Fix: Handled IG unsupported file type (#5650)
We get 'unsupported_type' in the web-hook event only when Instagram faces issues processing the attachments. https://developers.facebook.com/docs/messenger-platform/instagram/features/webhook/ according to their document, we are handling the given types and are ignoring this one for now.

Fixes: #5428
2022-10-19 13:44:17 -07:00
Sojan Jose
199f462af4
chore: Update translations from Crowdin (#5663) 2022-10-19 13:27:10 -07:00
David Kubeš
c542d2e0ff
chore: Refactor urlParamsHelper.js (#5639) 2022-10-19 13:20:40 -07:00
Fayaz Ahmed
6bc34db932
fix: Make links more accessible on helpcenter public views (#5681)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-10-19 13:07:44 -07:00
Tejaswini Chile
0343acdb7e
fix: ensure contact_inbox if contact exists(#5667)
- Fixing Instagram issue for existing contacts in the inbox
2022-10-19 12:56:39 -07:00
Nitin Ramnani
f740727177
Added testcases for AddReminder Component (#5538)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2022-10-20 00:54:08 +05:30
Fayaz Ahmed
3de8f256cb
fix: Avoid overflowing submenus in conversation context menu (#5113)
* Scroll overflowing content inside submenus

* Disable submenus if options are not available
2022-10-19 12:23:53 -07:00
Muhsin Keloth
2e7ab484bd
fix: Discard invalid contact attributes in widget conversation end point (#5664)
Fixes: chatwoot/product#601
2022-10-18 13:16:29 -07:00
smartdev58
e34e975776
chore: ability to delete user in super admin console
fixes: #4164
2022-10-18 01:05:28 -07:00
Sojan
a1ce188dab Merge branch 'release/2.10.0' into develop 2022-10-17 19:38:42 -07:00
Sojan
2f7a16ae16 Bump version to 2.10.0 2022-10-17 19:31:53 -07:00
Pranav Raj S
71ca530292
fix: Fix typo in help center (#5661) 2022-10-17 18:59:22 -07:00
Pranav Raj S
e19c6d5671
chore: Add editor toggle for API inbox (#5660) 2022-10-17 18:52:51 -07:00
Sojan Jose
2423def8e8
chore: Add attachments key to message_created webhook payload (#5659)
- Add attachments key to `message_created` webhook payload
2022-10-17 17:36:56 -07:00
Nithin David Thomas
1c44e43c43
fix: Fix overflow issue for category name in article list (#5658)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-10-17 15:59:18 -07:00
Sivin Varghese
444809cc68
fix: Added validation for check box in the pre-chat form (#5648) 2022-10-17 15:00:02 -07:00
Pranav Raj S
20b4a91122
chore: Add feature flags in the settings console (#5657) 2022-10-17 14:59:44 -07:00
Nithin David Thomas
73f5595762
chore: Sync colors from dashboard to tailwind config (#5656) 2022-10-17 13:13:02 -07:00
Sojan Jose
704554d453
chore: Update translations (#5644) 2022-10-17 11:14:29 -07:00
Muhsin Keloth
706ab872f3
fix: Disable name in pre-chat form if the name is already provided in setUser (#5466) 2022-10-17 20:59:17 +05:30
Nithin David Thomas
252eda14c6
chore: Add woot button to message context menu (#5638) 2022-10-16 16:48:32 -07:00
Simon Pankovski
ce3730d640
fix: Avoid email icon getting distorted (#5633)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-10-14 13:38:08 -07:00
Sojan Jose
db53af91e7
chore (#5636)
* New translations conversation.json (Latvian)

* New translations bulkActions.json (Portuguese, Brazilian)

* New translations agentMgmt.json (Latvian)

* New translations teamsSettings.json (Latvian)

* New translations contactFilters.json (Latvian)

* New translations automation.json (Latvian)

* New translations attributesMgmt.json (Latvian)

* New translations labelsMgmt.json (Latvian)

* New translations settings.json (Latvian)

* New translations integrations.json (Latvian)

* New translations inboxMgmt.json (Latvian)

* New translations generalSettings.json (Latvian)

* New translations contact.json (Latvian)

* New translations helpCenter.json (Latvian)
2022-10-14 12:46:41 -07:00
David Kubeš
a6960dc2d3
chore: Refactor widget (#5621) 2022-10-14 09:13:11 +05:30
Sojan Jose
e310230f62
chore: Refactor Contact Inbox Builders (#5617)
- Remove duplicate code and move everything to builders
- fixes: #4680
2022-10-13 15:12:04 -07:00
Nithin David Thomas
1f271356ca
feat: Update the design for dropdown buttons (#5625) 2022-10-13 13:36:42 -07:00
Nithin David Thomas
bf4338ef9e
feat: Make category name in article table clickable (#5626)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-10-13 13:35:11 -07:00
Tejaswini Chile
a533f43fbf
fix: Stop raising errors for unsupported Whatsapp messages (#5541)
- Handle unsupported Whatsapp messages
2022-10-13 13:31:49 -07:00
Vishnu Narayanan
8f4944fda0
chore: revert arm64 docker build in gh action (#5619)
ref: https://github.com/chatwoot/chatwoot/pull/5575

https://github.com/chatwoot/chatwoot/pull/5575#issuecomment-1277208625
2022-10-13 13:46:28 +05:30
Pranav Raj S
d2fd05ee4e
fix: Show webhook url only on WhatsApp inbox (#5618) 2022-10-12 21:45:28 -07:00
Nithin David Thomas
ee520bdf98
feat: Show last active portal articles when sidebar portal icon is clicked (#5616) 2022-10-12 16:22:44 -07:00
David Kubeš
6c048626d0
chore: Replace deprecated functions (#5611)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-10-12 14:55:59 -07:00
David Kubeš
0c8f744c33
chore: Remove unnecessary methods and polyfills (#5614) 2022-10-12 14:42:06 -07:00
Tejaswini Chile
fca629a32a
fix: Update timezone to get wday from working_hours (#5605)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-10-12 14:32:54 -07:00
David Kubeš
1b5a335f93
fix: Update .editorconfig to fix spaces and indent_style (#5612) 2022-10-12 14:00:42 -07:00
Muhsin Keloth
242de0b3f9
chore: Vscode extension recommendations (#5607)
* Added vscode extension recommendations

* Revert the change

* Revert .gitignore

* Change `volar` to `vetur`
2022-10-12 23:32:24 +05:30
Jordan Brough
1bdd59f025
Find by downcased email in SupportMailbox (#5211) 2022-10-12 13:38:18 +05:30
Fayaz Ahmed
32d885a19b
feat: Add macros routes and views (#5604) 2022-10-11 23:20:20 -07:00
Fayaz Ahmed
6c160ccad5
feat: Add API module and Vuex store for Macros (#5603) 2022-10-11 22:54:17 -07:00
Pranav Raj S
9b5c0de0ea
chore: Add router views for agent_bots (#5600) 2022-10-11 17:58:52 -07:00
Pranav Raj S
38776906ab
chore: Generate webhook-verify-token automatically (#5593)
- Autogenerate webhook verification token in the WhatsAppCloud channel.

Co-authored-by: Sojan <sojan@pepalo.com>
2022-10-11 17:32:31 -07:00
Pranav Raj S
5f4b6f2ce4
chore: Add AgentBot API module (#5599) 2022-10-11 17:23:57 -07:00
Tejaswini Chile
7419e413f4
fix: Added "None" option in bulk actions assignment menu (#5585)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-10-11 13:44:35 -07:00
Konstantin Nosov
0b5a956e05
chore: Update the design for emoji picker (#4244)
Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-10-11 12:59:28 -07:00
Tejaswini Chile
2c8ded98aa
fix: Update format in password reset page (#5596) 2022-10-11 11:27:22 -07:00
Sojan Jose
d727093538
chore: Update translations from Crowdin (#5578) 2022-10-10 15:40:12 -07:00
Sojan Jose
5bd5395d31
chore: Add missing indexes for attachments table (#5588)
- index for attachments table
- index for conversations table
2022-10-10 15:23:33 -07:00
Sojan Jose
779f815f8e
chore: Update translations (#5566) 2022-10-07 09:08:18 -07:00
Jordan Brough
ce7d9be633
Fix "presence" checks in Channel::TwilioSms (#5206) 2022-10-07 10:22:33 +05:30
Pranav Raj S
788b766179
feat: Quickly create canned responses (#5563) 2022-10-05 22:00:15 -07:00
dependabot[bot]
0a9ea6e272
chore(deps): bump google-protobuf from 3.21.2 to 3.21.7 (#5550)
Bumps [google-protobuf](https://github.com/protocolbuffers/protobuf) from 3.21.2 to 3.21.7.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/generate_changelog.py)
- [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.21.2...v3.21.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2022-10-05 17:32:00 -07:00
Muhsin Keloth
b668723313
chore: Ability to change default account (#5393)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Tejaswini Chile <tejaswini@chatwoot.com>
2022-10-05 17:31:12 -07:00
Geophilus Durairaj
bd445216e9
fix: DEPRECATION WARNING: Rendering actions with '.' in the name is deprecated (#5560)
fixes the warning: Rendering actions with '.' in the name is deprecated
2022-10-05 17:24:34 -07:00
Pranav Raj S
cd4c1ef27e
feat: Update the design of mentions with thumbnail (#5551) 2022-10-05 14:18:16 -07:00
Sojan Jose
8b0e95ece8
fix: Flakiness in CI pipeline (#5562)
- Fixing the recent flakiness in CI pipelines
2022-10-05 10:59:31 -07:00
Marcel Stör
7b1630b468
feat: Enable Docker Buildx multi-arch builds with arm64 support(#5545)
Fixes #2575

multi-arch images with arm64 support CE edition images

Co-authored-by: Vishnu Narayanan <vishnu@chatwoot.com>
2022-10-05 17:07:49 +05:30
Sojan Jose
c76aed6d5e
chore: Update translations from Crowdin (#5546) 2022-10-04 18:14:35 -07:00
Nithin David Thomas
8df7547043
feat: Add support for draft messages in reply box (#4440)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
Co-authored-by: Fayaz Ahmed <15716057+fayazara@users.noreply.github.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2022-10-03 21:29:02 -07:00
Sivin Varghese
beedfc47bf
feat: Allow users to select Cmd+Enter as a hotkey (#4401)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-10-03 15:27:34 -07:00
David Kubeš
9ea43a2678
chore: Improve Nginx settings for speed and security (#5144)
* fix: Fixes #5138

* Move to helper function

* Improve Nginx settings

* chore: set ssl_prefer_server_ciphers to off
ssl_prefer_server_ciphers should be set to  `off` in a modern context.

ref: https://ssl-config.mozilla.org/#server=nginx&version=1.17.7&config=modern&openssl=1.1.1k&guideline=5.6

Co-authored-by: Vishnu Narayanan <vishnu@chatwoot.com>
2022-10-03 17:43:50 +05:30
Nithin David Thomas
705d06ac3c
fix: Avoid editor formatting issues when a canned response is edited (#5533) 2022-09-30 15:03:33 -07:00
Tejaswini Chile
7b54990ae6
fix: Updated IMAP errors add method (#5520)
fixes: #5519
2022-09-30 11:33:00 -07:00
Jordan Brough
4f0360c7a2
chore: Allow setting "users.display_name" in Platform API (#5532) 2022-09-30 11:28:18 -07:00
Tejaswini Chile
57fcb79d71
fix: Article slug auto saves (#5524)
- Auto save article slug
2022-09-30 07:25:23 -07:00
OMAR.A
1819041f5a
fix: "wa_source_id" function return value (#5451)
- Fix contact inbox builder returning invalid WhatsApp source id
- Add specs to cover source id validations

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2022-09-29 10:34:55 -07:00
Sojan Jose
74e03f9beb
chore: Update translations from Crowdin (#5523) 2022-09-28 21:59:41 -07:00
Chamath K.B. Attanayaka
6b47c7b43d
fix: Attachment not sending to bots message creation (#5353) 2022-09-28 13:13:29 -07:00
Tejaswini Chile
fcb9a9ab0c
fix: contact is not available for inaccessible sender (#5509)
Fixes: #5508

We can not read contact information because of this error, as the messages echo when the sender sends messages to contacts. We don't have the user's consent until and unless they send messages to us.

So after this result, information about the contact is empty, and we are trying to create a contact inbox for the same, and the error appears.

type: OAuthException, code: 230, message: (#230) User consent is required to access user profile, x-fb-trace-id: AaitxF/whwY [HTTP 403] (Koala::Facebook::ClientError)
2022-09-28 12:50:23 -07:00
Pranav Raj S
83eee7df91
chore: Set locale in default_locale (#5515) 2022-09-28 08:29:00 -07:00
Tejaswini Chile
543854eaa8
fix: Remove the notification subscription if present (#5510) 2022-09-27 12:36:57 -07:00
Tejaswini Chile
336c09e072
fix: Add all articles count to article API (#5497) 2022-09-27 12:35:53 -07:00
Tejaswini Chile
c1c57fb2cd
fix: Add slug to articles (#5500) 2022-09-27 12:27:18 -07:00
Sojan
8e5d8fcdaf Merge branch 'release/2.9.1' into develop 2022-09-22 14:22:40 -07:00
Sojan
d2d7355e2d Bump version to 2.9.1 2022-09-22 14:18:30 -07:00
Sojan Jose
913224ad84
chore: Normalize portal slug to nil (#5487) 2022-09-22 14:08:48 -07:00
dependabot[bot]
b463ce5b1a
chore(deps): bump commonmarker from 0.23.5 to 0.23.6 (#5480)
Bumps [commonmarker](https://github.com/gjtorikian/commonmarker) from 0.23.5 to 0.23.6.
- [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.5...v0.23.6)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-21 20:43:08 -07:00
Tejaswini Chile
111016fe4c
fix: Add not found status if macro not found (#5473) 2022-09-21 14:10:35 +05:30
Pranav Raj S
cc4ef14faa
chore: Fix link generation logic in help-center (#5470) 2022-09-20 18:23:28 -07:00
Pranav Raj S
2d871a1ed5
fix: Hide conversation links in input_csat message in non-website channels (#5469)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2022-09-20 17:15:32 -07:00
Tejaswini Chile
c556e9c694
Fix: Redirect to portal with default locale (#5467)
- Redirect to default locale if URL does not provide the locale param
2022-09-20 10:31:39 -07:00
Nithin David Thomas
d28502b917
Bug: Adds support multiline text input on article edit page (#5465)
* Bug: Scroll issue with article list page

* Adds support multiline text input on article edit page
2022-09-20 22:42:09 +05:30
Pranav Raj S
1761100c77
chore: Remove static URLs from the documentation (#5461) 2022-09-19 21:52:01 -07:00
Sojan
3a1e521b4c Merge branch 'release/2.9.0' into develop 2022-09-19 20:37:02 -07:00
Sojan
06c9aad7b3 Bump version to 2.9.0 2022-09-19 20:34:49 -07:00
Pranav Raj S
eb8e348ec1
chore: Update design of the help-center articles (#5459)
- Update designs
- Fixes meta tags
- Update typography
2022-09-19 20:31:38 -07:00
Nithin David Thomas
54d0055e86
chore: Helpcenter routing and UI fixes (#5460)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-09-19 19:40:32 -07:00
Sojan Jose
3b7cae19e6
chore: Enable help center for self-hosted accounts (#5458) 2022-09-19 19:39:41 -07:00
Shivam Chahar
6b80afaf50
fix: un-assign agent from conversation after removal (#5417)
Unassign agents from the conversation when they are removed from the account.

Fixes: #4555
2022-09-19 18:14:55 -07:00
Sojan Jose
99de8f4500
chore: Improve Helpcenter custom domains (#5456)
- Support rendering articles over frontend URL
- Support rendering articles over help center URL
- Support rendering help center home page in the custom domain root
2022-09-19 17:36:01 -07:00
Sivin Varghese
a773ad7d08
fix: Read status not updated in widget popout mode (#5454) 2022-09-19 15:35:41 -07:00
Sojan Jose
97583e410c
feat: Ability to manage account features from super admin (#5455)
- This PR adds the ability to enable and disable features for an account from the super admin.
2022-09-19 14:57:21 -07:00
Sivin Varghese
678a0af962
chore: Enable Help Center on Sidebar (#5435)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2022-09-18 13:08:30 -07:00
Vishnu Narayanan
bc23c69605
chore: Fix gh nightly failure (#5442)
* chore: fix gh nightly failure
2022-09-15 23:16:45 +05:30
Fayaz Ahmed
e585b227f1
chore: Check for empty strings in name formatter (#5434) 2022-09-14 07:15:04 -07:00
Nithin David Thomas
cb4dd7e633
fix: Fixes bug with locale switching from popover (#5426) 2022-09-13 18:49:55 +05:30
Pranav Raj S
8af27d861b
chore: Default to rich content editor in website and email channel (#5357) 2022-09-13 05:41:42 -07:00
Jordan Brough
59b31615ed
chore: Use "create!" and "save!" bang methods when not checking the result (#5358)
* Use "create!" when not checking for errors on the result
* Use "save!" when not checking the result
2022-09-13 17:40:06 +05:30
Sivin Varghese
44f498be6d
fix: Not able to create a new category through the sidebar (#5421)
* fix: Not able to create a new category through the sidebar

* chore: Minor spacing fixes
2022-09-13 00:15:24 +05:30
Nithin David Thomas
1ea289e8b7
feat: Sets up portal public views with rails ERB and tailwind (#5309)
* feat: Sets up portal public views with rails ERB and tailwind

* linter fixes

* Remove duplicate style file

* Shows articles and categories

* Specify layout for articles page

* Updates public portal styles

* Fixes blog content styles

* Portal style updates for article page

* Review fixes

* Adds breadcrumbs

* fix: rspec

* fix: public portal spec

* Code climate fixes

* Adds test cases for missing files

* Show only published articles

* Updates help center routes

* Review fixes

* Render markdown content for aticle body

* Update app/views/public/api/v1/portals/articles/index.html.erb

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>

Co-authored-by: Sojan <sojan@pepalo.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: tejaswini chile <tejaswini@chatwoot.com>
2022-09-12 23:36:24 +05:30
Sivin Varghese
a680b08251
chore: Fixes height issues with seconday sidebar (#5407) 2022-09-12 19:36:49 +05:30
1537 changed files with 59355 additions and 11315 deletions

View file

@ -19,6 +19,7 @@ defaults: &defaults
- COVERAGE: true
- LOG_LEVEL: warn
parallelism: 4
resource_class: large
jobs:
build:
@ -122,9 +123,11 @@ jobs:
mkdir -p coverage
~/tmp/cc-test-reporter before-build
TESTFILES=$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)
bundle exec rspec --profile 10 \
--out test-results/rspec/rspec.xml \
bundle exec rspec --format progress \
--format RspecJunitFormatter \
--out ~/tmp/test-results/rspec.xml \
-- ${TESTFILES}
no_output_timeout: 30m
- run:
name: Code Climate Test Coverage
command: |
@ -137,7 +140,7 @@ jobs:
~/tmp/cc-test-reporter before-build
TESTFILES=$(circleci tests glob **/specs/*.spec.js | circleci tests split --split-by=timings)
yarn test:coverage --profile 10 \
--out test-results/frontend_specs/rspec.xml \
--out ~/tmp/test-results/yarn.xml \
-- ${TESTFILES}
- run:
name: Code Climate Test Coverage

View file

@ -54,3 +54,5 @@ exclude_patterns:
- 'app/javascript/widget/i18n/index.js'
- 'app/javascript/survey/i18n/index.js'
- 'app/javascript/shared/constants/locales.js'
- 'app/javascript/dashboard/helper/specs/macrosFixtures.js'
- 'app/javascript/dashboard/routes/dashboard/settings/macros/constants.js'

View file

@ -7,8 +7,8 @@ end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = spaces
indent_style = space
tab_width = 2
[{*.{rb,erb,js,coffee,json,yml,css,scss,sh,markdown,md,html}]
[*.{rb,erb,js,coffee,json,yml,css,scss,sh,markdown,md,html}]
indent_size = 2

View file

@ -3,6 +3,8 @@ SECRET_KEY_BASE=replace_with_lengthy_secure_hex
# Replace with the URL you are planning to use for your app
FRONTEND_URL=http://0.0.0.0:3000
# To use a dedicated URL for help center pages
# HELPCENTER_URL=http://0.0.0.0:3000
# If the variable is set, all non-authenticated pages would fallback to the default locale.
# Whenever a new account is created, the default language will be DEFAULT_LOCALE instead of en
@ -32,6 +34,11 @@ REDIS_SENTINELS=
# You can find list of master using "SENTINEL masters" command
REDIS_SENTINEL_MASTER_NAME=
# By default Chatwoot will pass REDIS_PASSWORD as the password value for sentinels
# Use the following environment variable to customize passwords for sentinels.
# Use empty string if sentinels are configured with out passwords
# REDIS_SENTINEL_PASSWORD=
# Redis premium breakage in heroku fix
# enable the following configuration
# ref: https://github.com/chatwoot/chatwoot/issues/2420
@ -49,13 +56,14 @@ RAILS_MAX_THREADS=5
# The email from which all outgoing emails are sent
# could user either `email@yourdomain.com` or `BrandName <email@yourdomain.com>`
MAILER_SENDER_EMAIL="Chatwoot <accounts@chatwoot.com>"
MAILER_SENDER_EMAIL=Chatwoot <accounts@chatwoot.com>
#SMTP domain key is set up for HELO checking
SMTP_DOMAIN=chatwoot.com
# the default value is set "mailhog" and is used by docker-compose for development environments,
# Set the value to "mailhog" if using docker-compose for development environments,
# Set the value as "localhost" or your SMTP address in other environments
SMTP_ADDRESS=mailhog
# If SMTP_ADDRESS is empty, Chatwoot would try to use sendmail(postfix)
SMTP_ADDRESS=
SMTP_PORT=1025
SMTP_USERNAME=
SMTP_PASSWORD=

View file

@ -6,6 +6,7 @@ labels: 'Bug'
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
@ -16,11 +17,11 @@ Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
4. See the error
**Expected behavior**
A clear and concise description of what you expected to happen.
Share a clear and concise description of what you expected to happen.
**Screenshots**
@ -28,27 +29,50 @@ If applicable, add screenshots to help explain your problem.
**Browser logs**
Share the browser logs to debug the issue further
Share the browser logs to debug the issue further.
**Server logs**
Share the server logs to debug the issue further
Share the server logs to debug the issue further.
**Environment**
Describe whether you are using Chatwoot Cloud (app.chatwoot.com) or a self hosted installation of Chatwoot. If you are using a self hosted installation of Chatwoot describe the type of deployment (Docker/Linux VM installation/Heroku)
Describe whether you are using Chatwoot Cloud (app.chatwoot.com) or a self-hosted installation of Chatwoot. If you are using a self-hosted installation of Chatwoot, describe the type of deployment (Docker/Linux VM installation/Heroku/Kubernetes/Other).
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- [ ] app.chatwoot.com (Chatwoot Cloud)
- [ ] Self-hosted
- - [ ] Linux VM
- - [ ] Docker
- - [ ] Kubernetes
- - [ ] Heroku
- - [ ] Other (Please specify)
**Desktop (please complete the following information)** (If applicable)
- OS: [e.g. Linux, Windows, MacOS]
- Browser [e.g. chrome, firefox, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
**Smartphone (please complete the following information)** (If applicable)
- Device: [e.g. iPhone6, Pixel7]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Browser [e.g. stock browser, firefox, safari]
- Version [e.g. 22]
**Docker** (If applicable)
Please share the output of the following.
- `docker version`
- `docker info`
- `docker-compose version`
**Cloud Provider** (If applicable)
- [ ] AWS
- [ ] GCP
- [ ] Azure
- [ ] DigitalOcean
- [ ] Others
**Additional context**
Add any other context about the problem here.

View file

@ -2,8 +2,7 @@
## Description
Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.
Please include a summary of the change and issue(s) fixed. Also, mention relevant motivation, context, and any dependencies that this change requires.
Fixes # (issue)
## Type of change
@ -12,18 +11,18 @@ Please delete options that are not relevant.
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected)
- [ ] This change requires a documentation update
## How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration.
## Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings

View file

@ -23,6 +23,8 @@ jobs:
run: |
wget https://get.chatwoot.app/linux/install.sh
chmod +x install.sh
#fix for postgtres not starting automatically in gh action env
sed -i '/function configure_db() {/a sudo service postgresql start' install.sh
- name: create input file
run: |
@ -33,20 +35,6 @@ jobs:
run: |
sudo ./install.sh --install < input
# temp fix for postgresql not starting
# automatically in gh action env
- name: start postgresql service
if: always()
run: |
sudo service postgresql start
#re-running the installer again
- name: Run the installer again
if: always()
run: |
sudo ./install.sh --install < input
# disabling http verify for now as http
# access to port 3000 fails in gh action env
# - name: Verify

View file

@ -58,5 +58,6 @@ jobs:
with:
context: .
file: docker/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ env.DOCKER_TAG }}

5
.gitignore vendored
View file

@ -39,9 +39,6 @@ public/packs*
*.un~
.jest-cache
#VS Code files
.vscode
# ignore jetbrains IDE files
.idea
@ -63,3 +60,5 @@ test/cypress/videos/*
/config/master.key
/config/*.enc
.vscode/settings.json

32
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,32 @@
{
"recommendations": [
// Spell check
"streetsidesoftware.code-spell-checker",
// Better Comments
"aaron-bond.better-comments",
// Rails Test Runner
"davidpallinder.rails-test-runner",
// Eslint
"dbaeumer.vscode-eslint",
// Auto Close Tag
"formulahendry.auto-close-tag",
// Auto Rename Tag
"formulahendry.auto-rename-tag",
// Hight light colors
"naumovs.color-highlight",
// GitLens
"eamodio.gitlens",
// Ruby
"rebornix.ruby",
// Vue
"octref.vetur",
// Prettier
"esbenp.prettier-vscode",
// Dot Env
"mikestead.dotenv",
// HTML CSS Support
"ecmel.vscode-html-css",
// Tailwind CSS Intellisense
"bradlc.vscode-tailwindcss",
]
}

1
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1 @@
{}

11
Gemfile
View file

@ -4,7 +4,7 @@ ruby '3.0.4'
##-- base gems for rails --##
gem 'rack-cors', require: 'rack/cors'
gem 'rails', '~>6.1'
gem 'rails', '~> 6.1', '>= 6.1.6.1'
# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', require: false
@ -56,7 +56,7 @@ gem 'activerecord-import'
gem 'dotenv-rails'
gem 'foreman'
gem 'puma'
gem 'webpacker', '~> 5.x'
gem 'webpacker', '~> 5.4', '>= 5.4.3'
# metrics on heroku
gem 'barnes'
@ -94,7 +94,7 @@ gem 'ddtrace'
gem 'elastic-apm'
gem 'newrelic_rpm'
gem 'scout_apm'
gem 'sentry-rails', '~> 5.3'
gem 'sentry-rails', '~> 5.3', '>= 5.3.1'
gem 'sentry-ruby', '~> 5.3'
gem 'sentry-sidekiq', '~> 5.3'
@ -135,8 +135,6 @@ gem 'stripe'
## to populate db with sample data
gem 'faker'
gem 'ruby-saml', '= 1.11'
group :production, :staging do
# we dont want request timing out in development while using byebug
gem 'rack-timeout'
@ -176,7 +174,8 @@ group :development, :test do
gem 'listen'
gem 'mock_redis'
gem 'pry-rails'
gem 'rspec-rails', '~> 5.0.0'
gem 'rspec_junit_formatter'
gem 'rspec-rails', '~> 5.0.3'
gem 'rubocop', require: false
gem 'rubocop-performance', require: false
gem 'rubocop-rails', require: false

View file

@ -135,7 +135,7 @@ GEM
byebug (11.1.3)
climate_control (1.1.1)
coderay (1.1.3)
commonmarker (0.23.5)
commonmarker (0.23.6)
concurrent-ruby (1.1.10)
connection_pool (2.2.5)
crack (0.4.5)
@ -286,9 +286,9 @@ GEM
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
google-protobuf (3.21.2)
google-protobuf (3.21.2-x86_64-darwin)
google-protobuf (3.21.2-x86_64-linux)
google-protobuf (3.21.7)
google-protobuf (3.21.7-x86_64-darwin)
google-protobuf (3.21.7-x86_64-linux)
googleapis-common-protos (1.3.12)
google-protobuf (~> 3.14)
googleapis-common-protos-types (~> 1.2)
@ -398,7 +398,7 @@ GEM
llhttp-ffi (0.4.0)
ffi-compiler (~> 1.0)
rake (~> 13.0)
loofah (2.18.0)
loofah (2.19.1)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
@ -427,14 +427,14 @@ GEM
netrc (0.11.0)
newrelic_rpm (8.9.0)
nio4r (2.5.8)
nokogiri (1.13.8)
nokogiri (1.13.10)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
nokogiri (1.13.8-arm64-darwin)
nokogiri (1.13.10-arm64-darwin)
racc (~> 1.4)
nokogiri (1.13.8-x86_64-darwin)
nokogiri (1.13.10-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.13.8-x86_64-linux)
nokogiri (1.13.10-x86_64-linux)
racc (~> 1.4)
oauth (0.5.10)
orm_adapter (0.5.0)
@ -459,7 +459,7 @@ GEM
pundit (2.2.0)
activesupport (>= 3.0.0)
raabro (1.4.0)
racc (1.6.0)
racc (1.6.1)
rack (2.2.4)
rack-attack (6.6.1)
rack (>= 1.0, < 3)
@ -488,8 +488,8 @@ GEM
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.4.3)
loofah (~> 2.3)
rails-html-sanitizer (1.4.4)
loofah (~> 2.19, >= 2.19.1)
railties (6.1.6.1)
actionpack (= 6.1.6.1)
activesupport (= 6.1.6.1)
@ -536,6 +536,8 @@ GEM
rspec-mocks (~> 3.10)
rspec-support (~> 3.10)
rspec-support (3.11.0)
rspec_junit_formatter (0.6.0)
rspec-core (>= 2, < 4, != 2.12.0)
rubocop (1.31.2)
json (~> 2.3)
parallel (~> 1.10)
@ -558,8 +560,6 @@ GEM
rubocop-rspec (2.12.1)
rubocop (~> 1.31)
ruby-progressbar (1.11.0)
ruby-saml (1.11.0)
nokogiri (>= 1.5.10)
ruby-vips (2.1.4)
ffi (~> 1.12)
ruby2_keywords (0.0.5)
@ -765,20 +765,20 @@ DEPENDENCIES
rack-attack
rack-cors
rack-timeout
rails (~> 6.1)
rails (~> 6.1, >= 6.1.6.1)
redis
redis-namespace
responders
rest-client
rspec-rails (~> 5.0.0)
rspec-rails (~> 5.0.3)
rspec_junit_formatter
rubocop
rubocop-performance
rubocop-rails
rubocop-rspec
ruby-saml (= 1.11)
scout_apm
seed_dump
sentry-rails (~> 5.3)
sentry-rails (~> 5.3, >= 5.3.1)
sentry-ruby (~> 5.3)
sentry-sidekiq (~> 5.3)
shoulda-matchers
@ -799,7 +799,7 @@ DEPENDENCIES
valid_email2
web-console
webmock
webpacker (~> 5.x)
webpacker (~> 5.4, >= 5.4.3)
webpush
wisper (= 2.0.0)
working_hours

View file

@ -41,16 +41,24 @@
"formation": {
"web": {
"quantity": 1,
"size": "FREE"
"size": "basic"
},
"worker": {
"quantity": 1,
"size": "FREE"
"size": "basic"
}
},
"stack": "heroku-20",
"image": "heroku/ruby",
"addons": [ "heroku-redis", "heroku-postgresql"],
"addons": [
{
"plan": "heroku-redis:mini"
},
{
"plan": "heroku-postgresql:mini"
}
],
"stack": "heroku-20",
"buildpacks": [
{
"url": "heroku/ruby"

View file

@ -1,13 +1,12 @@
# This Builder will create a contact inbox with specified attributes. If the contact inbox already exists, it will be returned.
# For Specific Channels like whatsapp, email etc . it smartly generated appropriate the source id when none is provided.
class ContactInboxBuilder
pattr_initialize [:contact_id!, :inbox_id!, :source_id]
pattr_initialize [:contact, :inbox, :source_id, { hmac_verified: false }]
def perform
@contact = Contact.find(contact_id)
@inbox = @contact.account.inboxes.find(inbox_id)
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?
@source_id ||= generate_source_id
create_contact_inbox if source_id.present?
end
private
@ -19,23 +18,37 @@ class ContactInboxBuilder
when 'Channel::Whatsapp'
wa_source_id
when 'Channel::Email'
@contact.email
email_source_id
when 'Channel::Sms'
@contact.phone_number
when 'Channel::Api'
phone_source_id
when 'Channel::Api', 'Channel::WebWidget'
SecureRandom.uuid
else
raise "Unsupported operation for this channel: #{@inbox.channel_type}"
end
end
def email_source_id
raise ActionController::ParameterMissing, 'contact email' unless @contact.email
@contact.email
end
def phone_source_id
raise ActionController::ParameterMissing, 'contact phone number' unless @contact.phone_number
@contact.phone_number
end
def wa_source_id
return unless @contact.phone_number
raise ActionController::ParameterMissing, 'contact phone number' unless @contact.phone_number
# whatsapp doesn't want the + in e164 format
"#{@contact.phone_number}.delete('+')"
@contact.phone_number.delete('+').to_s
end
def twilio_source_id
return unless @contact.phone_number
raise ActionController::ParameterMissing, 'contact phone number' unless @contact.phone_number
case @inbox.channel.medium
when 'sms'
@ -45,11 +58,11 @@ class ContactInboxBuilder
end
end
def create_contact_inbox(source_id)
::ContactInbox.find_or_create_by!(
def create_contact_inbox
::ContactInbox.create_with(hmac_verified: hmac_verified || false).find_or_create_by!(
contact_id: @contact.id,
inbox_id: @inbox.id,
source_id: source_id
source_id: @source_id
)
end
end

View file

@ -1,25 +1,47 @@
class ContactBuilder
pattr_initialize [:source_id!, :inbox!, :contact_attributes!, :hmac_verified]
# This Builder will create a contact and contact inbox with specified attributes.
# If an existing identified contact exisits, it will be returned.
# for contact inbox logic it uses the contact inbox builder
class ContactInboxWithContactBuilder
pattr_initialize [:inbox!, :contact_attributes!, :source_id, :hmac_verified]
def perform
contact_inbox = inbox.contact_inboxes.find_by(source_id: source_id)
return contact_inbox if contact_inbox
find_or_create_contact_and_contact_inbox
# in case of race conditions where contact is created by another thread
# we will try to find the contact and create a contact inbox
rescue ActiveRecord::RecordNotUnique
find_or_create_contact_and_contact_inbox
end
build_contact_inbox
def find_or_create_contact_and_contact_inbox
@contact_inbox = inbox.contact_inboxes.find_by(source_id: source_id) if source_id.present?
return @contact_inbox if @contact_inbox
ActiveRecord::Base.transaction(requires_new: true) do
build_contact_with_contact_inbox
update_contact_avatar(@contact) unless @contact.avatar.attached?
@contact_inbox
end
end
private
def build_contact_with_contact_inbox
@contact = find_contact || create_contact
@contact_inbox = create_contact_inbox
end
def account
@account ||= inbox.account
end
def create_contact_inbox(contact)
::ContactInbox.create_with(hmac_verified: hmac_verified || false).find_or_create_by!(
contact_id: contact.id,
inbox_id: inbox.id,
source_id: source_id
)
def create_contact_inbox
ContactInboxBuilder.new(
contact: @contact,
inbox: @inbox,
source_id: @source_id,
hmac_verified: hmac_verified
).perform
end
def update_contact_avatar(contact)
@ -61,16 +83,4 @@ class ContactBuilder
account.contacts.find_by(phone_number: phone_number)
end
def build_contact_inbox
ActiveRecord::Base.transaction do
contact = find_contact || create_contact
contact_inbox = create_contact_inbox(contact)
update_contact_avatar(contact)
contact_inbox
rescue StandardError => e
Rails.logger.error e
raise e
end
end
end

View file

@ -0,0 +1,40 @@
class ConversationBuilder
pattr_initialize [:params!, :contact_inbox!]
def perform
look_up_exising_conversation || create_new_conversation
end
private
def look_up_exising_conversation
return unless @contact_inbox.inbox.lock_to_single_conversation?
@contact_inbox.conversations.last
end
def create_new_conversation
::Conversation.create!(conversation_params)
end
def conversation_params
additional_attributes = params[:additional_attributes]&.permit! || {}
custom_attributes = params[:custom_attributes]&.permit! || {}
status = params[:status].present? ? { status: params[:status] } : {}
# TODO: temporary fallback for the old bot status in conversation, we will remove after couple of releases
# commenting this out to see if there are any errors, if not we can remove this in subsequent releases
# status = { status: 'pending' } if status[:status] == 'bot'
{
account_id: @contact_inbox.inbox.account_id,
inbox_id: @contact_inbox.inbox_id,
contact_id: @contact_inbox.contact_id,
contact_inbox_id: @contact_inbox.id,
additional_attributes: additional_attributes,
custom_attributes: custom_attributes,
snoozed_until: params[:snoozed_until],
assignee_id: params[:assignee_id],
team_id: params[:team_id]
}.merge(status)
end
end

View file

@ -22,10 +22,9 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
return if @inbox.channel.reauthorization_required?
ActiveRecord::Base.transaction do
build_contact
build_contact_inbox
build_message
end
ensure_contact_avatar
rescue Koala::Facebook::AuthenticationError
@inbox.channel.authorization_error!
rescue StandardError => e
@ -35,15 +34,12 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
private
def contact
@contact ||= @inbox.contact_inboxes.find_by(source_id: @sender_id)&.contact
end
def build_contact
return if contact.present?
@contact = Contact.create!(contact_params.except(:remote_avatar_url))
@contact_inbox = ContactInbox.find_or_create_by!(contact: contact, inbox: @inbox, source_id: @sender_id)
def build_contact_inbox
@contact_inbox = ::ContactInboxWithContactBuilder.new(
source_id: @sender_id,
inbox: @inbox,
contact_attributes: contact_params
).perform
end
def build_message
@ -54,19 +50,11 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
end
end
def ensure_contact_avatar
return if contact_params[:remote_avatar_url].blank?
return if @contact.avatar.attached?
Avatar::AvatarFromUrlJob.perform_later(@contact, contact_params[:remote_avatar_url])
end
def conversation
@conversation ||= Conversation.find_by(conversation_params) || build_conversation
end
def build_conversation
@contact_inbox ||= contact.contact_inboxes.find_by!(source_id: @sender_id)
Conversation.create!(conversation_params.merge(
contact_inbox_id: @contact_inbox.id
))
@ -94,7 +82,7 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
{
account_id: @inbox.account_id,
inbox_id: @inbox.id,
contact_id: contact.id
contact_id: @contact_inbox.contact_id
}
end
@ -105,7 +93,7 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
message_type: @message_type,
content: response.content,
source_id: response.identifier,
sender: @outgoing_echo ? nil : contact
sender: @outgoing_echo ? nil : @contact_inbox.contact
}
end
@ -113,7 +101,7 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
{
name: "#{result['first_name'] || 'John'} #{result['last_name'] || 'Doe'}",
account_id: @inbox.account_id,
remote_avatar_url: result['profile_pic'] || ''
avatar_url: result['profile_pic']
}
end

View file

@ -72,6 +72,7 @@ class Messages::Instagram::MessageBuilder < Messages::Messenger::MessageBuilder
def build_message
return if @outgoing_echo && already_sent_from_chatwoot?
return if message_content.blank? && all_unsupported_files?
@message = conversation.messages.create!(message_params)
@ -117,6 +118,13 @@ class Messages::Instagram::MessageBuilder < Messages::Messenger::MessageBuilder
cw_message.present?
end
def all_unsupported_files?
return if attachments.empty?
attachments_type = attachments.pluck(:type).uniq.first
unsupported_file_type?(attachments_type)
end
### Sample response
# {
# "object": "instagram",

View file

@ -35,7 +35,13 @@ class Messages::MessageBuilder
file: uploaded_attachment
)
attachment.file_type = file_type(uploaded_attachment&.content_type) if uploaded_attachment.is_a?(ActionDispatch::Http::UploadedFile)
attachment.file_type = if uploaded_attachment.is_a?(String)
file_type_by_signed_id(
uploaded_attachment
)
else
file_type(uploaded_attachment&.content_type)
end
end
end

View file

@ -2,7 +2,8 @@ class Messages::Messenger::MessageBuilder
include ::FileTypeHelper
def process_attachment(attachment)
return if attachment['type'].to_sym == :template
# This check handles very rare case if there are multiple files to attach with only one usupported file
return if unsupported_file_type?(attachment['type'])
attachment_obj = @message.attachments.new(attachment_params(attachment).except(:remote_file_url))
attachment_obj.save!
@ -45,6 +46,7 @@ class Messages::Messenger::MessageBuilder
end
def update_attachment_file_type(attachment)
return if @message.reload.attachments.blank?
return unless attachment.file_type == 'share' || attachment.file_type == 'story_mention'
attachment.file_type = file_type(attachment.file&.content_type)
@ -61,6 +63,7 @@ class Messages::Messenger::MessageBuilder
story_sender = result['from']['username']
message.content_attributes[:story_sender] = story_sender
message.content_attributes[:story_id] = story_id
message.content_attributes[:image_type] = 'story_mention'
message.content = I18n.t('conversations.messages.instagram_story_content', story_sender: story_sender)
message.save!
end
@ -73,6 +76,7 @@ class Messages::Messenger::MessageBuilder
raise
rescue Koala::Facebook::ClientError => e
# The exception occurs when we are trying fetch the deleted story or blocked story.
@message.attachments.destroy_all
@message.update(content: I18n.t('conversations.messages.instagram_deleted_story_content'))
Rails.logger.error e
{}
@ -80,4 +84,10 @@ class Messages::Messenger::MessageBuilder
ChatwootExceptionTracker.new(e, account: @inbox.account).capture_exception
{}
end
private
def unsupported_file_type?(attachment_type)
[:template, :unsupported_type].include? attachment_type.to_sym
end
end

View file

@ -25,7 +25,7 @@ class NotificationSubscriptionBuilder
end
def build_identifier_subscription
@identifier_subscription = user.notification_subscriptions.create(params.merge(identifier: identifier))
@identifier_subscription = user.notification_subscriptions.create!(params.merge(identifier: identifier))
end
def update_identifier_subscription

View file

@ -5,9 +5,10 @@ class Api::V1::Accounts::ArticlesController < Api::V1::Accounts::BaseController
before_action :set_current_page, only: [:index]
def index
@articles_count = @portal.articles.count
@articles = @portal.articles
@articles = @articles.search(list_params) if list_params.present?
@portal_articles = @portal.articles
@all_articles = @portal_articles.search(list_params)
@articles_count = @all_articles.count
@articles = @all_articles.page(@current_page)
end
def create
@ -37,12 +38,12 @@ class Api::V1::Accounts::ArticlesController < Api::V1::Accounts::BaseController
end
def portal
@portal ||= Current.account.portals.find_by(slug: params[:portal_id])
@portal ||= Current.account.portals.find_by!(slug: params[:portal_id])
end
def article_params
params.require(:article).permit(
:title, :content, :description, :position, :category_id, :author_id, :associated_article_id, :status, meta: [:title, :description,
:title, :slug, :content, :description, :position, :category_id, :author_id, :associated_article_id, :status, meta: [:title, :description,
{ tags: [] }]
)
end

View file

@ -49,7 +49,7 @@ class Api::V1::Accounts::AutomationRulesController < Api::V1::Accounts::BaseCont
def clone
automation_rule = Current.account.automation_rules.find_by(id: params[:automation_rule_id])
new_rule = automation_rule.dup
new_rule.save
new_rule.save!
@automation_rule = new_rule
end

View file

@ -5,6 +5,7 @@ class Api::V1::Accounts::CategoriesController < Api::V1::Accounts::BaseControlle
before_action :set_current_page, only: [:index]
def index
@current_locale = params[:locale]
@categories = @portal.categories.search(params)
end

View file

@ -44,7 +44,7 @@ class Api::V1::Accounts::Channels::TwilioChannelsController < Api::V1::Accounts:
phone_number: phone_number,
medium: medium
)
@inbox = Current.account.inboxes.create(
@inbox = Current.account.inboxes.create!(
name: permitted_params[:name],
channel: @twilio_channel
)

View file

@ -2,8 +2,11 @@ class Api::V1::Accounts::Contacts::ContactInboxesController < Api::V1::Accounts:
before_action :ensure_inbox, only: [:create]
def create
source_id = params[:source_id] || SecureRandom.uuid
@contact_inbox = ContactInbox.create!(contact: @contact, inbox: @inbox, source_id: source_id)
@contact_inbox = ContactInboxBuilder.new(
contact: @contact,
inbox: @inbox,
source_id: params[:source_id]
).perform
end
private

View file

@ -2,7 +2,7 @@ class Api::V1::Accounts::Contacts::ConversationsController < Api::V1::Accounts::
def index
@conversations = Current.account.conversations.includes(
:assignee, :contact, :inbox, :taggings
).where(inbox_id: inbox_ids, contact_id: @contact.id)
).where(inbox_id: inbox_ids, contact_id: @contact.id).order(id: :desc).limit(20)
end
private

View file

@ -134,8 +134,11 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
return if params[:inbox_id].blank?
inbox = Current.account.inboxes.find(params[:inbox_id])
source_id = params[:source_id] || SecureRandom.uuid
ContactInbox.create(contact: @contact, inbox: inbox, source_id: source_id)
ContactInboxBuilder.new(
contact: @contact,
inbox: inbox,
source_id: params[:source_id]
).perform
end
def permitted_params

View file

@ -3,7 +3,7 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
include DateRangeHelper
before_action :conversation, except: [:index, :meta, :search, :create, :filter]
before_action :contact_inbox, only: [:create]
before_action :inbox, :contact, :contact_inbox, only: [:create]
def index
result = conversation_finder.perform
@ -24,7 +24,7 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
def create
ActiveRecord::Base.transaction do
@conversation = ::Conversation.create!(conversation_params)
@conversation = ConversationBuilder.new(params: params, contact_inbox: @contact_inbox).perform
Messages::MessageBuilder.new(Current.user, @conversation, params[:message]).perform if params[:message].present?
end
end
@ -75,10 +75,13 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
end
def update_last_seen
# rubocop:disable Rails/SkipsModelValidations
@conversation.update_column(:agent_last_seen_at, DateTime.now.utc)
@conversation.update_column(:assignee_last_seen_at, DateTime.now.utc) if assignee?
# rubocop:enable Rails/SkipsModelValidations
update_last_seen_on_conversation(DateTime.now.utc, assignee?)
end
def unread
last_incoming_message = @conversation.messages.incoming.last
last_seen_at = last_incoming_message.created_at - 1.second if last_incoming_message.present?
update_last_seen_on_conversation(last_seen_at, true)
end
def custom_attributes
@ -88,9 +91,18 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
private
def update_last_seen_on_conversation(last_seen_at, update_assignee)
# rubocop:disable Rails/SkipsModelValidations
@conversation.update_column(:agent_last_seen_at, last_seen_at)
@conversation.update_column(:assignee_last_seen_at, last_seen_at) if update_assignee.present?
# rubocop:enable Rails/SkipsModelValidations
end
def set_conversation_status
status = params[:status] == 'bot' ? 'pending' : params[:status]
@conversation.status = status
# TODO: temporary fallback for the old bot status in conversation, we will remove after couple of releases
# commenting this out to see if there are any errors, if not we can remove this in subsequent releases
# status = params[:status] == 'bot' ? 'pending' : params[:status]
@conversation.status = params[:status]
@conversation.snoozed_until = parse_date_time(params[:snoozed_until].to_s) if params[:snoozed_until]
end
@ -109,51 +121,44 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
authorize @conversation.inbox, :show?
end
def inbox
return if params[:inbox_id].blank?
@inbox = Current.account.inboxes.find(params[:inbox_id])
authorize @inbox, :show?
end
def contact
return if params[:contact_id].blank?
@contact = Current.account.contacts.find(params[:contact_id])
end
def contact_inbox
@contact_inbox = build_contact_inbox
# fallback for the old case where we do look up only using source id
# In future we need to change this and make sure we do look up on combination of inbox_id and source_id
# and deprecate the support of passing only source_id as the param
@contact_inbox ||= ::ContactInbox.find_by!(source_id: params[:source_id])
authorize @contact_inbox.inbox, :show?
end
def build_contact_inbox
return if params[:contact_id].blank? || params[:inbox_id].blank?
inbox = Current.account.inboxes.find(params[:inbox_id])
authorize inbox, :show?
return if @inbox.blank? || @contact.blank?
ContactInboxBuilder.new(
contact_id: params[:contact_id],
inbox_id: inbox.id,
contact: @contact,
inbox: @inbox,
source_id: params[:source_id]
).perform
end
def conversation_params
additional_attributes = params[:additional_attributes]&.permit! || {}
custom_attributes = params[:custom_attributes]&.permit! || {}
status = params[:status].present? ? { status: params[:status] } : {}
# TODO: temporary fallback for the old bot status in conversation, we will remove after couple of releases
status = { status: 'pending' } if status[:status] == 'bot'
{
account_id: Current.account.id,
inbox_id: @contact_inbox.inbox_id,
contact_id: @contact_inbox.contact_id,
contact_inbox_id: @contact_inbox.id,
additional_attributes: additional_attributes,
custom_attributes: custom_attributes,
snoozed_until: params[:snoozed_until],
assignee_id: params[:assignee_id],
team_id: params[:team_id]
}.merge(status)
end
def conversation_finder
@conversation_finder ||= ConversationFinder.new(current_user, params)
@conversation_finder ||= ConversationFinder.new(Current.user, params)
end
def assignee?
@conversation.assignee_id? && current_user == @conversation.assignee
@conversation.assignee_id? && Current.user == @conversation.assignee
end
end

View file

@ -22,7 +22,7 @@ class Api::V1::Accounts::CsatSurveyResponsesController < Api::V1::Accounts::Base
def download
response.headers['Content-Type'] = 'text/csv'
response.headers['Content-Disposition'] = 'attachment; filename=csat_report.csv'
render layout: false, template: 'api/v1/accounts/csat_survey_responses/download.csv.erb', format: 'csv'
render layout: false, template: 'api/v1/accounts/csat_survey_responses/download', formats: [:csv]
end
private

View file

@ -113,7 +113,8 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
def inbox_attributes
[:name, :avatar, :greeting_enabled, :greeting_message, :enable_email_collect, :csat_survey_enabled,
:enable_auto_assignment, :working_hours_enabled, :out_of_office_message, :timezone, :allow_messages_after_resolved]
:enable_auto_assignment, :working_hours_enabled, :out_of_office_message, :timezone, :allow_messages_after_resolved,
:lock_to_single_conversation]
end
def permitted_params(channel_attributes = [])

View file

@ -1,6 +1,6 @@
class Api::V1::Accounts::MacrosController < Api::V1::Accounts::BaseController
before_action :check_authorization
before_action :fetch_macro, only: [:show, :update, :destroy, :execute]
before_action :check_authorization, only: [:show, :update, :destroy, :execute]
def index
@macros = Macro.with_visibility(current_user, params)
@ -14,19 +14,34 @@ class Api::V1::Accounts::MacrosController < Api::V1::Accounts::BaseController
render json: { error: @macro.errors.messages }, status: :unprocessable_entity and return unless @macro.valid?
@macro.save!
process_attachments
@macro
end
def show; end
def show
head :not_found if @macro.nil?
end
def destroy
@macro.destroy!
head :ok
end
def attach_file
file_blob = ActiveStorage::Blob.create_and_upload!(
key: nil,
io: params[:attachment].tempfile,
filename: params[:attachment].original_filename,
content_type: params[:attachment].content_type
)
render json: { blob_key: file_blob.key, blob_id: file_blob.id }
end
def update
ActiveRecord::Base.transaction do
@macro.update!(macros_with_user)
@macro.set_visibility(current_user, permitted_params)
process_attachments
@macro.save!
rescue StandardError => e
Rails.logger.error e
@ -40,6 +55,19 @@ class Api::V1::Accounts::MacrosController < Api::V1::Accounts::BaseController
head :ok
end
private
def process_attachments
actions = @macro.actions.filter_map { |k, _v| k if k['action_name'] == 'send_attachment' }
return if actions.blank?
actions.each do |action|
blob_id = action['action_params']
blob = ActiveStorage::Blob.find_by(id: blob_id)
@macro.files.attach(blob)
end
end
def permitted_params
params.permit(
:name, :account_id, :visibility,
@ -54,4 +82,8 @@ class Api::V1::Accounts::MacrosController < Api::V1::Accounts::BaseController
def fetch_macro
@macro = Current.account.macros.find_by(id: params[:id])
end
def check_authorization
authorize(@macro) if @macro.present?
end
end

View file

@ -14,10 +14,14 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController
@portal.members << agents
end
def show; end
def show
@all_articles = @portal.articles
@articles = @all_articles.search(locale: params[:locale])
end
def create
@portal = Current.account.portals.build(portal_params)
@portal.custom_domain = parsed_custom_domain
@portal.save!
process_attached_logo
end
@ -25,6 +29,7 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController
def update
ActiveRecord::Base.transaction do
@portal.update!(portal_params) if params[:portal].present?
# @portal.custom_domain = parsed_custom_domain
process_attached_logo
rescue StandardError => e
Rails.logger.error e
@ -70,4 +75,9 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController
def set_current_page
@current_page = params[:page] || 1
end
def parsed_custom_domain
domain = URI.parse(@portal.custom_domain)
domain.is_a?(URI::HTTP) ? domain.host : @portal.custom_domain
end
end

View file

@ -24,7 +24,7 @@ class Api::V1::AccountsController < Api::BaseController
).perform
if @user
send_auth_headers(@user)
render 'api/v1/accounts/create.json', locals: { resource: @user }
render 'api/v1/accounts/create', format: :json, locals: { resource: @user }
else
render_error_response(CustomExceptions::Account::SignupFailed.new({}))
end
@ -32,7 +32,7 @@ class Api::V1::AccountsController < Api::BaseController
def show
@latest_chatwoot_version = ::Redis::Alfred.get(::Redis::Alfred::LATEST_CHATWOOT_VERSION)
render 'api/v1/accounts/show.json'
render 'api/v1/accounts/show', format: :json
end
def update

View file

@ -9,7 +9,7 @@ class Api::V1::NotificationSubscriptionsController < Api::BaseController
def destroy
notification_subscription = NotificationSubscription.where(["subscription_attributes->>'push_token' = ?", params[:push_token]]).first
notification_subscription.destroy!
notification_subscription.destroy! if notification_subscription.present?
head :ok
end

View file

@ -18,10 +18,19 @@ class Api::V1::ProfilesController < Api::BaseController
head :ok
end
def auto_offline
@user.account_users.find_by!(account_id: auto_offline_params[:account_id]).update!(auto_offline: auto_offline_params[:auto_offline] || false)
end
def availability
@user.account_users.find_by!(account_id: availability_params[:account_id]).update!(availability: availability_params[:availability])
end
def set_active_account
@user.account_users.find_by(account_id: profile_params[:account_id]).update(active_at: Time.now.utc)
head :ok
end
private
def set_user
@ -32,6 +41,10 @@ class Api::V1::ProfilesController < Api::BaseController
params.require(:profile).permit(:account_id, :availability)
end
def auto_offline_params
params.require(:profile).permit(:account_id, :auto_offline)
end
def profile_params
params.require(:profile).permit(
:email,
@ -39,6 +52,7 @@ class Api::V1::ProfilesController < Api::BaseController
:display_name,
:avatar,
:message_signature,
:account_id,
ui_settings: {}
)
end

View file

@ -50,7 +50,9 @@ class Api::V1::Widget::BaseController < ApplicationController
end
def contact_name
params[:contact][:name] || contact_email.split('@')[0] if contact_email.present?
return if @contact.email.present? || @contact.phone_number.present? || @contact.identifier.present?
permitted_params.dig(:contact, :name) || (contact_email.split('@')[0] if contact_email.present?)
end
def contact_phone_number

View file

@ -9,7 +9,7 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
ActiveRecord::Base.transaction do
process_update_contact
@conversation = create_conversation
conversation.messages.create(message_params)
conversation.messages.create!(message_params)
end
end
@ -17,7 +17,8 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
@contact = ContactIdentifyAction.new(
contact: @contact,
params: { email: contact_email, phone_number: contact_phone_number, name: contact_name },
retain_original_contact_name: true
retain_original_contact_name: true,
discard_invalid_attrs: true
).perform
end
@ -59,7 +60,7 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
unless conversation.resolved?
conversation.status = :resolved
conversation.save
conversation.save!
end
head :ok
end

View file

@ -14,22 +14,22 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
def agents
@report_data = generate_agents_report
generate_csv('agents_report', 'api/v2/accounts/reports/agents.csv.erb')
generate_csv('agents_report', 'api/v2/accounts/reports/agents')
end
def inboxes
@report_data = generate_inboxes_report
generate_csv('inboxes_report', 'api/v2/accounts/reports/inboxes.csv.erb')
generate_csv('inboxes_report', 'api/v2/accounts/reports/inboxes')
end
def labels
@report_data = generate_labels_report
generate_csv('labels_report', 'api/v2/accounts/reports/labels.csv.erb')
generate_csv('labels_report', 'api/v2/accounts/reports/labels')
end
def teams
@report_data = generate_teams_report
generate_csv('teams_report', 'api/v2/accounts/reports/teams.csv.erb')
generate_csv('teams_report', 'api/v2/accounts/reports/teams')
end
def conversations
@ -43,7 +43,7 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
def generate_csv(filename, template)
response.headers['Content-Type'] = 'text/csv'
response.headers['Content-Disposition'] = "attachment; filename=#{filename}.csv"
render layout: false, template: template, format: 'csv'
render layout: false, template: template, formats: [:csv]
end
def check_authorization

View file

@ -13,6 +13,8 @@ module RequestExceptionHandler
render_not_found_error('Resource could not be found')
rescue Pundit::NotAuthorizedError
render_unauthorized('You are not authorized to do this action')
rescue ActionController::ParameterMissing => e
render_could_not_create_error(e.message)
ensure
# to address the thread variable leak issues in Puma/Thin webserver
Current.reset

View file

@ -4,8 +4,7 @@ class DashboardController < ActionController::Base
before_action :set_global_config
around_action :switch_locale
before_action :ensure_installation_onboarding, only: [:index]
before_action :redirect_to_custom_domain_page
before_action :redirect_to_saml_login
before_action :render_hc_if_custom_domain, only: [:index]
layout 'vueapp'
@ -17,8 +16,7 @@ class DashboardController < ActionController::Base
@global_config = GlobalConfig.get(
'LOGO', 'LOGO_THUMBNAIL',
'INSTALLATION_NAME',
'WIDGET_BRAND_URL',
'TERMS_URL',
'WIDGET_BRAND_URL', 'TERMS_URL',
'PRIVACY_URL',
'DISPLAY_MANIFEST',
'CREATE_NEW_ACCOUNT_FROM_DASHBOARD',
@ -26,12 +24,12 @@ class DashboardController < ActionController::Base
'API_CHANNEL_NAME',
'API_CHANNEL_THUMBNAIL',
'ANALYTICS_TOKEN',
'ANALYTICS_HOST',
'DIRECT_UPLOADS_ENABLED',
'HCAPTCHA_SITE_KEY',
'LOGOUT_REDIRECT_LINK',
'DISABLE_USER_PROFILE_UPDATE',
'DEPLOYMENT_ENV'
'DEPLOYMENT_ENV',
'CSML_EDITOR_HOST'
).merge(app_config)
end
@ -39,17 +37,15 @@ class DashboardController < ActionController::Base
redirect_to '/installation/onboarding' if ::Redis::Alfred.get(::Redis::Alfred::CHATWOOT_INSTALLATION_ONBOARDING)
end
def redirect_to_custom_domain_page
custom_domain = request.host
portal = Portal.find_by(custom_domain: custom_domain)
def render_hc_if_custom_domain
domain = request.host
return if domain == URI.parse(ENV.fetch('FRONTEND_URL', '')).host
return unless portal
@portal = Portal.find_by(custom_domain: domain)
return unless @portal
redirect_to "/hc/#{portal.slug}"
end
def redirect_to_saml_login
redirect_to '/saml' and return unless current_user
@locale = @portal.default_locale
render 'public/api/v1/portals/show', layout: 'portal', portal: @portal and return
end
def app_config
@ -62,29 +58,4 @@ class DashboardController < ActionController::Base
IS_ENTERPRISE: ChatwootApp.enterprise?
}
end
def saml_settings
settings = OneLogin::RubySaml::Settings.new
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
settings.sp_entity_id = "http://#{request.host}/saml/metadata"
settings.idp_entity_id = 'https://app.onelogin.com/saml/metadata/1835014'
settings.idp_sso_target_url = 'https://app.onelogin.com/trust/saml2/http-post/sso/1835014'
settings.idp_slo_target_url = 'https://app.onelogin.com/trust/saml2/http-redirect/slo/1835014'
settings.name_identifier_format = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'
# Optional for most SAML IdPs
settings.authn_context = 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'
# or as an array
settings.authn_context = [
'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport',
'urn:oasis:names:tc:SAML:2.0:ac:classes:Password'
]
# Optional bindings (defaults to Redirect for logout POST for ACS)
settings.single_logout_service_binding = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' # or :post, :redirect
settings.assertion_consumer_service_binding = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' # or :post, :redirect
settings
end
end

View file

@ -14,7 +14,7 @@ class DeviseOverrides::ConfirmationsController < Devise::ConfirmationsController
def render_confirmation_success
send_auth_headers(@confirmable)
render partial: 'devise/auth.json', locals: { resource: @confirmable }
render partial: 'devise/auth', formats: [:json], locals: { resource: @confirmable }
end
def render_confirmation_error

View file

@ -11,7 +11,7 @@ class DeviseOverrides::PasswordsController < Devise::PasswordsController
@recoverable = User.find_by(reset_password_token: reset_password_token)
if @recoverable && reset_password_and_confirmation(@recoverable)
send_auth_headers(@recoverable)
render partial: 'devise/auth.json', locals: { resource: @recoverable }
render partial: 'devise/auth', formats: [:json], locals: { resource: @recoverable }
else
render json: { message: 'Invalid token', redirect_url: '/' }, status: :unprocessable_entity
end

View file

@ -16,14 +16,14 @@ class DeviseOverrides::SessionsController < ::DeviseTokenAuth::SessionsControlle
end
def render_create_success
render partial: 'devise/auth.json', locals: { resource: @resource }
render partial: 'devise/auth', formats: [:json], locals: { resource: @resource }
end
private
def authenticate_resource_with_sso_token
@token = @resource.create_token
@resource.save
@resource.save!
sign_in(:user, @resource, store: false, bypass: false)
# invalidate the token after the user is signed in

View file

@ -2,7 +2,7 @@ class DeviseOverrides::TokenValidationsController < ::DeviseTokenAuth::TokenVali
def validate_token
# @resource will have been set by set_user_by_token concern
if @resource
render 'devise/token.json'
render 'devise/token', formats: [:json]
else
render_validate_token_error
end

View file

@ -1,18 +1,16 @@
class Platform::Api::V1::AccountsController < PlatformController
def create
@resource = Account.new(account_params)
@resource.save!
@resource = Account.create!(account_params)
update_resource_features
@platform_app.platform_app_permissibles.find_or_create_by(permissible: @resource)
render json: @resource
end
def show
render json: @resource
end
def show; end
def update
@resource.update!(account_params)
render json: @resource
@resource.assign_attributes(account_params)
update_resource_features
@resource.save!
end
def destroy
@ -27,6 +25,18 @@ class Platform::Api::V1::AccountsController < PlatformController
end
def account_params
params.permit(:name, :locale)
permitted_params.except(:features)
end
def update_resource_features
return if permitted_params[:features].blank?
permitted_params[:features].each do |key, value|
value.present? ? @resource.enable_features(key) : @resource.disable_features(key)
end
end
def permitted_params
params.permit(:name, :locale, :domain, :support_email, :status, features: {}, limits: {}, custom_attributes: {})
end
end

View file

@ -51,6 +51,6 @@ class Platform::Api::V1::UsersController < PlatformController
end
def user_params
params.permit(:name, :email, :password, custom_attributes: {})
params.permit(:name, :display_name, :email, :password, custom_attributes: {})
end
end

View file

@ -4,10 +4,10 @@ class Public::Api::V1::Inboxes::ContactsController < Public::Api::V1::InboxesCon
def create
source_id = params[:source_id] || SecureRandom.uuid
@contact_inbox = ::ContactBuilder.new(
@contact_inbox = ::ContactInboxWithContactBuilder.new(
source_id: source_id,
inbox: @inbox_channel.inbox,
contact_attributes: permitted_params.except(:identifier, :identifier_hash)
contact_attributes: permitted_params.except(:identifier_hash)
).perform
end

View file

@ -3,9 +3,15 @@ class Public::Api::V1::InboxesController < PublicController
before_action :set_contact_inbox
before_action :set_conversation
def show
@inbox_channel = ::Channel::Api.find_by!(identifier: params[:id])
end
private
def set_inbox_channel
return if params[:inbox_id].blank?
@inbox_channel = ::Channel::Api.find_by!(identifier: params[:inbox_id])
end

View file

@ -1,8 +1,9 @@
class Public::Api::V1::Portals::ArticlesController < PublicController
before_action :ensure_custom_domain_request, only: [:show, :index]
before_action :set_portal
before_action :set_category
before_action :portal
before_action :set_category, except: [:index]
before_action :set_article, only: [:show]
layout 'portal'
def index
@articles = @portal.articles
@ -15,17 +16,24 @@ class Public::Api::V1::Portals::ArticlesController < PublicController
def set_article
@article = @category.articles.find(params[:id])
@parsed_content = render_article_content(@article.content)
end
def set_category
@category = @portal.categories.find_by!(slug: params[:category_slug])
@category = @portal.categories.find_by!(slug: params[:category_slug]) if params[:category_slug].present?
end
def set_portal
@portal = @portals.find_by!(slug: params[:slug], archived: false)
def portal
@portal ||= Portal.find_by!(slug: params[:slug], archived: false)
end
def list_params
params.permit(:query)
end
def render_article_content(content)
# rubocop:disable Rails/OutputSafety
CommonMarker.render_html(content).html_safe
# rubocop:enable Rails/OutputSafety
end
end

View file

@ -1,7 +1,8 @@
class Public::Api::V1::Portals::CategoriesController < PublicController
before_action :ensure_custom_domain_request, only: [:show, :index]
before_action :set_portal
before_action :portal
before_action :set_category, only: [:show]
layout 'portal'
def index
@categories = @portal.categories
@ -12,10 +13,10 @@ class Public::Api::V1::Portals::CategoriesController < PublicController
private
def set_category
@category = @portal.categories.find_by!(locale: params[:locale])
@category = @portal.categories.find_by!(locale: params[:locale], slug: params[:category_slug])
end
def set_portal
@portal = @portals.find_by!(slug: params[:slug], archived: false)
def portal
@portal ||= Portal.find_by!(slug: params[:slug], archived: false)
end
end

View file

@ -1,12 +1,21 @@
class Public::Api::V1::PortalsController < PublicController
before_action :ensure_custom_domain_request, only: [:show]
before_action :set_portal
before_action :portal
before_action :redirect_to_portal_with_locale, only: [:show]
layout 'portal'
def show; end
private
def set_portal
@portal = @portals.find_by!(slug: params[:slug], archived: false)
def portal
@portal ||= Portal.find_by!(slug: params[:slug], archived: false)
@locale = params[:locale] || @portal.default_locale
end
def redirect_to_portal_with_locale
return if params[:locale].present?
redirect_to "/hc/#{@portal.slug}/#{@portal.default_locale}"
end
end

View file

@ -7,14 +7,15 @@ class PublicController < ActionController::Base
private
def ensure_custom_domain_request
custom_domain = request.host
domain = request.host
@portals = ::Portal.where(custom_domain: custom_domain)
return if [URI.parse(ENV.fetch('FRONTEND_URL', '')).host, URI.parse(ENV.fetch('HELPCENTER_URL', '')).host].include?(domain)
return if @portals.present?
@portal = ::Portal.find_by(custom_domain: domain)
return if @portal.present?
render json: {
error: "Domain: #{custom_domain} is not registered with us. \
error: "Domain: #{domain} is not registered with us. \
Please send us an email at support@chatwoot.com with the custom domain name and account API key"
}, status: :unauthorized and return
end

View file

@ -1,106 +0,0 @@
# This controller expects you to use the URLs /saml/init and /saml/consume in your OneLogin application.
class SamlController < ApplicationController
# skip_before_action :verify_authenticity_token, :only => [:consume]
layout 'vueapp'
include SsoAuthenticatable
def index
request = OneLogin::RubySaml::Authrequest.new
redirect_to(request.create(saml_settings))
end
def consume
response = OneLogin::RubySaml::Response.new(params['SAMLResponse'])
response.settings = saml_settings
# We validate the SAML Response and check if the user already exists in the system
if response.is_valid?
# authorize_success, log the user
find_the_resource(response.nameid)
create_session_and_assign_token
encoded_email = ERB::Util.url_encode(@resource.email)
redirect_to "https://db7b-103-51-75-84.in.ngrok.io/app/login?email=#{encoded_email}&sso_auth_token=#{@resource.generate_sso_auth_token}"
else
Rails.logger.error "Response Invalid. Errors: #{response.errors}"
end
end
def metadata
settings = saml_settings
meta = OneLogin::RubySaml::Metadata.new
render xml: meta.generate(settings, true)
end
def logout
# If we're given a logout request, handle it in the IdP logout initiated method
idp_logout_request
end
# Method to handle IdP initiated logouts
def idp_logout_request
settings = saml_settings
logout_request = OneLogin::RubySaml::SloLogoutrequest.new(params[:SAMLRequest], settings: settings)
unless logout_request.is_valid?
error_msg = "IdP initiated LogoutRequest was not valid!. Errors: #{logout_request.errors}"
Rails.logger.error error_msg
render inline: error_msg
end
Rails.logger.info "IdP initiated Logout for #{logout_request.nameid}"
# Actually log out this session
reset_session
logout_response = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id, nil, RelayState: params[:RelayState])
redirect_to logout_response
end
private
def find_the_resource(email)
@resource = User.find_by(email: email)
end
def create_session_and_assign_token
create_and_assign_token
sign_in(:user, @resource, store: true, bypass: false)
end
def create_and_assign_token
if @resource.respond_to?(:with_lock)
@resource.with_lock do
@token = @resource.create_token
@resource.save!
end
else
@token = @resource.create_token
@resource.save!
end
end
def render_create_success
render partial: 'devise/auth.json', locals: { resource: @resource }
end
def saml_settings
settings = OneLogin::RubySaml::Settings.new
settings.soft = true
settings.assertion_consumer_service_url = 'https://db7b-103-51-75-84.in.ngrok.io/saml/consume'
settings.sp_entity_id = 'https://db7b-103-51-75-84.in.ngrok.io/saml/metadata'
settings.idp_entity_id = 'https://app.onelogin.com/saml2'
settings.idp_sso_target_url = 'https://chatwoot-dev.onelogin.com/trust/saml2/http-post/sso/de789d10-0617-44e9-8fd6-9d798809cfbf'
settings.idp_slo_target_url = 'https://chatwoot-dev.onelogin.com/trust/saml2/http-redirect/slo/1861655'
settings.name_identifier_format = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'
settings.idp_cert_fingerprint = 'FD:17:5E:81:F8:F5:88:EF:21:AB:94:44:3E:4A:C4:72:94:E2:63:AE'
settings.idp_cert_fingerprint_algorithm = 'http://www.w3.org/2000/09/xmldsig#sha1'
# Optional bindings (defaults to Redirect for logout POST for ACS)
settings.single_logout_service_binding = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' # or :post, :redirect
settings.assertion_consumer_service_binding = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' # or :post, :redirect
settings
end
end

View file

@ -36,6 +36,7 @@ class SuperAdmin::AccountsController < SuperAdmin::ApplicationController
def resource_params
permitted_params = super
permitted_params[:limits] = permitted_params[:limits].to_h.compact
permitted_params[:selected_feature_flags] = params[:enabled_features].keys.map(&:to_sym) if params[:enabled_features].present?
permitted_params
end

View file

@ -44,12 +44,12 @@ class Twitter::CallbacksController < Twitter::BaseController
end
def create_inbox
twitter_profile = account.twitter_profiles.create(
twitter_profile = account.twitter_profiles.create!(
twitter_access_token: parsed_body['oauth_token'],
twitter_access_token_secret: parsed_body['oauth_token_secret'],
profile_id: parsed_body['user_id']
)
account.inboxes.create(
account.inboxes.create!(
name: parsed_body['screen_name'],
channel: twitter_profile
)

View file

@ -8,7 +8,15 @@ class AccountDashboard < Administrate::BaseDashboard
# which determines how the attribute is displayed
# on pages throughout the dashboard.
enterprise_attribute_types = ChatwootApp.enterprise? ? { limits: Enterprise::AccountLimitsField } : {}
enterprise_attribute_types = if ChatwootApp.enterprise?
{
limits: Enterprise::AccountLimitsField,
all_features: Enterprise::AccountFeaturesField
}
else
{}
end
ATTRIBUTE_TYPES = {
id: Field::Number,
name: Field::String,
@ -37,7 +45,7 @@ class AccountDashboard < Administrate::BaseDashboard
# SHOW_PAGE_ATTRIBUTES
# an array of attributes that will be displayed on the model's show page.
enterprise_show_page_attributes = ChatwootApp.enterprise? ? %i[limits] : []
enterprise_show_page_attributes = ChatwootApp.enterprise? ? %i[limits all_features] : []
SHOW_PAGE_ATTRIBUTES = (%i[
id
name
@ -52,7 +60,7 @@ class AccountDashboard < Administrate::BaseDashboard
# FORM_ATTRIBUTES
# an array of attributes that will be displayed
# on the model's form (`new` and `edit`) pages.
enterprise_form_attributes = ChatwootApp.enterprise? ? %i[limits] : []
enterprise_form_attributes = ChatwootApp.enterprise? ? %i[limits all_features] : []
FORM_ATTRIBUTES = (%i[
name
locale

View file

@ -0,0 +1,7 @@
require 'administrate/field/base'
class Enterprise::AccountFeaturesField < Administrate::Field::Base
def to_s
data
end
end

View file

@ -56,7 +56,6 @@ class ConversationFinder
filter_by_team if @team
filter_by_labels if params[:labels]
filter_by_query if params[:q]
filter_by_reply_status
end
def set_inboxes
@ -76,12 +75,9 @@ class ConversationFinder
end
def find_all_conversations
if params[:conversation_type] == 'mention'
conversation_ids = current_account.mentions.where(user: current_user).pluck(:conversation_id)
@conversations = current_account.conversations.where(id: conversation_ids)
else
@conversations = current_account.conversations.where(inbox_id: @inbox_ids)
end
filter_by_conversation_type if params[:conversation_type]
@conversations
end
def filter_by_assignee_type
@ -96,8 +92,15 @@ class ConversationFinder
@conversations
end
def filter_by_reply_status
@conversations = @conversations.where(first_reply_created_at: nil) if params[:reply_status] == 'unattended'
def filter_by_conversation_type
case @params[:conversation_type]
when 'mention'
conversation_ids = current_account.mentions.where(user: current_user).pluck(:conversation_id)
@conversations = @conversations.where(id: conversation_ids)
when 'unattended'
@conversations = @conversations.where(first_reply_created_at: nil)
end
@conversations
end
def filter_by_query

View file

@ -21,7 +21,9 @@ class MessageFinder
end
def current_messages
if @params[:before].present?
if @params[:after].present?
messages.reorder('created_at asc').where('id >= ?', @params[:before].to_i).limit(20)
elsif @params[:before].present?
messages.reorder('created_at desc').where('id < ?', @params[:before].to_i).limit(20).reverse
else
messages.reorder('created_at desc').limit(20).reverse

View file

@ -8,6 +8,12 @@ module FileTypeHelper
:file
end
# Used in case of DIRECT_UPLOADS_ENABLED=true
def file_type_by_signed_id(signed_id)
blob = ActiveStorage::Blob.find_signed(signed_id)
file_type(blob&.content_type)
end
def image_file?(content_type)
[
'image/jpeg',

View file

@ -87,6 +87,9 @@ export default {
},
async initializeAccount() {
await this.$store.dispatch('accounts/get');
this.$store.dispatch('setActiveAccount', {
accountId: this.currentAccountId,
});
const {
locale,
latest_chatwoot_version: latestChatwootVersion,

View file

@ -0,0 +1,9 @@
import ApiClient from './ApiClient';
class AgentBotsAPI extends ApiClient {
constructor() {
super('agent_bots', { accountScoped: true });
}
}
export default new AgentBotsAPI();

View file

@ -144,7 +144,22 @@ export default {
});
},
updateAutoOffline(accountId, autoOffline = false) {
return axios.post(endPoints('autoOffline').url, {
profile: { account_id: accountId, auto_offline: autoOffline },
});
},
deleteAvatar() {
return axios.delete(endPoints('deleteAvatar').url);
},
setActiveAccount({ accountId }) {
const urlData = endPoints('setActiveAccount');
return axios.put(urlData.url, {
profile: {
account_id: accountId,
},
});
},
};

View file

@ -16,6 +16,9 @@ const endPoints = {
availabilityUpdate: {
url: '/api/v1/profile/availability',
},
autoOffline: {
url: '/api/v1/profile/auto_offline',
},
logout: {
url: 'auth/sign_out',
},
@ -40,6 +43,10 @@ const endPoints = {
deleteAvatar: {
url: '/api/v1/profile/avatar',
},
setActiveAccount: {
url: '/api/v1/profile/set_active_account',
},
};
export default page => {

View file

@ -7,8 +7,8 @@ class CategoriesAPI extends PortalsAPI {
super('categories', { accountScoped: true });
}
get({ portalSlug }) {
return axios.get(`${this.url}/${portalSlug}/categories`);
get({ portalSlug, locale }) {
return axios.get(`${this.url}/${portalSlug}/categories?locale=${locale}`);
}
create({ portalSlug, categoryObj }) {

View file

@ -6,6 +6,10 @@ class PortalsAPI extends ApiClient {
super('portals', { accountScoped: true });
}
getPortal({ portalSlug, locale }) {
return axios.get(`${this.url}/${portalSlug}?locale=${locale}`);
}
updatePortal({ portalSlug, portalObj }) {
return axios.patch(`${this.url}/${portalSlug}`, portalObj);
}

View file

@ -68,6 +68,10 @@ class ConversationApi extends ApiClient {
return axios.post(`${this.url}/${id}/update_last_seen`);
}
markMessagesUnread({ id }) {
return axios.post(`${this.url}/${id}/unread`);
}
toggleTyping({ conversationId, status, isPrivate }) {
return axios.post(`${this.url}/${conversationId}/toggle_typing_status`, {
typing_status: status,
@ -105,6 +109,16 @@ class ConversationApi extends ApiClient {
custom_attributes: customAttributes,
});
}
fetchParticipants(conversationId) {
return axios.get(`${this.url}/${conversationId}/participants`);
}
updateParticipants({ conversationId, userIds }) {
return axios.patch(`${this.url}/${conversationId}/participants`, {
user_ids: userIds,
});
}
}
export default new ConversationApi();

View file

@ -13,6 +13,16 @@ class Inboxes extends ApiClient {
deleteInboxAvatar(inboxId) {
return axios.delete(`${this.url}/${inboxId}/avatar`);
}
getAgentBot(inboxId) {
return axios.get(`${this.url}/${inboxId}/agent_bot`);
}
setAgentBot(inboxId, botId) {
return axios.post(`${this.url}/${inboxId}/set_agent_bot`, {
agent_bot: botId,
});
}
}
export default new Inboxes();

View file

@ -0,0 +1,16 @@
/* global axios */
import ApiClient from './ApiClient';
class MacrosAPI extends ApiClient {
constructor() {
super('macros', { accountScoped: true });
}
executeMacro({ macroId, conversationIds }) {
return axios.post(`${this.url}/${macroId}/execute`, {
conversation_ids: conversationIds,
});
}
}
export default new MacrosAPI();

View file

@ -0,0 +1,13 @@
import AgentBotsAPI from '../agentBots';
import ApiClient from '../ApiClient';
describe('#AgentBotsAPI', () => {
it('creates correct instance', () => {
expect(AgentBotsAPI).toBeInstanceOf(ApiClient);
expect(AgentBotsAPI).toHaveProperty('get');
expect(AgentBotsAPI).toHaveProperty('show');
expect(AgentBotsAPI).toHaveProperty('create');
expect(AgentBotsAPI).toHaveProperty('update');
expect(AgentBotsAPI).toHaveProperty('delete');
});
});

View file

@ -11,6 +11,8 @@ describe('#InboxesAPI', () => {
expect(inboxesAPI).toHaveProperty('update');
expect(inboxesAPI).toHaveProperty('delete');
expect(inboxesAPI).toHaveProperty('getCampaigns');
expect(inboxesAPI).toHaveProperty('getAgentBot');
expect(inboxesAPI).toHaveProperty('setAgentBot');
});
describeWithAPIMock('API calls', context => {
it('#getCampaigns', () => {

View file

@ -0,0 +1,14 @@
import macros from '../macros';
import ApiClient from '../ApiClient';
describe('#macrosAPI', () => {
it('creates correct instance', () => {
expect(macros).toBeInstanceOf(ApiClient);
expect(macros).toHaveProperty('get');
expect(macros).toHaveProperty('create');
expect(macros).toHaveProperty('update');
expect(macros).toHaveProperty('delete');
expect(macros).toHaveProperty('show');
expect(macros.url).toBe('/api/v1/macros');
});
});

View file

@ -0,0 +1,6 @@
/* global axios */
import wootConstants from 'dashboard/constants';
export const getTestimonialContent = () => {
return axios.get(wootConstants.TESTIMONIAL_URL);
};

View file

@ -74,8 +74,8 @@ Tahoma,
Arial,
sans-serif;
$body-antialiased: true;
$global-margin: $space-one;
$global-padding: $space-one;
$global-margin: $space-small;
$global-padding: $space-micro;
$global-weight-normal: normal;
$global-weight-bold: bold;
$global-radius: 0;

View file

@ -20,6 +20,24 @@
@include foundation-everything($flex: true);
@include foundation-prototype-text-utilities;
@include foundation-prototype-text-transformation;
@include foundation-prototype-text-decoration;
@include foundation-prototype-font-styling;
@include foundation-prototype-list-style-type;
@include foundation-prototype-rounded;
@include foundation-prototype-bordered;
@include foundation-prototype-shadow;
@include foundation-prototype-separator;
@include foundation-prototype-overflow;
@include foundation-prototype-display;
@include foundation-prototype-position;
@include foundation-prototype-border-box;
@include foundation-prototype-border-none;
@include foundation-prototype-sizing;
@include foundation-prototype-spacing;
@import 'typography';
@import 'layout';
@import 'animations';

View file

@ -113,9 +113,22 @@ $default-button-height: 4.0rem;
}
&.clear {
color: var(--w-700);
&.secondary {
color: var(--s-700)
}
&.success {
color: var(--g-700)
}
&.alert {
color: var(--r-700)
}
&.warning {
color: var(--y-600);
color: var(--y-700)
}
&:hover {
@ -142,10 +155,20 @@ $default-button-height: 4.0rem;
// Sizes
&.tiny {
height: var(--space-medium);
.icon+.button__content {
padding-left: var(--space-micro);
}
}
&.small {
height: var(--space-large);
padding-bottom: var(--space-smaller);
padding-top: var(--space-smaller);
.icon+.button__content {
padding-left: var(--space-smaller);
}
}
&.large {
@ -175,6 +198,10 @@ $default-button-height: 4.0rem;
height: auto;
margin: 0;
padding: 0;
&:hover {
text-decoration: underline;
}
}
}

View file

@ -14,15 +14,9 @@
}
.modal--close {
border-radius: 50%;
color: $color-heading;
cursor: pointer;
font-size: $font-size-big;
line-height: $space-normal;
padding: $space-normal;
position: absolute;
right: $space-micro;
top: $space-micro;
right: $space-small;
top: $space-small;
&:hover {
background: $color-background;

View file

@ -59,12 +59,8 @@
.hamburger--menu {
cursor: pointer;
display: none;
margin-right: $space-normal;
@media screen and (max-width: 1200px) {
display: block;
}
margin-right: $space-normal;
}
.header--icon {

View file

@ -102,6 +102,7 @@
@assign-agent="onAssignAgent"
@update-conversations="onUpdateConversations"
@assign-labels="onAssignLabels"
@assign-team="onAssignTeamsForBulk"
/>
<div
ref="activeConversation"
@ -125,6 +126,7 @@
@assign-label="onAssignLabels"
@update-conversation-status="toggleConversationStatus"
@context-menu-toggle="onContextMenuToggle"
@mark-as-unread="markAsUnread"
/>
<div v-if="chatListLoading" class="text-center">
@ -184,6 +186,11 @@ import {
hasPressedAltAndJKey,
hasPressedAltAndKKey,
} from 'shared/helpers/KeyboardHelpers';
import { conversationListPageURL } from '../helper/URLHelper';
import {
isOnMentionsView,
isOnUnattendedView,
} from '../store/modules/conversations/helpers/actionHelpers';
export default {
components: {
@ -332,14 +339,15 @@ export default {
status: this.activeStatus,
page: this.currentPage + 1,
labels: this.label ? [this.label] : undefined,
teamId: this.teamId ? this.teamId : undefined,
conversationType: this.conversationType
? this.conversationType
: undefined,
teamId: this.teamId || undefined,
conversationType: this.conversationType || undefined,
folders: this.hasActiveFolders ? this.savedFoldersValue : undefined,
};
},
pageTitle() {
if (this.hasAppliedFilters) {
return this.$t('CHAT_LIST.TAB_HEADING');
}
if (this.inbox.name) {
return this.inbox.name;
}
@ -352,6 +360,9 @@ export default {
if (this.conversationType === 'mention') {
return this.$t('CHAT_LIST.MENTION_HEADING');
}
if (this.conversationType === 'unattended') {
return this.$t('CHAT_LIST.UNATTENDED_HEADING');
}
if (this.hasActiveFolders) {
return this.activeFolder.name;
}
@ -431,9 +442,6 @@ export default {
},
methods: {
onApplyFilter(payload) {
if (this.$route.name !== 'home') {
this.$router.push({ name: 'home' });
}
this.resetBulkActions();
this.foldersQuery = filterQueryGenerator(payload);
this.$store.dispatch('conversationPage/reset');
@ -636,6 +644,35 @@ export default {
this.showAlert(this.$t('BULK_ACTION.ASSIGN_FAILED'));
}
},
async markAsUnread(conversationId) {
try {
await this.$store.dispatch('markMessagesUnread', {
id: conversationId,
});
const {
params: { accountId, inbox_id: inboxId, label, teamId },
name,
} = this.$route;
let conversationType = '';
if (isOnMentionsView({ route: { name } })) {
conversationType = 'mention';
} else if (isOnUnattendedView({ route: { name } })) {
conversationType = 'unattended';
}
this.$router.push(
conversationListPageURL({
accountId,
conversationType: conversationType,
customViewId: this.foldersId,
inboxId,
label,
teamId,
})
);
} catch (error) {
// Ignore error
}
},
async onAssignTeam(team, conversationId = null) {
try {
await this.$store.dispatch('assignTeam', {
@ -685,6 +722,21 @@ export default {
this.showAlert(this.$t('BULK_ACTION.LABELS.ASSIGN_FAILED'));
}
},
async onAssignTeamsForBulk(team) {
try {
await this.$store.dispatch('bulkActions/process', {
type: 'Conversation',
ids: this.selectedConversations,
fields: {
team_id: team.id,
},
});
this.selectedConversations = [];
this.showAlert(this.$t('BULK_ACTION.TEAMS.ASSIGN_SUCCESFUL'));
} catch (err) {
this.showAlert(this.$t('BULK_ACTION.TEAMS.ASSIGN_FAILED'));
}
},
async onUpdateConversations(status) {
try {
await this.$store.dispatch('bulkActions/process', {

View file

@ -7,9 +7,13 @@
@click="onBackDropClick"
>
<div :class="modalContainerClassName" @click.stop>
<button class="modal--close" @click="close">
<fluent-icon icon="dismiss" />
</button>
<woot-button
color-scheme="secondary"
icon="dismiss"
variant="clear"
class="modal--close"
@click="close"
/>
<slot />
</div>
</div>

View file

@ -1,7 +1,12 @@
<template>
<button @click="onMenuItemClick">
<fluent-icon class="hamburger--menu" icon="list" />
</button>
<woot-button
size="small"
variant="clear"
color-scheme="secondary"
icon="list"
class="toggle-sidebar"
@click="onMenuItemClick"
/>
</template>
<script>
@ -16,13 +21,8 @@ export default {
};
</script>
<style scoped lang="scss">
.hamburger--menu {
cursor: pointer;
display: none;
margin-right: var(--space-normal);
@media screen and (max-width: 1200px) {
display: block;
}
.toggle-sidebar {
margin-right: var(--space-small);
margin-left: var(--space-minus-small);
}
</style>

View file

@ -5,9 +5,12 @@ import Button from './ui/WootButton';
import Code from './Code';
import ColorPicker from './widgets/ColorPicker';
import ConfirmDeleteModal from './widgets/modal/ConfirmDeleteModal.vue';
import ConfirmModal from './widgets/modal/ConfirmationModal.vue';
import ContextMenu from './ui/ContextMenu.vue';
import DeleteModal from './widgets/modal/DeleteModal.vue';
import DropdownItem from 'shared/components/ui/dropdown/DropdownItem';
import DropdownMenu from 'shared/components/ui/dropdown/DropdownMenu';
import FeatureToggle from './widgets/FeatureToggle';
import HorizontalBar from './widgets/chart/HorizontalBarChart';
import Input from './widgets/forms/Input.vue';
import Label from './ui/Label';
@ -21,8 +24,6 @@ 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';
import ContextMenu from './ui/ContextMenu.vue';
const WootUIKit = {
AvatarUploader,
@ -31,9 +32,12 @@ const WootUIKit = {
Code,
ColorPicker,
ConfirmDeleteModal,
ConfirmModal,
ContextMenu,
DeleteModal,
DropdownItem,
DropdownMenu,
FeatureToggle,
HorizontalBar,
Input,
Label,
@ -47,8 +51,6 @@ const WootUIKit = {
Tabs,
TabsItem,
Thumbnail,
ConfirmModal,
ContextMenu,
install(Vue) {
const keys = Object.keys(this);
keys.pop(); // remove 'install' from keys

View file

@ -18,12 +18,35 @@
</woot-button>
</woot-dropdown-item>
<woot-dropdown-divider />
<woot-dropdown-item class="auto-offline--toggle">
<div class="info-wrap">
<fluent-icon
v-tooltip.right-start="$t('SIDEBAR.SET_AUTO_OFFLINE.INFO_TEXT')"
icon="info"
size="14"
class="info-icon"
/>
<span class="auto-offline--text">
{{ $t('SIDEBAR.SET_AUTO_OFFLINE.TEXT') }}
</span>
</div>
<woot-switch
size="small"
class="auto-offline--switch"
:value="currentUserAutoOffline"
@input="updateAutoOffline"
/>
</woot-dropdown-item>
<woot-dropdown-divider />
</woot-dropdown-menu>
</template>
<script>
import { mapGetters } from 'vuex';
import { mixin as clickaway } from 'vue-clickaway';
import alertMixin from 'shared/mixins/alertMixin';
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem';
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu';
import WootDropdownHeader from 'shared/components/ui/dropdown/DropdownHeader';
@ -41,7 +64,7 @@ export default {
AvailabilityStatusBadge,
},
mixins: [clickaway],
mixins: [clickaway, alertMixin],
data() {
return {
@ -54,6 +77,7 @@ export default {
...mapGetters({
getCurrentUserAvailability: 'getCurrentUserAvailability',
currentAccountId: 'getCurrentAccountId',
currentUserAutoOffline: 'getCurrentUserAutoOffline',
}),
availabilityDisplayLabel() {
const availabilityIndex = AVAILABILITY_STATUS_KEYS.findIndex(
@ -85,21 +109,30 @@ export default {
closeStatusMenu() {
this.isStatusMenuOpened = false;
},
updateAutoOffline(autoOffline) {
this.$store.dispatch('updateAutoOffline', {
accountId: this.currentAccountId,
autoOffline,
});
},
changeAvailabilityStatus(availability) {
const accountId = this.currentAccountId;
if (this.isUpdating) {
return;
}
this.isUpdating = true;
this.$store
.dispatch('updateAvailability', {
availability: availability,
account_id: accountId,
})
.finally(() => {
this.isUpdating = false;
try {
this.$store.dispatch('updateAvailability', {
availability,
account_id: this.currentAccountId,
});
} catch (error) {
this.showAlert(
this.$t('PROFILE_SETTINGS.FORM.AVAILABILITY.SET_AVAILABILITY_ERROR')
);
} finally {
this.isUpdating = false;
}
},
},
};
@ -143,4 +176,32 @@ export default {
align-items: baseline;
}
}
.auto-offline--toggle {
align-items: center;
display: flex;
justify-content: space-between;
padding: var(--space-smaller) 0 var(--space-smaller) var(--space-small);
margin: 0;
.info-wrap {
display: flex;
align-items: center;
}
.info-icon {
margin-top: -1px;
}
.auto-offline--switch {
margin: -1px var(--space-micro) 0;
}
.auto-offline--text {
margin: 0 var(--space-smaller);
font-size: var(--font-size-mini);
font-weight: var(--font-weight-medium);
color: var(--s-700);
}
}
</style>

View file

@ -73,13 +73,14 @@ export default {
computed: {
...mapGetters({
currentUser: 'getCurrentUser',
globalConfig: 'globalConfig/get',
isACustomBrandedInstance: 'globalConfig/isACustomBrandedInstance',
isOnChatwootCloud: 'globalConfig/isOnChatwootCloud',
inboxes: 'inboxes/getInboxes',
accountId: 'getCurrentAccountId',
currentRole: 'getCurrentRole',
currentUser: 'getCurrentUser',
globalConfig: 'globalConfig/get',
inboxes: 'inboxes/getInboxes',
isACustomBrandedInstance: 'globalConfig/isACustomBrandedInstance',
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
isOnChatwootCloud: 'globalConfig/isOnChatwootCloud',
labels: 'labels/getLabelsOnSidebar',
teams: 'teams/getMyTeams',
}),
@ -108,9 +109,21 @@ export default {
},
primaryMenuItems() {
const menuItems = this.sideMenuConfig.primaryMenu;
return menuItems.filter(menuItem =>
menuItem.roles.includes(this.currentRole)
return menuItems.filter(menuItem => {
const isAvailableForTheUser = menuItem.roles.includes(this.currentRole);
if (!isAvailableForTheUser) {
return false;
}
if (menuItem.featureFlag) {
return this.isFeatureEnabledonAccount(
this.accountId,
menuItem.featureFlag
);
}
return true;
});
},
activeSecondaryMenu() {
const { secondaryMenu } = this.sideMenuConfig;
@ -285,8 +298,6 @@ export default {
}
.secondary-menu .nested.vertical.menu {
overflow-y: auto;
height: 100%;
margin-left: var(--space-small);
}
</style>

View file

@ -16,6 +16,8 @@ const conversations = accountId => ({
'conversation_through_mentions',
'folder_conversations',
'conversations_through_folders',
'conversation_unattended',
'conversation_through_unattended',
],
menuItems: [
{
@ -33,6 +35,13 @@ const conversations = accountId => ({
toState: frontendURL(`accounts/${accountId}/mentions/conversations`),
toStateName: 'conversation_mentions',
},
{
icon: 'mail-unread',
label: 'UNATTENDED_CONVERSATIONS',
key: 'conversation_unattended',
toState: frontendURL(`accounts/${accountId}/unattended/conversations`),
toStateName: 'conversation_unattended',
},
],
});

View file

@ -1,3 +1,4 @@
import { FEATURE_FLAGS } from '../../../../featureFlags';
import { frontendURL } from '../../../../helper/URLHelper';
const primaryMenuItems = accountId => [
@ -13,6 +14,7 @@ const primaryMenuItems = accountId => [
icon: 'book-contacts',
key: 'contacts',
label: 'CONTACTS',
featureFlag: FEATURE_FLAGS.CRM,
toState: frontendURL(`accounts/${accountId}/contacts`),
toStateName: 'contacts_dashboard',
roles: ['administrator', 'agent'],
@ -21,6 +23,7 @@ const primaryMenuItems = accountId => [
icon: 'arrow-trending-lines',
key: 'reports',
label: 'REPORTS',
featureFlag: FEATURE_FLAGS.REPORTS,
toState: frontendURL(`accounts/${accountId}/reports`),
toStateName: 'settings_account_reports',
roles: ['administrator'],
@ -29,10 +32,20 @@ const primaryMenuItems = accountId => [
icon: 'megaphone',
key: 'campaigns',
label: 'CAMPAIGNS',
featureFlag: FEATURE_FLAGS.CAMPAIGNS,
toState: frontendURL(`accounts/${accountId}/campaigns`),
toStateName: 'settings_account_campaigns',
roles: ['administrator'],
},
{
icon: 'library',
key: 'helpcenter',
label: 'HELP_CENTER.TITLE',
featureFlag: FEATURE_FLAGS.HELP_CENTER,
toState: frontendURL(`accounts/${accountId}/portals`),
toStateName: 'default_portal_articles',
roles: ['administrator'],
},
{
icon: 'settings',
key: 'settings',

View file

@ -1,45 +1,58 @@
import { FEATURE_FLAGS } from '../../../../featureFlags';
import { frontendURL } from '../../../../helper/URLHelper';
const settings = accountId => ({
parentNav: 'settings',
routes: [
'agent_bots',
'agent_list',
'canned_list',
'labels_list',
'settings_inbox',
'attributes_list',
'settings_inbox_new',
'settings_inbox_list',
'settings_inbox_show',
'settings_inboxes_page_channel',
'settings_inboxes_add_agents',
'settings_inbox_finish',
'settings_integrations',
'settings_integrations_webhook',
'settings_integrations_integration',
'settings_applications',
'settings_integrations_dashboard_apps',
'settings_applications_webhook',
'settings_applications_integration',
'general_settings',
'automation_list',
'billing_settings_index',
'canned_list',
'general_settings_index',
'general_settings',
'labels_list',
'macros_edit',
'macros_new',
'macros_wrapper',
'settings_applications_integration',
'settings_applications_webhook',
'settings_applications',
'settings_inbox_finish',
'settings_inbox_list',
'settings_inbox_new',
'settings_inbox_show',
'settings_inbox',
'settings_inboxes_add_agents',
'settings_inboxes_page_channel',
'settings_integrations_dashboard_apps',
'settings_integrations_integration',
'settings_integrations_webhook',
'settings_integrations',
'settings_teams_add_agents',
'settings_teams_edit_finish',
'settings_teams_edit_members',
'settings_teams_edit',
'settings_teams_finish',
'settings_teams_list',
'settings_teams_new',
'settings_teams_add_agents',
'settings_teams_finish',
'settings_teams_edit',
'settings_teams_edit_members',
'settings_teams_edit_finish',
'billing_settings_index',
'automation_list',
],
menuItems: [
{
icon: 'briefcase',
label: 'ACCOUNT_SETTINGS',
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/general`),
toStateName: 'general_settings_index',
},
{
icon: 'people',
label: 'AGENTS',
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/agents/list`),
toStateName: 'agent_list',
featureFlag: FEATURE_FLAGS.AGENT_MANAGEMENT,
},
{
icon: 'people-team',
@ -47,6 +60,7 @@ const settings = accountId => ({
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/teams/list`),
toStateName: 'settings_teams_list',
featureFlag: FEATURE_FLAGS.TEAM_MANAGEMENT,
},
{
icon: 'mail-inbox-all',
@ -54,6 +68,7 @@ const settings = accountId => ({
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/inboxes/list`),
toStateName: 'settings_inbox_list',
featureFlag: FEATURE_FLAGS.INBOX_MANAGEMENT,
},
{
icon: 'tag',
@ -61,6 +76,7 @@ const settings = accountId => ({
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/labels/list`),
toStateName: 'labels_list',
featureFlag: FEATURE_FLAGS.LABELS,
},
{
icon: 'code',
@ -70,13 +86,35 @@ const settings = accountId => ({
`accounts/${accountId}/settings/custom-attributes/list`
),
toStateName: 'attributes_list',
featureFlag: FEATURE_FLAGS.CUSTOM_ATTRIBUTES,
},
{
icon: 'automation',
label: 'AUTOMATION',
beta: true,
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/automation/list`),
toStateName: 'automation_list',
featureFlag: FEATURE_FLAGS.AUTOMATIONS,
},
{
icon: 'bot',
label: 'AGENT_BOTS',
beta: true,
hasSubMenu: false,
globalConfigFlag: 'csmlEditorHost',
toState: frontendURL(`accounts/${accountId}/settings/agent-bots`),
toStateName: 'agent_bots',
featureFlag: FEATURE_FLAGS.AGENT_BOTS,
},
{
icon: 'flash-settings',
label: 'MACROS',
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/macros`),
toStateName: 'macros_wrapper',
beta: true,
featureFlag: FEATURE_FLAGS.MACROS,
},
{
icon: 'chat-multiple',
@ -86,6 +124,7 @@ const settings = accountId => ({
`accounts/${accountId}/settings/canned-response/list`
),
toStateName: 'canned_list',
featureFlag: FEATURE_FLAGS.CANNED_RESPONSES,
},
{
icon: 'flash-on',
@ -93,6 +132,7 @@ const settings = accountId => ({
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/integrations`),
toStateName: 'settings_integrations',
featureFlag: FEATURE_FLAGS.INTEGRATIONS,
},
{
icon: 'star-emphasis',
@ -100,6 +140,7 @@ const settings = accountId => ({
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/applications`),
toStateName: 'settings_applications',
featureFlag: FEATURE_FLAGS.INTEGRATIONS,
},
{
icon: 'credit-card-person',
@ -109,13 +150,6 @@ const settings = accountId => ({
toStateName: 'billing_settings_index',
showOnlyOnCloud: true,
},
{
icon: 'settings',
label: 'ACCOUNT_SETTINGS',
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/general`),
toStateName: 'general_settings_index',
},
],
});

View file

@ -8,25 +8,33 @@
:header-title="$t('SIDEBAR_ITEMS.CHANGE_ACCOUNTS')"
:header-content="$t('SIDEBAR_ITEMS.SELECTOR_SUBTITLE')"
/>
<div class="account-selector--wrap">
<div
v-for="account in currentUser.accounts"
:key="account.id"
class="account-selector"
>
<a :href="`/app/accounts/${account.id}/dashboard`">
<button
class="button expanded clear link"
@click="onChangeAccount(account.id)"
>
<span class="button__content">
<label :for="account.name" class="account-details--wrap">
<div class="account--name">{{ account.name }}</div>
<div class="account--role">{{ account.role }}</div>
</label>
</span>
<fluent-icon
v-if="account.id === accountId"
v-show="account.id === accountId"
class="selected--account"
icon="checkmark-circle"
type="solid"
size="24"
/>
<label :for="account.name" class="account--details">
<div class="account--name">{{ account.name }}</div>
<div class="account--role">{{ account.role }}</div>
</label>
</a>
</button>
</div>
</div>
<div
v-if="globalConfig.createNewAccountFromDashboard"
class="modal-footer delete-item"
@ -58,5 +66,40 @@ export default {
globalConfig: 'globalConfig/get',
}),
},
methods: {
onChangeAccount(accountId) {
const accountUrl = `/app/accounts/${accountId}/dashboard`;
window.location.href = accountUrl;
},
},
};
</script>
<style lang="scss" scoped>
.account-selector--wrap {
margin-top: var(--space-normal);
}
.account-selector {
padding-top: 0;
padding-bottom: 0;
.button {
display: flex;
justify-content: space-between;
padding: var(--space-one) var(--space-normal);
.account-details--wrap {
text-align: left;
.account--name {
cursor: pointer;
font-size: var(--font-size-medium);
font-weight: var(--font-weight-medium);
line-height: 1;
}
.account--role {
cursor: pointer;
font-size: var(--font-size-mini);
text-transform: capitalize;
}
}
}
}
</style>

View file

@ -61,6 +61,24 @@
</a>
</router-link>
</woot-dropdown-item>
<woot-dropdown-item v-if="currentUser.type === 'SuperAdmin'">
<a
href="/super_admin"
class="button small clear secondary"
target="_blank"
rel="noopener nofollow noreferrer"
@click="$emit('close')"
>
<fluent-icon
icon="content-settings"
size="14"
class="icon icon--font"
/>
<span class="button__content">
{{ $t('SIDEBAR_ITEMS.SUPER_ADMIN_CONSOLE') }}
</span>
</a>
</woot-dropdown-item>
<woot-dropdown-item>
<woot-button
variant="clear"
@ -135,7 +153,7 @@ export default {
.dropdown-pane {
left: var(--space-slab);
bottom: var(--space-larger);
min-width: 16.8rem;
z-index: var(--z-index-much-higher);
min-width: 22rem;
z-index: var(--z-index-low);
}
</style>

View file

@ -20,6 +20,8 @@
import { frontendURL } from '../../../helper/URLHelper';
import SecondaryNavItem from './SecondaryNavItem.vue';
import AccountContext from './AccountContext.vue';
import { mapGetters } from 'vuex';
import { FEATURE_FLAGS } from '../../../featureFlags';
export default {
components: {
@ -61,6 +63,9 @@ export default {
},
},
computed: {
...mapGetters({
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
}),
hasSecondaryMenu() {
return this.menuConfig.menuItems && this.menuConfig.menuItems.length;
},
@ -89,7 +94,7 @@ export default {
icon: 'folder',
label: 'INBOXES',
hasSubMenu: true,
newLink: true,
newLink: this.showNewLink(FEATURE_FLAGS.INBOX_MANAGEMENT),
newLinkTag: 'NEW_INBOX',
key: 'inbox',
toState: frontendURL(`accounts/${this.accountId}/settings/inboxes/new`),
@ -117,7 +122,7 @@ export default {
icon: 'number-symbol',
label: 'LABELS',
hasSubMenu: true,
newLink: true,
newLink: this.showNewLink(FEATURE_FLAGS.TEAM_MANAGEMENT),
newLinkTag: 'NEW_LABEL',
key: 'label',
toState: frontendURL(`accounts/${this.accountId}/settings/labels`),
@ -141,7 +146,7 @@ export default {
label: 'TAGGED_WITH',
hasSubMenu: true,
key: 'label',
newLink: true,
newLink: this.showNewLink(FEATURE_FLAGS.TEAM_MANAGEMENT),
newLinkTag: 'NEW_LABEL',
toState: frontendURL(`accounts/${this.accountId}/settings/labels`),
toStateName: 'labels_list',
@ -163,7 +168,7 @@ export default {
icon: 'people-team',
label: 'TEAMS',
hasSubMenu: true,
newLink: true,
newLink: this.showNewLink(FEATURE_FLAGS.TEAM_MANAGEMENT),
newLinkTag: 'NEW_TEAM',
key: 'team',
toState: frontendURL(`accounts/${this.accountId}/settings/teams/new`),
@ -238,6 +243,9 @@ export default {
toggleAccountModal() {
this.$emit('toggle-accounts');
},
showNewLink(featureFlag) {
return this.isFeatureEnabledonAccount(this.accountId, featureFlag);
},
},
};
</script>
@ -245,20 +253,15 @@ export default {
@import '~dashboard/assets/scss/woot';
.secondary-menu {
display: flex;
flex-direction: column;
background: var(--white);
border-right: 1px solid var(--s-50);
height: 100%;
width: 20rem;
flex-shrink: 0;
overflow-y: hidden;
@include breakpoint(xlarge down) {
position: absolute;
}
@include breakpoint(xlarge up) {
position: unset;
}
&:hover {
overflow-y: hidden;
@ -267,7 +270,6 @@ export default {
.menu {
padding: var(--space-small);
overflow-y: auto;
height: 94%;
}
}
</style>

View file

@ -26,7 +26,7 @@
:class="{ 'text-truncate': shouldTruncate }"
>
{{ label }}
<span v-if="isHelpCenterSidebar && childItemCount" class="count-view">
<span v-if="showChildCount" class="count-view">
{{ childItemCount }}
</span>
</span>
@ -76,7 +76,7 @@ export default {
type: String,
default: '',
},
isHelpCenterSidebar: {
showChildCount: {
type: Boolean,
default: false,
},
@ -112,6 +112,7 @@ $label-badge-size: var(--space-slab);
padding: var(--space-smaller) var(--space-smaller);
margin: var(--space-smaller) 0;
text-align: left;
line-height: 1.2;
&:hover {
background: var(--s-25);
@ -127,11 +128,14 @@ $label-badge-size: var(--space-slab);
color: var(--w-500);
border-color: var(--w-25);
}
&.is-active .count-view {
background: var(--w-75);
color: var(--w-500);
}
}
.menu-label {
flex-grow: 1;
line-height: var(--space-two);
}
.inbox-icon {
@ -175,10 +179,6 @@ $label-badge-size: var(--space-slab);
font-weight: var(--font-weight-bold);
margin-left: var(--space-smaller);
padding: var(--space-zero) var(--space-smaller);
&.is-active {
background: var(--w-50);
color: var(--w-500);
}
line-height: var(--font-size-small);
}
</style>

View file

@ -1,19 +1,18 @@
<template>
<li class="sidebar-item">
<li v-show="isMenuItemVisible" class="sidebar-item">
<div v-if="hasSubMenu" class="secondary-menu--wrap">
<span class="secondary-menu--header fs-small">
{{ $t(`SIDEBAR.${menuItem.label}`) }}
</span>
<div v-if="isHelpCenterSidebar" class="submenu-icons">
<div v-if="menuItem.showNewButton" class="submenu-icons">
<woot-button
size="tiny"
variant="clear"
color-scheme="secondary"
icon="add"
class="submenu-icon"
@click="onClickOpen"
>
<fluent-icon icon="add" size="16" />
</woot-button>
/>
</div>
</div>
<router-link
@ -28,15 +27,11 @@
size="14"
/>
{{ $t(`SIDEBAR.${menuItem.label}`) }}
<span
v-if="isHelpCenterSidebar"
class="count-view"
:class="computedClass"
>
<span v-if="showChildCount(menuItem.count)" class="count-view">
{{ `${menuItem.count}` }}
</span>
<span
v-if="menuItem.label === 'AUTOMATION'"
v-if="menuItem.beta"
data-view-component="true"
label="Beta"
class="beta"
@ -55,7 +50,7 @@
:should-truncate="child.truncateLabel"
:icon="computedInboxClass(child)"
:warning-icon="computedInboxErrorClass(child)"
:is-help-center-sidebar="isHelpCenterSidebar"
:show-child-count="showChildCount(child.count)"
:child-item-count="child.count"
/>
<router-link
@ -64,10 +59,10 @@
:to="menuItem.toState"
custom
>
<li>
<li class="menu-item--new">
<a
:href="href"
class="button small clear menu-item--new secondary"
class="button small link clear secondary"
:class="{ 'is-active': isActive }"
@click="e => newLinkClick(e, navigate)"
>
@ -78,9 +73,6 @@
</a>
</li>
</router-link>
<p v-if="isHelpCenterSidebar && isCategoryEmpty" class="empty-text">
{{ $t('SIDEBAR.HELP_CENTER.CATEGORY_EMPTY_MESSAGE') }}
</p>
</ul>
</li>
</template>
@ -95,6 +87,10 @@ import {
} from 'dashboard/helper/inbox';
import SecondaryChildNavItem from './SecondaryChildNavItem';
import {
isOnMentionsView,
isOnUnattendedView,
} from '../../../store/modules/conversations/helpers/actionHelpers';
export default {
components: { SecondaryChildNavItem },
@ -104,33 +100,54 @@ export default {
type: Object,
default: () => ({}),
},
isHelpCenterSidebar: {
type: Boolean,
default: false,
},
isCategoryEmpty: {
type: Boolean,
default: false,
},
},
computed: {
...mapGetters({ activeInbox: 'getSelectedInbox' }),
...mapGetters({
activeInbox: 'getSelectedInbox',
accountId: 'getCurrentAccountId',
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
globalConfig: 'globalConfig/get',
}),
hasSubMenu() {
return !!this.menuItem.children;
},
isInboxConversation() {
isMenuItemVisible() {
if (this.menuItem.globalConfigFlag) {
return !!this.globalConfig[this.menuItem.globalConfigFlag];
}
if (this.menuItem.featureFlag) {
return this.isFeatureEnabledonAccount(
this.accountId,
this.menuItem.featureFlag
);
}
return true;
},
isAllConversations() {
return (
this.$store.state.route.name === 'inbox_conversation' &&
this.menuItem.toStateName === 'home'
);
},
isMentions() {
return (
isOnMentionsView({ route: this.$route }) &&
this.menuItem.toStateName === 'conversation_mentions'
);
},
isUnattended() {
return (
isOnUnattendedView({ route: this.$route }) &&
this.menuItem.toStateName === 'conversation_unattended'
);
},
isTeamsSettings() {
return (
this.$store.state.route.name === 'settings_teams_edit' &&
this.menuItem.toStateName === 'settings_teams_list'
);
},
isInboxsSettings() {
isInboxSettings() {
return (
this.$store.state.route.name === 'settings_inbox_show' &&
this.menuItem.toStateName === 'settings_inbox_list'
@ -148,19 +165,25 @@ export default {
this.menuItem.toStateName === 'settings_applications'
);
},
isArticlesView() {
return this.$store.state.route.name === this.menuItem.toStateName;
isCurrentRoute() {
return this.$store.state.route.name.includes(this.menuItem.toStateName);
},
computedClass() {
// If active Inbox is present
// donot highlight conversations
// If active inbox is present, do not highlight conversations
if (this.activeInbox) return ' ';
if (
this.isAllConversations ||
this.isMentions ||
this.isUnattended ||
this.isCurrentRoute
) {
return 'is-active';
}
if (this.hasSubMenu) {
if (
this.isInboxConversation ||
this.isTeamsSettings ||
this.isInboxsSettings ||
this.isInboxSettings ||
this.isIntegrationsSettings ||
this.isApplicationsSettings
) {
@ -168,12 +191,7 @@ export default {
}
return ' ';
}
if (this.isHelpCenterSidebar) {
if (this.isArticlesView) {
return 'is-active';
}
return ' ';
}
return '';
},
},
@ -204,11 +222,14 @@ export default {
}
},
showItem(item) {
return this.isAdmin && item.newLink !== undefined;
return this.isAdmin && !!item.newLink;
},
onClickOpen() {
this.$emit('open');
},
showChildCount(count) {
return Number.isInteger(count);
},
},
};
</script>
@ -264,6 +285,11 @@ export default {
color: var(--w-500);
border-color: var(--w-25);
}
&.is-active .count-view {
background: var(--w-75);
color: var(--w-600);
}
}
.secondary-menu--icon {
@ -293,22 +319,19 @@ export default {
top: -1px;
}
.sidebar-item .button.menu-item--new {
display: inline-flex;
height: var(--space-medium);
margin: var(--space-smaller) 0;
padding: var(--space-smaller);
color: var(--s-500);
.sidebar-item .menu-item--new {
padding: var(--space-small) 0;
&:hover {
color: var(--w-500);
.button {
display: inline-flex;
color: var(--s-500);
}
}
.beta {
padding-right: var(--space-smaller) !important;
padding-left: var(--space-smaller) !important;
margin-left: var(--space-half) !important;
margin-left: var(--space-smaller) !important;
display: inline-block;
font-size: var(--font-size-micro);
font-weight: var(--font-weight-medium);
@ -327,11 +350,6 @@ export default {
font-weight: var(--font-weight-bold);
margin-left: var(--space-smaller);
padding: var(--space-zero) var(--space-smaller);
&.is-active {
background: var(--w-50);
color: var(--w-500);
}
}
.submenu-icons {
@ -343,10 +361,4 @@ export default {
margin-left: var(--space-small);
}
}
.empty-text {
color: var(--s-500);
font-size: var(--font-size-small);
margin: var(--space-smaller);
}
</style>

View file

@ -1,10 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SidemenuIcon matches snapshot 1`] = `
<button>
<fluent-icon
class="hamburger--menu"
<woot-button
class="toggle-sidebar"
color-scheme="secondary"
icon="list"
size="small"
variant="clear"
/>
</button>
`;

View file

@ -4,7 +4,7 @@
<fluent-icon :icon="icon" size="12" class="label--icon" />
</span>
<span
v-if="variant === 'smooth'"
v-if="variant === 'smooth' && title && !icon"
:style="{ background: color }"
class="label-color-dot"
/>
@ -117,14 +117,16 @@ export default {
height: var(--space-medium);
&.small {
font-size: var(--font-size-micro);
font-size: var(--font-size-mini);
padding: var(--space-micro) var(--space-smaller);
line-height: 1.2;
letter-spacing: 0.15px;
height: var(--space-two);
}
.label--icon {
cursor: pointer;
}
.label-color-dot {
margin-right: var(--space-smaller);
}
@ -199,8 +201,8 @@ export default {
&.smooth {
background: transparent;
border: 1px solid var(--s-75);
color: var(--s-800);
border: 1px solid var(--s-100);
color: var(--s-700);
}
}
@ -221,14 +223,22 @@ export default {
}
.label-action--button {
margin-bottom: var(--space-minus-micro);
display: flex;
margin-right: var(--space-smaller);
}
.label-color-dot {
display: inline-block;
width: var(--space-one);
height: var(--space-one);
width: var(--space-slab);
height: var(--space-slab);
border-radius: var(--border-radius-small);
margin-right: var(--space-smaller);
box-shadow: var(--shadow-small);
}
.label.small .label-color-dot {
width: var(--space-small);
height: var(--space-small);
border-radius: var(--border-radius-small);
box-shadow: var(--shadow-small);
}
</style>

View file

@ -0,0 +1,113 @@
<template>
<div class="preview-card--wrap" :class="{ activecard: active }">
<div class="header--wrap" :class="{ active: active }">
<div class="heading-wrap text-block-title">{{ heading }}</div>
<fluent-icon
v-if="active"
icon="checkmark-circle"
type="solid"
size="24"
class="checkmark"
/>
</div>
<div class="content-wrap">
{{ content }}
</div>
<div class="image-wrap">
<img :src="src" class="image" :class="{ activeimage: active }" />
</div>
</div>
</template>
<script>
export default {
props: {
heading: {
type: String,
default: '',
},
content: {
type: String,
default: '',
},
active: {
type: Boolean,
default: false,
},
buttonText: {
type: String,
default: 'Active',
},
src: {
type: String,
default: '',
},
},
};
</script>
<style lang="scss" scoped>
.preview-card--wrap {
border-radius: var(--border-radius-normal);
border: 1px solid var(--color-border);
display: flex;
flex-direction: column;
max-height: 34rem;
max-width: 38rem;
min-width: 24rem;
.header--wrap {
background: var(--s-50);
border-bottom: 1px solid var(--color-border);
border-top-left-radius: var(--border-radius-normal);
border-top-right-radius: var(--border-radius-normal);
display: flex;
height: 4rem;
justify-content: space-between;
padding: var(--space-small);
width: 100%;
}
.active {
background: var(--w-50);
border-bottom: 1px solid var(--w-75);
}
.heading-wrap {
align-items: center;
display: flex;
font-weight: var(--font-weight-medium);
padding: var(--space-smaller);
}
.checkmark {
color: var(--w-500);
}
.content-wrap {
color: var(--s-700);
font-size: var(--font-size-mini);
line-height: 1.4;
padding: var(--space-slab) var(--space-slab) 0 var(--space-slab);
text-align: start;
}
.image-wrap {
padding: var(--space-slab);
}
.image {
border: 1px solid var(--color-border);
border-radius: var(--border-radius-normal);
}
.activeimage {
border: 1px solid var(--w-75);
}
}
.activecard {
background: var(--w-25);
border: 1px solid var(--w-300);
}
</style>

View file

@ -2,7 +2,7 @@
<button
type="button"
class="toggle-button"
:class="{ active: value }"
:class="{ active: value, small: size === 'small' }"
role="switch"
:aria-checked="value.toString()"
@click="onClick"
@ -15,6 +15,7 @@
export default {
props: {
value: { type: Boolean, default: false },
size: { type: String, default: '' },
},
methods: {
onClick() {
@ -45,6 +46,20 @@ export default {
background-color: var(--w-500);
}
&.small {
width: 22px;
height: 14px;
span {
height: var(--space-one);
width: var(--space-one);
&.active {
transform: translate(var(--space-small), var(--space-zero));
}
}
}
span {
--space-one-point-five: 1.5rem;
background-color: var(--white);

View file

@ -5,13 +5,11 @@
</template>
<script>
const ZERO = 0;
const MINUTE_IN_MILLI_SECONDS = 60000;
const HOUR_IN_MILLI_SECONDS = MINUTE_IN_MILLI_SECONDS * 60;
const DAY_IN_MILLI_SECONDS = HOUR_IN_MILLI_SECONDS * 24;
import timeMixin from 'dashboard/mixins/time';
import { differenceInMilliseconds } from 'date-fns';
export default {
name: 'TimeAgo',
@ -28,51 +26,40 @@ export default {
},
data() {
return {
timeAgo: '',
timeAgo: this.dynamicTime(this.timestamp),
timer: null,
};
},
computed: {
watch: {
timestamp() {
this.timeAgo = this.dynamicTime(this.timestamp);
},
},
mounted() {
if (this.isAutoRefreshEnabled) {
this.createTimer();
}
},
beforeDestroy() {
clearTimeout(this.timer);
},
methods: {
createTimer() {
this.timer = setTimeout(() => {
this.timeAgo = this.dynamicTime(this.timestamp);
this.createTimer();
}, this.refreshTime());
},
refreshTime() {
const timeDiff = differenceInMilliseconds(
new Date(),
new Date(this.timestamp * 1000)
);
const timeDiff = Date.now() - this.timestamp * 1000;
if (timeDiff > DAY_IN_MILLI_SECONDS) {
return DAY_IN_MILLI_SECONDS;
}
if (timeDiff > HOUR_IN_MILLI_SECONDS) {
return HOUR_IN_MILLI_SECONDS;
}
if (timeDiff > MINUTE_IN_MILLI_SECONDS) {
return MINUTE_IN_MILLI_SECONDS;
}
return ZERO;
},
},
mounted() {
this.timeAgo = this.dynamicTime(this.timestamp);
if (this.isAutoRefreshEnabled) {
this.createTimer();
}
},
beforeDestroy() {
this.clearTimer();
},
methods: {
createTimer() {
const refreshTime = this.refreshTime;
if (refreshTime > ZERO) {
this.timer = setTimeout(() => {
this.timeAgo = this.dynamicTime(this.timestamp);
this.createTimer();
}, refreshTime);
}
},
clearTimer() {
if (this.timer) {
clearTimeout(this.timer);
}
},
},
};

View file

@ -1,8 +1,5 @@
<template>
<div
class="filter"
:class="{ error: v.action_params.$dirty && v.action_params.$error }"
>
<div class="filter" :class="actionInputStyles">
<div class="filter-inputs">
<select
v-model="action_name"
@ -21,14 +18,32 @@
<div v-if="showActionInput" class="filter__answer--wrap">
<div v-if="inputType">
<div
v-if="inputType === 'multi_select'"
v-if="inputType === 'search_select'"
class="multiselect-wrap--small"
>
<multiselect
v-model="action_params"
track-by="id"
label="name"
:placeholder="'Select'"
:placeholder="$t('FORMS.MULTISELECT.SELECT')"
selected-label
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
deselect-label=""
:max-height="160"
:options="dropdownValues"
:allow-empty="false"
:option-height="104"
/>
</div>
<div
v-else-if="inputType === 'multi_select'"
class="multiselect-wrap--small"
>
<multiselect
v-model="action_params"
track-by="id"
label="name"
:placeholder="$t('FORMS.MULTISELECT.SELECT')"
:multiple="true"
selected-label
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
@ -36,6 +51,7 @@
:max-height="160"
:options="dropdownValues"
:allow-empty="false"
:option-height="104"
/>
</div>
<input
@ -60,6 +76,7 @@
</div>
</div>
<woot-button
v-if="!isMacro"
icon="dismiss"
variant="clear"
color-scheme="secondary"
@ -120,6 +137,10 @@ export default {
type: String,
default: '',
},
isMacro: {
type: Boolean,
default: false,
},
},
computed: {
action_name: {
@ -146,6 +167,12 @@ export default {
return this.actionTypes.find(action => action.key === this.action_name)
.inputType;
},
actionInputStyles() {
return {
'has-error': this.v.action_params.$dirty && this.v.action_params.$error,
'is-a-macro': this.isMacro,
};
},
},
methods: {
removeAction() {
@ -165,9 +192,21 @@ export default {
border: 1px solid var(--color-border);
border-radius: var(--border-radius-medium);
margin-bottom: var(--space-small);
&.is-a-macro {
margin-bottom: 0;
background: var(--white);
padding: var(--space-zero);
border: unset;
border-radius: unset;
}
}
.filter.error {
.no-margin-bottom {
margin-bottom: 0;
}
.filter.has-error {
background: var(--r-50);
}
@ -240,6 +279,6 @@ export default {
margin-bottom: var(--space-zero);
}
.action-message {
margin: var(--space-small) 0 0;
margin: var(--space-small) var(--space-zero) var(--space-zero);
}
</style>

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