Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
692c747ec2 | ||
|
|
09ceefe053 |
@@ -7,7 +7,7 @@ defaults: &defaults
|
||||
working_directory: ~/build
|
||||
docker:
|
||||
# specify the version you desire here
|
||||
- image: cimg/ruby:3.3.3-browsers
|
||||
- image: cimg/ruby:3.2.2-browsers
|
||||
|
||||
# Specify service dependencies here if necessary
|
||||
# CircleCI maintains a library of pre-built images
|
||||
@@ -130,7 +130,10 @@ jobs:
|
||||
command: |
|
||||
mkdir -p ~/tmp/test-results/frontend_specs
|
||||
~/tmp/cc-test-reporter before-build
|
||||
yarn test:coverage
|
||||
TESTFILES=$(circleci tests glob **/specs/*.spec.js | circleci tests split --split-by=timings)
|
||||
yarn test:coverage --profile 10 \
|
||||
--out ~/tmp/test-results/yarn.xml \
|
||||
-- ${TESTFILES}
|
||||
- run:
|
||||
name: Code Climate Test Coverage
|
||||
command: |
|
||||
|
||||
@@ -12,7 +12,7 @@ services:
|
||||
args:
|
||||
VARIANT: "ubuntu-22.04"
|
||||
NODE_VERSION: "20.9.0"
|
||||
RUBY_VERSION: "3.3.3"
|
||||
RUBY_VERSION: "3.2.2"
|
||||
# On Linux, you may need to update USER_UID and USER_GID below if not your local UID is not 1000.
|
||||
USER_UID: "1000"
|
||||
USER_GID: "1000"
|
||||
@@ -25,7 +25,7 @@ services:
|
||||
args:
|
||||
VARIANT: "ubuntu-22.04"
|
||||
NODE_VERSION: "20.9.0"
|
||||
RUBY_VERSION: "3.3.3"
|
||||
RUBY_VERSION: "3.2.2"
|
||||
# On Linux, you may need to update USER_UID and USER_GID below if not your local UID is not 1000.
|
||||
USER_UID: "1000"
|
||||
USER_GID: "1000"
|
||||
|
||||
+11
-10
@@ -1,9 +1,4 @@
|
||||
# Learn about the various environment variables at
|
||||
# https://www.chatwoot.com/docs/self-hosted/configuration/environment-variables/#rails-production-variables
|
||||
|
||||
# Used to verify the integrity of signed cookies. so ensure a secure value is set
|
||||
# SECRET_KEY_BASE should be alphanumeric. Avoid special characters or symbols.
|
||||
# Use `rake secret` to generate this variable
|
||||
SECRET_KEY_BASE=replace_with_lengthy_secure_hex
|
||||
|
||||
# Replace with the URL you are planning to use for your app
|
||||
@@ -28,9 +23,6 @@ FORCE_SSL=false
|
||||
ENABLE_ACCOUNT_SIGNUP=false
|
||||
|
||||
# Redis config
|
||||
# specify the configs via single URL or individual variables
|
||||
# ref: https://www.iana.org/assignments/uri-schemes/prov/redis
|
||||
# You can also use the following format for the URL: redis://:password@host:port/db_number
|
||||
REDIS_URL=redis://redis:6379
|
||||
# If you are using docker-compose, set this variable's value to be any string,
|
||||
# which will be the password for the redis service running inside the docker-compose
|
||||
@@ -85,8 +77,6 @@ SMTP_OPENSSL_VERIFY_MODE=peer
|
||||
# Comment out the following environment variables if required by your SMTP server
|
||||
# SMTP_TLS=
|
||||
# SMTP_SSL=
|
||||
# SMTP_OPEN_TIMEOUT
|
||||
# SMTP_READ_TIMEOUT
|
||||
|
||||
# Mail Incoming
|
||||
# This is the domain set for the reply emails when conversation continuity is enabled
|
||||
@@ -190,6 +180,14 @@ ANDROID_SHA256_CERT_FINGERPRINT=AC:73:8E:DE:EB:56:EA:CC:10:87:02:A7:65:37:7B:38:
|
||||
## Sentry
|
||||
# SENTRY_DSN=
|
||||
|
||||
## LogRocket
|
||||
# LOG_ROCKET_PROJECT_ID=xxxxx/some-project
|
||||
|
||||
# MICROSOFT CLARITY
|
||||
# MS_CLARITY_TOKEN=xxxxxxxxx
|
||||
|
||||
# GOOGLE_TAG_MANAGER
|
||||
# GOOGLE_TAG = GTM-XXXXXXX
|
||||
|
||||
## Scout
|
||||
## https://scoutapm.com/docs/ruby/configuration
|
||||
@@ -253,6 +251,9 @@ AZURE_APP_SECRET=
|
||||
## OpenAI key
|
||||
# OPENAI_API_KEY=
|
||||
|
||||
# Sentiment analysis model file path
|
||||
SENTIMENT_FILE_PATH=
|
||||
|
||||
# Housekeeping/Performance related configurations
|
||||
# Set to true if you want to remove stale contact inboxes
|
||||
# contact_inboxes with no conversation older than 90 days will be removed
|
||||
|
||||
+1
-1
@@ -65,10 +65,10 @@ module.exports = {
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
jest: true,
|
||||
node: true,
|
||||
},
|
||||
globals: {
|
||||
bus: true,
|
||||
vi: true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,2 +1,7 @@
|
||||
## All javascript files should be reviewed by pranav before merging
|
||||
*.js @pranavrajs
|
||||
*.vue @pranavrajs
|
||||
|
||||
|
||||
## All enterprise related files should be reviewed by sojan before merging
|
||||
/enterprise/* @sojan-official
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
3.3.3
|
||||
3.2.2
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
ruby '3.3.3'
|
||||
ruby '3.2.2'
|
||||
|
||||
##-- base gems for rails --##
|
||||
gem 'rack-cors', '2.0.0', require: 'rack/cors'
|
||||
gem 'rails', '~> 7.0.8.4'
|
||||
gem 'rails', '~> 7.0.8.1'
|
||||
# Reduces boot times through caching; required in config/boot.rb
|
||||
gem 'bootsnap', require: false
|
||||
|
||||
@@ -61,7 +61,7 @@ gem 'redis-namespace'
|
||||
gem 'activerecord-import'
|
||||
|
||||
##--- gems for server & infra configuration ---##
|
||||
gem 'dotenv-rails', '>= 3.0.0'
|
||||
gem 'dotenv-rails'
|
||||
gem 'foreman'
|
||||
gem 'puma'
|
||||
gem 'webpacker'
|
||||
@@ -69,15 +69,15 @@ gem 'webpacker'
|
||||
gem 'barnes'
|
||||
|
||||
##--- gems for authentication & authorization ---##
|
||||
gem 'devise', '>= 4.9.4'
|
||||
gem 'devise', '>= 4.9.3'
|
||||
gem 'devise-secure_password', git: 'https://github.com/chatwoot/devise-secure_password', branch: 'chatwoot'
|
||||
gem 'devise_token_auth', '>= 1.2.3'
|
||||
gem 'devise_token_auth'
|
||||
# authorization
|
||||
gem 'jwt'
|
||||
gem 'pundit'
|
||||
# super admin
|
||||
gem 'administrate', '>= 0.20.1'
|
||||
gem 'administrate-field-active_storage', '>= 1.0.3'
|
||||
gem 'administrate-field-active_storage', '>= 1.0.1'
|
||||
gem 'administrate-field-belongs_to_search', '>= 0.9.0'
|
||||
|
||||
##--- gems for pubsub service ---##
|
||||
@@ -111,18 +111,18 @@ gem 'elastic-apm', require: false
|
||||
gem 'newrelic_rpm', require: false
|
||||
gem 'newrelic-sidekiq-metrics', '>= 1.6.2', require: false
|
||||
gem 'scout_apm', require: false
|
||||
gem 'sentry-rails', '>= 5.18.1', require: false
|
||||
gem 'sentry-rails', '>= 5.14.0', require: false
|
||||
gem 'sentry-ruby', require: false
|
||||
gem 'sentry-sidekiq', '>= 5.18.1', require: false
|
||||
gem 'sentry-sidekiq', '>= 5.14.0', require: false
|
||||
|
||||
##-- background job processing --##
|
||||
gem 'sidekiq', '>= 7.3.0'
|
||||
gem 'sidekiq', '>= 7.2.1'
|
||||
# We want cron jobs
|
||||
gem 'sidekiq-cron', '>= 1.12.0'
|
||||
|
||||
##-- Push notification service --##
|
||||
gem 'fcm'
|
||||
gem 'web-push', '>= 3.0.1'
|
||||
gem 'web-push'
|
||||
|
||||
##-- geocoding / parse location from ip --##
|
||||
# http://www.rubygeocoder.com/
|
||||
@@ -165,8 +165,8 @@ gem 'audited', '~> 5.4', '>= 5.4.1'
|
||||
|
||||
# need for google auth
|
||||
gem 'omniauth', '>= 2.1.2'
|
||||
gem 'omniauth-google-oauth2', '>= 1.1.2'
|
||||
gem 'omniauth-rails_csrf_protection', '~> 1.0', '>= 1.0.2'
|
||||
gem 'omniauth-google-oauth2'
|
||||
gem 'omniauth-rails_csrf_protection', '~> 1.0'
|
||||
|
||||
## Gems for reponse bot
|
||||
# adds cosine similarity to postgres using vector extension
|
||||
@@ -175,6 +175,9 @@ gem 'pgvector'
|
||||
# Convert Website HTML to Markdown
|
||||
gem 'reverse_markdown'
|
||||
|
||||
# Sentiment analysis
|
||||
gem 'informers'
|
||||
|
||||
### Gems required only in specific deployment environments ###
|
||||
##############################################################
|
||||
|
||||
@@ -200,7 +203,7 @@ group :development do
|
||||
gem 'rack-mini-profiler', '>= 3.2.0', require: false
|
||||
gem 'stackprof'
|
||||
# Should install the associated chrome extension to view query logs
|
||||
gem 'meta_request', '>= 0.8.0'
|
||||
gem 'meta_request'
|
||||
end
|
||||
|
||||
group :test do
|
||||
@@ -228,7 +231,7 @@ group :development, :test do
|
||||
gem 'mock_redis'
|
||||
gem 'pry-rails'
|
||||
gem 'rspec_junit_formatter'
|
||||
gem 'rspec-rails', '>= 6.1.3'
|
||||
gem 'rspec-rails'
|
||||
gem 'rubocop', require: false
|
||||
gem 'rubocop-performance', require: false
|
||||
gem 'rubocop-rails', require: false
|
||||
|
||||
+181
-184
@@ -33,78 +33,78 @@ GIT
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (7.0.8.4)
|
||||
actionpack (= 7.0.8.4)
|
||||
activesupport (= 7.0.8.4)
|
||||
actioncable (7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (7.0.8.4)
|
||||
actionpack (= 7.0.8.4)
|
||||
activejob (= 7.0.8.4)
|
||||
activerecord (= 7.0.8.4)
|
||||
activestorage (= 7.0.8.4)
|
||||
activesupport (= 7.0.8.4)
|
||||
actionmailbox (7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
activejob (= 7.0.8.1)
|
||||
activerecord (= 7.0.8.1)
|
||||
activestorage (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
mail (>= 2.7.1)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
actionmailer (7.0.8.4)
|
||||
actionpack (= 7.0.8.4)
|
||||
actionview (= 7.0.8.4)
|
||||
activejob (= 7.0.8.4)
|
||||
activesupport (= 7.0.8.4)
|
||||
actionmailer (7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
actionview (= 7.0.8.1)
|
||||
activejob (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (7.0.8.4)
|
||||
actionview (= 7.0.8.4)
|
||||
activesupport (= 7.0.8.4)
|
||||
actionpack (7.0.8.1)
|
||||
actionview (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
rack (~> 2.0, >= 2.2.4)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (7.0.8.4)
|
||||
actionpack (= 7.0.8.4)
|
||||
activerecord (= 7.0.8.4)
|
||||
activestorage (= 7.0.8.4)
|
||||
activesupport (= 7.0.8.4)
|
||||
actiontext (7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
activerecord (= 7.0.8.1)
|
||||
activestorage (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (7.0.8.4)
|
||||
activesupport (= 7.0.8.4)
|
||||
actionview (7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||
active_record_query_trace (1.8)
|
||||
activejob (7.0.8.4)
|
||||
activesupport (= 7.0.8.4)
|
||||
activejob (7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (7.0.8.4)
|
||||
activesupport (= 7.0.8.4)
|
||||
activerecord (7.0.8.4)
|
||||
activemodel (= 7.0.8.4)
|
||||
activesupport (= 7.0.8.4)
|
||||
activemodel (7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
activerecord (7.0.8.1)
|
||||
activemodel (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
activerecord-import (1.4.1)
|
||||
activerecord (>= 4.2)
|
||||
activestorage (7.0.8.4)
|
||||
actionpack (= 7.0.8.4)
|
||||
activejob (= 7.0.8.4)
|
||||
activerecord (= 7.0.8.4)
|
||||
activesupport (= 7.0.8.4)
|
||||
activestorage (7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
activejob (= 7.0.8.1)
|
||||
activerecord (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
marcel (~> 1.0)
|
||||
mini_mime (>= 1.1.0)
|
||||
activesupport (7.0.8.4)
|
||||
activesupport (7.0.8.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
tzinfo (~> 2.0)
|
||||
acts-as-taggable-on (9.0.1)
|
||||
activerecord (>= 6.0, < 7.1)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
addressable (2.8.4)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
administrate (0.20.1)
|
||||
actionpack (>= 6.0, < 8.0)
|
||||
actionview (>= 6.0, < 8.0)
|
||||
@@ -113,7 +113,7 @@ GEM
|
||||
kaminari (~> 1.2.2)
|
||||
sassc-rails (~> 2.1)
|
||||
selectize-rails (~> 0.6)
|
||||
administrate-field-active_storage (1.0.3)
|
||||
administrate-field-active_storage (1.0.1)
|
||||
administrate (>= 0.2.2)
|
||||
rails (>= 7.0)
|
||||
administrate-field-belongs_to_search (0.9.0)
|
||||
@@ -148,15 +148,15 @@ GEM
|
||||
barnes (0.0.9)
|
||||
multi_json (~> 1)
|
||||
statsd-ruby (~> 1.1)
|
||||
base64 (0.2.0)
|
||||
bcrypt (3.1.20)
|
||||
bigdecimal (3.1.8)
|
||||
base64 (0.1.1)
|
||||
bcrypt (3.1.19)
|
||||
bindex (0.8.1)
|
||||
blingfire (0.1.8)
|
||||
bootsnap (1.16.0)
|
||||
msgpack (~> 1.2)
|
||||
brakeman (5.4.1)
|
||||
browser (5.3.1)
|
||||
builder (3.3.0)
|
||||
builder (3.2.4)
|
||||
bullet (7.0.7)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.11)
|
||||
@@ -169,15 +169,12 @@ GEM
|
||||
climate_control (1.2.0)
|
||||
coderay (1.1.3)
|
||||
commonmarker (0.23.10)
|
||||
concurrent-ruby (1.3.3)
|
||||
concurrent-ruby (1.2.3)
|
||||
connection_pool (2.4.1)
|
||||
crack (1.0.0)
|
||||
bigdecimal
|
||||
crack (0.4.5)
|
||||
rexml
|
||||
crass (1.0.6)
|
||||
csv (3.3.0)
|
||||
csv-safe (3.3.1)
|
||||
csv (~> 3.0)
|
||||
csv-safe (3.2.1)
|
||||
cypress-on-rails (1.16.0)
|
||||
rack
|
||||
database_cleaner (2.0.2)
|
||||
@@ -186,40 +183,37 @@ GEM
|
||||
activerecord (>= 5.a)
|
||||
database_cleaner-core (~> 2.0.0)
|
||||
database_cleaner-core (2.0.1)
|
||||
datadog-ci (0.8.3)
|
||||
msgpack
|
||||
date (3.3.4)
|
||||
ddtrace (1.23.2)
|
||||
datadog-ci (~> 0.8.1)
|
||||
debase-ruby_core_source (= 3.3.1)
|
||||
libdatadog (~> 7.0.0.1.0)
|
||||
libddwaf (~> 1.14.0.0.0)
|
||||
ddtrace (1.11.1)
|
||||
debase-ruby_core_source (>= 0.10.16, <= 3.2.0)
|
||||
libdatadog (~> 2.0.0.1.0)
|
||||
libddwaf (~> 1.8.2.0.0)
|
||||
msgpack
|
||||
debase-ruby_core_source (3.3.1)
|
||||
debase-ruby_core_source (3.2.0)
|
||||
debug (1.8.0)
|
||||
irb (>= 1.5.0)
|
||||
reline (>= 0.3.1)
|
||||
declarative (0.0.20)
|
||||
devise (4.9.4)
|
||||
devise (4.9.3)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 4.1.0)
|
||||
responders
|
||||
warden (~> 1.2.3)
|
||||
devise_token_auth (1.2.3)
|
||||
devise_token_auth (1.2.1)
|
||||
bcrypt (~> 3.0)
|
||||
devise (> 3.5.2, < 5)
|
||||
rails (>= 4.2.0, < 7.2)
|
||||
diff-lcs (1.5.1)
|
||||
rails (>= 4.2.0, < 7.1)
|
||||
diff-lcs (1.5.0)
|
||||
digest-crc (0.6.4)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
docile (1.4.0)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (3.1.2)
|
||||
dotenv-rails (3.1.2)
|
||||
dotenv (= 3.1.2)
|
||||
railties (>= 6.1)
|
||||
dotenv (2.8.1)
|
||||
dotenv-rails (2.8.1)
|
||||
dotenv (= 2.8.1)
|
||||
railties (>= 3.2)
|
||||
down (5.4.0)
|
||||
addressable (~> 2.8)
|
||||
ecma-re-validator (0.4.0)
|
||||
@@ -229,7 +223,7 @@ GEM
|
||||
http (>= 3.0)
|
||||
ruby2_keywords
|
||||
email_reply_trimmer (0.1.13)
|
||||
erubi (1.13.0)
|
||||
erubi (1.12.0)
|
||||
et-orbi (1.2.7)
|
||||
tzinfo
|
||||
execjs (2.8.1)
|
||||
@@ -243,8 +237,9 @@ GEM
|
||||
railties (>= 5.0.0)
|
||||
faker (3.2.0)
|
||||
i18n (>= 1.8.11, < 2)
|
||||
faraday (2.9.0)
|
||||
faraday-net_http (>= 2.0, < 3.2)
|
||||
faraday (2.7.4)
|
||||
faraday-net_http (>= 2.0, < 3.1)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-follow_redirects (0.3.0)
|
||||
faraday (>= 1, < 3)
|
||||
faraday-mashify (0.1.1)
|
||||
@@ -252,8 +247,7 @@ GEM
|
||||
hashie
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-net_http (3.1.0)
|
||||
net-http
|
||||
faraday-net_http (3.0.2)
|
||||
faraday-net_http_persistent (2.1.0)
|
||||
faraday (~> 2.5)
|
||||
net-http-persistent (~> 4.0)
|
||||
@@ -322,15 +316,15 @@ GEM
|
||||
google-cloud-translate-v3 (0.6.0)
|
||||
gapic-common (>= 0.17.1, < 2.a)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-protobuf (3.25.3)
|
||||
google-protobuf (3.25.3-arm64-darwin)
|
||||
google-protobuf (3.25.3-x86_64-darwin)
|
||||
google-protobuf (3.25.3-x86_64-linux)
|
||||
google-protobuf (3.25.2)
|
||||
google-protobuf (3.25.2-arm64-darwin)
|
||||
google-protobuf (3.25.2-x86_64-darwin)
|
||||
google-protobuf (3.25.2-x86_64-linux)
|
||||
googleapis-common-protos (1.4.0)
|
||||
google-protobuf (~> 3.14)
|
||||
googleapis-common-protos-types (~> 1.2)
|
||||
grpc (~> 1.27)
|
||||
googleapis-common-protos-types (1.14.0)
|
||||
googleapis-common-protos-types (1.11.0)
|
||||
google-protobuf (~> 3.18)
|
||||
googleauth (1.5.2)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
@@ -341,17 +335,14 @@ GEM
|
||||
signet (>= 0.16, < 2.a)
|
||||
groupdate (6.2.1)
|
||||
activesupport (>= 5.2)
|
||||
grpc (1.62.0)
|
||||
google-protobuf (~> 3.25)
|
||||
grpc (1.54.3)
|
||||
google-protobuf (~> 3.21)
|
||||
googleapis-common-protos-types (~> 1.0)
|
||||
grpc (1.62.0-arm64-darwin)
|
||||
google-protobuf (~> 3.25)
|
||||
grpc (1.54.3-x86_64-darwin)
|
||||
google-protobuf (~> 3.21)
|
||||
googleapis-common-protos-types (~> 1.0)
|
||||
grpc (1.62.0-x86_64-darwin)
|
||||
google-protobuf (~> 3.25)
|
||||
googleapis-common-protos-types (~> 1.0)
|
||||
grpc (1.62.0-x86_64-linux)
|
||||
google-protobuf (~> 3.25)
|
||||
grpc (1.54.3-x86_64-linux)
|
||||
google-protobuf (~> 3.21)
|
||||
googleapis-common-protos-types (~> 1.0)
|
||||
haikunator (1.1.1)
|
||||
hairtrigger (1.0.0)
|
||||
@@ -359,8 +350,9 @@ GEM
|
||||
ruby2ruby (~> 2.4)
|
||||
ruby_parser (~> 3.10)
|
||||
hana (1.3.7)
|
||||
hashdiff (1.1.0)
|
||||
hashdiff (1.0.1)
|
||||
hashie (5.0.0)
|
||||
hkdf (1.0.0)
|
||||
http (5.1.1)
|
||||
addressable (~> 2.8)
|
||||
http-cookie (~> 1.0)
|
||||
@@ -374,11 +366,15 @@ GEM
|
||||
mini_mime (>= 1.0.0)
|
||||
multi_xml (>= 0.5.2)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.14.5)
|
||||
i18n (1.14.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
image_processing (1.12.2)
|
||||
mini_magick (>= 4.9.5, < 5)
|
||||
ruby-vips (>= 2.0.17, < 3)
|
||||
informers (0.2.0)
|
||||
blingfire (>= 0.1.7)
|
||||
numo-narray
|
||||
onnxruntime (>= 0.5.1)
|
||||
io-console (0.6.0)
|
||||
irb (1.7.2)
|
||||
reline (>= 0.3.6)
|
||||
@@ -398,8 +394,7 @@ GEM
|
||||
hana (~> 1.3)
|
||||
regexp_parser (~> 2.0)
|
||||
uri_template (~> 0.7)
|
||||
jwt (2.8.1)
|
||||
base64
|
||||
jwt (2.7.0)
|
||||
kaminari (1.2.2)
|
||||
activesupport (>= 4.1.0)
|
||||
kaminari-actionview (= 1.2.2)
|
||||
@@ -422,15 +417,15 @@ GEM
|
||||
addressable (~> 2.8)
|
||||
letter_opener (1.8.1)
|
||||
launchy (>= 2.2, < 3)
|
||||
libdatadog (7.0.0.1.0)
|
||||
libdatadog (7.0.0.1.0-x86_64-linux)
|
||||
libddwaf (1.14.0.0.0)
|
||||
libdatadog (2.0.0.1.0)
|
||||
libdatadog (2.0.0.1.0-x86_64-linux)
|
||||
libddwaf (1.8.2.0.0)
|
||||
ffi (~> 1.0)
|
||||
libddwaf (1.14.0.0.0-arm64-darwin)
|
||||
libddwaf (1.8.2.0.0-arm64-darwin)
|
||||
ffi (~> 1.0)
|
||||
libddwaf (1.14.0.0.0-x86_64-darwin)
|
||||
libddwaf (1.8.2.0.0-x86_64-darwin)
|
||||
ffi (~> 1.0)
|
||||
libddwaf (1.14.0.0.0-x86_64-linux)
|
||||
libddwaf (1.8.2.0.0-x86_64-linux)
|
||||
ffi (~> 1.0)
|
||||
line-bot-api (1.28.0)
|
||||
liquid (5.4.0)
|
||||
@@ -440,7 +435,6 @@ GEM
|
||||
llhttp-ffi (0.4.0)
|
||||
ffi-compiler (~> 1.0)
|
||||
rake (~> 13.0)
|
||||
logger (1.6.0)
|
||||
lograge (0.14.0)
|
||||
actionpack (>= 4)
|
||||
activesupport (>= 4)
|
||||
@@ -454,20 +448,20 @@ GEM
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
marcel (1.0.4)
|
||||
marcel (1.0.2)
|
||||
maxminddb (0.1.22)
|
||||
memoist (0.16.2)
|
||||
meta_request (0.8.2)
|
||||
meta_request (0.7.4)
|
||||
rack-contrib (>= 1.1, < 3)
|
||||
railties (>= 3.0.0, < 8)
|
||||
method_source (1.1.0)
|
||||
railties (>= 3.0.0, < 7.1)
|
||||
method_source (1.0.0)
|
||||
mime-types (3.4.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2023.0218.1)
|
||||
mini_magick (4.12.0)
|
||||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.7)
|
||||
minitest (5.24.1)
|
||||
mini_portile2 (2.8.5)
|
||||
minitest (5.21.2)
|
||||
mock_redis (0.36.0)
|
||||
ruby2_keywords
|
||||
msgpack (1.7.0)
|
||||
@@ -476,11 +470,9 @@ GEM
|
||||
multipart-post (2.3.0)
|
||||
neighbor (0.2.3)
|
||||
activerecord (>= 5.2)
|
||||
net-http (0.4.1)
|
||||
uri
|
||||
net-http-persistent (4.0.2)
|
||||
connection_pool (~> 2.2)
|
||||
net-imap (0.4.12)
|
||||
net-imap (0.4.9)
|
||||
date
|
||||
net-protocol
|
||||
net-pop (0.1.2)
|
||||
@@ -495,16 +487,17 @@ GEM
|
||||
sidekiq
|
||||
newrelic_rpm (9.6.0)
|
||||
base64
|
||||
nio4r (2.7.3)
|
||||
nokogiri (1.16.6)
|
||||
nio4r (2.7.0)
|
||||
nokogiri (1.16.2)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.16.6-arm64-darwin)
|
||||
nokogiri (1.16.2-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.16.6-x86_64-darwin)
|
||||
nokogiri (1.16.2-x86_64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.16.6-x86_64-linux)
|
||||
nokogiri (1.16.2-x86_64-linux)
|
||||
racc (~> 1.4)
|
||||
numo-narray (0.9.2.1)
|
||||
oauth (1.1.0)
|
||||
oauth-tty (~> 1.0, >= 1.0.1)
|
||||
snaky_hash (~> 2.0)
|
||||
@@ -522,18 +515,26 @@ GEM
|
||||
hashie (>= 3.4.6)
|
||||
rack (>= 2.2.3)
|
||||
rack-protection
|
||||
omniauth-google-oauth2 (1.1.2)
|
||||
omniauth-google-oauth2 (1.1.1)
|
||||
jwt (>= 2.0)
|
||||
oauth2 (~> 2.0)
|
||||
oauth2 (~> 2.0.6)
|
||||
omniauth (~> 2.0)
|
||||
omniauth-oauth2 (~> 1.8)
|
||||
omniauth-oauth2 (~> 1.8.0)
|
||||
omniauth-oauth2 (1.8.0)
|
||||
oauth2 (>= 1.4, < 3)
|
||||
omniauth (~> 2.0)
|
||||
omniauth-rails_csrf_protection (1.0.2)
|
||||
omniauth-rails_csrf_protection (1.0.1)
|
||||
actionpack (>= 4.2)
|
||||
omniauth (~> 2.0)
|
||||
openssl (3.2.0)
|
||||
onnxruntime (0.7.6)
|
||||
ffi
|
||||
onnxruntime (0.7.6-arm64-darwin)
|
||||
ffi
|
||||
onnxruntime (0.7.6-x86_64-darwin)
|
||||
ffi
|
||||
onnxruntime (0.7.6-x86_64-linux)
|
||||
ffi
|
||||
openssl (3.1.0)
|
||||
orm_adapter (0.5.0)
|
||||
os (1.1.4)
|
||||
parallel (1.23.0)
|
||||
@@ -551,44 +552,43 @@ GEM
|
||||
method_source (~> 1.0)
|
||||
pry-rails (0.3.9)
|
||||
pry (>= 0.10.4)
|
||||
public_suffix (6.0.0)
|
||||
public_suffix (5.0.1)
|
||||
puma (6.4.2)
|
||||
nio4r (~> 2.0)
|
||||
pundit (2.3.0)
|
||||
activesupport (>= 3.0.0)
|
||||
raabro (1.4.0)
|
||||
racc (1.8.0)
|
||||
rack (2.2.9)
|
||||
racc (1.7.3)
|
||||
rack (2.2.8.1)
|
||||
rack-attack (6.7.0)
|
||||
rack (>= 1.0, < 4)
|
||||
rack-contrib (2.5.0)
|
||||
rack-contrib (2.4.0)
|
||||
rack (< 4)
|
||||
rack-cors (2.0.0)
|
||||
rack (>= 2.0.0)
|
||||
rack-mini-profiler (3.2.0)
|
||||
rack (>= 1.2.0)
|
||||
rack-protection (3.2.0)
|
||||
base64 (>= 0.1.0)
|
||||
rack-protection (3.1.0)
|
||||
rack (~> 2.2, >= 2.2.4)
|
||||
rack-proxy (0.7.6)
|
||||
rack
|
||||
rack-test (2.1.0)
|
||||
rack (>= 1.3)
|
||||
rack-timeout (0.6.3)
|
||||
rails (7.0.8.4)
|
||||
actioncable (= 7.0.8.4)
|
||||
actionmailbox (= 7.0.8.4)
|
||||
actionmailer (= 7.0.8.4)
|
||||
actionpack (= 7.0.8.4)
|
||||
actiontext (= 7.0.8.4)
|
||||
actionview (= 7.0.8.4)
|
||||
activejob (= 7.0.8.4)
|
||||
activemodel (= 7.0.8.4)
|
||||
activerecord (= 7.0.8.4)
|
||||
activestorage (= 7.0.8.4)
|
||||
activesupport (= 7.0.8.4)
|
||||
rails (7.0.8.1)
|
||||
actioncable (= 7.0.8.1)
|
||||
actionmailbox (= 7.0.8.1)
|
||||
actionmailer (= 7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
actiontext (= 7.0.8.1)
|
||||
actionview (= 7.0.8.1)
|
||||
activejob (= 7.0.8.1)
|
||||
activemodel (= 7.0.8.1)
|
||||
activerecord (= 7.0.8.1)
|
||||
activestorage (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 7.0.8.4)
|
||||
railties (= 7.0.8.1)
|
||||
rails-dom-testing (2.2.0)
|
||||
activesupport (>= 5.0.0)
|
||||
minitest
|
||||
@@ -596,21 +596,21 @@ GEM
|
||||
rails-html-sanitizer (1.6.0)
|
||||
loofah (~> 2.21)
|
||||
nokogiri (~> 1.14)
|
||||
railties (7.0.8.4)
|
||||
actionpack (= 7.0.8.4)
|
||||
activesupport (= 7.0.8.4)
|
||||
railties (7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
method_source
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0)
|
||||
zeitwerk (~> 2.5)
|
||||
rainbow (3.1.1)
|
||||
rake (13.2.1)
|
||||
rake (13.1.0)
|
||||
rb-fsevent (0.11.2)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
redis (5.0.6)
|
||||
redis-client (>= 0.9.0)
|
||||
redis-client (0.22.2)
|
||||
redis-client (0.19.1)
|
||||
connection_pool
|
||||
redis-namespace (1.10.0)
|
||||
redis (>= 4)
|
||||
@@ -634,25 +634,24 @@ GEM
|
||||
retriable (3.1.2)
|
||||
reverse_markdown (2.1.1)
|
||||
nokogiri
|
||||
rexml (3.3.2)
|
||||
strscan
|
||||
rspec-core (3.13.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-expectations (3.13.1)
|
||||
rexml (3.2.5)
|
||||
rspec-core (3.12.2)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-expectations (3.12.3)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-mocks (3.13.1)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-mocks (3.12.5)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-rails (6.1.3)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-rails (6.0.2)
|
||||
actionpack (>= 6.1)
|
||||
activesupport (>= 6.1)
|
||||
railties (>= 6.1)
|
||||
rspec-core (~> 3.13)
|
||||
rspec-expectations (~> 3.13)
|
||||
rspec-mocks (~> 3.13)
|
||||
rspec-support (~> 3.13)
|
||||
rspec-support (3.13.1)
|
||||
rspec-core (~> 3.12)
|
||||
rspec-expectations (~> 3.12)
|
||||
rspec-mocks (~> 3.12)
|
||||
rspec-support (~> 3.12)
|
||||
rspec-support (3.12.0)
|
||||
rspec_junit_formatter (0.6.0)
|
||||
rspec-core (>= 2, < 4, != 2.12.0)
|
||||
rubocop (1.50.2)
|
||||
@@ -710,24 +709,22 @@ GEM
|
||||
activesupport (>= 4)
|
||||
selectize-rails (0.12.6)
|
||||
semantic_range (3.0.0)
|
||||
sentry-rails (5.18.1)
|
||||
sentry-rails (5.14.0)
|
||||
railties (>= 5.0)
|
||||
sentry-ruby (~> 5.18.1)
|
||||
sentry-ruby (5.18.1)
|
||||
bigdecimal
|
||||
sentry-ruby (~> 5.14.0)
|
||||
sentry-ruby (5.14.0)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
sentry-sidekiq (5.18.1)
|
||||
sentry-ruby (~> 5.18.1)
|
||||
sentry-sidekiq (5.14.0)
|
||||
sentry-ruby (~> 5.14.0)
|
||||
sidekiq (>= 3.0)
|
||||
sexp_processor (4.17.0)
|
||||
shoulda-matchers (5.3.0)
|
||||
activesupport (>= 5.2.0)
|
||||
sidekiq (7.3.0)
|
||||
sidekiq (7.2.1)
|
||||
concurrent-ruby (< 2)
|
||||
connection_pool (>= 2.3.0)
|
||||
logger
|
||||
rack (>= 2.2.4)
|
||||
redis-client (>= 0.22.2)
|
||||
redis-client (>= 0.19.0)
|
||||
sidekiq-cron (1.12.0)
|
||||
fugit (~> 1.8)
|
||||
globalid (>= 1.0.1)
|
||||
@@ -766,10 +763,9 @@ GEM
|
||||
stackprof (0.2.25)
|
||||
statsd-ruby (1.5.0)
|
||||
stripe (8.5.0)
|
||||
strscan (3.1.0)
|
||||
telephone_number (1.4.20)
|
||||
test-prof (1.2.1)
|
||||
thor (1.3.1)
|
||||
thor (1.3.0)
|
||||
tilt (2.3.0)
|
||||
time_diff (0.3.0)
|
||||
activesupport
|
||||
@@ -794,12 +790,11 @@ GEM
|
||||
unf_ext (0.0.8.2)
|
||||
unicode-display_width (2.4.2)
|
||||
uniform_notifier (1.16.0)
|
||||
uri (0.13.0)
|
||||
uri_template (0.7.0)
|
||||
valid_email2 (4.0.6)
|
||||
activemodel (>= 3.2)
|
||||
mail (~> 2.5)
|
||||
version_gem (1.1.4)
|
||||
version_gem (1.1.3)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
web-console (4.2.1)
|
||||
@@ -807,10 +802,11 @@ GEM
|
||||
activemodel (>= 6.0.0)
|
||||
bindex (>= 0.4.0)
|
||||
railties (>= 6.0.0)
|
||||
web-push (3.0.1)
|
||||
web-push (3.0.0)
|
||||
hkdf (~> 1.0)
|
||||
jwt (~> 2.0)
|
||||
openssl (~> 3.0)
|
||||
webmock (3.23.1)
|
||||
webmock (3.18.1)
|
||||
addressable (>= 2.8.0)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
@@ -827,7 +823,7 @@ GEM
|
||||
working_hours (1.4.1)
|
||||
activesupport (>= 3.2)
|
||||
tzinfo
|
||||
zeitwerk (2.6.16)
|
||||
zeitwerk (2.6.12)
|
||||
|
||||
PLATFORMS
|
||||
arm64-darwin-20
|
||||
@@ -845,7 +841,7 @@ DEPENDENCIES
|
||||
activerecord-import
|
||||
acts-as-taggable-on
|
||||
administrate (>= 0.20.1)
|
||||
administrate-field-active_storage (>= 1.0.3)
|
||||
administrate-field-active_storage (>= 1.0.1)
|
||||
administrate-field-belongs_to_search (>= 0.9.0)
|
||||
annotate
|
||||
attr_extras
|
||||
@@ -866,10 +862,10 @@ DEPENDENCIES
|
||||
database_cleaner
|
||||
ddtrace
|
||||
debug (~> 1.8)
|
||||
devise (>= 4.9.4)
|
||||
devise (>= 4.9.3)
|
||||
devise-secure_password!
|
||||
devise_token_auth (>= 1.2.3)
|
||||
dotenv-rails (>= 3.0.0)
|
||||
devise_token_auth
|
||||
dotenv-rails
|
||||
down
|
||||
elastic-apm
|
||||
email_reply_trimmer
|
||||
@@ -891,6 +887,7 @@ DEPENDENCIES
|
||||
hashie
|
||||
html2text!
|
||||
image_processing
|
||||
informers
|
||||
jbuilder
|
||||
json_refs
|
||||
json_schemer
|
||||
@@ -903,16 +900,16 @@ DEPENDENCIES
|
||||
listen
|
||||
lograge (~> 0.14.0)
|
||||
maxminddb
|
||||
meta_request (>= 0.8.0)
|
||||
meta_request
|
||||
mock_redis
|
||||
neighbor
|
||||
net-smtp (~> 0.3.4)
|
||||
newrelic-sidekiq-metrics (>= 1.6.2)
|
||||
newrelic_rpm
|
||||
omniauth (>= 2.1.2)
|
||||
omniauth-google-oauth2 (>= 1.1.2)
|
||||
omniauth-google-oauth2
|
||||
omniauth-oauth2
|
||||
omniauth-rails_csrf_protection (~> 1.0, >= 1.0.2)
|
||||
omniauth-rails_csrf_protection (~> 1.0)
|
||||
pg
|
||||
pg_search
|
||||
pgvector
|
||||
@@ -924,13 +921,13 @@ DEPENDENCIES
|
||||
rack-cors (= 2.0.0)
|
||||
rack-mini-profiler (>= 3.2.0)
|
||||
rack-timeout
|
||||
rails (~> 7.0.8.4)
|
||||
rails (~> 7.0.8.1)
|
||||
redis
|
||||
redis-namespace
|
||||
responders (>= 3.1.1)
|
||||
rest-client
|
||||
reverse_markdown
|
||||
rspec-rails (>= 6.1.3)
|
||||
rspec-rails
|
||||
rspec_junit_formatter
|
||||
rubocop
|
||||
rubocop-performance
|
||||
@@ -939,11 +936,11 @@ DEPENDENCIES
|
||||
scout_apm
|
||||
scss_lint
|
||||
seed_dump
|
||||
sentry-rails (>= 5.18.1)
|
||||
sentry-rails (>= 5.14.0)
|
||||
sentry-ruby
|
||||
sentry-sidekiq (>= 5.18.1)
|
||||
sentry-sidekiq (>= 5.14.0)
|
||||
shoulda-matchers
|
||||
sidekiq (>= 7.3.0)
|
||||
sidekiq (>= 7.2.1)
|
||||
sidekiq-cron (>= 1.12.0)
|
||||
simplecov (= 0.17.1)
|
||||
slack-ruby-client (~> 2.2.0)
|
||||
@@ -961,14 +958,14 @@ DEPENDENCIES
|
||||
uglifier
|
||||
valid_email2
|
||||
web-console (>= 4.2.1)
|
||||
web-push (>= 3.0.1)
|
||||
web-push
|
||||
webmock
|
||||
webpacker
|
||||
wisper (= 2.0.0)
|
||||
working_hours
|
||||
|
||||
RUBY VERSION
|
||||
ruby 3.3.3p89
|
||||
ruby 3.2.2p185
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.16
|
||||
2.4.6
|
||||
|
||||
@@ -17,9 +17,6 @@ db_migrate:
|
||||
db_seed:
|
||||
RAILS_ENV=$(RAILS_ENV) bundle exec rails db:seed
|
||||
|
||||
db_reset:
|
||||
RAILS_ENV=$(RAILS_ENV) bundle exec rails db:reset
|
||||
|
||||
db:
|
||||
RAILS_ENV=$(RAILS_ENV) bundle exec rails db:chatwoot_prepare
|
||||
|
||||
@@ -52,4 +49,4 @@ debug_worker:
|
||||
docker:
|
||||
docker build -t $(APP_NAME) -f ./docker/Dockerfile .
|
||||
|
||||
.PHONY: setup db_create db_migrate db_seed db_reset db console server burn docker run force_run debug debug_worker
|
||||
.PHONY: setup db_create db_migrate db_seed db console server burn docker run force_run debug debug_worker
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
3.9.0
|
||||
3.3.1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2.8.0
|
||||
2.7.0
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
"plan": "heroku-redis:mini"
|
||||
},
|
||||
{
|
||||
"plan": "heroku-postgresql:essential-0"
|
||||
"plan": "heroku-postgresql:mini"
|
||||
}
|
||||
],
|
||||
"stack": "heroku-20",
|
||||
|
||||
@@ -16,6 +16,7 @@ class AgentBuilder
|
||||
def perform
|
||||
ActiveRecord::Base.transaction do
|
||||
@user = find_or_create_user
|
||||
send_confirmation_if_required
|
||||
create_account_user
|
||||
end
|
||||
@user
|
||||
@@ -33,6 +34,11 @@ class AgentBuilder
|
||||
User.create!(email: email, name: name, password: temp_password, password_confirmation: temp_password)
|
||||
end
|
||||
|
||||
# Sends confirmation instructions if the user is persisted and not confirmed.
|
||||
def send_confirmation_if_required
|
||||
@user.send_confirmation_instructions if user_needs_confirmation?
|
||||
end
|
||||
|
||||
# Checks if the user needs confirmation.
|
||||
# @return [Boolean] true if the user is persisted and not confirmed, false otherwise.
|
||||
def user_needs_confirmation?
|
||||
|
||||
@@ -19,9 +19,9 @@ class ContactInboxWithContactBuilder
|
||||
|
||||
ActiveRecord::Base.transaction(requires_new: true) do
|
||||
build_contact_with_contact_inbox
|
||||
update_contact_avatar(@contact) unless @contact.avatar.attached?
|
||||
@contact_inbox
|
||||
end
|
||||
update_contact_avatar(@contact) unless @contact.avatar.attached?
|
||||
@contact_inbox
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -53,23 +53,7 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
|
||||
end
|
||||
|
||||
def conversation
|
||||
@conversation ||= set_conversation_based_on_inbox_config
|
||||
end
|
||||
|
||||
def set_conversation_based_on_inbox_config
|
||||
if @inbox.lock_to_single_conversation
|
||||
Conversation.where(conversation_params).order(created_at: :desc).first || build_conversation
|
||||
else
|
||||
find_or_build_for_multiple_conversations
|
||||
end
|
||||
end
|
||||
|
||||
def find_or_build_for_multiple_conversations
|
||||
# If lock to single conversation is disabled, we will create a new conversation if previous conversation is resolved
|
||||
last_conversation = Conversation.where(conversation_params).where.not(status: :resolved).order(created_at: :desc).first
|
||||
return build_conversation if last_conversation.nil?
|
||||
|
||||
last_conversation
|
||||
@conversation ||= Conversation.find_by(conversation_params) || build_conversation
|
||||
end
|
||||
|
||||
def build_conversation
|
||||
|
||||
@@ -69,28 +69,9 @@ class Messages::Instagram::MessageBuilder < Messages::Messenger::MessageBuilder
|
||||
end
|
||||
|
||||
def conversation
|
||||
@conversation ||= set_conversation_based_on_inbox_config
|
||||
end
|
||||
|
||||
def instagram_direct_message_conversation
|
||||
Conversation.where(conversation_params)
|
||||
.where("additional_attributes ->> 'type' = 'instagram_direct_message'")
|
||||
end
|
||||
|
||||
def set_conversation_based_on_inbox_config
|
||||
if @inbox.lock_to_single_conversation
|
||||
instagram_direct_message_conversation.order(created_at: :desc).first || build_conversation
|
||||
else
|
||||
find_or_build_for_multiple_conversations
|
||||
end
|
||||
end
|
||||
|
||||
def find_or_build_for_multiple_conversations
|
||||
last_conversation = instagram_direct_message_conversation.where.not(status: :resolved).order(created_at: :desc).first
|
||||
|
||||
return build_conversation if last_conversation.nil?
|
||||
|
||||
last_conversation
|
||||
@conversation ||= Conversation.where(conversation_params).find_by(
|
||||
"additional_attributes ->> 'type' = 'instagram_direct_message'"
|
||||
) || build_conversation
|
||||
end
|
||||
|
||||
def message_content
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
class V2::Reports::Conversations::BaseReportBuilder
|
||||
pattr_initialize :account, :params
|
||||
|
||||
private
|
||||
|
||||
AVG_METRICS = %w[avg_first_response_time avg_resolution_time reply_time].freeze
|
||||
COUNT_METRICS = %w[
|
||||
conversations_count
|
||||
incoming_messages_count
|
||||
outgoing_messages_count
|
||||
resolutions_count
|
||||
bot_resolutions_count
|
||||
bot_handoffs_count
|
||||
].freeze
|
||||
|
||||
def builder_class(metric)
|
||||
case metric
|
||||
when *AVG_METRICS
|
||||
V2::Reports::Timeseries::AverageReportBuilder
|
||||
when *COUNT_METRICS
|
||||
V2::Reports::Timeseries::CountReportBuilder
|
||||
end
|
||||
end
|
||||
|
||||
def log_invalid_metric
|
||||
Rails.logger.error "ReportBuilder: Invalid metric - #{params[:metric]}"
|
||||
|
||||
{}
|
||||
end
|
||||
end
|
||||
@@ -1,30 +0,0 @@
|
||||
class V2::Reports::Conversations::MetricBuilder < V2::Reports::Conversations::BaseReportBuilder
|
||||
def summary
|
||||
{
|
||||
conversations_count: count('conversations_count'),
|
||||
incoming_messages_count: count('incoming_messages_count'),
|
||||
outgoing_messages_count: count('outgoing_messages_count'),
|
||||
avg_first_response_time: count('avg_first_response_time'),
|
||||
avg_resolution_time: count('avg_resolution_time'),
|
||||
resolutions_count: count('resolutions_count'),
|
||||
reply_time: count('reply_time')
|
||||
}
|
||||
end
|
||||
|
||||
def bot_summary
|
||||
{
|
||||
bot_resolutions_count: count('bot_resolutions_count'),
|
||||
bot_handoffs_count: count('bot_handoffs_count')
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def count(metric)
|
||||
builder_class(metric).new(account, builder_params(metric)).aggregate_value
|
||||
end
|
||||
|
||||
def builder_params(metric)
|
||||
params.merge({ metric: metric })
|
||||
end
|
||||
end
|
||||
@@ -1,21 +0,0 @@
|
||||
class V2::Reports::Conversations::ReportBuilder < V2::Reports::Conversations::BaseReportBuilder
|
||||
def timeseries
|
||||
perform_action(:timeseries)
|
||||
end
|
||||
|
||||
def aggregate_value
|
||||
perform_action(:aggregate_value)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def perform_action(method_name)
|
||||
return builder.new(account, params).public_send(method_name) if builder.present?
|
||||
|
||||
log_invalid_metric
|
||||
end
|
||||
|
||||
def builder
|
||||
builder_class(params[:metric])
|
||||
end
|
||||
end
|
||||
@@ -1,48 +0,0 @@
|
||||
class V2::Reports::Timeseries::AverageReportBuilder < V2::Reports::Timeseries::BaseTimeseriesBuilder
|
||||
def timeseries
|
||||
grouped_average_time = reporting_events.average(average_value_key)
|
||||
grouped_event_count = reporting_events.count
|
||||
grouped_average_time.each_with_object([]) do |element, arr|
|
||||
event_date, average_time = element
|
||||
arr << {
|
||||
value: average_time,
|
||||
timestamp: event_date.in_time_zone(timezone).to_i,
|
||||
count: grouped_event_count[event_date]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def aggregate_value
|
||||
object_scope.average(average_value_key)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def event_name
|
||||
metric_to_event_name = {
|
||||
avg_first_response_time: :first_response,
|
||||
avg_resolution_time: :conversation_resolved,
|
||||
reply_time: :reply_time
|
||||
}
|
||||
metric_to_event_name[params[:metric].to_sym]
|
||||
end
|
||||
|
||||
def object_scope
|
||||
scope.reporting_events.where(name: event_name, created_at: range)
|
||||
end
|
||||
|
||||
def reporting_events
|
||||
@grouped_values = object_scope.group_by_period(
|
||||
group_by,
|
||||
:created_at,
|
||||
default_value: 0,
|
||||
range: range,
|
||||
permit: %w[day week month year hour],
|
||||
time_zone: timezone
|
||||
)
|
||||
end
|
||||
|
||||
def average_value_key
|
||||
@average_value_key ||= params[:business_hours].present? ? :value_in_business_hours : :value
|
||||
end
|
||||
end
|
||||
@@ -1,46 +0,0 @@
|
||||
class V2::Reports::Timeseries::BaseTimeseriesBuilder
|
||||
include TimezoneHelper
|
||||
include DateRangeHelper
|
||||
DEFAULT_GROUP_BY = 'day'.freeze
|
||||
|
||||
pattr_initialize :account, :params
|
||||
|
||||
def scope
|
||||
case params[:type].to_sym
|
||||
when :account
|
||||
account
|
||||
when :inbox
|
||||
inbox
|
||||
when :agent
|
||||
user
|
||||
when :label
|
||||
label
|
||||
when :team
|
||||
team
|
||||
end
|
||||
end
|
||||
|
||||
def inbox
|
||||
@inbox ||= account.inboxes.find(params[:id])
|
||||
end
|
||||
|
||||
def user
|
||||
@user ||= account.users.find(params[:id])
|
||||
end
|
||||
|
||||
def label
|
||||
@label ||= account.labels.find(params[:id])
|
||||
end
|
||||
|
||||
def team
|
||||
@team ||= account.teams.find(params[:id])
|
||||
end
|
||||
|
||||
def group_by
|
||||
@group_by ||= %w[day week month year hour].include?(params[:group_by]) ? params[:group_by] : DEFAULT_GROUP_BY
|
||||
end
|
||||
|
||||
def timezone
|
||||
@timezone ||= timezone_name_from_offset(params[:timezone_offset])
|
||||
end
|
||||
end
|
||||
@@ -1,71 +0,0 @@
|
||||
class V2::Reports::Timeseries::CountReportBuilder < V2::Reports::Timeseries::BaseTimeseriesBuilder
|
||||
def timeseries
|
||||
grouped_count.each_with_object([]) do |element, arr|
|
||||
event_date, event_count = element
|
||||
|
||||
# The `event_date` is in Date format (without time), such as "Wed, 15 May 2024".
|
||||
# We need a timestamp for the start of the day. However, we can't use `event_date.to_time.to_i`
|
||||
# because it converts the date to 12:00 AM server timezone.
|
||||
# The desired output should be 12:00 AM in the specified timezone.
|
||||
arr << { value: event_count, timestamp: event_date.in_time_zone(timezone).to_i }
|
||||
end
|
||||
end
|
||||
|
||||
def aggregate_value
|
||||
object_scope.count
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def metric
|
||||
@metric ||= params[:metric]
|
||||
end
|
||||
|
||||
def object_scope
|
||||
send("scope_for_#{metric}")
|
||||
end
|
||||
|
||||
def scope_for_conversations_count
|
||||
scope.conversations.where(account_id: account.id, created_at: range)
|
||||
end
|
||||
|
||||
def scope_for_incoming_messages_count
|
||||
scope.messages.where(account_id: account.id, created_at: range).incoming.unscope(:order)
|
||||
end
|
||||
|
||||
def scope_for_outgoing_messages_count
|
||||
scope.messages.where(account_id: account.id, created_at: range).outgoing.unscope(:order)
|
||||
end
|
||||
|
||||
def scope_for_resolutions_count
|
||||
scope.reporting_events.joins(:conversation).select(:conversation_id).where(
|
||||
name: :conversation_resolved,
|
||||
conversations: { status: :resolved }, created_at: range
|
||||
).distinct
|
||||
end
|
||||
|
||||
def scope_for_bot_resolutions_count
|
||||
scope.reporting_events.joins(:conversation).select(:conversation_id).where(
|
||||
name: :conversation_bot_resolved,
|
||||
conversations: { status: :resolved }, created_at: range
|
||||
).distinct
|
||||
end
|
||||
|
||||
def scope_for_bot_handoffs_count
|
||||
scope.reporting_events.joins(:conversation).select(:conversation_id).where(
|
||||
name: :conversation_bot_handoff,
|
||||
created_at: range
|
||||
).distinct
|
||||
end
|
||||
|
||||
def grouped_count
|
||||
@grouped_values = object_scope.group_by_period(
|
||||
group_by,
|
||||
:created_at,
|
||||
default_value: 0,
|
||||
range: range,
|
||||
permit: %w[day week month year hour],
|
||||
time_zone: timezone
|
||||
).count
|
||||
end
|
||||
end
|
||||
@@ -19,7 +19,7 @@ class Api::V1::Accounts::AgentsController < Api::V1::Accounts::BaseController
|
||||
account: Current.account
|
||||
)
|
||||
|
||||
@agent = builder.perform
|
||||
builder.perform
|
||||
end
|
||||
|
||||
def update
|
||||
|
||||
@@ -21,6 +21,6 @@ class Api::V1::Accounts::BulkActionsController < Api::V1::Accounts::BaseControll
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:type, :snoozed_until, ids: [], fields: [:status, :assignee_id, :team_id], labels: [add: [], remove: []])
|
||||
params.permit(:type, ids: [], fields: [:status, :assignee_id, :team_id], labels: [add: [], remove: []])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,9 +2,13 @@ class Api::V1::Accounts::Channels::TwilioChannelsController < Api::V1::Accounts:
|
||||
before_action :authorize_request
|
||||
|
||||
def create
|
||||
process_create
|
||||
rescue StandardError => e
|
||||
render_could_not_create_error(e.message)
|
||||
ActiveRecord::Base.transaction do
|
||||
authenticate_twilio
|
||||
build_inbox
|
||||
setup_webhooks if @twilio_channel.sms?
|
||||
rescue StandardError => e
|
||||
render_could_not_create_error(e.message)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
@@ -13,14 +17,6 @@ class Api::V1::Accounts::Channels::TwilioChannelsController < Api::V1::Accounts:
|
||||
authorize ::Inbox
|
||||
end
|
||||
|
||||
def process_create
|
||||
ActiveRecord::Base.transaction do
|
||||
authenticate_twilio
|
||||
build_inbox
|
||||
setup_webhooks if @twilio_channel.sms?
|
||||
end
|
||||
end
|
||||
|
||||
def authenticate_twilio
|
||||
client = if permitted_params[:api_key_sid].present?
|
||||
Twilio::REST::Client.new(permitted_params[:api_key_sid], permitted_params[:auth_token], permitted_params[:account_sid])
|
||||
|
||||
@@ -46,8 +46,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
||||
|
||||
def export
|
||||
column_names = params['column_names']
|
||||
filter_params = { :payload => params.permit!['payload'], :label => params.permit!['label'] }
|
||||
Account::ContactsExportJob.perform_later(Current.account.id, Current.user.id, column_names, filter_params)
|
||||
Account::ContactsExportJob.perform_later(Current.account.id, column_names, Current.user.email)
|
||||
head :ok, message: I18n.t('errors.contacts.export.success')
|
||||
end
|
||||
|
||||
@@ -62,14 +61,10 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
||||
def show; end
|
||||
|
||||
def filter
|
||||
result = ::Contacts::FilterService.new(Current.account, Current.user, params.permit!).perform
|
||||
result = ::Contacts::FilterService.new(params.permit!, current_user).perform
|
||||
contacts = result[:contacts]
|
||||
@contacts_count = result[:count]
|
||||
@contacts = fetch_contacts(contacts)
|
||||
rescue CustomExceptions::CustomFilter::InvalidAttribute,
|
||||
CustomExceptions::CustomFilter::InvalidOperator,
|
||||
CustomExceptions::CustomFilter::InvalidValue => e
|
||||
render_could_not_create_error(e.message)
|
||||
end
|
||||
|
||||
def contactable_inboxes
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
class Api::V1::Accounts::Conversations::DraftMessagesController < Api::V1::Accounts::Conversations::BaseController
|
||||
def show
|
||||
render json: { has_draft: false } and return unless Redis::Alfred.exists?(draft_redis_key)
|
||||
|
||||
draft_message = Redis::Alfred.get(draft_redis_key)
|
||||
render json: { has_draft: true, message: draft_message }
|
||||
end
|
||||
|
||||
def update
|
||||
Redis::Alfred.set(draft_redis_key, draft_message_params)
|
||||
head :ok
|
||||
end
|
||||
|
||||
def destroy
|
||||
Redis::Alfred.delete(draft_redis_key)
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def draft_redis_key
|
||||
format(Redis::Alfred::CONVERSATION_DRAFT_MESSAGE, id: @conversation.id)
|
||||
end
|
||||
|
||||
def draft_message_params
|
||||
params.dig(:draft_message, :message) || ''
|
||||
end
|
||||
end
|
||||
@@ -36,18 +36,10 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@conversation.update!(permitted_update_params)
|
||||
end
|
||||
|
||||
def filter
|
||||
result = ::Conversations::FilterService.new(params.permit!, current_user).perform
|
||||
@conversations = result[:conversations]
|
||||
@conversations_count = result[:count]
|
||||
rescue CustomExceptions::CustomFilter::InvalidAttribute,
|
||||
CustomExceptions::CustomFilter::InvalidOperator,
|
||||
CustomExceptions::CustomFilter::InvalidValue => e
|
||||
render_could_not_create_error(e.message)
|
||||
end
|
||||
|
||||
def mute
|
||||
@@ -118,11 +110,6 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
||||
|
||||
private
|
||||
|
||||
def permitted_update_params
|
||||
# TODO: Move the other conversation attributes to this method and remove specific endpoints for each attribute
|
||||
params.permit(:priority)
|
||||
end
|
||||
|
||||
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)
|
||||
@@ -189,5 +176,3 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
||||
@conversation.assignee_id? && Current.user == @conversation.assignee
|
||||
end
|
||||
end
|
||||
|
||||
Api::V1::Accounts::ConversationsController.prepend_mod_with('Api::V1::Accounts::ConversationsController')
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
class Api::V1::Accounts::Google::AuthorizationsController < Api::V1::Accounts::BaseController
|
||||
include GoogleConcern
|
||||
before_action :check_authorization
|
||||
|
||||
def create
|
||||
email = params[:authorization][:email]
|
||||
redirect_url = google_client.auth_code.authorize_url(
|
||||
{
|
||||
redirect_uri: "#{base_url}/google/callback",
|
||||
scope: 'email profile https://mail.google.com/',
|
||||
response_type: 'code',
|
||||
prompt: 'consent', # the oauth flow does not return a refresh token, this is supposed to fix it
|
||||
access_type: 'offline', # the default is 'online'
|
||||
client_id: GlobalConfigService.load('GOOGLE_OAUTH_CLIENT_ID', nil)
|
||||
}
|
||||
)
|
||||
|
||||
if redirect_url
|
||||
cache_key = "google::#{email.downcase}"
|
||||
::Redis::Alfred.setex(cache_key, Current.account.id, 5.minutes)
|
||||
render json: { success: true, url: redirect_url }
|
||||
else
|
||||
render json: { success: false }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_authorization
|
||||
raise Pundit::NotAuthorizedError unless Current.account_user.administrator?
|
||||
end
|
||||
end
|
||||
@@ -1,93 +0,0 @@
|
||||
class Api::V1::Accounts::Integrations::LinearController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_conversation, only: [:link_issue, :linked_issues]
|
||||
|
||||
def teams
|
||||
teams = linear_processor_service.teams
|
||||
if teams[:error]
|
||||
render json: { error: teams[:error] }, status: :unprocessable_entity
|
||||
else
|
||||
render json: teams[:data], status: :ok
|
||||
end
|
||||
end
|
||||
|
||||
def team_entities
|
||||
team_id = permitted_params[:team_id]
|
||||
team_entities = linear_processor_service.team_entities(team_id)
|
||||
if team_entities[:error]
|
||||
render json: { error: team_entities[:error] }, status: :unprocessable_entity
|
||||
else
|
||||
render json: team_entities[:data], status: :ok
|
||||
end
|
||||
end
|
||||
|
||||
def create_issue
|
||||
issue = linear_processor_service.create_issue(permitted_params)
|
||||
if issue[:error]
|
||||
render json: { error: issue[:error] }, status: :unprocessable_entity
|
||||
else
|
||||
render json: issue[:data], status: :ok
|
||||
end
|
||||
end
|
||||
|
||||
def link_issue
|
||||
issue_id = permitted_params[:issue_id]
|
||||
title = permitted_params[:title]
|
||||
issue = linear_processor_service.link_issue(conversation_link, issue_id, title)
|
||||
if issue[:error]
|
||||
render json: { error: issue[:error] }, status: :unprocessable_entity
|
||||
else
|
||||
render json: issue[:data], status: :ok
|
||||
end
|
||||
end
|
||||
|
||||
def unlink_issue
|
||||
link_id = permitted_params[:link_id]
|
||||
issue = linear_processor_service.unlink_issue(link_id)
|
||||
|
||||
if issue[:error]
|
||||
render json: { error: issue[:error] }, status: :unprocessable_entity
|
||||
else
|
||||
render json: issue[:data], status: :ok
|
||||
end
|
||||
end
|
||||
|
||||
def linked_issues
|
||||
issues = linear_processor_service.linked_issues(conversation_link)
|
||||
|
||||
if issues[:error]
|
||||
render json: { error: issues[:error] }, status: :unprocessable_entity
|
||||
else
|
||||
render json: issues[:data], status: :ok
|
||||
end
|
||||
end
|
||||
|
||||
def search_issue
|
||||
render json: { error: 'Specify search string with parameter q' }, status: :unprocessable_entity if params[:q].blank? && return
|
||||
|
||||
term = params[:q]
|
||||
issues = linear_processor_service.search_issue(term)
|
||||
if issues[:error]
|
||||
render json: { error: issues[:error] }, status: :unprocessable_entity
|
||||
else
|
||||
render json: issues[:data], status: :ok
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def conversation_link
|
||||
"#{ENV.fetch('FRONTEND_URL', nil)}/app/accounts/#{Current.account.id}/conversations/#{@conversation.display_id}"
|
||||
end
|
||||
|
||||
def fetch_conversation
|
||||
@conversation = Current.account.conversations.find_by!(display_id: permitted_params[:conversation_id])
|
||||
end
|
||||
|
||||
def linear_processor_service
|
||||
Integrations::Linear::ProcessorService.new(account: Current.account)
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:team_id, :project_id, :conversation_id, :issue_id, :link_id, :title, :description, :assignee_id, :priority, label_ids: [])
|
||||
end
|
||||
end
|
||||
@@ -12,8 +12,8 @@ class Api::V1::Accounts::Microsoft::AuthorizationsController < Api::V1::Accounts
|
||||
}
|
||||
)
|
||||
if redirect_url
|
||||
cache_key = "microsoft::#{email.downcase}"
|
||||
::Redis::Alfred.setex(cache_key, Current.account.id, 5.minutes)
|
||||
email = email.downcase
|
||||
::Redis::Alfred.setex(email, Current.account.id, 5.minutes)
|
||||
render json: { success: true, url: redirect_url }
|
||||
else
|
||||
render json: { success: false }, status: :unprocessable_entity
|
||||
|
||||
@@ -33,10 +33,10 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
|
||||
end
|
||||
|
||||
def transcript
|
||||
if conversation.present? && conversation.contact.present? && conversation.contact.email.present?
|
||||
if permitted_params[:email].present? && conversation.present?
|
||||
ConversationReplyMailer.with(account: conversation.account).conversation_transcript(
|
||||
conversation,
|
||||
conversation.contact.email
|
||||
permitted_params[:email]
|
||||
)&.deliver_later
|
||||
end
|
||||
head :ok
|
||||
|
||||
@@ -5,17 +5,19 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
||||
before_action :check_authorization
|
||||
|
||||
def index
|
||||
builder = V2::Reports::Conversations::ReportBuilder.new(Current.account, report_params)
|
||||
data = builder.timeseries
|
||||
builder = V2::ReportBuilder.new(Current.account, report_params)
|
||||
data = builder.build
|
||||
render json: data
|
||||
end
|
||||
|
||||
def summary
|
||||
render json: build_summary(:summary)
|
||||
render json: summary_metrics
|
||||
end
|
||||
|
||||
def bot_summary
|
||||
render json: build_summary(:bot_summary)
|
||||
summary = V2::ReportBuilder.new(Current.account, current_summary_params).bot_summary
|
||||
summary[:previous] = V2::ReportBuilder.new(Current.account, previous_summary_params).bot_summary
|
||||
render json: summary
|
||||
end
|
||||
|
||||
def agents
|
||||
@@ -124,11 +126,10 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
||||
}
|
||||
end
|
||||
|
||||
def build_summary(method)
|
||||
builder = V2::Reports::Conversations::MetricBuilder
|
||||
current_summary = builder.new(Current.account, current_summary_params).send(method)
|
||||
previous_summary = builder.new(Current.account, previous_summary_params).send(method)
|
||||
current_summary.merge(previous: previous_summary)
|
||||
def summary_metrics
|
||||
summary = V2::ReportBuilder.new(Current.account, current_summary_params).summary
|
||||
summary[:previous] = V2::ReportBuilder.new(Current.account, previous_summary_params).summary
|
||||
summary
|
||||
end
|
||||
|
||||
def conversation_metrics
|
||||
|
||||
@@ -25,4 +25,3 @@ class ApplicationController < ActionController::Base
|
||||
}
|
||||
end
|
||||
end
|
||||
ApplicationController.include_mod_with('Concerns::ApplicationControllerConcern')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module AccessTokenAuthHelper
|
||||
BOT_ACCESSIBLE_ENDPOINTS = {
|
||||
'api/v1/accounts/conversations' => %w[toggle_status toggle_priority create update],
|
||||
'api/v1/accounts/conversations' => %w[toggle_status toggle_priority create],
|
||||
'api/v1/accounts/conversations/messages' => ['create'],
|
||||
'api/v1/accounts/conversations/assignments' => ['create']
|
||||
}.freeze
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
module DomainHelper
|
||||
def self.chatwoot_domain?(domain = request.host)
|
||||
[URI.parse(ENV.fetch('FRONTEND_URL', '')).host, URI.parse(ENV.fetch('HELPCENTER_URL', '')).host].include?(domain)
|
||||
end
|
||||
end
|
||||
@@ -1,20 +0,0 @@
|
||||
module GoogleConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def google_client
|
||||
app_id = GlobalConfigService.load('GOOGLE_OAUTH_CLIENT_ID', nil)
|
||||
app_secret = GlobalConfigService.load('GOOGLE_OAUTH_CLIENT_SECRET', nil)
|
||||
|
||||
::OAuth2::Client.new(app_id, app_secret, {
|
||||
site: 'https://oauth2.googleapis.com',
|
||||
authorize_url: 'https://accounts.google.com/o/oauth2/auth',
|
||||
token_url: 'https://accounts.google.com/o/oauth2/token'
|
||||
})
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def base_url
|
||||
ENV.fetch('FRONTEND_URL', 'http://localhost:3000')
|
||||
end
|
||||
end
|
||||
@@ -2,10 +2,7 @@ module MicrosoftConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def microsoft_client
|
||||
app_id = GlobalConfigService.load('AZURE_APP_ID', nil)
|
||||
app_secret = GlobalConfigService.load('AZURE_APP_SECRET', nil)
|
||||
|
||||
::OAuth2::Client.new(app_id, app_secret,
|
||||
::OAuth2::Client.new(ENV.fetch('AZURE_APP_ID', nil), ENV.fetch('AZURE_APP_SECRET', nil),
|
||||
{
|
||||
site: 'https://login.microsoftonline.com',
|
||||
authorize_url: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
|
||||
@@ -15,6 +12,10 @@ module MicrosoftConcern
|
||||
|
||||
private
|
||||
|
||||
def parsed_body
|
||||
@parsed_body ||= Rack::Utils.parse_nested_query(@response.raw_response.body)
|
||||
end
|
||||
|
||||
def base_url
|
||||
ENV.fetch('FRONTEND_URL', 'http://localhost:3000')
|
||||
end
|
||||
|
||||
@@ -6,7 +6,6 @@ module SwitchLocale
|
||||
def switch_locale(&)
|
||||
# priority is for locale set in query string (mostly for widget/from js sdk)
|
||||
locale ||= locale_from_params
|
||||
locale ||= locale_from_custom_domain
|
||||
# if locale is not set in account, let's use DEFAULT_LOCALE env variable
|
||||
locale ||= locale_from_env_variable
|
||||
set_locale(locale, &)
|
||||
@@ -17,20 +16,6 @@ module SwitchLocale
|
||||
set_locale(locale, &)
|
||||
end
|
||||
|
||||
# If the request is coming from a custom domain, it should be for a helpcenter portal
|
||||
# We will use the portal locale in such cases
|
||||
def locale_from_custom_domain(&)
|
||||
return if params[:locale]
|
||||
|
||||
domain = request.host
|
||||
return if DomainHelper.chatwoot_domain?(domain)
|
||||
|
||||
@portal = Portal.find_by(custom_domain: domain)
|
||||
return unless @portal
|
||||
|
||||
@portal.default_locale
|
||||
end
|
||||
|
||||
def set_locale(locale, &)
|
||||
# if locale is empty, use default_locale
|
||||
locale ||= I18n.default_locale
|
||||
|
||||
@@ -3,7 +3,6 @@ class DashboardController < ActionController::Base
|
||||
|
||||
before_action :set_application_pack
|
||||
before_action :set_global_config
|
||||
before_action :set_dashboard_scripts
|
||||
around_action :switch_locale
|
||||
before_action :ensure_installation_onboarding, only: [:index]
|
||||
before_action :render_hc_if_custom_domain, only: [:index]
|
||||
@@ -19,7 +18,6 @@ class DashboardController < ActionController::Base
|
||||
'LOGO', 'LOGO_DARK', 'LOGO_THUMBNAIL',
|
||||
'INSTALLATION_NAME',
|
||||
'WIDGET_BRAND_URL', 'TERMS_URL',
|
||||
'BRAND_URL', 'BRAND_NAME',
|
||||
'PRIVACY_URL',
|
||||
'DISPLAY_MANIFEST',
|
||||
'CREATE_NEW_ACCOUNT_FROM_DASHBOARD',
|
||||
@@ -36,10 +34,6 @@ class DashboardController < ActionController::Base
|
||||
).merge(app_config)
|
||||
end
|
||||
|
||||
def set_dashboard_scripts
|
||||
@dashboard_scripts = sensitive_path? ? nil : GlobalConfig.get_value('DASHBOARD_SCRIPTS')
|
||||
end
|
||||
|
||||
def ensure_installation_onboarding
|
||||
redirect_to '/installation/onboarding' if ::Redis::Alfred.get(::Redis::Alfred::CHATWOOT_INSTALLATION_ONBOARDING)
|
||||
end
|
||||
@@ -63,7 +57,7 @@ class DashboardController < ActionController::Base
|
||||
FB_APP_ID: GlobalConfigService.load('FB_APP_ID', ''),
|
||||
FACEBOOK_API_VERSION: GlobalConfigService.load('FACEBOOK_API_VERSION', 'v17.0'),
|
||||
IS_ENTERPRISE: ChatwootApp.enterprise?,
|
||||
AZURE_APP_ID: GlobalConfigService.load('AZURE_APP_ID', ''),
|
||||
AZURE_APP_ID: ENV.fetch('AZURE_APP_ID', ''),
|
||||
GIT_SHA: GIT_HASH
|
||||
}
|
||||
end
|
||||
@@ -75,14 +69,4 @@ class DashboardController < ActionController::Base
|
||||
'application'
|
||||
end
|
||||
end
|
||||
|
||||
def sensitive_path?
|
||||
# dont load dashboard scripts on sensitive paths like password reset
|
||||
sensitive_paths = [edit_user_password_path].freeze
|
||||
|
||||
# remove app prefix
|
||||
current_path = request.path.gsub(%r{^/app}, '')
|
||||
|
||||
sensitive_paths.include?(current_path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,10 +4,6 @@ class DeviseOverrides::SessionsController < DeviseTokenAuth::SessionsController
|
||||
wrap_parameters format: []
|
||||
before_action :process_sso_auth_token, only: [:create]
|
||||
|
||||
def new
|
||||
redirect_to login_page_url(error: 'access-denied')
|
||||
end
|
||||
|
||||
def create
|
||||
# Authenticate user via the temporary sso auth token
|
||||
if params[:sso_auth_token].present? && @resource.present?
|
||||
@@ -25,12 +21,6 @@ class DeviseOverrides::SessionsController < DeviseTokenAuth::SessionsController
|
||||
|
||||
private
|
||||
|
||||
def login_page_url(error: nil)
|
||||
frontend_url = ENV.fetch('FRONTEND_URL', nil)
|
||||
|
||||
"#{frontend_url}/app/login?error=#{error}"
|
||||
end
|
||||
|
||||
def authenticate_resource_with_sso_token
|
||||
@token = @resource.create_token
|
||||
@resource.save!
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
class Google::CallbacksController < OauthCallbackController
|
||||
include GoogleConcern
|
||||
|
||||
private
|
||||
|
||||
def provider_name
|
||||
'google'
|
||||
end
|
||||
|
||||
def imap_address
|
||||
'imap.gmail.com'
|
||||
end
|
||||
|
||||
def oauth_client
|
||||
# from GoogleConcern
|
||||
google_client
|
||||
end
|
||||
end
|
||||
@@ -1,17 +1,77 @@
|
||||
class Microsoft::CallbacksController < OauthCallbackController
|
||||
class Microsoft::CallbacksController < ApplicationController
|
||||
include MicrosoftConcern
|
||||
|
||||
def show
|
||||
@response = microsoft_client.auth_code.get_token(
|
||||
oauth_code,
|
||||
redirect_uri: "#{base_url}/microsoft/callback"
|
||||
)
|
||||
|
||||
inbox = find_or_create_inbox
|
||||
::Redis::Alfred.delete(users_data['email'].downcase)
|
||||
redirect_to app_microsoft_inbox_agents_url(account_id: account.id, inbox_id: inbox.id)
|
||||
rescue StandardError => e
|
||||
ChatwootExceptionTracker.new(e).capture_exception
|
||||
redirect_to '/'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def oauth_client
|
||||
microsoft_client
|
||||
def oauth_code
|
||||
params[:code]
|
||||
end
|
||||
|
||||
def provider_name
|
||||
'microsoft'
|
||||
def users_data
|
||||
decoded_token = JWT.decode parsed_body[:id_token], nil, false
|
||||
decoded_token[0]
|
||||
end
|
||||
|
||||
def imap_address
|
||||
'outlook.office365.com'
|
||||
def parsed_body
|
||||
@parsed_body ||= @response.response.parsed
|
||||
end
|
||||
|
||||
def account_id
|
||||
::Redis::Alfred.get(users_data['email'].downcase)
|
||||
end
|
||||
|
||||
def account
|
||||
@account ||= Account.find(account_id)
|
||||
end
|
||||
|
||||
def find_or_create_inbox
|
||||
channel_email = Channel::Email.find_by(email: users_data['email'], account: account)
|
||||
channel_email ||= create_microsoft_channel_with_inbox
|
||||
update_microsoft_channel(channel_email)
|
||||
channel_email.inbox
|
||||
end
|
||||
|
||||
# Fallback name, for when name field is missing from users_data
|
||||
def fallback_name
|
||||
users_data['email'].split('@').first.parameterize.titleize
|
||||
end
|
||||
|
||||
def create_microsoft_channel_with_inbox
|
||||
ActiveRecord::Base.transaction do
|
||||
channel_email = Channel::Email.create!(email: users_data['email'], account: account)
|
||||
account.inboxes.create!(
|
||||
account: account,
|
||||
channel: channel_email,
|
||||
name: users_data['name'] || fallback_name
|
||||
)
|
||||
channel_email
|
||||
end
|
||||
end
|
||||
|
||||
def update_microsoft_channel(channel_email)
|
||||
channel_email.update!({
|
||||
imap_login: users_data['email'], imap_address: 'outlook.office365.com',
|
||||
imap_port: '993', imap_enabled: true,
|
||||
provider: 'microsoft',
|
||||
provider_config: {
|
||||
access_token: parsed_body['access_token'],
|
||||
refresh_token: parsed_body['refresh_token'],
|
||||
expires_on: (Time.current.utc + 1.hour).to_s
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,6 +12,6 @@ class MicrosoftController < ApplicationController
|
||||
end
|
||||
|
||||
def microsoft_indentity
|
||||
@identity_json = GlobalConfigService.load('AZURE_APP_ID', nil)
|
||||
@identity_json = ENV.fetch('AZURE_APP_ID', nil)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
class OauthCallbackController < ApplicationController
|
||||
def show
|
||||
@response = oauth_client.auth_code.get_token(
|
||||
oauth_code,
|
||||
redirect_uri: "#{base_url}/#{provider_name}/callback"
|
||||
)
|
||||
|
||||
handle_response
|
||||
::Redis::Alfred.delete(cache_key)
|
||||
rescue StandardError => e
|
||||
ChatwootExceptionTracker.new(e).capture_exception
|
||||
redirect_to '/'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def handle_response
|
||||
inbox, already_exists = find_or_create_inbox
|
||||
|
||||
if already_exists
|
||||
redirect_to app_email_inbox_settings_url(account_id: account.id, inbox_id: inbox.id)
|
||||
else
|
||||
redirect_to app_email_inbox_agents_url(account_id: account.id, inbox_id: inbox.id)
|
||||
end
|
||||
end
|
||||
|
||||
def find_or_create_inbox
|
||||
channel_email = Channel::Email.find_by(email: users_data['email'], account: account)
|
||||
# we need this value to know where to redirect on sucessful processing of the callback
|
||||
channel_exists = channel_email.present?
|
||||
|
||||
channel_email ||= create_channel_with_inbox
|
||||
update_channel(channel_email)
|
||||
|
||||
# reauthorize channel, this code path only triggers when microsoft auth is successful
|
||||
# reauthorized will also update cache keys for the associated inbox
|
||||
channel_email.reauthorized!
|
||||
|
||||
[channel_email.inbox, channel_exists]
|
||||
end
|
||||
|
||||
def update_channel(channel_email)
|
||||
channel_email.update!({
|
||||
imap_login: users_data['email'], imap_address: imap_address,
|
||||
imap_port: '993', imap_enabled: true,
|
||||
provider: provider_name,
|
||||
provider_config: {
|
||||
access_token: parsed_body['access_token'],
|
||||
refresh_token: parsed_body['refresh_token'],
|
||||
expires_on: (Time.current.utc + 1.hour).to_s
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
def provider_name
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def oauth_client
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def cache_key
|
||||
"#{provider_name}::#{users_data['email'].downcase}"
|
||||
end
|
||||
|
||||
def create_channel_with_inbox
|
||||
ActiveRecord::Base.transaction do
|
||||
channel_email = Channel::Email.create!(email: users_data['email'], account: account)
|
||||
account.inboxes.create!(
|
||||
account: account,
|
||||
channel: channel_email,
|
||||
name: users_data['name'] || fallback_name
|
||||
)
|
||||
channel_email
|
||||
end
|
||||
end
|
||||
|
||||
def users_data
|
||||
decoded_token = JWT.decode parsed_body[:id_token], nil, false
|
||||
decoded_token[0]
|
||||
end
|
||||
|
||||
def account_id
|
||||
::Redis::Alfred.get(cache_key)
|
||||
end
|
||||
|
||||
def account
|
||||
@account ||= Account.find(account_id)
|
||||
end
|
||||
|
||||
# Fallback name, for when name field is missing from users_data
|
||||
def fallback_name
|
||||
users_data['email'].split('@').first.parameterize.titleize
|
||||
end
|
||||
|
||||
def oauth_code
|
||||
params[:code]
|
||||
end
|
||||
|
||||
def base_url
|
||||
ENV.fetch('FRONTEND_URL', 'http://localhost:3000')
|
||||
end
|
||||
|
||||
def parsed_body
|
||||
@parsed_body ||= @response.response.parsed
|
||||
end
|
||||
end
|
||||
@@ -7,7 +7,7 @@ class Public::Api::V1::Portals::ArticlesController < Public::Api::V1::Portals::B
|
||||
|
||||
def index
|
||||
@articles = @portal.articles
|
||||
search_articles
|
||||
@articles = @articles.search(list_params) if list_params.present?
|
||||
order_by_sort_param
|
||||
@articles.page(list_params[:page]) if list_params[:page].present?
|
||||
end
|
||||
@@ -16,10 +16,6 @@ class Public::Api::V1::Portals::ArticlesController < Public::Api::V1::Portals::B
|
||||
|
||||
private
|
||||
|
||||
def search_articles
|
||||
@articles = @articles.search(list_params) if list_params.present?
|
||||
end
|
||||
|
||||
def order_by_sort_param
|
||||
@articles = if list_params[:sort].present? && list_params[:sort] == 'views'
|
||||
@articles.order_by_views
|
||||
@@ -55,5 +51,3 @@ class Public::Api::V1::Portals::ArticlesController < Public::Api::V1::Portals::B
|
||||
ChatwootMarkdownRenderer.new(content).render_article
|
||||
end
|
||||
end
|
||||
|
||||
Public::Api::V1::Portals::ArticlesController.prepend_mod_with('Public::Api::V1::Portals::ArticlesController')
|
||||
|
||||
@@ -47,7 +47,7 @@ class Public::Api::V1::Portals::BaseController < PublicController
|
||||
@locale = if article.category.present?
|
||||
article.category.locale
|
||||
else
|
||||
article.portal.default_locale
|
||||
'en'
|
||||
end
|
||||
|
||||
I18n.with_locale(@locale, &)
|
||||
|
||||
@@ -6,12 +6,6 @@ class Public::Api::V1::PortalsController < Public::Api::V1::Portals::BaseControl
|
||||
|
||||
def show; end
|
||||
|
||||
def sitemap
|
||||
@help_center_url = @portal.custom_domain || ChatwootApp.help_center_root
|
||||
# if help_center_url does not contain a protocol, prepend it with https
|
||||
@help_center_url = "https://#{@help_center_url}" unless @help_center_url.include?('://')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def portal
|
||||
|
||||
@@ -8,7 +8,8 @@ class PublicController < ActionController::Base
|
||||
|
||||
def ensure_custom_domain_request
|
||||
domain = request.host
|
||||
return if DomainHelper.chatwoot_domain?(domain)
|
||||
|
||||
return if [URI.parse(ENV.fetch('FRONTEND_URL', '')).host, URI.parse(ENV.fetch('HELPCENTER_URL', '')).host].include?(domain)
|
||||
|
||||
@portal = ::Portal.find_by(custom_domain: domain)
|
||||
return if @portal.present?
|
||||
|
||||
@@ -35,12 +35,10 @@ class SuperAdmin::AppConfigsController < SuperAdmin::ApplicationController
|
||||
@allowed_configs = case @config
|
||||
when 'facebook'
|
||||
%w[FB_APP_ID FB_VERIFY_TOKEN FB_APP_SECRET IG_VERIFY_TOKEN FACEBOOK_API_VERSION ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT]
|
||||
when 'microsoft'
|
||||
%w[AZURE_APP_ID AZURE_APP_SECRET]
|
||||
when 'email'
|
||||
['MAILER_INBOUND_EMAIL_DOMAIN']
|
||||
else
|
||||
%w[ENABLE_ACCOUNT_SIGNUP FIREBASE_PROJECT_ID FIREBASE_CREDENTIALS]
|
||||
%w[ENABLE_ACCOUNT_SIGNUP]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,15 +10,14 @@ class AsyncDispatcher < BaseDispatcher
|
||||
|
||||
def listeners
|
||||
[
|
||||
AutomationRuleListener.instance,
|
||||
CampaignListener.instance,
|
||||
CsatSurveyListener.instance,
|
||||
HookListener.instance,
|
||||
InstallationWebhookListener.instance,
|
||||
NotificationListener.instance,
|
||||
ParticipationListener.instance,
|
||||
ReportingEventListener.instance,
|
||||
WebhookListener.instance
|
||||
WebhookListener.instance,
|
||||
AutomationRuleListener.instance
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -163,14 +163,10 @@ class ConversationFinder
|
||||
params[:page] || 1
|
||||
end
|
||||
|
||||
def conversations_base_query
|
||||
@conversations.includes(
|
||||
def conversations
|
||||
@conversations = @conversations.includes(
|
||||
:taggings, :inbox, { assignee: { avatar_attachment: [:blob] } }, { contact: { avatar_attachment: [:blob] } }, :team, :contact_inbox
|
||||
)
|
||||
end
|
||||
|
||||
def conversations
|
||||
@conversations = conversations_base_query
|
||||
|
||||
sort_by, sort_order = SORT_OPTIONS[params[:sort_by]] || SORT_OPTIONS['last_activity_at_desc']
|
||||
@conversations = @conversations.send(sort_by, sort_order)
|
||||
@@ -182,4 +178,3 @@ class ConversationFinder
|
||||
end
|
||||
end
|
||||
end
|
||||
ConversationFinder.prepend_mod_with('ConversationFinder')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module Api::V2::Accounts::ReportsHelper
|
||||
def generate_agents_report
|
||||
Current.account.users.map do |agent|
|
||||
agent_report = report_builder({ type: :agent, id: agent.id }).summary
|
||||
agent_report = generate_report({ type: :agent, id: agent.id })
|
||||
[agent.name] + generate_readable_report_metrics(agent_report)
|
||||
end
|
||||
end
|
||||
@@ -15,7 +15,7 @@ module Api::V2::Accounts::ReportsHelper
|
||||
|
||||
def generate_teams_report
|
||||
Current.account.teams.map do |team|
|
||||
team_report = report_builder({ type: :team, id: team.id }).summary
|
||||
team_report = generate_report({ type: :team, id: team.id })
|
||||
[team.name] + generate_readable_report_metrics(team_report)
|
||||
end
|
||||
end
|
||||
@@ -27,7 +27,7 @@ module Api::V2::Accounts::ReportsHelper
|
||||
end
|
||||
end
|
||||
|
||||
def report_builder(report_params)
|
||||
def generate_report(report_params)
|
||||
V2::ReportBuilder.new(
|
||||
Current.account,
|
||||
report_params.merge(
|
||||
@@ -37,11 +37,7 @@ module Api::V2::Accounts::ReportsHelper
|
||||
business_hours: ActiveModel::Type::Boolean.new.cast(params[:business_hours])
|
||||
}
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def generate_report(report_params)
|
||||
report_builder(report_params).short_summary
|
||||
).short_summary
|
||||
end
|
||||
|
||||
private
|
||||
@@ -50,9 +46,7 @@ module Api::V2::Accounts::ReportsHelper
|
||||
[
|
||||
report_metric[:conversations_count],
|
||||
Reports::TimeFormatPresenter.new(report_metric[:avg_first_response_time]).format,
|
||||
Reports::TimeFormatPresenter.new(report_metric[:avg_resolution_time]).format,
|
||||
Reports::TimeFormatPresenter.new(report_metric[:reply_time]).format,
|
||||
report_metric[:resolutions_count]
|
||||
Reports::TimeFormatPresenter.new(report_metric[:avg_resolution_time]).format
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,11 +2,4 @@ module ApplicationHelper
|
||||
def available_locales_with_name
|
||||
LANGUAGES_CONFIG.map { |_key, val| val.slice(:name, :iso_639_1_code) }
|
||||
end
|
||||
|
||||
def feature_help_urls
|
||||
features = YAML.safe_load(Rails.root.join('config/features.yml').read).freeze
|
||||
features.each_with_object({}) do |feature, hash|
|
||||
hash[feature['name']] = feature['help_url'] if feature['help_url']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
module FilterHelper
|
||||
def build_condition_query(model_filters, query_hash, current_index)
|
||||
current_filter = model_filters[query_hash['attribute_key']]
|
||||
|
||||
# Throw InvalidOperator Error if the attribute is a standard attribute
|
||||
# and the operator is not allowed in the config
|
||||
if current_filter.present? && current_filter['filter_operators'].exclude?(query_hash[:filter_operator])
|
||||
raise CustomExceptions::CustomFilter::InvalidOperator.new(
|
||||
attribute_name: query_hash['attribute_key'],
|
||||
allowed_keys: current_filter['filter_operators']
|
||||
)
|
||||
end
|
||||
|
||||
# Every other filter expects a value to be present
|
||||
if %w[is_present is_not_present].exclude?(query_hash[:filter_operator]) && query_hash['values'].blank?
|
||||
raise CustomExceptions::CustomFilter::InvalidValue.new(attribute_name: query_hash['attribute_key'])
|
||||
end
|
||||
|
||||
condition_query = build_condition_query_string(current_filter, query_hash, current_index)
|
||||
# The query becomes empty only when it doesn't match to any supported
|
||||
# standard attribute or custom attribute defined in the account.
|
||||
if condition_query.empty?
|
||||
raise CustomExceptions::CustomFilter::InvalidAttribute.new(key: query_hash['attribute_key'],
|
||||
allowed_keys: model_filters.keys)
|
||||
end
|
||||
|
||||
condition_query
|
||||
end
|
||||
|
||||
def build_condition_query_string(current_filter, query_hash, current_index)
|
||||
filter_operator_value = filter_operation(query_hash, current_index)
|
||||
|
||||
return handle_nil_filter(query_hash, current_index) if current_filter.nil?
|
||||
|
||||
case current_filter['attribute_type']
|
||||
when 'additional_attributes'
|
||||
handle_additional_attributes(query_hash, filter_operator_value, current_filter['data_type'])
|
||||
else
|
||||
handle_standard_attributes(current_filter, query_hash, current_index, filter_operator_value)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_nil_filter(query_hash, current_index)
|
||||
attribute_type = "#{filter_config[:entity].downcase}_attribute"
|
||||
custom_attribute_query(query_hash, attribute_type, current_index)
|
||||
end
|
||||
|
||||
def handle_additional_attributes(query_hash, filter_operator_value, data_type)
|
||||
if data_type == 'text_case_insensitive'
|
||||
"LOWER(#{filter_config[:table_name]}.additional_attributes ->> '#{query_hash[:attribute_key]}') " \
|
||||
"#{filter_operator_value} #{query_hash[:query_operator]}"
|
||||
else
|
||||
"#{filter_config[:table_name]}.additional_attributes ->> '#{query_hash[:attribute_key]}' " \
|
||||
"#{filter_operator_value} #{query_hash[:query_operator]} "
|
||||
end
|
||||
end
|
||||
|
||||
def handle_standard_attributes(current_filter, query_hash, current_index, filter_operator_value)
|
||||
case current_filter['data_type']
|
||||
when 'date'
|
||||
date_filter(current_filter, query_hash, filter_operator_value)
|
||||
when 'labels'
|
||||
tag_filter_query(query_hash, current_index)
|
||||
when 'text_case_insensitive'
|
||||
text_case_insensitive_filter(query_hash, filter_operator_value)
|
||||
else
|
||||
default_filter(query_hash, filter_operator_value)
|
||||
end
|
||||
end
|
||||
|
||||
def date_filter(current_filter, query_hash, filter_operator_value)
|
||||
"(#{filter_config[:table_name]}.#{query_hash[:attribute_key]})::#{current_filter['data_type']} " \
|
||||
"#{filter_operator_value}#{current_filter['data_type']} #{query_hash[:query_operator]}"
|
||||
end
|
||||
|
||||
def text_case_insensitive_filter(query_hash, filter_operator_value)
|
||||
"LOWER(#{filter_config[:table_name]}.#{query_hash[:attribute_key]}) " \
|
||||
"#{filter_operator_value} #{query_hash[:query_operator]}"
|
||||
end
|
||||
|
||||
def default_filter(query_hash, filter_operator_value)
|
||||
"#{filter_config[:table_name]}.#{query_hash[:attribute_key]} #{filter_operator_value} #{query_hash[:query_operator]}"
|
||||
end
|
||||
end
|
||||
@@ -1,19 +0,0 @@
|
||||
module TimezoneHelper
|
||||
# ActiveSupport TimeZone is not aware of the current time, so ActiveSupport::Timezone[offset]
|
||||
# would return the timezone without considering day light savings. To get the correct timezone,
|
||||
# this method uses zone.now.utc_offset for comparison as referenced in the issues below
|
||||
#
|
||||
# https://github.com/rails/rails/pull/22243
|
||||
# https://github.com/rails/rails/issues/21501
|
||||
# https://github.com/rails/rails/issues/7297
|
||||
def timezone_name_from_offset(offset)
|
||||
return 'UTC' if offset.blank?
|
||||
|
||||
offset_in_seconds = offset.to_f * 3600
|
||||
matching_zone = ActiveSupport::TimeZone.all.find do |zone|
|
||||
zone.now.utc_offset == offset_in_seconds
|
||||
end
|
||||
|
||||
return matching_zone.name if matching_zone
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,11 @@
|
||||
import App from './App';
|
||||
import '../../test-matchers';
|
||||
|
||||
describe(`App component`, () => {
|
||||
it(`should be a component`, () => {
|
||||
// Arrange
|
||||
// Act
|
||||
expect(App).toBeVueComponent('App');
|
||||
// Assert
|
||||
});
|
||||
});
|
||||
@@ -27,7 +27,6 @@
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import router from '../dashboard/routes';
|
||||
import AddAccountModal from '../dashboard/components/layout/sidebarComponents/AddAccountModal.vue';
|
||||
import LoadingState from './components/widgets/LoadingState.vue';
|
||||
import NetworkNotification from './components/NetworkNotification.vue';
|
||||
@@ -44,7 +43,6 @@ import {
|
||||
registerSubscription,
|
||||
verifyServiceWorkerExistence,
|
||||
} from './helper/pushHelper';
|
||||
import ReconnectService from 'dashboard/helper/ReconnectService';
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
@@ -66,7 +64,6 @@ export default {
|
||||
return {
|
||||
showAddAccountModal: false,
|
||||
latestChatwootVersion: null,
|
||||
reconnectService: null,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -105,11 +102,6 @@ export default {
|
||||
this.listenToThemeChanges();
|
||||
this.setLocale(window.chatwootConfig.selectedLocale);
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.reconnectService) {
|
||||
this.reconnectService.disconnect();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initializeColorTheme() {
|
||||
setColorTheme(window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
@@ -133,7 +125,6 @@ export default {
|
||||
this.updateRTLDirectionView(locale);
|
||||
this.latestChatwootVersion = latestChatwootVersion;
|
||||
vueActionCable.init(pubsubToken);
|
||||
this.reconnectService = new ReconnectService(this.$store, router);
|
||||
|
||||
verifyServiceWorkerExistence(registration =>
|
||||
registration.pushManager.getSubscription().then(subscription => {
|
||||
|
||||
@@ -9,13 +9,6 @@ class AccountAPI extends ApiClient {
|
||||
createAccount(data) {
|
||||
return axios.post(`${this.apiVersion}/accounts`, data);
|
||||
}
|
||||
|
||||
async getCacheKeys() {
|
||||
const response = await axios.get(
|
||||
`/api/v1/accounts/${this.accountIdFromRoute}/cache_keys`
|
||||
);
|
||||
return response.data.cache_keys;
|
||||
}
|
||||
}
|
||||
|
||||
export default new AccountAPI();
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
/* global axios */
|
||||
import ApiClient from '../ApiClient';
|
||||
|
||||
class MicrosoftClient extends ApiClient {
|
||||
constructor() {
|
||||
super('google', { accountScoped: true });
|
||||
}
|
||||
|
||||
generateAuthorization(payload) {
|
||||
return axios.post(`${this.url}/authorization`, payload);
|
||||
}
|
||||
}
|
||||
|
||||
export default new MicrosoftClient();
|
||||
@@ -77,8 +77,8 @@ class ContactAPI extends ApiClient {
|
||||
return axios.delete(`${this.url}/${contactId}/avatar`);
|
||||
}
|
||||
|
||||
exportContacts(queryPayload) {
|
||||
return axios.post(`${this.url}/export`, queryPayload);
|
||||
exportContacts() {
|
||||
return axios.get(`${this.url}/export`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,10 +15,10 @@ describe('#enterpriseAccountAPI', () => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -15,7 +15,6 @@ class ConversationApi extends ApiClient {
|
||||
teamId,
|
||||
conversationType,
|
||||
sortBy,
|
||||
updatedWithin,
|
||||
}) {
|
||||
return axios.get(this.url, {
|
||||
params: {
|
||||
@@ -27,7 +26,6 @@ class ConversationApi extends ApiClient {
|
||||
labels,
|
||||
conversation_type: conversationType,
|
||||
sort_by: sortBy,
|
||||
updated_within: updatedWithin,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
/* global axios */
|
||||
|
||||
import ApiClient from '../ApiClient';
|
||||
|
||||
class LinearAPI extends ApiClient {
|
||||
constructor() {
|
||||
super('integrations/linear', { accountScoped: true });
|
||||
}
|
||||
|
||||
getTeams() {
|
||||
return axios.get(`${this.url}/teams`);
|
||||
}
|
||||
|
||||
getTeamEntities(teamId) {
|
||||
return axios.get(`${this.url}/team_entities?team_id=${teamId}`);
|
||||
}
|
||||
|
||||
createIssue(data) {
|
||||
return axios.post(`${this.url}/create_issue`, data);
|
||||
}
|
||||
|
||||
link_issue(conversationId, issueId, title) {
|
||||
return axios.post(`${this.url}/link_issue`, {
|
||||
issue_id: issueId,
|
||||
conversation_id: conversationId,
|
||||
title: title,
|
||||
});
|
||||
}
|
||||
|
||||
getLinkedIssue(conversationId) {
|
||||
return axios.get(
|
||||
`${this.url}/linked_issues?conversation_id=${conversationId}`
|
||||
);
|
||||
}
|
||||
|
||||
unlinkIssue(linkId) {
|
||||
return axios.post(`${this.url}/unlink_issue`, {
|
||||
link_id: linkId,
|
||||
});
|
||||
}
|
||||
|
||||
searchIssues(query) {
|
||||
return axios.get(`${this.url}/search_issue?q=${query}`);
|
||||
}
|
||||
}
|
||||
|
||||
export default new LinearAPI();
|
||||
@@ -84,24 +84,6 @@ class ReportsAPI extends ApiClient {
|
||||
params: { since, until, business_hours: businessHours },
|
||||
});
|
||||
}
|
||||
|
||||
getBotMetrics({ from, to } = {}) {
|
||||
return axios.get(`${this.url}/bot_metrics`, {
|
||||
params: { since: from, until: to },
|
||||
});
|
||||
}
|
||||
|
||||
getBotSummary({ from, to, groupBy, businessHours } = {}) {
|
||||
return axios.get(`${this.url}/bot_summary`, {
|
||||
params: {
|
||||
since: from,
|
||||
until: to,
|
||||
type: 'account',
|
||||
group_by: groupBy,
|
||||
business_hours: businessHours,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new ReportsAPI();
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
/* global axios */
|
||||
import ApiClient from './ApiClient';
|
||||
|
||||
class SLAReportsAPI extends ApiClient {
|
||||
constructor() {
|
||||
super('applied_slas', { accountScoped: true });
|
||||
}
|
||||
|
||||
get({
|
||||
from,
|
||||
to,
|
||||
assigned_agent_id,
|
||||
inbox_id,
|
||||
team_id,
|
||||
sla_policy_id,
|
||||
label_list,
|
||||
page,
|
||||
} = {}) {
|
||||
return axios.get(this.url, {
|
||||
params: {
|
||||
since: from,
|
||||
until: to,
|
||||
assigned_agent_id,
|
||||
inbox_id,
|
||||
team_id,
|
||||
sla_policy_id,
|
||||
label_list,
|
||||
page,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
download({
|
||||
from,
|
||||
to,
|
||||
assigned_agent_id,
|
||||
inbox_id,
|
||||
team_id,
|
||||
sla_policy_id,
|
||||
label_list,
|
||||
} = {}) {
|
||||
return axios.get(`${this.url}/download`, {
|
||||
params: {
|
||||
since: from,
|
||||
until: to,
|
||||
assigned_agent_id,
|
||||
inbox_id,
|
||||
team_id,
|
||||
label_list,
|
||||
sla_policy_id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getMetrics({
|
||||
from,
|
||||
to,
|
||||
assigned_agent_id,
|
||||
inbox_id,
|
||||
team_id,
|
||||
label_list,
|
||||
sla_policy_id,
|
||||
} = {}) {
|
||||
return axios.get(`${this.url}/metrics`, {
|
||||
params: {
|
||||
since: from,
|
||||
until: to,
|
||||
assigned_agent_id,
|
||||
inbox_id,
|
||||
label_list,
|
||||
team_id,
|
||||
sla_policy_id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new SLAReportsAPI();
|
||||
@@ -15,10 +15,10 @@ describe('#accountAPI', () => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -10,10 +10,10 @@ describe('#ContactsAPI', () => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -14,7 +14,7 @@ describe('#AgentAPI', () => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -14,10 +14,10 @@ describe('#PortalAPI', () => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -44,10 +44,10 @@ describe('#PortalAPI', () => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -71,10 +71,10 @@ describe('#PortalAPI', () => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -98,10 +98,10 @@ describe('#PortalAPI', () => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -129,10 +129,10 @@ describe('#PortalAPI', () => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -4,10 +4,10 @@ describe('#AssignableAgentsAPI', () => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -13,10 +13,10 @@ describe('#FBChannel', () => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -17,10 +17,10 @@ describe('#ContactsAPI', () => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -16,10 +16,10 @@ describe('#ConversationApi', () => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -11,10 +11,10 @@ describe('#Reports API', () => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -24,10 +24,10 @@ describe('#ConversationAPI', () => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -46,7 +46,6 @@ describe('#ConversationAPI', () => {
|
||||
page: 1,
|
||||
labels: [],
|
||||
teamId: 1,
|
||||
updatedWithin: 20,
|
||||
});
|
||||
expect(axiosMock.get).toHaveBeenCalledWith('/api/v1/conversations', {
|
||||
params: {
|
||||
@@ -56,7 +55,6 @@ describe('#ConversationAPI', () => {
|
||||
assignee_type: 'me',
|
||||
page: 1,
|
||||
labels: [],
|
||||
updated_within: 20,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,10 +15,10 @@ describe('#ConversationAPI', () => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -17,10 +17,10 @@ describe('#InboxesAPI', () => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -18,10 +18,10 @@ describe('#integrationAPI', () => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -11,10 +11,10 @@ describe('#accountAPI', () => {
|
||||
describe('createAMeeting', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -39,10 +39,10 @@ describe('#accountAPI', () => {
|
||||
describe('addParticipantToMeeting', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
import LinearAPIClient from '../../integrations/linear';
|
||||
import ApiClient from '../../ApiClient';
|
||||
|
||||
describe('#linearAPI', () => {
|
||||
it('creates correct instance', () => {
|
||||
expect(LinearAPIClient).toBeInstanceOf(ApiClient);
|
||||
expect(LinearAPIClient).toHaveProperty('getTeams');
|
||||
expect(LinearAPIClient).toHaveProperty('getTeamEntities');
|
||||
expect(LinearAPIClient).toHaveProperty('createIssue');
|
||||
expect(LinearAPIClient).toHaveProperty('link_issue');
|
||||
expect(LinearAPIClient).toHaveProperty('getLinkedIssue');
|
||||
expect(LinearAPIClient).toHaveProperty('unlinkIssue');
|
||||
expect(LinearAPIClient).toHaveProperty('searchIssues');
|
||||
});
|
||||
|
||||
describe('getTeams', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('creates a valid request', () => {
|
||||
LinearAPIClient.getTeams();
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/integrations/linear/teams'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTeamEntities', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('creates a valid request', () => {
|
||||
LinearAPIClient.getTeamEntities(1);
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/integrations/linear/team_entities?team_id=1'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createIssue', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('creates a valid request', () => {
|
||||
const issueData = {
|
||||
title: 'New Issue',
|
||||
description: 'Issue description',
|
||||
};
|
||||
LinearAPIClient.createIssue(issueData);
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/integrations/linear/create_issue',
|
||||
issueData
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('link_issue', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('creates a valid request', () => {
|
||||
LinearAPIClient.link_issue(1, 2);
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/integrations/linear/link_issue',
|
||||
{
|
||||
issue_id: 2,
|
||||
conversation_id: 1,
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLinkedIssue', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('creates a valid request', () => {
|
||||
LinearAPIClient.getLinkedIssue(1);
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/integrations/linear/linked_issues?conversation_id=1'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('unlinkIssue', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('creates a valid request', () => {
|
||||
LinearAPIClient.unlinkIssue(1);
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/integrations/linear/unlink_issue',
|
||||
{
|
||||
link_id: 1,
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('searchIssues', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('creates a valid request', () => {
|
||||
LinearAPIClient.searchIssues('query');
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/integrations/linear/search_issue?q=query'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -13,10 +13,10 @@ describe('#NotificationAPI', () => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -20,10 +20,10 @@ describe('#Reports API', () => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -111,40 +111,6 @@ describe('#Reports API', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('#getBotMetrics', () => {
|
||||
reportsAPI.getBotMetrics({ from: 1621103400, to: 1621621800 });
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v2/reports/bot_metrics',
|
||||
{
|
||||
params: {
|
||||
since: 1621103400,
|
||||
until: 1621621800,
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('#getBotSummary', () => {
|
||||
reportsAPI.getBotSummary({
|
||||
from: 1621103400,
|
||||
to: 1621621800,
|
||||
groupBy: 'date',
|
||||
businessHours: true,
|
||||
});
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v2/reports/bot_summary',
|
||||
{
|
||||
params: {
|
||||
since: 1621103400,
|
||||
until: 1621621800,
|
||||
type: 'account',
|
||||
group_by: 'date',
|
||||
business_hours: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('#getConversationMetric', () => {
|
||||
reportsAPI.getConversationMetric('account');
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
import SLAReportsAPI from '../slaReports';
|
||||
import ApiClient from '../ApiClient';
|
||||
|
||||
describe('#SLAReports API', () => {
|
||||
it('creates correct instance', () => {
|
||||
expect(SLAReportsAPI).toBeInstanceOf(ApiClient);
|
||||
expect(SLAReportsAPI.apiVersion).toBe('/api/v1');
|
||||
expect(SLAReportsAPI).toHaveProperty('get');
|
||||
expect(SLAReportsAPI).toHaveProperty('getMetrics');
|
||||
});
|
||||
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('#get', () => {
|
||||
SLAReportsAPI.get({
|
||||
page: 1,
|
||||
from: 1622485800,
|
||||
to: 1623695400,
|
||||
assigned_agent_id: 1,
|
||||
inbox_id: 1,
|
||||
team_id: 1,
|
||||
sla_policy_id: 1,
|
||||
label_list: ['label1'],
|
||||
});
|
||||
expect(axiosMock.get).toHaveBeenCalledWith('/api/v1/applied_slas', {
|
||||
params: {
|
||||
page: 1,
|
||||
since: 1622485800,
|
||||
until: 1623695400,
|
||||
assigned_agent_id: 1,
|
||||
inbox_id: 1,
|
||||
team_id: 1,
|
||||
sla_policy_id: 1,
|
||||
label_list: ['label1'],
|
||||
},
|
||||
});
|
||||
});
|
||||
it('#getMetrics', () => {
|
||||
SLAReportsAPI.getMetrics({
|
||||
from: 1622485800,
|
||||
to: 1623695400,
|
||||
assigned_agent_id: 1,
|
||||
inbox_id: 1,
|
||||
team_id: 1,
|
||||
sla_policy_id: 1,
|
||||
label_list: ['label1'],
|
||||
});
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/applied_slas/metrics',
|
||||
{
|
||||
params: {
|
||||
since: 1622485800,
|
||||
until: 1623695400,
|
||||
assigned_agent_id: 1,
|
||||
inbox_id: 1,
|
||||
team_id: 1,
|
||||
sla_policy_id: 1,
|
||||
label_list: ['label1'],
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
it('#download', () => {
|
||||
SLAReportsAPI.download({
|
||||
from: 1622485800,
|
||||
to: 1623695400,
|
||||
assigned_agent_id: 1,
|
||||
inbox_id: 1,
|
||||
team_id: 1,
|
||||
sla_policy_id: 1,
|
||||
label_list: ['label1'],
|
||||
});
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/applied_slas/download',
|
||||
{
|
||||
params: {
|
||||
since: 1622485800,
|
||||
until: 1623695400,
|
||||
assigned_agent_id: 1,
|
||||
inbox_id: 1,
|
||||
team_id: 1,
|
||||
sla_policy_id: 1,
|
||||
label_list: ['label1'],
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -16,10 +16,10 @@ describe('#TeamsAPI', () => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: vi.fn(() => Promise.resolve()),
|
||||
get: vi.fn(() => Promise.resolve()),
|
||||
patch: vi.fn(() => Promise.resolve()),
|
||||
delete: vi.fn(() => Promise.resolve()),
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
@import '~vue2-datepicker/scss/index';
|
||||
|
||||
.date-picker {
|
||||
// To be removed one SLA reports date picker is created
|
||||
&.small {
|
||||
.mx-input {
|
||||
@apply h-8 text-sm;
|
||||
}
|
||||
}
|
||||
|
||||
&.no-margin {
|
||||
.mx-input {
|
||||
@apply mb-0;
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
// scss-lint:disable SpaceAfterPropertyColon
|
||||
@import 'shared/assets/fonts/inter';
|
||||
// Inter,
|
||||
// @import 'shared/assets/fonts/inter';
|
||||
|
||||
html,
|
||||
body {
|
||||
font-family:
|
||||
'PlusJakarta',
|
||||
Inter,
|
||||
-apple-system,
|
||||
system-ui,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
'Helvetica Neue',
|
||||
Tahoma,
|
||||
Arial,
|
||||
sans-serif !important;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
@@ -40,347 +40,3 @@
|
||||
.hide {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
// scss-lint:disable PropertySortOrder
|
||||
:root {
|
||||
--color-amber-25: 254 253 251;
|
||||
--color-amber-50: 255 249 237;
|
||||
--color-amber-75: 255 243 208;
|
||||
--color-amber-100: 255 236 183;
|
||||
--color-amber-200: 255 224 161;
|
||||
--color-amber-300: 245 208 140;
|
||||
--color-amber-400: 228 187 120;
|
||||
--color-amber-500: 214 163 92;
|
||||
--color-amber-600: 214 163 92;
|
||||
--color-amber-700: 255 186 26;
|
||||
--color-amber-800: 145 89 48;
|
||||
--color-amber-900: 79 52 34;
|
||||
|
||||
--color-ash-100: 235 235 239;
|
||||
--color-ash-200: 228 228 233;
|
||||
--color-ash-25: 252 252 253;
|
||||
--color-ash-300: 221 221 227;
|
||||
--color-ash-400: 211 212 219;
|
||||
--color-ash-50: 249 249 251;
|
||||
--color-ash-500: 185 187 198;
|
||||
--color-ash-600: 139 141 152;
|
||||
--color-ash-700: 126 128 138;
|
||||
--color-ash-75: 242 242 245;
|
||||
--color-ash-800: 96 100 108;
|
||||
--color-ash-900: 28 32 36;
|
||||
|
||||
--color-primary-25: 251 253 255;
|
||||
--color-primary-50: 245 249 255;
|
||||
--color-primary-75: 233 243 255;
|
||||
--color-primary-100: 218 236 255;
|
||||
--color-primary-200: 201 226 255;
|
||||
--color-primary-300: 181 213 255;
|
||||
--color-primary-400: 155 195 252;
|
||||
--color-primary-500: 117 171 247;
|
||||
--color-primary-600: 39 129 246;
|
||||
--color-primary-700: 16 115 233;
|
||||
--color-primary-800: 8 109 224;
|
||||
--color-primary-900: 11 50 101;
|
||||
|
||||
--color-ruby-100: 255 220 225;
|
||||
--color-ruby-200: 255 206 214;
|
||||
--color-ruby-25: 255 252 253;
|
||||
--color-ruby-300: 248 191 200;
|
||||
--color-ruby-400: 239 172 184;
|
||||
--color-ruby-50: 255 247 248;
|
||||
--color-ruby-500: 229 146 163;
|
||||
--color-ruby-600: 229 70 102;
|
||||
--color-ruby-700: 220 59 93;
|
||||
--color-ruby-75: 254 234 237;
|
||||
--color-ruby-800: 202 36 77;
|
||||
--color-ruby-900: 100 23 43;
|
||||
|
||||
--color-teal-100: 224 248 243;
|
||||
--color-teal-200: 204 243 234;
|
||||
--color-teal-25: 250 254 253;
|
||||
--color-teal-300: 184 234 224;
|
||||
--color-teal-400: 161 222 210;
|
||||
--color-teal-50: 243 251 249;
|
||||
--color-teal-500: 83 185 171;
|
||||
--color-teal-600: 18 165 148;
|
||||
--color-teal-700: 13 155 138;
|
||||
--color-teal-75: 236 249 255;
|
||||
--color-teal-800: 0 133 115;
|
||||
--color-teal-900: 13 61 56;
|
||||
|
||||
--color-green-25: 251 254 252;
|
||||
--color-green-50: 244 251 246;
|
||||
--color-green-75: 230 246 235;
|
||||
--color-green-100: 214 241 223;
|
||||
--color-green-200: 196 232 209;
|
||||
--color-green-300: 173 221 192;
|
||||
--color-green-400: 142 206 170;
|
||||
--color-green-500: 91 185 139;
|
||||
--color-green-600: 48 164 108;
|
||||
--color-green-700: 43 154 102;
|
||||
--color-green-800: 33 131 88;
|
||||
--color-green-900: 25 59 45;
|
||||
|
||||
--color-mint-25: 249 254 253;
|
||||
--color-mint-50: 242 251 249;
|
||||
--color-mint-75: 221 249 242;
|
||||
--color-mint-100: 200 244 233;
|
||||
--color-mint-200: 179 236 222;
|
||||
--color-mint-300: 156 224 208;
|
||||
--color-mint-400: 126 207 189;
|
||||
--color-mint-500: 76 187 165;
|
||||
--color-mint-600: 134 234 212;
|
||||
--color-mint-700: 125 224 203;
|
||||
--color-mint-800: 2 120 100;
|
||||
--color-mint-900: 22 67 60;
|
||||
|
||||
--color-sky-25: 249 254 255;
|
||||
--color-sky-50: 241 250 253;
|
||||
--color-sky-75: 225 246 253;
|
||||
--color-sky-100: 209 240 250;
|
||||
--color-sky-200: 190 231 245;
|
||||
--color-sky-300: 169 218 237;
|
||||
--color-sky-400: 141 202 227;
|
||||
--color-sky-500: 96 179 215;
|
||||
--color-sky-600: 124 226 254;
|
||||
--color-sky-700: 116 218 248;
|
||||
--color-sky-800: 0 116 158;
|
||||
--color-sky-900: 29 62 86;
|
||||
|
||||
--color-indigo-25: 253 253 254;
|
||||
--color-indigo-50: 247 249 255;
|
||||
--color-indigo-75: 237 242 254;
|
||||
--color-indigo-100: 225 233 255;
|
||||
--color-indigo-200: 210 222 255;
|
||||
--color-indigo-300: 193 208 255;
|
||||
--color-indigo-400: 171 189 249;
|
||||
--color-indigo-500: 141 164 239;
|
||||
--color-indigo-600: 62 99 221;
|
||||
--color-indigo-700: 51 88 212;
|
||||
--color-indigo-800: 58 91 199;
|
||||
--color-indigo-900: 31 45 92;
|
||||
|
||||
--color-iris-25: 253 253 255;
|
||||
--color-iris-50: 248 248 255;
|
||||
--color-iris-75: 240 241 254;
|
||||
--color-iris-100: 230 231 255;
|
||||
--color-iris-200: 218 220 255;
|
||||
--color-iris-300: 203 205 255;
|
||||
--color-iris-400: 184 186 248;
|
||||
--color-iris-500: 155 158 240;
|
||||
--color-iris-600: 91 91 214;
|
||||
--color-iris-700: 81 81 205;
|
||||
--color-iris-800: 87 83 198;
|
||||
--color-iris-900: 39 41 98;
|
||||
|
||||
--color-violet-25: 253 252 254;
|
||||
--color-violet-50: 250 248 255;
|
||||
--color-violet-75: 244 240 254;
|
||||
--color-violet-100: 235 228 255;
|
||||
--color-violet-200: 225 217 255;
|
||||
--color-violet-300: 212 202 254;
|
||||
--color-violet-400: 194 181 245;
|
||||
--color-violet-500: 170 153 236;
|
||||
--color-violet-600: 110 86 207;
|
||||
--color-violet-700: 101 77 196;
|
||||
--color-violet-800: 101 80 185;
|
||||
--color-violet-900: 47 38 95;
|
||||
|
||||
--color-pink-25: 255 252 254;
|
||||
--color-pink-50: 254 247 251;
|
||||
--color-pink-75: 254 233 245;
|
||||
--color-pink-100: 251 220 239;
|
||||
--color-pink-200: 246 206 231;
|
||||
--color-pink-300: 239 191 221;
|
||||
--color-pink-400: 231 172 208;
|
||||
--color-pink-500: 221 147 194;
|
||||
--color-pink-600: 214 64 159;
|
||||
--color-pink-700: 207 56 151;
|
||||
--color-pink-800: 194 41 138;
|
||||
--color-pink-900: 101 18 73;
|
||||
|
||||
--color-orange-25: 254 252 251;
|
||||
--color-orange-50: 255 247 237;
|
||||
--color-orange-75: 255 239 214;
|
||||
--color-orange-100: 255 223 181;
|
||||
--color-orange-200: 255 209 154;
|
||||
--color-orange-300: 255 193 130;
|
||||
--color-orange-400: 245 174 115;
|
||||
--color-orange-500: 236 148 85;
|
||||
--color-orange-600: 247 107 21;
|
||||
--color-orange-700: 239 95 0;
|
||||
--color-orange-800: 204 78 0;
|
||||
--color-orange-900: 88 45 29;
|
||||
}
|
||||
// scss-lint:disable QualifyingElement
|
||||
body.dark {
|
||||
--color-amber-25: 31 19 0;
|
||||
--color-amber-50: 37 24 4;
|
||||
--color-amber-75: 48 32 11;
|
||||
--color-amber-100: 57 39 15;
|
||||
--color-amber-200: 67 46 18;
|
||||
--color-amber-300: 83 57 22;
|
||||
--color-amber-400: 111 77 29;
|
||||
--color-amber-500: 169 118 42;
|
||||
--color-amber-600: 169 118 42;
|
||||
--color-amber-700: 255 203 71;
|
||||
--color-amber-800: 255 204 77;
|
||||
--color-amber-900: 255 231 179;
|
||||
|
||||
--color-ash-100: 46 48 53;
|
||||
--color-ash-200: 53 55 60;
|
||||
--color-ash-25: 24 24 26;
|
||||
--color-ash-300: 60 63 68;
|
||||
--color-ash-400: 70 75 80;
|
||||
--color-ash-50: 27 27 31;
|
||||
--color-ash-500: 90 97 101;
|
||||
--color-ash-600: 105 110 119;
|
||||
--color-ash-700: 120 127 133;
|
||||
--color-ash-75: 39 40 45;
|
||||
--color-ash-800: 173 177 184;
|
||||
--color-ash-900: 237 238 240;
|
||||
|
||||
--color-primary-25: 10 17 28;
|
||||
--color-primary-50: 15 24 38;
|
||||
--color-primary-75: 15 39 72;
|
||||
--color-primary-100: 10 49 99;
|
||||
--color-primary-200: 18 61 117;
|
||||
--color-primary-300: 29 74 134;
|
||||
--color-primary-400: 40 89 156;
|
||||
--color-primary-500: 48 106 186;
|
||||
--color-primary-600: 39 129 246;
|
||||
--color-primary-700: 21 116 231;
|
||||
--color-primary-800: 126 182 255;
|
||||
--color-primary-900: 205 227 255;
|
||||
|
||||
--color-ruby-100: 78 19 37;
|
||||
--color-ruby-200: 94 26 46;
|
||||
--color-ruby-25: 25 17 19;
|
||||
--color-ruby-300: 111 37 57;
|
||||
--color-ruby-400: 136 52 71;
|
||||
--color-ruby-50: 30 21 23;
|
||||
--color-ruby-500: 179 68 90;
|
||||
--color-ruby-600: 229 70 102;
|
||||
--color-ruby-700: 236 90 114;
|
||||
--color-ruby-75: 58 20 30;
|
||||
--color-ruby-800: 255 148 157;
|
||||
--color-ruby-900: 254 210 225;
|
||||
|
||||
--color-teal-100: 2 59 55;
|
||||
--color-teal-200: 8 72 67;
|
||||
--color-teal-25: 13 21 20;
|
||||
--color-teal-300: 28 105 97;
|
||||
--color-teal-400: 28 105 97;
|
||||
--color-teal-50: 17 28 27;
|
||||
--color-teal-500: 32 126 115;
|
||||
--color-teal-600: 41 163 131;
|
||||
--color-teal-700: 14 179 158;
|
||||
--color-teal-75: 13 45 42;
|
||||
--color-teal-800: 11 216 182;
|
||||
--color-teal-900: 173 240 221;
|
||||
|
||||
--color-green-25: 14 21 18;
|
||||
--color-green-50: 18 27 23;
|
||||
--color-green-75: 19 45 33;
|
||||
--color-green-100: 17 59 41;
|
||||
--color-green-200: 23 73 51;
|
||||
--color-green-300: 32 87 62;
|
||||
--color-green-400: 40 104 74;
|
||||
--color-green-500: 47 124 87;
|
||||
--color-green-600: 48 164 108;
|
||||
--color-green-700: 51 176 116;
|
||||
--color-green-800: 61 214 140;
|
||||
--color-green-900: 177 241 203;
|
||||
|
||||
--color-mint-25: 14 21 21;
|
||||
--color-mint-50: 15 27 27;
|
||||
--color-mint-75: 9 44 43;
|
||||
--color-mint-100: 0 58 56;
|
||||
--color-mint-200: 0 71 68;
|
||||
--color-mint-300: 16 86 80;
|
||||
--color-mint-400: 30 104 95;
|
||||
--color-mint-500: 39 127 112;
|
||||
--color-mint-600: 134 234 212;
|
||||
--color-mint-700: 168 245 229;
|
||||
--color-mint-800: 88 213 186;
|
||||
--color-mint-900: 196 245 225;
|
||||
|
||||
--color-sky-25: 14 21 21;
|
||||
--color-sky-50: 15 27 27;
|
||||
--color-sky-75: 9 44 43;
|
||||
--color-sky-100: 0 58 56;
|
||||
--color-sky-200: 0 71 68;
|
||||
--color-sky-300: 16 86 80;
|
||||
--color-sky-400: 30 104 95;
|
||||
--color-sky-500: 39 127 112;
|
||||
--color-sky-600: 134 234 212;
|
||||
--color-sky-700: 168 245 229;
|
||||
--color-sky-800: 88 213 186;
|
||||
--color-sky-900: 196 245 225;
|
||||
|
||||
--color-indigo-25: 17 19 31;
|
||||
--color-indigo-50: 20 23 38;
|
||||
--color-indigo-75: 24 36 73;
|
||||
--color-indigo-100: 29 46 98;
|
||||
--color-indigo-200: 37 57 116;
|
||||
--color-indigo-300: 48 67 132;
|
||||
--color-indigo-400: 58 79 151;
|
||||
--color-indigo-500: 67 93 177;
|
||||
--color-indigo-600: 62 99 221;
|
||||
--color-indigo-700: 84 114 228;
|
||||
--color-indigo-800: 158 177 255;
|
||||
--color-indigo-900: 214 225 255;
|
||||
|
||||
--color-iris-25: 19 19 30;
|
||||
--color-iris-50: 23 22 37;
|
||||
--color-iris-75: 32 34 72;
|
||||
--color-iris-100: 38 42 101;
|
||||
--color-iris-200: 48 51 116;
|
||||
--color-iris-300: 61 62 130;
|
||||
--color-iris-400: 74 74 149;
|
||||
--color-iris-500: 89 88 177;
|
||||
--color-iris-600: 91 91 214;
|
||||
--color-iris-700: 110 106 222;
|
||||
--color-iris-800: 177 169 255;
|
||||
--color-iris-900: 224 223 254;
|
||||
|
||||
--color-violet-25: 20 18 31;
|
||||
--color-violet-50: 27 21 37;
|
||||
--color-violet-75: 41 31 67;
|
||||
--color-violet-100: 51 37 91;
|
||||
--color-violet-200: 60 46 105;
|
||||
--color-violet-300: 71 56 118;
|
||||
--color-violet-400: 86 70 139;
|
||||
--color-violet-500: 105 88 173;
|
||||
--color-violet-600: 110 86 207;
|
||||
--color-violet-700: 125 102 217;
|
||||
--color-violet-800: 186 167 255;
|
||||
--color-violet-900: 226 221 254;
|
||||
|
||||
--color-pink-25: 25 17 23;
|
||||
--color-pink-50: 33 18 29;
|
||||
--color-pink-75: 55 23 47;
|
||||
--color-pink-100: 75 20 61;
|
||||
--color-pink-200: 89 28 71;
|
||||
--color-pink-300: 105 41 85;
|
||||
--color-pink-400: 131 56 105;
|
||||
--color-pink-500: 168 72 133;
|
||||
--color-pink-600: 214 64 159;
|
||||
--color-pink-700: 222 81 168;
|
||||
--color-pink-800: 255 141 204;
|
||||
--color-pink-900: 253 209 234;
|
||||
--color-orange-25: 23 18 14;
|
||||
--color-orange-50: 30 22 15;
|
||||
--color-orange-75: 51 30 11;
|
||||
--color-orange-100: 70 33 0;
|
||||
--color-orange-200: 86 40 0;
|
||||
--color-orange-300: 102 53 12;
|
||||
--color-orange-400: 126 69 29;
|
||||
--color-orange-500: 163 88 41;
|
||||
--color-orange-600: 247 107 21;
|
||||
--color-orange-700: 255 128 31;
|
||||
--color-orange-800: 255 160 87;
|
||||
--color-orange-900: 255 224 194;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,29 +153,6 @@
|
||||
}
|
||||
|
||||
.multiselect-wrap--small {
|
||||
// To be removed one SLA reports date picker is created
|
||||
&.tiny {
|
||||
.multiselect.no-margin {
|
||||
@apply min-h-[32px];
|
||||
}
|
||||
|
||||
.multiselect__select {
|
||||
@apply min-h-[32px] h-8;
|
||||
|
||||
&::before {
|
||||
@apply top-[60%];
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect__tags {
|
||||
@apply min-h-[32px] max-h-[32px];
|
||||
|
||||
.multiselect__single {
|
||||
@apply pt-1 pb-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect__tags,
|
||||
.multiselect__input,
|
||||
.multiselect {
|
||||
|
||||
@@ -12,6 +12,7 @@ h6 {
|
||||
|
||||
p {
|
||||
text-rendering: optimizeLegibility;
|
||||
word-spacing: 0.12em;
|
||||
|
||||
@apply mb-2 leading-[1.65] text-sm;
|
||||
|
||||
@@ -62,12 +63,12 @@ input:focus {
|
||||
}
|
||||
|
||||
// Inputs
|
||||
input[type='text']:not(.reset-base),
|
||||
input[type='number']:not(.reset-base),
|
||||
input[type='password']:not(.reset-base),
|
||||
input[type='date']:not(.reset-base),
|
||||
input[type='email']:not(.reset-base),
|
||||
input[type='url']:not(.reset-base) {
|
||||
input[type='text'],
|
||||
input[type='number'],
|
||||
input[type='password'],
|
||||
input[type='date'],
|
||||
input[type='email'],
|
||||
input[type='url'] {
|
||||
@apply block box-border w-full transition-colors focus:border-woot-500 dark:focus:border-woot-600 duration-[0.25s] ease-[ease-in-out] h-10 appearance-none mx-0 mt-0 mb-4 p-2 rounded-md text-base font-normal bg-white dark:bg-slate-900 focus:bg-white focus:dark:bg-slate-900 text-slate-900 dark:text-slate-100 border border-solid border-slate-200 dark:border-slate-600;
|
||||
|
||||
&[disabled] {
|
||||
|
||||
@@ -118,7 +118,7 @@ button {
|
||||
@apply border border-woot-500 bg-transparent dark:bg-transparent dark:border-woot-500 text-woot-500 dark:text-woot-500 hover:bg-woot-50 dark:hover:bg-woot-900;
|
||||
|
||||
&.secondary {
|
||||
@apply text-slate-700 border-slate-100 dark:border-slate-800 dark:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-700;
|
||||
@apply text-slate-700 border-slate-200 dark:border-slate-600 dark:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-700;
|
||||
}
|
||||
|
||||
&.success {
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
.modal-image {
|
||||
@apply max-h-[76vh] max-w-[76vw];
|
||||
transition: transform 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.modal-video {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<button
|
||||
class="bg-white dark:bg-slate-900 cursor-pointer flex flex-col justify-end transition-all duration-200 ease-in -m-px py-4 px-0 items-center border border-solid border-slate-25 dark:border-slate-800 hover:border-woot-500 dark:hover:border-woot-500 hover:shadow-md hover:z-50 disabled:opacity-60"
|
||||
class="sm:w-[50%] md:w-1/3 lg:w-1/4 bg-white dark:bg-slate-900 cursor-pointer flex flex-col transition-all duration-200 ease-in -m-px py-4 px-0 items-center border border-solid border-slate-25 dark:border-slate-800 hover:border-woot-500 dark:hover:border-woot-500 hover:shadow-md hover:z-50 disabled:opacity-60"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<img :src="src" :alt="title" class="w-1/2 my-4 mx-auto" />
|
||||
<img :src="src" :alt="title" class="w-[50%] my-4 mx-auto" />
|
||||
<h3
|
||||
class="text-slate-800 dark:text-slate-100 text-base text-center capitalize"
|
||||
>
|
||||
|
||||
@@ -1,23 +1,85 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col flex-shrink-0 overflow-hidden border-r conversations-list-wrap rtl:border-r-0 rtl:border-l border-slate-50 dark:border-slate-800/50"
|
||||
:class="[
|
||||
{ hidden: !showConversationList },
|
||||
isOnExpandedLayout ? 'basis-full' : 'flex-basis-clamp',
|
||||
]"
|
||||
class="conversations-list-wrap flex-basis-clamp flex-shrink-0 overflow-hidden flex flex-col border-r rtl:border-r-0 rtl:border-l border-slate-50 dark:border-slate-800/50"
|
||||
:class="{
|
||||
hide: !showConversationList,
|
||||
'list--full-width': isOnExpandedLayout,
|
||||
}"
|
||||
>
|
||||
<slot />
|
||||
<chat-list-header
|
||||
:page-title="pageTitle"
|
||||
:has-applied-filters="hasAppliedFilters"
|
||||
:has-active-folders="hasActiveFolders"
|
||||
:active-status="activeStatus"
|
||||
@add-folders="onClickOpenAddFoldersModal"
|
||||
@delete-folders="onClickOpenDeleteFoldersModal"
|
||||
@filters-modal="onToggleAdvanceFiltersModal"
|
||||
@reset-filters="resetAndFetchData"
|
||||
@basic-filter-change="onBasicFilterChange"
|
||||
/>
|
||||
<div
|
||||
class="flex items-center justify-between py-0 px-4"
|
||||
:class="{
|
||||
'pb-3 border-b border-slate-75 dark:border-slate-700':
|
||||
hasAppliedFiltersOrActiveFolders,
|
||||
}"
|
||||
>
|
||||
<div class="flex max-w-[85%] justify-center items-center">
|
||||
<h1
|
||||
class="text-xl break-words overflow-hidden whitespace-nowrap font-medium text-ellipsis text-black-900 dark:text-slate-100 mb-0"
|
||||
:title="pageTitle"
|
||||
>
|
||||
{{ pageTitle }}
|
||||
</h1>
|
||||
<span
|
||||
v-if="!hasAppliedFiltersOrActiveFolders"
|
||||
class="p-1 my-0.5 mx-1 rounded-md capitalize bg-slate-50 dark:bg-slate-800 text-xxs text-slate-600 dark:text-slate-300"
|
||||
>
|
||||
{{ $t(`CHAT_LIST.CHAT_STATUS_FILTER_ITEMS.${activeStatus}.TEXT`) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<div v-if="hasAppliedFilters && !hasActiveFolders">
|
||||
<woot-button
|
||||
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.ADD.SAVE_BUTTON')"
|
||||
size="tiny"
|
||||
variant="smooth"
|
||||
color-scheme="secondary"
|
||||
icon="save"
|
||||
@click="onClickOpenAddFoldersModal"
|
||||
/>
|
||||
<woot-button
|
||||
v-tooltip.top-end="$t('FILTER.CLEAR_BUTTON_LABEL')"
|
||||
size="tiny"
|
||||
variant="smooth"
|
||||
color-scheme="alert"
|
||||
icon="dismiss-circle"
|
||||
@click="resetAndFetchData"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="hasActiveFolders">
|
||||
<woot-button
|
||||
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.EDIT.EDIT_BUTTON')"
|
||||
size="tiny"
|
||||
variant="smooth"
|
||||
color-scheme="secondary"
|
||||
icon="edit"
|
||||
@click="onToggleAdvanceFiltersModal"
|
||||
/>
|
||||
<woot-button
|
||||
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.DELETE.DELETE_BUTTON')"
|
||||
size="tiny"
|
||||
variant="smooth"
|
||||
color-scheme="alert"
|
||||
icon="delete"
|
||||
@click="onClickOpenDeleteFoldersModal"
|
||||
/>
|
||||
</div>
|
||||
<woot-button
|
||||
v-else
|
||||
v-tooltip.right="$t('FILTER.TOOLTIP_LABEL')"
|
||||
variant="smooth"
|
||||
color-scheme="secondary"
|
||||
icon="filter"
|
||||
size="tiny"
|
||||
@click="onToggleAdvanceFiltersModal"
|
||||
/>
|
||||
<conversation-basic-filter
|
||||
v-if="!hasAppliedFiltersOrActiveFolders"
|
||||
@changeFilter="onBasicFilterChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<add-custom-views
|
||||
v-if="showAddFoldersModal"
|
||||
@@ -45,7 +107,7 @@
|
||||
|
||||
<p
|
||||
v-if="!chatListLoading && !conversationList.length"
|
||||
class="flex items-center justify-center p-4 overflow-auto"
|
||||
class="overflow-auto p-4 flex justify-center items-center"
|
||||
>
|
||||
{{ $t('CHAT_LIST.LIST.404') }}
|
||||
</p>
|
||||
@@ -65,7 +127,7 @@
|
||||
/>
|
||||
<div
|
||||
ref="conversationList"
|
||||
class="flex-1 conversations-list"
|
||||
class="conversations-list flex-1"
|
||||
:class="{ 'overflow-hidden': isContextMenuOpen }"
|
||||
>
|
||||
<virtual-list
|
||||
@@ -74,16 +136,16 @@
|
||||
:data-sources="conversationList"
|
||||
:data-component="itemComponent"
|
||||
:extra-props="virtualListExtraProps"
|
||||
class="w-full h-full overflow-auto"
|
||||
class="w-full overflow-auto h-full"
|
||||
footer-tag="div"
|
||||
>
|
||||
<template #footer>
|
||||
<div v-if="chatListLoading" class="text-center">
|
||||
<span class="mt-4 mb-4 spinner" />
|
||||
<span class="spinner mt-4 mb-4" />
|
||||
</div>
|
||||
<p
|
||||
v-if="showEndOfListMessage"
|
||||
class="p-4 text-center text-slate-400 dark:text-slate-300"
|
||||
class="text-center text-slate-400 dark:text-slate-300 p-4"
|
||||
>
|
||||
{{ $t('CHAT_LIST.EOF') }}
|
||||
</p>
|
||||
@@ -116,15 +178,14 @@
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import VirtualList from 'vue-virtual-scroll-list';
|
||||
|
||||
import ChatListHeader from './ChatListHeader.vue';
|
||||
import ConversationAdvancedFilter from './widgets/conversation/ConversationAdvancedFilter.vue';
|
||||
import ConversationBasicFilter from './widgets/conversation/ConversationBasicFilter.vue';
|
||||
import ChatTypeTabs from './widgets/ChatTypeTabs.vue';
|
||||
import ConversationItem from './ConversationItem.vue';
|
||||
import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins';
|
||||
import timeMixin from '../mixins/time';
|
||||
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
||||
import conversationMixin from '../mixins/conversations';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
import advancedFilterTypes from './widgets/conversation/advancedFilterItems';
|
||||
@@ -132,10 +193,17 @@ import filterQueryGenerator from '../helper/filterQueryGenerator.js';
|
||||
import AddCustomViews from 'dashboard/routes/dashboard/customviews/AddCustomViews.vue';
|
||||
import DeleteCustomViews from 'dashboard/routes/dashboard/customviews/DeleteCustomViews.vue';
|
||||
import ConversationBulkActions from './widgets/conversation/conversationBulkActions/Index.vue';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import filterMixin from 'shared/mixins/filterMixin';
|
||||
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
|
||||
import languages from 'dashboard/components/widgets/conversation/advancedFilterItems/languages';
|
||||
import countries from 'shared/constants/countries';
|
||||
import { generateValuesForEditCustomViews } from 'dashboard/helper/customViewsHelper';
|
||||
|
||||
import {
|
||||
hasPressedAltAndJKey,
|
||||
hasPressedAltAndKKey,
|
||||
} from 'shared/helpers/KeyboardHelpers';
|
||||
import { conversationListPageURL } from '../helper/URLHelper';
|
||||
import {
|
||||
isOnMentionsView,
|
||||
@@ -146,7 +214,6 @@ import IntersectionObserver from './IntersectionObserver.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ChatListHeader,
|
||||
AddCustomViews,
|
||||
ChatTypeTabs,
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
@@ -154,10 +221,18 @@ export default {
|
||||
ConversationAdvancedFilter,
|
||||
DeleteCustomViews,
|
||||
ConversationBulkActions,
|
||||
ConversationBasicFilter,
|
||||
IntersectionObserver,
|
||||
VirtualList,
|
||||
},
|
||||
mixins: [conversationMixin, keyboardEventListenerMixins, filterMixin],
|
||||
mixins: [
|
||||
timeMixin,
|
||||
conversationMixin,
|
||||
eventListenerMixins,
|
||||
alertMixin,
|
||||
filterMixin,
|
||||
uiSettingsMixin,
|
||||
],
|
||||
provide() {
|
||||
return {
|
||||
// Actions to be performed on virtual list item and context menu.
|
||||
@@ -202,13 +277,6 @@ export default {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { uiSettings } = useUISettings();
|
||||
|
||||
return {
|
||||
uiSettings,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeAssigneeTab: wootConstants.ASSIGNEE_TYPE.ME,
|
||||
@@ -225,6 +293,7 @@ export default {
|
||||
foldersQuery: {},
|
||||
showAddFoldersModal: false,
|
||||
showDeleteFoldersModal: false,
|
||||
selectedConversations: [],
|
||||
selectedInboxes: [],
|
||||
isContextMenuOpen: false,
|
||||
appliedFilter: [],
|
||||
@@ -252,7 +321,6 @@ export default {
|
||||
chatLists: 'getAllConversations',
|
||||
mineChatsList: 'getMineChats',
|
||||
allChatList: 'getAllStatusChats',
|
||||
chatListFilters: 'getChatListFilters',
|
||||
unAssignedChatsList: 'getUnAssignedChats',
|
||||
chatListLoading: 'getChatListLoadingStatus',
|
||||
currentUserID: 'getCurrentUserID',
|
||||
@@ -266,17 +334,23 @@ export default {
|
||||
inboxesList: 'inboxes/getInboxes',
|
||||
campaigns: 'campaigns/getAllCampaigns',
|
||||
labels: 'labels/getLabels',
|
||||
selectedConversations: 'bulkActions/getSelectedConversationIds',
|
||||
}),
|
||||
hasAppliedFilters() {
|
||||
return this.appliedFilters.length !== 0;
|
||||
},
|
||||
hasActiveFolders() {
|
||||
return Boolean(this.activeFolder && this.foldersId !== 0);
|
||||
return this.activeFolder && this.foldersId !== 0;
|
||||
},
|
||||
hasAppliedFiltersOrActiveFolders() {
|
||||
return this.hasAppliedFilters || this.hasActiveFolders;
|
||||
},
|
||||
savedFoldersValue() {
|
||||
if (this.hasActiveFolders) {
|
||||
const payload = this.activeFolder.query;
|
||||
this.fetchSavedFilteredConversations(payload);
|
||||
}
|
||||
return {};
|
||||
},
|
||||
showEndOfListMessage() {
|
||||
return (
|
||||
this.conversationList.length &&
|
||||
@@ -352,6 +426,7 @@ export default {
|
||||
labels: this.label ? [this.label] : undefined,
|
||||
teamId: this.teamId || undefined,
|
||||
conversationType: this.conversationType || undefined,
|
||||
folders: this.hasActiveFolders ? this.savedFoldersValue : undefined,
|
||||
};
|
||||
},
|
||||
conversationListPagination() {
|
||||
@@ -464,13 +539,7 @@ export default {
|
||||
this.resetAndFetchData();
|
||||
this.updateVirtualListProps('conversationType', this.conversationType);
|
||||
},
|
||||
activeFolder(newVal, oldVal) {
|
||||
if (newVal !== oldVal) {
|
||||
this.$store.dispatch(
|
||||
'customViews/setActiveConversationFolder',
|
||||
newVal || null
|
||||
);
|
||||
}
|
||||
activeFolder() {
|
||||
this.resetAndFetchData();
|
||||
this.updateVirtualListProps('foldersId', this.foldersId);
|
||||
},
|
||||
@@ -480,14 +549,8 @@ export default {
|
||||
showAssigneeInConversationCard(newVal) {
|
||||
this.updateVirtualListProps('showAssignee', newVal);
|
||||
},
|
||||
conversationFilters(newVal, oldVal) {
|
||||
if (newVal !== oldVal) {
|
||||
this.$store.dispatch('updateChatListFilters', newVal);
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('setChatListFilters', this.conversationFilters);
|
||||
this.setFiltersFromUISettings();
|
||||
this.$store.dispatch('setChatStatusFilter', this.activeStatus);
|
||||
this.$store.dispatch('setChatSortFilter', this.activeSortBy);
|
||||
@@ -497,7 +560,7 @@ export default {
|
||||
this.$store.dispatch('campaigns/get');
|
||||
}
|
||||
|
||||
this.$emitter.on('fetch_conversation_stats', () => {
|
||||
bus.$on('fetch_conversation_stats', () => {
|
||||
this.$store.dispatch('conversationStats/get', this.conversationFilters);
|
||||
});
|
||||
},
|
||||
@@ -628,40 +691,30 @@ export default {
|
||||
lastConversationIndex,
|
||||
};
|
||||
},
|
||||
handlePreviousConversation() {
|
||||
const { allConversations, activeConversationIndex } =
|
||||
this.getKeyboardListenerParams();
|
||||
if (activeConversationIndex === -1) {
|
||||
allConversations[0].click();
|
||||
handleKeyEvents(e) {
|
||||
if (hasPressedAltAndJKey(e)) {
|
||||
const { allConversations, activeConversationIndex } =
|
||||
this.getKeyboardListenerParams();
|
||||
if (activeConversationIndex === -1) {
|
||||
allConversations[0].click();
|
||||
}
|
||||
if (activeConversationIndex >= 1) {
|
||||
allConversations[activeConversationIndex - 1].click();
|
||||
}
|
||||
}
|
||||
if (activeConversationIndex >= 1) {
|
||||
allConversations[activeConversationIndex - 1].click();
|
||||
if (hasPressedAltAndKKey(e)) {
|
||||
const {
|
||||
allConversations,
|
||||
activeConversationIndex,
|
||||
lastConversationIndex,
|
||||
} = this.getKeyboardListenerParams();
|
||||
if (activeConversationIndex === -1) {
|
||||
allConversations[lastConversationIndex].click();
|
||||
} else if (activeConversationIndex < lastConversationIndex) {
|
||||
allConversations[activeConversationIndex + 1].click();
|
||||
}
|
||||
}
|
||||
},
|
||||
handleNextConversation() {
|
||||
const {
|
||||
allConversations,
|
||||
activeConversationIndex,
|
||||
lastConversationIndex,
|
||||
} = this.getKeyboardListenerParams();
|
||||
if (activeConversationIndex === -1) {
|
||||
allConversations[lastConversationIndex].click();
|
||||
} else if (activeConversationIndex < lastConversationIndex) {
|
||||
allConversations[activeConversationIndex + 1].click();
|
||||
}
|
||||
},
|
||||
getKeyboardEvents() {
|
||||
return {
|
||||
'Alt+KeyJ': {
|
||||
action: () => this.handlePreviousConversation(),
|
||||
allowOnFocusedInput: true,
|
||||
},
|
||||
'Alt+KeyK': {
|
||||
action: () => this.handleNextConversation(),
|
||||
allowOnFocusedInput: true,
|
||||
},
|
||||
};
|
||||
},
|
||||
resetAndFetchData() {
|
||||
this.appliedFilter = [];
|
||||
this.resetBulkActions();
|
||||
@@ -678,9 +731,8 @@ export default {
|
||||
this.fetchConversations();
|
||||
},
|
||||
fetchConversations() {
|
||||
this.$store.dispatch('updateChatListFilters', this.conversationFilters);
|
||||
this.$store
|
||||
.dispatch('fetchAllConversations')
|
||||
.dispatch('fetchAllConversations', this.conversationFilters)
|
||||
.then(this.emitConversationLoaded);
|
||||
},
|
||||
loadMoreConversations() {
|
||||
@@ -720,7 +772,7 @@ export default {
|
||||
updateAssigneeTab(selectedTab) {
|
||||
if (this.activeAssigneeTab !== selectedTab) {
|
||||
this.resetBulkActions();
|
||||
this.$emitter.emit('clearSearchInput');
|
||||
bus.$emit('clearSearchInput');
|
||||
this.activeAssigneeTab = selectedTab;
|
||||
if (!this.currentPage) {
|
||||
this.fetchConversations();
|
||||
@@ -742,7 +794,7 @@ export default {
|
||||
});
|
||||
},
|
||||
resetBulkActions() {
|
||||
this.$store.dispatch('bulkActions/clearSelectedConversationIds');
|
||||
this.selectedConversations = [];
|
||||
this.selectedInboxes = [];
|
||||
},
|
||||
onBasicFilterChange(value, type) {
|
||||
@@ -773,16 +825,12 @@ export default {
|
||||
return this.selectedConversations.includes(id);
|
||||
},
|
||||
selectConversation(conversationId, inboxId) {
|
||||
this.$store.dispatch(
|
||||
'bulkActions/setSelectedConversationIds',
|
||||
conversationId
|
||||
);
|
||||
this.selectedConversations.push(conversationId);
|
||||
this.selectedInboxes.push(inboxId);
|
||||
},
|
||||
deSelectConversation(conversationId, inboxId) {
|
||||
this.$store.dispatch(
|
||||
'bulkActions/removeSelectedConversationIds',
|
||||
conversationId
|
||||
this.selectedConversations = this.selectedConversations.filter(
|
||||
item => item !== conversationId
|
||||
);
|
||||
this.selectedInboxes = this.selectedInboxes.filter(
|
||||
item => item !== inboxId
|
||||
@@ -790,10 +838,7 @@ export default {
|
||||
},
|
||||
selectAllConversations(check) {
|
||||
if (check) {
|
||||
this.$store.dispatch(
|
||||
'bulkActions/setSelectedConversationIds',
|
||||
this.conversationList.map(item => item.id)
|
||||
);
|
||||
this.selectedConversations = this.conversationList.map(item => item.id);
|
||||
this.selectedInboxes = this.conversationList.map(item => item.inbox_id);
|
||||
} else {
|
||||
this.resetBulkActions();
|
||||
@@ -809,9 +854,9 @@ export default {
|
||||
assignee_id: agent.id,
|
||||
},
|
||||
});
|
||||
this.$store.dispatch('bulkActions/clearSelectedConversationIds');
|
||||
this.selectedConversations = [];
|
||||
if (conversationId) {
|
||||
useAlert(
|
||||
this.showAlert(
|
||||
this.$t(
|
||||
'CONVERSATION.CARD_CONTEXT_MENU.API.AGENT_ASSIGNMENT.SUCCESFUL',
|
||||
{
|
||||
@@ -821,10 +866,10 @@ export default {
|
||||
)
|
||||
);
|
||||
} else {
|
||||
useAlert(this.$t('BULK_ACTION.ASSIGN_SUCCESFUL'));
|
||||
this.showAlert(this.$t('BULK_ACTION.ASSIGN_SUCCESFUL'));
|
||||
}
|
||||
} catch (err) {
|
||||
useAlert(this.$t('BULK_ACTION.ASSIGN_FAILED'));
|
||||
this.showAlert(this.$t('BULK_ACTION.ASSIGN_FAILED'));
|
||||
}
|
||||
},
|
||||
async assignPriority(priority, conversationId = null) {
|
||||
@@ -839,7 +884,7 @@ export default {
|
||||
newValue: priority,
|
||||
from: 'Context menu',
|
||||
});
|
||||
useAlert(
|
||||
this.showAlert(
|
||||
this.$t('CONVERSATION.PRIORITY.CHANGE_PRIORITY.SUCCESSFUL', {
|
||||
priority,
|
||||
conversationId,
|
||||
@@ -882,7 +927,7 @@ export default {
|
||||
conversationId,
|
||||
teamId: team.id,
|
||||
});
|
||||
useAlert(
|
||||
this.showAlert(
|
||||
this.$t(
|
||||
'CONVERSATION.CARD_CONTEXT_MENU.API.TEAM_ASSIGNMENT.SUCCESFUL',
|
||||
{
|
||||
@@ -892,7 +937,7 @@ export default {
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
useAlert(
|
||||
this.showAlert(
|
||||
this.$t('CONVERSATION.CARD_CONTEXT_MENU.API.TEAM_ASSIGNMENT.FAILED')
|
||||
);
|
||||
}
|
||||
@@ -907,9 +952,9 @@ export default {
|
||||
add: labels,
|
||||
},
|
||||
});
|
||||
this.$store.dispatch('bulkActions/clearSelectedConversationIds');
|
||||
this.selectedConversations = [];
|
||||
if (conversationId) {
|
||||
useAlert(
|
||||
this.showAlert(
|
||||
this.$t(
|
||||
'CONVERSATION.CARD_CONTEXT_MENU.API.LABEL_ASSIGNMENT.SUCCESFUL',
|
||||
{
|
||||
@@ -919,10 +964,10 @@ export default {
|
||||
)
|
||||
);
|
||||
} else {
|
||||
useAlert(this.$t('BULK_ACTION.LABELS.ASSIGN_SUCCESFUL'));
|
||||
this.showAlert(this.$t('BULK_ACTION.LABELS.ASSIGN_SUCCESFUL'));
|
||||
}
|
||||
} catch (err) {
|
||||
useAlert(this.$t('BULK_ACTION.LABELS.ASSIGN_FAILED'));
|
||||
this.showAlert(this.$t('BULK_ACTION.LABELS.ASSIGN_FAILED'));
|
||||
}
|
||||
},
|
||||
async onAssignTeamsForBulk(team) {
|
||||
@@ -934,13 +979,13 @@ export default {
|
||||
team_id: team.id,
|
||||
},
|
||||
});
|
||||
this.$store.dispatch('bulkActions/clearSelectedConversationIds');
|
||||
useAlert(this.$t('BULK_ACTION.TEAMS.ASSIGN_SUCCESFUL'));
|
||||
this.selectedConversations = [];
|
||||
this.showAlert(this.$t('BULK_ACTION.TEAMS.ASSIGN_SUCCESFUL'));
|
||||
} catch (err) {
|
||||
useAlert(this.$t('BULK_ACTION.TEAMS.ASSIGN_FAILED'));
|
||||
this.showAlert(this.$t('BULK_ACTION.TEAMS.ASSIGN_FAILED'));
|
||||
}
|
||||
},
|
||||
async onUpdateConversations(status, snoozedUntil) {
|
||||
async onUpdateConversations(status) {
|
||||
try {
|
||||
await this.$store.dispatch('bulkActions/process', {
|
||||
type: 'Conversation',
|
||||
@@ -948,12 +993,11 @@ export default {
|
||||
fields: {
|
||||
status,
|
||||
},
|
||||
snoozed_until: snoozedUntil,
|
||||
});
|
||||
this.$store.dispatch('bulkActions/clearSelectedConversationIds');
|
||||
useAlert(this.$t('BULK_ACTION.UPDATE.UPDATE_SUCCESFUL'));
|
||||
this.selectedConversations = [];
|
||||
this.showAlert(this.$t('BULK_ACTION.UPDATE.UPDATE_SUCCESFUL'));
|
||||
} catch (err) {
|
||||
useAlert(this.$t('BULK_ACTION.UPDATE.UPDATE_FAILED'));
|
||||
this.showAlert(this.$t('BULK_ACTION.UPDATE.UPDATE_FAILED'));
|
||||
}
|
||||
},
|
||||
toggleConversationStatus(conversationId, status, snoozedUntil) {
|
||||
@@ -964,14 +1008,14 @@ export default {
|
||||
snoozedUntil,
|
||||
})
|
||||
.then(() => {
|
||||
useAlert(this.$t('CONVERSATION.CHANGE_STATUS'));
|
||||
this.showAlert(this.$t('CONVERSATION.CHANGE_STATUS'));
|
||||
this.isLoading = false;
|
||||
});
|
||||
},
|
||||
allSelectedConversationsStatus(status) {
|
||||
if (!this.selectedConversations.length) return false;
|
||||
return this.selectedConversations.every(item => {
|
||||
return this.$store.getters.getConversationById(item)?.status === status;
|
||||
return this.$store.getters.getConversationById(item).status === status;
|
||||
});
|
||||
},
|
||||
onContextMenuToggle(state) {
|
||||
@@ -990,10 +1034,24 @@ export default {
|
||||
</style>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.conversations-list-wrap {
|
||||
&.hide {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
&.list--full-width {
|
||||
@apply basis-full;
|
||||
}
|
||||
}
|
||||
|
||||
.conversations-list {
|
||||
@apply overflow-hidden hover:overflow-y-auto;
|
||||
}
|
||||
|
||||
.load-more--button {
|
||||
@apply text-center rounded-none;
|
||||
}
|
||||
|
||||
.tab--chat-type {
|
||||
@apply py-0 px-4;
|
||||
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import ConversationBasicFilter from './widgets/conversation/ConversationBasicFilter.vue';
|
||||
|
||||
const props = defineProps({
|
||||
pageTitle: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
hasAppliedFilters: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
hasActiveFolders: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
activeStatus: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits([
|
||||
'add-folders',
|
||||
'delete-folders',
|
||||
'reset-filters',
|
||||
'basic-filter-change',
|
||||
'filters-modal',
|
||||
]);
|
||||
|
||||
const onBasicFilterChange = (value, type) => {
|
||||
emits('basic-filter-change', value, type);
|
||||
};
|
||||
|
||||
const hasAppliedFiltersOrActiveFolders = computed(() => {
|
||||
return props.hasAppliedFilters || props.hasActiveFolders;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex items-center justify-between px-4 py-0"
|
||||
:class="{
|
||||
'pb-3 border-b border-slate-75 dark:border-slate-700':
|
||||
hasAppliedFiltersOrActiveFolders,
|
||||
}"
|
||||
>
|
||||
<div class="flex max-w-[85%] justify-center items-center">
|
||||
<h1
|
||||
class="text-xl font-medium break-words truncate text-black-900 dark:text-slate-100"
|
||||
:title="pageTitle"
|
||||
>
|
||||
{{ pageTitle }}
|
||||
</h1>
|
||||
<span
|
||||
v-if="!hasAppliedFiltersOrActiveFolders"
|
||||
class="p-1 my-0.5 mx-1 rounded-md capitalize bg-slate-50 dark:bg-slate-800 text-xxs text-slate-600 dark:text-slate-300"
|
||||
>
|
||||
{{ $t(`CHAT_LIST.CHAT_STATUS_FILTER_ITEMS.${activeStatus}.TEXT`) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<div v-if="hasAppliedFilters && !hasActiveFolders">
|
||||
<woot-button
|
||||
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.ADD.SAVE_BUTTON')"
|
||||
size="tiny"
|
||||
variant="smooth"
|
||||
color-scheme="secondary"
|
||||
icon="save"
|
||||
@click="emits('add-folders')"
|
||||
/>
|
||||
<woot-button
|
||||
v-tooltip.top-end="$t('FILTER.CLEAR_BUTTON_LABEL')"
|
||||
size="tiny"
|
||||
variant="smooth"
|
||||
color-scheme="alert"
|
||||
icon="dismiss-circle"
|
||||
@click="emits('reset-filters')"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="hasActiveFolders">
|
||||
<woot-button
|
||||
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.EDIT.EDIT_BUTTON')"
|
||||
size="tiny"
|
||||
variant="smooth"
|
||||
color-scheme="secondary"
|
||||
icon="edit"
|
||||
@click="emits('filters-modal')"
|
||||
/>
|
||||
<woot-button
|
||||
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.DELETE.DELETE_BUTTON')"
|
||||
size="tiny"
|
||||
variant="smooth"
|
||||
color-scheme="alert"
|
||||
icon="delete"
|
||||
@click="emits('delete-folders')"
|
||||
/>
|
||||
</div>
|
||||
<woot-button
|
||||
v-else
|
||||
v-tooltip.right="$t('FILTER.TOOLTIP_LABEL')"
|
||||
variant="smooth"
|
||||
color-scheme="secondary"
|
||||
icon="filter"
|
||||
size="tiny"
|
||||
@click="emits('filters-modal')"
|
||||
/>
|
||||
<conversation-basic-filter
|
||||
v-if="!hasAppliedFiltersOrActiveFolders"
|
||||
@changeFilter="onBasicFilterChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -25,7 +25,6 @@
|
||||
<script>
|
||||
import 'highlight.js/styles/default.css';
|
||||
import { copyTextToClipboard } from 'shared/helpers/clipboard';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -60,7 +59,7 @@ export default {
|
||||
async onCopy(e) {
|
||||
e.preventDefault();
|
||||
await copyTextToClipboard(this.script);
|
||||
useAlert(this.$t('COMPONENTS.CODE.COPY_SUCCESSFUL'));
|
||||
bus.$emit('newToastMessage', this.$t('COMPONENTS.CODE.COPY_SUCCESSFUL'));
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2,32 +2,23 @@
|
||||
<div class="py-3 px-4">
|
||||
<div class="flex items-center mb-1">
|
||||
<h4 class="text-sm flex items-center m-0 w-full error">
|
||||
<div v-if="isAttributeTypeCheckbox" class="flex items-center">
|
||||
<div v-if="isAttributeTypeCheckbox" class="checkbox-wrap">
|
||||
<input
|
||||
v-model="editedValue"
|
||||
class="!my-0 mr-2 ml-0"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
@change="onUpdate"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<span
|
||||
class="w-full inline-flex gap-1.5 items-start font-medium whitespace-nowrap text-sm mb-0"
|
||||
:class="
|
||||
$v.editedValue.$error
|
||||
? 'text-red-400 dark:text-red-500'
|
||||
: 'text-slate-800 dark:text-slate-100'
|
||||
"
|
||||
class="attribute-name w-full text-slate-800 dark:text-slate-100 font-medium text-sm mb-0"
|
||||
:class="{ error: $v.editedValue.$error }"
|
||||
>
|
||||
{{ label }}
|
||||
<helper-text-popup
|
||||
v-if="description"
|
||||
:message="description"
|
||||
class="mt-0.5"
|
||||
/>
|
||||
</span>
|
||||
<woot-button
|
||||
v-if="showActions && value"
|
||||
v-if="showActions"
|
||||
v-tooltip.left="$t('CUSTOM_ATTRIBUTES.ACTIONS.DELETE')"
|
||||
variant="link"
|
||||
size="medium"
|
||||
@@ -40,13 +31,13 @@
|
||||
</h4>
|
||||
</div>
|
||||
<div v-if="notAttributeTypeCheckboxAndList">
|
||||
<div v-if="isEditing" v-on-clickaway="onClickAway">
|
||||
<div v-show="isEditing">
|
||||
<div class="mb-2 w-full flex items-center">
|
||||
<input
|
||||
ref="inputfield"
|
||||
v-model="editedValue"
|
||||
:type="inputType"
|
||||
class="!h-8 ltr:!rounded-r-none rtl:!rounded-l-none !mb-0 !text-sm"
|
||||
class="!h-8 ltr:rounded-r-none rtl:rounded-l-none !mb-0 !text-sm"
|
||||
autofocus="true"
|
||||
:class="{ error: $v.editedValue.$error }"
|
||||
@blur="$v.editedValue.$touch"
|
||||
@@ -70,7 +61,7 @@
|
||||
</div>
|
||||
<div
|
||||
v-show="!isEditing"
|
||||
class="flex group"
|
||||
class="value--view"
|
||||
:class="{ 'is-editable': showActions }"
|
||||
>
|
||||
<a
|
||||
@@ -78,25 +69,25 @@
|
||||
:href="hrefURL"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="group-hover:bg-slate-50 group-hover:dark:bg-slate-700 inline-block rounded-sm mb-0 break-all py-0.5 px-1"
|
||||
class="value inline-block rounded-sm mb-0 break-all py-0.5 px-1"
|
||||
>
|
||||
{{ urlValue }}
|
||||
</a>
|
||||
<p
|
||||
v-else
|
||||
class="group-hover:bg-slate-50 group-hover:dark:bg-slate-700 inline-block rounded-sm mb-0 break-all py-0.5 px-1"
|
||||
class="value inline-block rounded-sm mb-0 break-all py-0.5 px-1"
|
||||
>
|
||||
{{ displayValue || '---' }}
|
||||
</p>
|
||||
<div class="flex max-w-[2rem] gap-1 ml-1 rtl:mr-1 rtl:ml-0">
|
||||
<woot-button
|
||||
v-if="showActions && value"
|
||||
v-if="showActions"
|
||||
v-tooltip="$t('CUSTOM_ATTRIBUTES.ACTIONS.COPY')"
|
||||
variant="link"
|
||||
size="small"
|
||||
color-scheme="secondary"
|
||||
icon="clipboard"
|
||||
class-names="hidden group-hover:flex !w-6 flex-shrink-0"
|
||||
class-names="edit-button"
|
||||
@click="onCopy"
|
||||
/>
|
||||
<woot-button
|
||||
@@ -106,7 +97,7 @@
|
||||
size="small"
|
||||
color-scheme="secondary"
|
||||
icon="edit"
|
||||
class-names="hidden group-hover:flex !w-6 flex-shrink-0"
|
||||
class-names="edit-button"
|
||||
@click="onEdit"
|
||||
/>
|
||||
</div>
|
||||
@@ -139,21 +130,17 @@ import { format, parseISO } from 'date-fns';
|
||||
import { required, url } from 'vuelidate/lib/validators';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
import MultiselectDropdown from 'shared/components/ui/MultiselectDropdown.vue';
|
||||
import HelperTextPopup from 'dashboard/components/ui/HelperTextPopup.vue';
|
||||
import { isValidURL } from '../helper/URLHelper';
|
||||
import customAttributeMixin from '../mixins/customAttributeMixin';
|
||||
|
||||
const DATE_FORMAT = 'yyyy-MM-dd';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MultiselectDropdown,
|
||||
HelperTextPopup,
|
||||
},
|
||||
mixins: [customAttributeMixin],
|
||||
props: {
|
||||
label: { type: String, required: true },
|
||||
description: { type: String, default: '' },
|
||||
values: { type: Array, default: () => [] },
|
||||
value: { type: [String, Number, Boolean], default: '' },
|
||||
showActions: { type: Boolean, default: false },
|
||||
@@ -173,12 +160,11 @@ export default {
|
||||
editedValue: null,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
displayValue() {
|
||||
if (this.isAttributeTypeDate) {
|
||||
return this.value
|
||||
? new Date(this.value || new Date()).toLocaleDateString()
|
||||
: '';
|
||||
return new Date(this.value || new Date()).toLocaleDateString();
|
||||
}
|
||||
if (this.isAttributeTypeCheckbox) {
|
||||
return this.value === 'false' ? false : this.value;
|
||||
@@ -244,10 +230,6 @@ export default {
|
||||
this.isEditing = false;
|
||||
this.editedValue = this.formattedValue;
|
||||
},
|
||||
contactId() {
|
||||
// Fix to solve validation not resetting when contactId changes in contact page
|
||||
this.$v.$reset();
|
||||
},
|
||||
},
|
||||
|
||||
validations() {
|
||||
@@ -270,10 +252,10 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.editedValue = this.formattedValue;
|
||||
this.$emitter.on(BUS_EVENTS.FOCUS_CUSTOM_ATTRIBUTE, this.onFocusAttribute);
|
||||
bus.$on(BUS_EVENTS.FOCUS_CUSTOM_ATTRIBUTE, this.onFocusAttribute);
|
||||
},
|
||||
destroyed() {
|
||||
this.$emitter.off(BUS_EVENTS.FOCUS_CUSTOM_ATTRIBUTE, this.onFocusAttribute);
|
||||
bus.$off(BUS_EVENTS.FOCUS_CUSTOM_ATTRIBUTE, this.onFocusAttribute);
|
||||
},
|
||||
methods: {
|
||||
onFocusAttribute(focusAttributeKey) {
|
||||
@@ -286,10 +268,6 @@ export default {
|
||||
this.$refs.inputfield.focus();
|
||||
}
|
||||
},
|
||||
onClickAway() {
|
||||
this.$v.$reset();
|
||||
this.isEditing = false;
|
||||
},
|
||||
onEdit() {
|
||||
this.isEditing = true;
|
||||
this.$nextTick(() => {
|
||||
@@ -316,7 +294,6 @@ export default {
|
||||
},
|
||||
onDelete() {
|
||||
this.isEditing = false;
|
||||
this.$v.$reset();
|
||||
this.$emit('delete', this.attributeKey);
|
||||
},
|
||||
onCopy() {
|
||||
@@ -327,6 +304,35 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.checkbox-wrap {
|
||||
@apply flex items-center;
|
||||
}
|
||||
.checkbox {
|
||||
@apply my-0 mr-2 ml-0;
|
||||
}
|
||||
.attribute-name {
|
||||
&.error {
|
||||
@apply text-red-400 dark:text-red-500;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-button {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.value--view {
|
||||
@apply flex;
|
||||
|
||||
&.is-editable:hover {
|
||||
.value {
|
||||
@apply bg-slate-50 dark:bg-slate-700 mb-0;
|
||||
}
|
||||
.edit-button {
|
||||
@apply block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep {
|
||||
.selector-wrap {
|
||||
@apply m-0 top-1;
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
<template>
|
||||
<div class="flex flex-col items-start w-full gap-6">
|
||||
<div class="flex flex-col w-full gap-4">
|
||||
<h4 v-if="title" class="text-lg font-medium text-ash-900">
|
||||
{{ title }}
|
||||
</h4>
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<div class="flex-grow h-px bg-ash-200" />
|
||||
</div>
|
||||
<p v-if="description" class="mb-0 text-sm font-normal text-ash-900">
|
||||
{{ description }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col w-full gap-6">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -18,7 +18,6 @@
|
||||
<script>
|
||||
import 'highlight.js/styles/default.css';
|
||||
import { copyTextToClipboard } from 'shared/helpers/clipboard';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -36,7 +35,7 @@ export default {
|
||||
async onCopy(e) {
|
||||
e.preventDefault();
|
||||
await copyTextToClipboard(this.value);
|
||||
useAlert(this.$t('COMPONENTS.CODE.COPY_SUCCESSFUL'));
|
||||
bus.$emit('newToastMessage', this.$t('COMPONENTS.CODE.COPY_SUCCESSFUL'));
|
||||
},
|
||||
toggleMasked() {
|
||||
this.masked = !this.masked;
|
||||
|
||||
@@ -7,13 +7,7 @@
|
||||
@mousedown="handleMouseDown"
|
||||
>
|
||||
<div
|
||||
:class="{
|
||||
'modal-container rtl:text-right shadow-md max-h-full overflow-auto relative bg-white dark:bg-slate-800 skip-context-menu': true,
|
||||
'rounded-xl w-[37.5rem]': !fullWidth,
|
||||
'items-center rounded-none flex h-full justify-center w-full':
|
||||
fullWidth,
|
||||
[size]: true,
|
||||
}"
|
||||
:class="modalContainerClassName"
|
||||
@mouse.stop
|
||||
@mousedown="event => event.stopPropagation()"
|
||||
>
|
||||
@@ -22,7 +16,7 @@
|
||||
color-scheme="secondary"
|
||||
icon="dismiss"
|
||||
variant="clear"
|
||||
class="absolute z-10 ltr:right-2 rtl:left-2 top-2"
|
||||
class="absolute ltr:right-2 rtl:left-2 top-2 z-10"
|
||||
@click="close"
|
||||
/>
|
||||
<slot />
|
||||
@@ -66,6 +60,15 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
modalContainerClassName() {
|
||||
let className =
|
||||
'modal-container rtl:text-right shadow-md rounded-sm max-h-full overflow-auto relative w-[37.5rem] bg-white dark:bg-slate-800 skip-context-menu';
|
||||
if (this.fullWidth) {
|
||||
return `${className} items-center rounded-none flex h-full justify-center w-full`;
|
||||
}
|
||||
|
||||
return `${className} ${this.size}`;
|
||||
},
|
||||
modalClassName() {
|
||||
const modalClassNameMap = {
|
||||
centered: '',
|
||||
@@ -99,9 +102,7 @@ export default {
|
||||
onMouseUp() {
|
||||
if (this.mousedDownOnBackdrop) {
|
||||
this.mousedDownOnBackdrop = false;
|
||||
if (this.closeOnBackdropClick) {
|
||||
this.onClose();
|
||||
}
|
||||
this.onClose();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user