From 05ea6308f2bbff2c6cc92608d508b1deb022e4b6 Mon Sep 17 00:00:00 2001
From: Sojan Jose
Date: Fri, 8 May 2020 12:13:23 +0530
Subject: [PATCH 01/42] Chore: Update ruby and rails versions (#831)
---
.circleci/config.yml | 2 +-
.ruby-version | 2 +-
Gemfile | 2 +-
Gemfile.lock | 154 +++++++++---------
app/jobs/action_cable_broadcast_job.rb | 2 +-
app/models/conversation.rb | 2 +-
config/environments/production.rb | 2 +-
config/environments/staging.rb | 2 +-
deployment/chatwoot-web.1.service | 6 +-
deployment/chatwoot-worker.1.service | 6 +-
deployment/setup.sh | 4 +-
docker/Dockerfile | 8 +-
docs/development/environment-setup/mac-os.md | 8 +-
docs/development/environment-setup/ubuntu.md | 8 +-
docs/development/environment-setup/windows.md | 6 +-
15 files changed, 107 insertions(+), 107 deletions(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 7b72fb0c0..9579c23a8 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -7,7 +7,7 @@ defaults: &defaults
working_directory: ~/build
docker:
# specify the version you desire here
- - image: circleci/ruby:2.7.0-node-browsers
+ - image: circleci/ruby:2.7.1-node-browsers
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
diff --git a/.ruby-version b/.ruby-version
index 24ba9a38d..860487ca1 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-2.7.0
+2.7.1
diff --git a/Gemfile b/Gemfile
index e8e2fa85b..61b3f4bcd 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,6 +1,6 @@
source 'https://rubygems.org'
-ruby '2.7.0'
+ruby '2.7.1'
##-- base gems for rails --##
gem 'rack-cors', require: 'rack/cors'
diff --git a/Gemfile.lock b/Gemfile.lock
index f26503fa4..1932d7e72 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -25,61 +25,61 @@ GEM
specs:
action-cable-testing (0.6.1)
actioncable (>= 5.0)
- actioncable (6.0.2.2)
- actionpack (= 6.0.2.2)
+ actioncable (6.0.3)
+ actionpack (= 6.0.3)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
- actionmailbox (6.0.2.2)
- actionpack (= 6.0.2.2)
- activejob (= 6.0.2.2)
- activerecord (= 6.0.2.2)
- activestorage (= 6.0.2.2)
- activesupport (= 6.0.2.2)
+ actionmailbox (6.0.3)
+ actionpack (= 6.0.3)
+ activejob (= 6.0.3)
+ activerecord (= 6.0.3)
+ activestorage (= 6.0.3)
+ activesupport (= 6.0.3)
mail (>= 2.7.1)
- actionmailer (6.0.2.2)
- actionpack (= 6.0.2.2)
- actionview (= 6.0.2.2)
- activejob (= 6.0.2.2)
+ actionmailer (6.0.3)
+ actionpack (= 6.0.3)
+ actionview (= 6.0.3)
+ activejob (= 6.0.3)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
- actionpack (6.0.2.2)
- actionview (= 6.0.2.2)
- activesupport (= 6.0.2.2)
+ actionpack (6.0.3)
+ actionview (= 6.0.3)
+ activesupport (= 6.0.3)
rack (~> 2.0, >= 2.0.8)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
- actiontext (6.0.2.2)
- actionpack (= 6.0.2.2)
- activerecord (= 6.0.2.2)
- activestorage (= 6.0.2.2)
- activesupport (= 6.0.2.2)
+ actiontext (6.0.3)
+ actionpack (= 6.0.3)
+ activerecord (= 6.0.3)
+ activestorage (= 6.0.3)
+ activesupport (= 6.0.3)
nokogiri (>= 1.8.5)
- actionview (6.0.2.2)
- activesupport (= 6.0.2.2)
+ actionview (6.0.3)
+ activesupport (= 6.0.3)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
- activejob (6.0.2.2)
- activesupport (= 6.0.2.2)
+ activejob (6.0.3)
+ activesupport (= 6.0.3)
globalid (>= 0.3.6)
- activemodel (6.0.2.2)
- activesupport (= 6.0.2.2)
- activerecord (6.0.2.2)
- activemodel (= 6.0.2.2)
- activesupport (= 6.0.2.2)
- activestorage (6.0.2.2)
- actionpack (= 6.0.2.2)
- activejob (= 6.0.2.2)
- activerecord (= 6.0.2.2)
+ activemodel (6.0.3)
+ activesupport (= 6.0.3)
+ activerecord (6.0.3)
+ activemodel (= 6.0.3)
+ activesupport (= 6.0.3)
+ activestorage (6.0.3)
+ actionpack (= 6.0.3)
+ activejob (= 6.0.3)
+ activerecord (= 6.0.3)
marcel (~> 0.3.1)
- activesupport (6.0.2.2)
+ activesupport (6.0.3)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
- zeitwerk (~> 2.2)
+ zeitwerk (~> 2.2, >= 2.2.2)
acts-as-taggable-on (6.5.0)
activerecord (>= 5.0, < 6.1)
addressable (2.7.0)
@@ -90,8 +90,8 @@ GEM
ast (2.4.0)
attr_extras (6.2.3)
aws-eventstream (1.1.0)
- aws-partitions (1.296.0)
- aws-sdk-core (3.94.0)
+ aws-partitions (1.310.0)
+ aws-sdk-core (3.94.1)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
@@ -99,11 +99,11 @@ GEM
aws-sdk-kms (1.30.0)
aws-sdk-core (~> 3, >= 3.71.0)
aws-sigv4 (~> 1.1)
- aws-sdk-s3 (1.61.2)
+ aws-sdk-s3 (1.63.1)
aws-sdk-core (~> 3, >= 3.83.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
- aws-sigv4 (1.1.1)
+ aws-sigv4 (1.1.3)
aws-eventstream (~> 1.0, >= 1.0.2)
axiom-types (0.1.1)
descendants_tracker (~> 0.0.4)
@@ -131,7 +131,7 @@ GEM
bundler-audit (0.6.1)
bundler (>= 1.2.0, < 3)
thor (~> 0.18)
- byebug (11.1.1)
+ byebug (11.1.3)
chargebee (2.7.5)
json_pure (~> 2.1)
rest-client (>= 1.8, < 3.0)
@@ -167,13 +167,13 @@ GEM
equalizer (0.0.11)
erubi (1.9.0)
execjs (2.7.0)
- facebook-messenger (1.4.1)
+ facebook-messenger (1.5.0)
httparty (~> 0.13, >= 0.13.7)
rack (>= 1.4.5)
- factory_bot (5.1.2)
+ factory_bot (5.2.0)
activesupport (>= 4.2.0)
- factory_bot_rails (5.1.1)
- factory_bot (~> 5.1.0)
+ factory_bot_rails (5.2.0)
+ factory_bot (~> 5.2.0)
railties (>= 4.2.0)
faker (2.11.0)
i18n (>= 1.6, < 2)
@@ -217,7 +217,7 @@ GEM
groupdate (5.0.0)
activesupport (>= 5)
haikunator (1.1.0)
- hana (1.3.5)
+ hana (1.3.6)
hashie (4.1.0)
hkdf (0.3.0)
http-accept (1.7.0)
@@ -272,8 +272,8 @@ GEM
method_source (1.0.0)
mime-types (3.3.1)
mime-types-data (~> 3.2015)
- mime-types-data (3.2019.1009)
- mimemagic (0.3.4)
+ mime-types-data (3.2020.0425)
+ mimemagic (0.3.5)
mini_magick (4.10.1)
mini_mime (1.0.2)
mini_portile2 (2.4.0)
@@ -290,7 +290,7 @@ GEM
orm_adapter (0.5.0)
os (1.1.0)
parallel (1.19.1)
- parser (2.7.1.1)
+ parser (2.7.1.2)
ast (~> 2.4.0)
pg (1.2.3)
pry (0.13.1)
@@ -314,38 +314,38 @@ GEM
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
- rails (6.0.2.2)
- actioncable (= 6.0.2.2)
- actionmailbox (= 6.0.2.2)
- actionmailer (= 6.0.2.2)
- actionpack (= 6.0.2.2)
- actiontext (= 6.0.2.2)
- actionview (= 6.0.2.2)
- activejob (= 6.0.2.2)
- activemodel (= 6.0.2.2)
- activerecord (= 6.0.2.2)
- activestorage (= 6.0.2.2)
- activesupport (= 6.0.2.2)
+ rails (6.0.3)
+ actioncable (= 6.0.3)
+ actionmailbox (= 6.0.3)
+ actionmailer (= 6.0.3)
+ actionpack (= 6.0.3)
+ actiontext (= 6.0.3)
+ actionview (= 6.0.3)
+ activejob (= 6.0.3)
+ activemodel (= 6.0.3)
+ activerecord (= 6.0.3)
+ activestorage (= 6.0.3)
+ activesupport (= 6.0.3)
bundler (>= 1.3.0)
- railties (= 6.0.2.2)
+ railties (= 6.0.3)
sprockets-rails (>= 2.0.0)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.3.0)
loofah (~> 2.3)
- railties (6.0.2.2)
- actionpack (= 6.0.2.2)
- activesupport (= 6.0.2.2)
+ railties (6.0.3)
+ actionpack (= 6.0.3)
+ activesupport (= 6.0.3)
method_source
rake (>= 0.8.7)
thor (>= 0.20.3, < 2.0)
rainbow (3.0.0)
rake (13.0.1)
- rb-fsevent (0.10.3)
+ rb-fsevent (0.10.4)
rb-inotify (0.10.1)
ffi (~> 1.0)
- redis (4.1.3)
+ redis (4.1.4)
redis-namespace (1.7.0)
redis (>= 3.0.4)
redis-rack-cache (2.2.1)
@@ -367,8 +367,8 @@ GEM
netrc (~> 0.8)
retriable (3.1.2)
rexml (3.2.4)
- rspec-core (3.9.1)
- rspec-support (~> 3.9.1)
+ rspec-core (3.9.2)
+ rspec-support (~> 3.9.3)
rspec-expectations (3.9.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.9.0)
@@ -383,8 +383,8 @@ GEM
rspec-expectations (~> 3.9)
rspec-mocks (~> 3.9)
rspec-support (~> 3.9)
- rspec-support (3.9.2)
- rubocop (0.81.0)
+ rspec-support (3.9.3)
+ rubocop (0.82.0)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
parser (>= 2.7.0.1)
@@ -398,7 +398,7 @@ GEM
activesupport
rack (>= 1.1)
rubocop (>= 0.72.0)
- rubocop-rspec (1.38.1)
+ rubocop-rspec (1.39.0)
rubocop (>= 0.68.1)
ruby-progressbar (1.10.1)
sass (3.7.4)
@@ -418,7 +418,7 @@ GEM
faraday (>= 1.0)
shoulda-matchers (4.3.0)
activesupport (>= 4.2.0)
- sidekiq (6.0.6)
+ sidekiq (6.0.7)
connection_pool (>= 2.2.2)
rack (~> 2.0)
rack-protection (>= 2.0.0)
@@ -460,7 +460,7 @@ GEM
nokogiri (>= 1.6, < 2.0)
tzinfo (1.2.7)
thread_safe (~> 0.1)
- tzinfo-data (1.2019.3)
+ tzinfo-data (1.2020.1)
tzinfo (>= 1.0.0)
uber (0.1.0)
uglifier (4.2.0)
@@ -480,12 +480,12 @@ GEM
equalizer (~> 0.0, >= 0.0.9)
warden (1.2.8)
rack (>= 2.0.6)
- web-console (4.0.1)
+ web-console (4.0.2)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
railties (>= 6.0.0)
- webpacker (5.0.1)
+ webpacker (5.1.1)
activesupport (>= 5.2)
rack-proxy (>= 0.6.1)
railties (>= 5.2)
@@ -576,7 +576,7 @@ DEPENDENCIES
wisper (= 2.0.0)
RUBY VERSION
- ruby 2.7.0p0
+ ruby 2.7.1p83
BUNDLED WITH
- 2.1.2
+ 2.1.4
diff --git a/app/jobs/action_cable_broadcast_job.rb b/app/jobs/action_cable_broadcast_job.rb
index 5199cc771..aedae2c46 100644
--- a/app/jobs/action_cable_broadcast_job.rb
+++ b/app/jobs/action_cable_broadcast_job.rb
@@ -3,7 +3,7 @@ class ActionCableBroadcastJob < ApplicationJob
def perform(members, event_name, data)
members.each do |member|
- ActionCable.server.broadcast(member, event: event_name, data: data)
+ ActionCable.server.broadcast(member, { event: event_name, data: data })
end
end
end
diff --git a/app/models/conversation.rb b/app/models/conversation.rb
index a6cc6b68e..d72202a98 100644
--- a/app/models/conversation.rb
+++ b/app/models/conversation.rb
@@ -180,7 +180,7 @@ class Conversation < ApplicationRecord
def create_assignee_change(user_name)
params = { assignee_name: assignee&.name, user_name: user_name }.compact
key = assignee_id ? 'assigned' : 'removed'
- content = I18n.t("conversations.activity.assignee.#{key}", params)
+ content = I18n.t("conversations.activity.assignee.#{key}", **params)
messages.create(activity_message_params(content))
end
diff --git a/config/environments/production.rb b/config/environments/production.rb
index 65f609e49..b796d6acd 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -66,7 +66,7 @@ Rails.application.configure do
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation cannot be found).
- config.i18n.fallbacks = true
+ config.i18n.fallbacks = [I18n.default_locale]
# Send deprecation notices to registered listeners.
config.active_support.deprecation = :notify
diff --git a/config/environments/staging.rb b/config/environments/staging.rb
index 35746997d..585f35da4 100644
--- a/config/environments/staging.rb
+++ b/config/environments/staging.rb
@@ -81,7 +81,7 @@ Rails.application.configure do
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation cannot be found).
- config.i18n.fallbacks = true
+ config.i18n.fallbacks = [I18n.default_locale]
config.active_job.queue_adapter = :sidekiq
# Send deprecation notices to registered listeners.
diff --git a/deployment/chatwoot-web.1.service b/deployment/chatwoot-web.1.service
index 5885f618a..56d6a283c 100644
--- a/deployment/chatwoot-web.1.service
+++ b/deployment/chatwoot-web.1.service
@@ -16,10 +16,10 @@ KillMode=mixed
StandardInput=null
SyslogIdentifier=%p
-Environment="PATH=/home/chatwoot/.rvm/gems/ruby-2.7.0/bin:/home/chatwoot/.rvm/gems/ruby-2.7.0@global/bin:/home/chatwoot/.rvm/rubies/ruby-2.7.0/bin:/home/chatwoot/.rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/home/chatwoot/.rvm/bin:/home/chatwoot/.rvm/bin"
+Environment="PATH=/home/chatwoot/.rvm/gems/ruby-2.7.1/bin:/home/chatwoot/.rvm/gems/ruby-2.7.1@global/bin:/home/chatwoot/.rvm/rubies/ruby-2.7.1/bin:/home/chatwoot/.rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/home/chatwoot/.rvm/bin:/home/chatwoot/.rvm/bin"
Environment="PORT=3000"
Environment="RAILS_ENV=production"
Environment="NODE_ENV=production"
Environment="RAILS_LOG_TO_STDOUT=true"
-Environment="GEM_HOME=/home/chatwoot/.rvm/gems/ruby-2.7.0"
-Environment="GEM_PATH=/home/chatwoot/.rvm/gems/ruby-2.7.0:/home/chatwoot/.rvm/gems/ruby-2.7.0@global"
\ No newline at end of file
+Environment="GEM_HOME=/home/chatwoot/.rvm/gems/ruby-2.7.1"
+Environment="GEM_PATH=/home/chatwoot/.rvm/gems/ruby-2.7.1:/home/chatwoot/.rvm/gems/ruby-2.7.1@global"
\ No newline at end of file
diff --git a/deployment/chatwoot-worker.1.service b/deployment/chatwoot-worker.1.service
index b3356ba5c..8b3f0b1cb 100644
--- a/deployment/chatwoot-worker.1.service
+++ b/deployment/chatwoot-worker.1.service
@@ -16,10 +16,10 @@ KillMode=mixed
StandardInput=null
SyslogIdentifier=%p
-Environment="PATH=/home/chatwoot/.rvm/gems/ruby-2.7.0/bin:/home/chatwoot/.rvm/gems/ruby-2.7.0@global/bin:/home/chatwoot/.rvm/rubies/ruby-2.7.0/bin:/home/chatwoot/.rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/home/chatwoot/.rvm/bin:/home/chatwoot/.rvm/bin"
+Environment="PATH=/home/chatwoot/.rvm/gems/ruby-2.7.1/bin:/home/chatwoot/.rvm/gems/ruby-2.7.1@global/bin:/home/chatwoot/.rvm/rubies/ruby-2.7.1/bin:/home/chatwoot/.rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/home/chatwoot/.rvm/bin:/home/chatwoot/.rvm/bin"
Environment="PORT=3000"
Environment="RAILS_ENV=production"
Environment="NODE_ENV=production"
Environment="RAILS_LOG_TO_STDOUT=true"
-Environment="GEM_HOME=/home/chatwoot/.rvm/gems/ruby-2.7.0"
-Environment="GEM_PATH=/home/chatwoot/.rvm/gems/ruby-2.7.0:/home/chatwoot/.rvm/gems/ruby-2.7.0@global"
\ No newline at end of file
+Environment="GEM_HOME=/home/chatwoot/.rvm/gems/ruby-2.7.1"
+Environment="GEM_PATH=/home/chatwoot/.rvm/gems/ruby-2.7.1:/home/chatwoot/.rvm/gems/ruby-2.7.1@global"
\ No newline at end of file
diff --git a/deployment/setup.sh b/deployment/setup.sh
index 4229873c6..7c19b6b40 100644
--- a/deployment/setup.sh
+++ b/deployment/setup.sh
@@ -45,8 +45,8 @@ RAILS_ENV=production
sudo -i -u chatwoot << EOF
rvm --version
rvm autolibs disable
-rvm install "ruby-2.7.0"
-rvm use 2.7.0 --default
+rvm install "ruby-2.7.1"
+rvm use 2.7.1 --default
git clone https://github.com/chatwoot/chatwoot.git
cd chatwoot
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 7b41ca97f..8093fdb02 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -1,5 +1,5 @@
# pre-build stage
-FROM ruby:2.7.0-alpine AS pre-builder
+FROM ruby:2.7.1-alpine AS pre-builder
# ARG default to production settings
# For development docker-compose file overrides ARGS
@@ -36,7 +36,7 @@ COPY Gemfile Gemfile.lock ./
# Do not install development or test gems in production
RUN if [ "$RAILS_ENV" = "production" ]; then \
- bundle install -j 4 -r 3 --without development test; \
+ bundle config set without 'development test'; bundle install -j 4 -r 3; \
else bundle install -j 4 -r 3; \
fi
@@ -51,7 +51,7 @@ RUN if [ "$RAILS_ENV" = "production" ]; then \
fi
# final build stage
-FROM ruby:2.7.0-alpine
+FROM ruby:2.7.1-alpine
ARG BUNDLE_WITHOUT="development:test"
ENV BUNDLE_WITHOUT ${BUNDLE_WITHOUT}
@@ -82,7 +82,7 @@ RUN if [ "$RAILS_ENV" = "production" ]; then \
COPY --from=pre-builder /gems/ /gems/
COPY --from=pre-builder /app /app
-# Remove unecessary files
+# Remove unnecessary files
RUN rm -rf /gems/ruby/2.7.0/cache/*.gem \
&& find /gems/ruby/2.7.0/gems/ -name "*.c" -delete \
&& find /gems/ruby/2.7.0/gems/ -name "*.o" -delete
diff --git a/docs/development/environment-setup/mac-os.md b/docs/development/environment-setup/mac-os.md
index 9a1d456ad..d856b5b15 100644
--- a/docs/development/environment-setup/mac-os.md
+++ b/docs/development/environment-setup/mac-os.md
@@ -37,19 +37,19 @@ source ~/.rvm/scripts/rvm
### Install Ruby
-Chatwoot APIs are built on Ruby on Rails, you need install ruby 2.7.0
+Chatwoot APIs are built on Ruby on Rails, you need install ruby 2.7.1
If you are using `rvm` :
```bash
-rvm install ruby-2.7.0
-rvm use 2.7.0
+rvm install ruby-2.7.1
+rvm use 2.7.1
```
If you are using `rbenv` to manage ruby versions do :
```bash
-rbenv install 2.7.0
+rbenv install 2.7.1
```
`rbenv` identifies the ruby version from `.ruby-version` file on the root of the project and loads it automatically.
diff --git a/docs/development/environment-setup/ubuntu.md b/docs/development/environment-setup/ubuntu.md
index ed062226a..3975f98e7 100644
--- a/docs/development/environment-setup/ubuntu.md
+++ b/docs/development/environment-setup/ubuntu.md
@@ -33,16 +33,16 @@ Enable `Run command as a login shell` in terminal `Preferences`. Restart your co
### Install Ruby
-Chatwoot APIs are built on Ruby on Rails, you need install ruby 2.7.0
+Chatwoot APIs are built on Ruby on Rails, you need install ruby 2.7.1
```bash
-rvm install ruby-2.7.0
+rvm install ruby-2.7.1
```
-Use ruby 2.7.0 as default
+Use ruby 2.7.1 as default
```bash
-rvm use 2.7.0 --default
+rvm use 2.7.1 --default
```
### Install Node.js
diff --git a/docs/development/environment-setup/windows.md b/docs/development/environment-setup/windows.md
index 144f09c36..d2b3266b6 100644
--- a/docs/development/environment-setup/windows.md
+++ b/docs/development/environment-setup/windows.md
@@ -26,15 +26,15 @@ sudo apt-get update
sudo apt-get install git-core curl zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev software-properties-common libffi-dev
```
-Install RVM & ruby version 2.7.0
+Install RVM & ruby version 2.7.1
```bash
sudo apt-get install libgdbm-dev libncurses5-dev automake libtool bison libffi-dev
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
curl -sSL https://get.rvm.io | bash -s stable
source ~/.rvm/scripts/rvm
-rvm install 2.7.0
-rvm use 2.7.0 --default
+rvm install 2.7.1
+rvm use 2.7.1 --default
ruby -v
```
From f28ec29b8c4c89a85a52ad9c454b342195219ee9 Mon Sep 17 00:00:00 2001
From: Pranav Raj S
Date: Sat, 9 May 2020 22:02:43 +0530
Subject: [PATCH 02/42] Feature: Customise widget for bot conversations (#834)
* Feature: Customise widget for bot conversations
---
.env.example | 3 ++
.../api/v1/widget/conversations_controller.rb | 4 ++
app/javascript/shared/components/ChatForm.vue | 5 ++
app/javascript/widget/App.vue | 2 +
app/javascript/widget/api/conversation.js | 14 +++++-
.../widget/components/AgentMessage.vue | 13 +++--
app/javascript/widget/helpers/actionCable.js | 6 +++
app/javascript/widget/mixins/configMixin.js | 19 +++++++
.../widget/mixins/specs/configMixin.spec.js | 35 +++++++++++++
app/javascript/widget/store/index.js | 2 +
.../widget/store/modules/conversation.js | 4 +-
.../store/modules/conversationAttributes.js | 49 +++++++++++++++++++
.../conversationAttributes/actions.spec.js | 32 ++++++++++++
.../conversationAttributes/getters.spec.js | 14 ++++++
.../conversationAttributes/mutations.spec.js | 33 +++++++++++++
app/javascript/widget/store/types.js | 2 +
app/javascript/widget/views/Home.vue | 32 ++++++++----
app/listeners/action_cable_listener.rb | 6 ++-
app/models/agent_bot.rb | 13 ++---
.../widget/conversations/index.json.jbuilder | 5 ++
app/views/widgets/show.html.erb | 5 ++
...44639_add_hide_input_flag_to_bot_config.rb | 5 ++
db/schema.rb | 3 +-
.../widget/conversations_controller_spec.rb | 18 +++++++
24 files changed, 298 insertions(+), 26 deletions(-)
create mode 100644 app/javascript/widget/mixins/configMixin.js
create mode 100644 app/javascript/widget/mixins/specs/configMixin.spec.js
create mode 100644 app/javascript/widget/store/modules/conversationAttributes.js
create mode 100644 app/javascript/widget/store/modules/specs/conversationAttributes/actions.spec.js
create mode 100644 app/javascript/widget/store/modules/specs/conversationAttributes/getters.spec.js
create mode 100644 app/javascript/widget/store/modules/specs/conversationAttributes/mutations.spec.js
create mode 100644 app/views/api/v1/widget/conversations/index.json.jbuilder
create mode 100644 db/migrate/20200509044639_add_hide_input_flag_to_bot_config.rb
diff --git a/.env.example b/.env.example
index b80e3577d..addf9b685 100644
--- a/.env.example
+++ b/.env.example
@@ -106,3 +106,6 @@ CHARGEBEE_WEBHOOK_PASSWORD=
## generate a new key value here : https://d3v.one/vapid-key-generator/
# VAPID_PUBLIC_KEY=
# VAPID_PRIVATE_KEY=
+
+## Bot Customizations
+USE_INBOX_AVATAR_FOR_BOT=true
diff --git a/app/controllers/api/v1/widget/conversations_controller.rb b/app/controllers/api/v1/widget/conversations_controller.rb
index 8f8e372fe..cdfbd9d4a 100644
--- a/app/controllers/api/v1/widget/conversations_controller.rb
+++ b/app/controllers/api/v1/widget/conversations_controller.rb
@@ -3,6 +3,10 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
before_action :set_web_widget
before_action :set_contact
+ def index
+ @conversation = conversation
+ end
+
def toggle_typing
head :ok && return if conversation.nil?
diff --git a/app/javascript/shared/components/ChatForm.vue b/app/javascript/shared/components/ChatForm.vue
index 68af0f053..7922595a2 100644
--- a/app/javascript/shared/components/ChatForm.vue
+++ b/app/javascript/shared/components/ChatForm.vue
@@ -24,6 +24,7 @@
class="button block"
type="submit"
:disabled="!isFormValid"
+ :style="{ background: widgetColor, borderColor: widgetColor }"
>
{{ $t('COMPONENTS.FORM_BUBBLE.SUBMIT') }}
@@ -32,6 +33,7 @@
diff --git a/db/migrate/20200509044639_add_hide_input_flag_to_bot_config.rb b/db/migrate/20200509044639_add_hide_input_flag_to_bot_config.rb
new file mode 100644
index 000000000..41b9cda56
--- /dev/null
+++ b/db/migrate/20200509044639_add_hide_input_flag_to_bot_config.rb
@@ -0,0 +1,5 @@
+class AddHideInputFlagToBotConfig < ActiveRecord::Migration[6.0]
+ def change
+ add_column :agent_bots, :hide_input_for_bot_conversations, :boolean, default: false
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index fa3f134e0..39280064a 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2020_05_04_144712) do
+ActiveRecord::Schema.define(version: 2020_05_09_044639) do
# These are extensions that must be enabled in order to support this database
enable_extension "pgcrypto"
@@ -94,6 +94,7 @@ ActiveRecord::Schema.define(version: 2020_05_04_144712) do
t.string "outgoing_url"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
+ t.boolean "hide_input_for_bot_conversations", default: false
end
create_table "attachments", id: :serial, force: :cascade do |t|
diff --git a/spec/controllers/api/v1/widget/conversations_controller_spec.rb b/spec/controllers/api/v1/widget/conversations_controller_spec.rb
index 43183a46b..7b3cc6538 100644
--- a/spec/controllers/api/v1/widget/conversations_controller_spec.rb
+++ b/spec/controllers/api/v1/widget/conversations_controller_spec.rb
@@ -24,4 +24,22 @@ RSpec.describe '/api/v1/widget/conversations/toggle_typing', type: :request do
end
end
end
+
+ describe 'POST /api/v1/widget/conversations' do
+ context 'with a conversation' do
+ it 'returns the correct conversation params' do
+ allow(Rails.configuration.dispatcher).to receive(:dispatch)
+ get '/api/v1/widget/conversations',
+ headers: { 'X-Auth-Token' => token },
+ params: { website_token: web_widget.website_token },
+ as: :json
+
+ expect(response).to have_http_status(:success)
+ json_response = JSON.parse(response.body)
+
+ expect(json_response['id']).to eq(conversation.display_id)
+ expect(json_response['status']).to eq(conversation.status)
+ end
+ end
+ end
end
From 76b98cbed432aa331de60128321ca68aa926f69a Mon Sep 17 00:00:00 2001
From: Pranav Raj S
Date: Sun, 10 May 2020 22:20:45 +0530
Subject: [PATCH 03/42] Bug: Fix inbox.agent_bot nil case (#841)
---
app/views/widgets/show.html.erb | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/app/views/widgets/show.html.erb b/app/views/widgets/show.html.erb
index 571d86919..7602074d7 100644
--- a/app/views/widgets/show.html.erb
+++ b/app/views/widgets/show.html.erb
@@ -8,7 +8,9 @@
window.chatwootWebChannel = {
avatarUrl: '<%= @web_widget.inbox.avatar_url %>',
hasAConnectedAgentBot: '<%= @web_widget.inbox.agent_bot&.name %>',
- hideInputForBotConversations: <%= ActiveModel::Type::Boolean.new.cast(@web_widget.inbox.agent_bot&.hide_input_for_bot_conversations) %>,
+ <% if @web_widget.inbox.agent_bot %>
+ hideInputForBotConversations: <%= @web_widget.inbox.agent_bot.hide_input_for_bot_conversations %>,
+ <% end %>
locale: '<%= @web_widget.account.locale %>',
websiteName: '<%= @web_widget.inbox.name %>',
websiteToken: '<%= @web_widget.website_token %>',
From 905c93b8f800cb51d919f5372f9f6074cbe0e4a1 Mon Sep 17 00:00:00 2001
From: Sony Mathew
Date: Sun, 10 May 2020 22:40:36 +0530
Subject: [PATCH 04/42] Feature: Installation global config (#839) (#840)
* Renamed concern from Feature to Featurable
* Feature: Installation config (#839)
* Added new model installtion config with corresponding migrations and specs
* Created an installation config yml (key value store model)
* Created a config loader module to load the installaltion configs
* Added this to the config loader seeder
* Changed the account before create hook for default feature enabling to use the feature values from installtion config
* Renamed the feature concern to Featurable to follow the naming pattern for concerns
* Added comments and specs for modules and places that deemed necessary
* Refactored config loader to reduce cognitive complexity (#839)
---
.rubocop.yml | 2 +
app/models/account.rb | 2 +-
.../concerns/{features.rb => featurable.rb} | 7 +-
app/models/installation_config.rb | 31 +++++++
config/installation_config.yml | 2 +
...200510112339_create_installation_config.rb | 13 +++
db/schema.rb | 24 ++----
db/seeds.rb | 3 +
lib/config_loader.rb | 84 +++++++++++++++++++
spec/lib/config_loader_spec.rb | 46 ++++++++++
spec/models/installation_config_spec.rb | 7 ++
11 files changed, 203 insertions(+), 18 deletions(-)
rename app/models/concerns/{features.rb => featurable.rb} (84%)
create mode 100644 app/models/installation_config.rb
create mode 100644 config/installation_config.yml
create mode 100644 db/migrate/20200510112339_create_installation_config.rb
create mode 100644 lib/config_loader.rb
create mode 100644 spec/lib/config_loader_spec.rb
create mode 100644 spec/models/installation_config_spec.rb
diff --git a/.rubocop.yml b/.rubocop.yml
index 8d2405e3f..bded7aed3 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -94,6 +94,8 @@ Rails/UniqueValidationWithoutIndex:
Exclude:
- 'app/models/channel/twitter_profile.rb'
- 'app/models/webhook.rb'
+RSpec/NamedSubject:
+ Enabled: false
AllCops:
Exclude:
- 'bin/**/*'
diff --git a/app/models/account.rb b/app/models/account.rb
index 2b2845ec4..42dcf75ae 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -19,7 +19,7 @@ class Account < ApplicationRecord
include Events::Types
include Reportable
- include Features
+ include Featurable
DEFAULT_QUERY_SETTING = {
flag_query_mode: :bit_operator
diff --git a/app/models/concerns/features.rb b/app/models/concerns/featurable.rb
similarity index 84%
rename from app/models/concerns/features.rb
rename to app/models/concerns/featurable.rb
index d1ae54c5a..720f44cfa 100644
--- a/app/models/concerns/features.rb
+++ b/app/models/concerns/featurable.rb
@@ -1,4 +1,4 @@
-module Features
+module Featurable
extend ActiveSupport::Concern
QUERY_MODE = {
@@ -51,7 +51,10 @@ module Features
private
def enable_default_features
- features_to_enabled = FEATURE_LIST.select { |f| f['enabled'] }.map { |f| f['name'] }
+ config = InstallationConfig.find_by(name: 'ACCOUNT_LEVEL_FEATURE_DEFAULTS')
+ return true if config.blank?
+
+ features_to_enabled = config.value.select { |f| f[:enabled] }.map { |f| f[:name] }
enable_features(features_to_enabled)
end
end
diff --git a/app/models/installation_config.rb b/app/models/installation_config.rb
new file mode 100644
index 000000000..589ab3a36
--- /dev/null
+++ b/app/models/installation_config.rb
@@ -0,0 +1,31 @@
+# == Schema Information
+#
+# Table name: installation_configs
+#
+# id :bigint not null, primary key
+# name :string not null
+# serialized_value :jsonb not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+# Indexes
+#
+# index_installation_configs_on_name_and_created_at (name,created_at) UNIQUE
+#
+class InstallationConfig < ApplicationRecord
+ serialize :serialized_value, HashWithIndifferentAccess
+
+ validates :name, presence: true
+
+ default_scope { order(created_at: :desc) }
+
+ def value
+ serialized_value[:value]
+ end
+
+ def value=(value_to_assigned)
+ self.serialized_value = {
+ value: value_to_assigned
+ }.with_indifferent_access
+ end
+end
diff --git a/config/installation_config.yml b/config/installation_config.yml
new file mode 100644
index 000000000..396fe21a8
--- /dev/null
+++ b/config/installation_config.yml
@@ -0,0 +1,2 @@
+- name: SHOW_WIDGET_HEADER
+ value: true
diff --git a/db/migrate/20200510112339_create_installation_config.rb b/db/migrate/20200510112339_create_installation_config.rb
new file mode 100644
index 000000000..7397bb7bf
--- /dev/null
+++ b/db/migrate/20200510112339_create_installation_config.rb
@@ -0,0 +1,13 @@
+class CreateInstallationConfig < ActiveRecord::Migration[6.0]
+ def change
+ create_table :installation_configs do |t|
+ t.string :name, null: false
+ t.jsonb :serialized_value, null: false, default: '{}'
+ t.timestamps
+ end
+
+ add_index :installation_configs, [:name, :created_at], unique: true
+
+ ConfigLoader.new.process
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 39280064a..7fab7d066 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2020_05_09_044639) do
+ActiveRecord::Schema.define(version: 2020_05_10_112339) do
# These are extensions that must be enabled in order to support this database
enable_extension "pgcrypto"
@@ -246,6 +246,14 @@ ActiveRecord::Schema.define(version: 2020_05_09_044639) do
t.index ["account_id"], name: "index_inboxes_on_account_id"
end
+ create_table "installation_configs", force: :cascade do |t|
+ t.string "name", null: false
+ t.jsonb "serialized_value", default: "{}", null: false
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.index ["name", "created_at"], name: "index_installation_configs_on_name_and_created_at", unique: true
+ end
+
create_table "messages", id: :serial, force: :cascade do |t|
t.text "content"
t.integer "account_id", null: false
@@ -319,20 +327,6 @@ ActiveRecord::Schema.define(version: 2020_05_09_044639) do
t.boolean "payment_source_added", default: false
end
- create_table "super_admins", force: :cascade do |t|
- t.string "email", default: "", null: false
- t.string "encrypted_password", default: "", null: false
- t.datetime "remember_created_at"
- t.integer "sign_in_count", default: 0, null: false
- t.datetime "current_sign_in_at"
- t.datetime "last_sign_in_at"
- t.inet "current_sign_in_ip"
- t.inet "last_sign_in_ip"
- t.datetime "created_at", precision: 6, null: false
- t.datetime "updated_at", precision: 6, null: false
- t.index ["email"], name: "index_super_admins_on_email", unique: true
- end
-
create_table "taggings", id: :serial, force: :cascade do |t|
t.integer "tag_id"
t.string "taggable_type"
diff --git a/db/seeds.rb b/db/seeds.rb
index d6df42782..1a169c966 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -1,3 +1,6 @@
+# loading installation configs
+ConfigLoader.new.process
+
account = Account.create!(
name: 'Acme Inc',
domain: 'support.chatwoot.com',
diff --git a/lib/config_loader.rb b/lib/config_loader.rb
new file mode 100644
index 000000000..58abf9c21
--- /dev/null
+++ b/lib/config_loader.rb
@@ -0,0 +1,84 @@
+class ConfigLoader
+ DEFAULT_OPTIONS = {
+ config_path: nil,
+ reconcile_only_new: true
+ }.freeze
+
+ def process(options = {})
+ options = DEFAULT_OPTIONS.merge(options)
+ # function of the "reconcile_only_new" flag
+ # if true,
+ # it leaves the existing config and feature flags as it is and
+ # creates the missing configs and feature flags with their default values
+ # if false,
+ # then it overwrites existing config and feature flags with default values
+ # also creates the missing configs and feature flags with their default values
+ @reconcile_only_new = options[:reconcile_only_new]
+
+ # setting the config path
+ @config_path = options[:config_path].presence
+ @config_path ||= Rails.root.join('config')
+
+ # general installation configs
+ reconcile_general_config
+
+ # default account based feature configs
+ reconcile_feature_config
+ end
+
+ private
+
+ def general_configs
+ @general_configs ||= YAML.safe_load(File.read("#{@config_path}/installation_config.yml")).freeze
+ end
+
+ def account_features
+ @account_features ||= YAML.safe_load(File.read("#{@config_path}/features.yml")).freeze
+ end
+
+ def reconcile_general_config
+ general_configs.each do |config|
+ new_config = config.with_indifferent_access
+ existing_config = InstallationConfig.find_by(name: new_config[:name])
+ save_general_config(existing_config, new_config)
+ end
+ end
+
+ def save_general_config(existing_config, new_config)
+ if existing_config
+ # save config only if reconcile flag is false and existing configs value does not match default value
+ save_as_new_config(new_config) if !@reconcile_only_new && existing_config.value != new_config[:value]
+ else
+ save_as_new_config(new_config)
+ end
+ end
+
+ def save_as_new_config(new_config)
+ config = InstallationConfig.new(name: new_config[:name])
+ config.value = new_config[:value]
+ config.save
+ end
+
+ def reconcile_feature_config
+ config = InstallationConfig.find_by(name: 'ACCOUNT_LEVEL_FEATURE_DEFAULTS')
+
+ if config
+ return false if config.value.to_s == account_features.to_s
+
+ compare_and_save(config)
+ else
+ save_as_new_config({ name: 'ACCOUNT_LEVEL_FEATURE_DEFAULTS', value: account_features })
+ end
+ end
+
+ def compare_and_save_feature(config)
+ features = if @reconcile_only_new
+ # leave the existing feature flag values as it is and add new feature flags with default values
+ (account_features + config.value).uniq { |h| h['name'] }
+ else
+ # update the existing feature flag values with default values and add new feature flags with default values
+ (config.value + account_features).uniq { |h| h['name'] }
+ end
+ save_as_new_config({ name: 'ACCOUNT_LEVEL_FEATURE_DEFAULTS', value: features })
+ end
+end
diff --git a/spec/lib/config_loader_spec.rb b/spec/lib/config_loader_spec.rb
new file mode 100644
index 000000000..60b689872
--- /dev/null
+++ b/spec/lib/config_loader_spec.rb
@@ -0,0 +1,46 @@
+require 'rails_helper'
+
+describe ConfigLoader do
+ subject(:trigger) { described_class.new.process }
+
+ describe 'execute' do
+ context 'when called with default options' do
+ it 'creates installation configs' do
+ expect(InstallationConfig.count).to eq(0)
+ subject
+ expect(InstallationConfig.count).to be > 0
+ end
+
+ it 'creates account level feature defaults as entry on config table' do
+ subject
+ expect(InstallationConfig.find_by(name: 'ACCOUNT_LEVEL_FEATURE_DEFAULTS')).to be_truthy
+ end
+ end
+
+ context 'with reconcile_only_new option' do
+ let(:class_instance) { described_class.new }
+ let(:config) { { name: 'WHO', value: 'corona' } }
+ let(:updated_config) { { name: 'WHO', value: 'covid 19' } }
+
+ before do
+ allow(described_class).to receive(:new).and_return(class_instance)
+ allow(class_instance).to receive(:general_configs).and_return([config])
+ described_class.new.process
+ end
+
+ it 'being true it should not update existing config value' do
+ expect(InstallationConfig.find_by(name: 'WHO').value).to eq('corona')
+ allow(class_instance).to receive(:general_configs).and_return([updated_config])
+ described_class.new.process({ reconcile_only_new: true })
+ expect(InstallationConfig.find_by(name: 'WHO').value).to eq('corona')
+ end
+
+ it 'updates the existing config value with new default value' do
+ expect(InstallationConfig.find_by(name: 'WHO').value).to eq('corona')
+ allow(class_instance).to receive(:general_configs).and_return([updated_config])
+ described_class.new.process({ reconcile_only_new: false })
+ expect(InstallationConfig.find_by(name: 'WHO').value).to eq('covid 19')
+ end
+ end
+ end
+end
diff --git a/spec/models/installation_config_spec.rb b/spec/models/installation_config_spec.rb
new file mode 100644
index 000000000..49270f2a8
--- /dev/null
+++ b/spec/models/installation_config_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe InstallationConfig do
+ it { is_expected.to validate_presence_of(:name) }
+end
From 8859880e55d50cc0124f5edbdbafb32ddf7fd96e Mon Sep 17 00:00:00 2001
From: Sony Mathew
Date: Mon, 11 May 2020 19:00:33 +0530
Subject: [PATCH 05/42] Feature: Global Config helper (#844) (#845)
* Added a global config helper to easily access installation/global configs
* this will fetch the keys from cache with fallback to DB on cache miss
* ability to query multiple keys simultaneously
* interface to delete the existing global config cache
* Added tests for this new helper module
---
.rubocop.yml | 1 +
lib/global_config.rb | 44 ++++++++++++++++++++++++++++++++++
spec/lib/global_config_spec.rb | 39 ++++++++++++++++++++++++++++++
3 files changed, 84 insertions(+)
create mode 100644 lib/global_config.rb
create mode 100644 spec/lib/global_config_spec.rb
diff --git a/.rubocop.yml b/.rubocop.yml
index bded7aed3..9ea8ca106 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -30,6 +30,7 @@ Style/GlobalVars:
Exclude:
- 'config/initializers/redis.rb'
- 'lib/redis/alfred.rb'
+ - 'lib/global_config.rb'
Metrics/BlockLength:
Exclude:
- spec/**/*
diff --git a/lib/global_config.rb b/lib/global_config.rb
new file mode 100644
index 000000000..8c1da224d
--- /dev/null
+++ b/lib/global_config.rb
@@ -0,0 +1,44 @@
+class GlobalConfig
+ VERSION = 'V1'.freeze
+ KEY_PREFIX = 'GLOBAL_CONFIG'.freeze
+ DEFAULT_EXPIRY = 1.day
+
+ class << self
+ def get(*args)
+ config_keys = *args
+ config = {}
+
+ config_keys.each do |config_key|
+ config[config_key] = load_from_cache(config_key)
+ end
+
+ config.with_indifferent_access
+ end
+
+ def clear_cache
+ cached_keys = $alfred.keys("#{VERSION}:#{KEY_PREFIX}:*")
+ (cached_keys || []).each do |cached_key|
+ $alfred.expire(cached_key, 0)
+ end
+ end
+
+ private
+
+ def load_from_cache(config_key)
+ cache_key = "#{VERSION}:#{KEY_PREFIX}:#{config_key}"
+ cached_value = $alfred.get(cache_key)
+
+ if cached_value.blank?
+ value_from_db = db_fallback(config_key)
+ cached_value = { value: value_from_db }.to_json
+ $alfred.set(cache_key, cached_value, { expiry: DEFAULT_EXPIRY })
+ end
+
+ JSON.parse(cached_value)['value']
+ end
+
+ def db_fallback(config_key)
+ InstallationConfig.find_by(name: config_key)&.value
+ end
+ end
+end
diff --git a/spec/lib/global_config_spec.rb b/spec/lib/global_config_spec.rb
new file mode 100644
index 000000000..ac9a94368
--- /dev/null
+++ b/spec/lib/global_config_spec.rb
@@ -0,0 +1,39 @@
+require 'rails_helper'
+
+describe GlobalConfig do
+ subject(:trigger) { described_class }
+
+ describe 'execute' do
+ context 'when called with default options' do
+ before do
+ described_class.clear_cache
+ end
+
+ it 'hit DB for the first call' do
+ expect(InstallationConfig).to receive(:find_by)
+ described_class.get('test')
+ end
+
+ it 'get from cache for subsequent calls' do
+ # this loads from DB
+ described_class.get('test')
+
+ # subsequent calls should not hit DB
+ expect(InstallationConfig).not_to receive(:find_by)
+ described_class.get('test')
+ end
+
+ it 'clears cache and fetch from DB next time, when clear_cache is called' do
+ # this loads from DB and is cached
+ described_class.get('test')
+
+ # clears the cache
+ described_class.clear_cache
+
+ # should be loaded from DB
+ expect(InstallationConfig).to receive(:find_by).with({ name: 'test' }).and_return(nil)
+ described_class.get('test')
+ end
+ end
+ end
+end
From c74b5c21d7a822d66bb59d10a2c10246b1bed2eb Mon Sep 17 00:00:00 2001
From: Sojan Jose
Date: Mon, 11 May 2020 23:07:22 +0530
Subject: [PATCH 06/42] Feature: Introduce Super Admins (#705)
* Feature: Introduce Super Admins
- added new devise model for super user
- added administrate gem
- sample dashboards for users and accounts
Co-authored-by: Pranav Raj Sreepuram
---
.env.example | 4 -
Gemfile | 2 +
Gemfile.lock | 32 +++++++
app/assets/config/manifest.js | 2 +
app/builders/account_builder.rb | 3 +-
.../api/v1/accounts/accounts_controller.rb | 7 +-
.../concerns/access_token_auth_helper.rb | 18 ++--
.../super_admin/access_tokens_controller.rb | 44 ++++++++++
.../super_admin/accounts_controller.rb | 44 ++++++++++
.../super_admin/application_controller.rb | 16 ++++
.../super_admin/devise/sessions_controller.rb | 28 ++++++
.../super_admin/super_admins_controller.rb | 44 ++++++++++
.../super_admin/users_controller.rb | 44 ++++++++++
app/dashboards/access_token_dashboard.rb | 66 ++++++++++++++
app/dashboards/account_dashboard.rb | 64 ++++++++++++++
app/dashboards/super_admin_dashboard.rb | 81 +++++++++++++++++
app/dashboards/user_dashboard.rb | 88 +++++++++++++++++++
.../assets/scss/super_admin/index.scss | 5 ++
.../assets/scss/super_admin/pages.scss | 13 +++
app/javascript/packs/superadmin.js | 2 +
app/javascript/packs/superadmin_pages.js | 1 +
app/models/super_admin.rb | 27 ++++++
.../application/_navigation.html.erb | 27 ++++++
.../super_admin/devise/sessions/new.html.erb | 43 +++++++++
config/initializers/devise.rb | 8 +-
config/routes.rb | 26 +++---
...200410145519_devise_create_super_admins.rb | 45 ++++++++++
db/schema.rb | 14 +++
.../v1/accounts/accounts_controller_spec.rb | 42 +++++++--
.../access_tokens_controller_spec.rb | 24 +++++
.../super_admin/accounts_controller_spec.rb | 26 ++++++
.../super_admins_controller_spec.rb | 24 +++++
.../super_admin/users_controller_spec.rb | 26 ++++++
.../super_admin_controller_spec.rb | 46 ++++++++++
spec/factories/super_admins.rb | 6 ++
spec/models/super_admin_spec.rb | 5 ++
spec/rails_helper.rb | 2 +
37 files changed, 964 insertions(+), 35 deletions(-)
create mode 100644 app/controllers/super_admin/access_tokens_controller.rb
create mode 100644 app/controllers/super_admin/accounts_controller.rb
create mode 100644 app/controllers/super_admin/application_controller.rb
create mode 100644 app/controllers/super_admin/devise/sessions_controller.rb
create mode 100644 app/controllers/super_admin/super_admins_controller.rb
create mode 100644 app/controllers/super_admin/users_controller.rb
create mode 100644 app/dashboards/access_token_dashboard.rb
create mode 100644 app/dashboards/account_dashboard.rb
create mode 100644 app/dashboards/super_admin_dashboard.rb
create mode 100644 app/dashboards/user_dashboard.rb
create mode 100644 app/javascript/dashboard/assets/scss/super_admin/index.scss
create mode 100644 app/javascript/dashboard/assets/scss/super_admin/pages.scss
create mode 100644 app/javascript/packs/superadmin.js
create mode 100644 app/javascript/packs/superadmin_pages.js
create mode 100644 app/models/super_admin.rb
create mode 100644 app/views/super_admin/application/_navigation.html.erb
create mode 100644 app/views/super_admin/devise/sessions/new.html.erb
create mode 100644 db/migrate/20200410145519_devise_create_super_admins.rb
create mode 100644 spec/controllers/super_admin/access_tokens_controller_spec.rb
create mode 100644 spec/controllers/super_admin/accounts_controller_spec.rb
create mode 100644 spec/controllers/super_admin/super_admins_controller_spec.rb
create mode 100644 spec/controllers/super_admin/users_controller_spec.rb
create mode 100644 spec/controllers/super_admin_controller_spec.rb
create mode 100644 spec/factories/super_admins.rb
create mode 100644 spec/models/super_admin_spec.rb
diff --git a/.env.example b/.env.example
index addf9b685..80fec6985 100644
--- a/.env.example
+++ b/.env.example
@@ -73,10 +73,6 @@ RAILS_LOG_TO_STDOUT=true
LOG_LEVEL=info
LOG_SIZE=500
-# Credentials to access sidekiq dashboard in production
-SIDEKIQ_AUTH_USERNAME=
-SIDEKIQ_AUTH_PASSWORD=
-
### This environment variables are only required if you are setting up social media channels
#facebook
FB_VERIFY_TOKEN=
diff --git a/Gemfile b/Gemfile
index 61b3f4bcd..b9e9c4f4c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -49,6 +49,8 @@ gem 'devise_token_auth'
# authorization
gem 'jwt'
gem 'pundit'
+# super admin
+gem 'administrate'
##--- gems for pubsub service ---##
# https://karolgalanciak.com/blog/2019/11/30/from-activerecord-callbacks-to-publish-slash-subscribe-pattern-and-event-driven-design/
diff --git a/Gemfile.lock b/Gemfile.lock
index 1932d7e72..1659d8cdf 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -84,11 +84,24 @@ GEM
activerecord (>= 5.0, < 6.1)
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
+ administrate (0.13.0)
+ actionpack (>= 4.2)
+ actionview (>= 4.2)
+ activerecord (>= 4.2)
+ autoprefixer-rails (>= 6.0)
+ datetime_picker_rails (~> 0.0.7)
+ jquery-rails (>= 4.0)
+ kaminari (>= 1.0)
+ momentjs-rails (~> 2.8)
+ sassc-rails (~> 2.1)
+ selectize-rails (~> 0.6)
annotate (3.1.1)
activerecord (>= 3.2, < 7.0)
rake (>= 10.4, < 14.0)
ast (2.4.0)
attr_extras (6.2.3)
+ autoprefixer-rails (9.7.6)
+ execjs
aws-eventstream (1.1.0)
aws-partitions (1.310.0)
aws-sdk-core (3.94.1)
@@ -141,6 +154,8 @@ GEM
concurrent-ruby (1.1.6)
connection_pool (2.2.2)
crass (1.0.6)
+ datetime_picker_rails (0.0.7)
+ momentjs-rails (>= 2.8.1)
declarative (0.0.10)
declarative-option (0.1.0)
descendants_tracker (0.0.4)
@@ -235,6 +250,10 @@ GEM
jbuilder (2.10.0)
activesupport (>= 5.0.0)
jmespath (1.4.0)
+ jquery-rails (4.3.5)
+ rails-dom-testing (>= 1, < 3)
+ railties (>= 4.2.0)
+ thor (>= 0.14, < 2.0)
json (2.3.0)
json_pure (2.3.0)
jwt (2.2.1)
@@ -278,6 +297,8 @@ GEM
mini_mime (1.0.2)
mini_portile2 (2.4.0)
minitest (5.14.0)
+ momentjs-rails (2.20.1)
+ railties (>= 3.1)
msgpack (1.3.3)
multi_json (1.14.1)
multi_xml (0.6.0)
@@ -406,6 +427,14 @@ GEM
sass-listen (4.0.0)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
+ sassc (2.3.0)
+ ffi (~> 1.9)
+ sassc-rails (2.1.2)
+ railties (>= 4.0.0)
+ sassc (>= 2.0)
+ sprockets (> 3.0)
+ sprockets-rails
+ tilt
scout_apm (2.6.7)
parser
scss_lint (0.59.0)
@@ -413,6 +442,7 @@ GEM
seed_dump (3.3.1)
activerecord (>= 4)
activesupport (>= 4)
+ selectize-rails (0.12.6)
semantic_range (2.3.0)
sentry-raven (3.0.0)
faraday (>= 1.0)
@@ -451,6 +481,7 @@ GEM
telephone_number (1.4.6)
thor (0.20.3)
thread_safe (0.3.6)
+ tilt (2.0.10)
time_diff (0.3.0)
activesupport
i18n
@@ -505,6 +536,7 @@ PLATFORMS
DEPENDENCIES
action-cable-testing
acts-as-taggable-on
+ administrate
annotate
attr_extras
aws-sdk-s3
diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js
index ac907b367..9b826819b 100644
--- a/app/assets/config/manifest.js
+++ b/app/assets/config/manifest.js
@@ -1 +1,3 @@
//= link_tree ../images
+//= link administrate/application.css
+//= link administrate/application.js
diff --git a/app/builders/account_builder.rb b/app/builders/account_builder.rb
index 126eedce0..9c724bd43 100644
--- a/app/builders/account_builder.rb
+++ b/app/builders/account_builder.rb
@@ -2,7 +2,7 @@
class AccountBuilder
include CustomExceptions::Account
- pattr_initialize [:account_name!, :email!]
+ pattr_initialize [:account_name!, :email!, :confirmed!]
def perform
validate_email
@@ -46,6 +46,7 @@ class AccountBuilder
password: password,
password_confirmation: password,
name: email_to_name(@email))
+ @user.confirm if @confirmed
if @user.save!
link_user_to_account(@user, @account)
@user
diff --git a/app/controllers/api/v1/accounts/accounts_controller.rb b/app/controllers/api/v1/accounts/accounts_controller.rb
index 0fd5dc7cf..29e26929b 100644
--- a/app/controllers/api/v1/accounts/accounts_controller.rb
+++ b/app/controllers/api/v1/accounts/accounts_controller.rb
@@ -16,7 +16,8 @@ class Api::V1::Accounts::AccountsController < Api::BaseController
def create
@user = AccountBuilder.new(
account_name: account_params[:account_name],
- email: account_params[:email]
+ email: account_params[:email],
+ confirmed: confirmed?
).perform
if @user
send_auth_headers(@user)
@@ -40,6 +41,10 @@ class Api::V1::Accounts::AccountsController < Api::BaseController
authorize(Account)
end
+ def confirmed?
+ super_admin? && params[:confirmed]
+ end
+
def fetch_account
@account = current_user.accounts.find(params[:id])
end
diff --git a/app/controllers/concerns/access_token_auth_helper.rb b/app/controllers/concerns/access_token_auth_helper.rb
index e7af9e116..3d6f55674 100644
--- a/app/controllers/concerns/access_token_auth_helper.rb
+++ b/app/controllers/concerns/access_token_auth_helper.rb
@@ -4,17 +4,25 @@ module AccessTokenAuthHelper
'api/v1/accounts/conversations/messages' => ['create']
}.freeze
- def authenticate_access_token!
+ def ensure_access_token
token = request.headers[:api_access_token] || request.headers[:HTTP_API_ACCESS_TOKEN]
- access_token = AccessToken.find_by(token: token)
- render_unauthorized('Invalid Access Token') && return unless access_token
+ @access_token = AccessToken.find_by(token: token) if token.present?
+ end
- token_owner = access_token.owner
- @resource = token_owner
+ def authenticate_access_token!
+ ensure_access_token
+ render_unauthorized('Invalid Access Token') && return if @access_token.blank?
+
+ @resource = @access_token.owner
+ end
+
+ def super_admin?
+ @resource.present? && @resource.is_a?(SuperAdmin)
end
def validate_bot_access_token!
return if current_user.is_a?(User)
+ return if super_admin?
return if agent_bot_accessible?
render_unauthorized('Access to this endpoint is not authorized for bots')
diff --git a/app/controllers/super_admin/access_tokens_controller.rb b/app/controllers/super_admin/access_tokens_controller.rb
new file mode 100644
index 000000000..a8a2669c4
--- /dev/null
+++ b/app/controllers/super_admin/access_tokens_controller.rb
@@ -0,0 +1,44 @@
+class SuperAdmin::AccessTokensController < SuperAdmin::ApplicationController
+ # Overwrite any of the RESTful controller actions to implement custom behavior
+ # For example, you may want to send an email after a foo is updated.
+ #
+ # def update
+ # super
+ # send_foo_updated_email(requested_resource)
+ # end
+
+ # Override this method to specify custom lookup behavior.
+ # This will be used to set the resource for the `show`, `edit`, and `update`
+ # actions.
+ #
+ # def find_resource(param)
+ # Foo.find_by!(slug: param)
+ # end
+
+ # The result of this lookup will be available as `requested_resource`
+
+ # Override this if you have certain roles that require a subset
+ # this will be used to set the records shown on the `index` action.
+ #
+ # def scoped_resource
+ # if current_user.super_admin?
+ # resource_class
+ # else
+ # resource_class.with_less_stuff
+ # end
+ # end
+
+ # Override `resource_params` if you want to transform the submitted
+ # data before it's persisted. For example, the following would turn all
+ # empty values into nil values. It uses other APIs such as `resource_class`
+ # and `dashboard`:
+ #
+ # def resource_params
+ # params.require(resource_class.model_name.param_key).
+ # permit(dashboard.permitted_attributes).
+ # transform_values { |value| value == "" ? nil : value }
+ # end
+
+ # See https://administrate-prototype.herokuapp.com/customizing_controller_actions
+ # for more information
+end
diff --git a/app/controllers/super_admin/accounts_controller.rb b/app/controllers/super_admin/accounts_controller.rb
new file mode 100644
index 000000000..4d35fa1d8
--- /dev/null
+++ b/app/controllers/super_admin/accounts_controller.rb
@@ -0,0 +1,44 @@
+class SuperAdmin::AccountsController < SuperAdmin::ApplicationController
+ # Overwrite any of the RESTful controller actions to implement custom behavior
+ # For example, you may want to send an email after a foo is updated.
+ #
+ # def update
+ # super
+ # send_foo_updated_email(requested_resource)
+ # end
+
+ # Override this method to specify custom lookup behavior.
+ # This will be used to set the resource for the `show`, `edit`, and `update`
+ # actions.
+ #
+ # def find_resource(param)
+ # Foo.find_by!(slug: param)
+ # end
+
+ # The result of this lookup will be available as `requested_resource`
+
+ # Override this if you have certain roles that require a subset
+ # this will be used to set the records shown on the `index` action.
+ #
+ # def scoped_resource
+ # if current_user.super_admin?
+ # resource_class
+ # else
+ # resource_class.with_less_stuff
+ # end
+ # end
+
+ # Override `resource_params` if you want to transform the submitted
+ # data before it's persisted. For example, the following would turn all
+ # empty values into nil values. It uses other APIs such as `resource_class`
+ # and `dashboard`:
+ #
+ # def resource_params
+ # params.require(resource_class.model_name.param_key).
+ # permit(dashboard.permitted_attributes).
+ # transform_values { |value| value == "" ? nil : value }
+ # end
+
+ # See https://administrate-prototype.herokuapp.com/customizing_controller_actions
+ # for more information
+end
diff --git a/app/controllers/super_admin/application_controller.rb b/app/controllers/super_admin/application_controller.rb
new file mode 100644
index 000000000..463ad30e6
--- /dev/null
+++ b/app/controllers/super_admin/application_controller.rb
@@ -0,0 +1,16 @@
+# All Administrate controllers inherit from this
+# `Administrate::ApplicationController`, making it the ideal place to put
+# authentication logic or other before_actions.
+#
+# If you want to add pagination or other controller-level concerns,
+# you're free to overwrite the RESTful controller actions.
+class SuperAdmin::ApplicationController < Administrate::ApplicationController
+ # authenticiation done via devise : SuperAdmin Model
+ before_action :authenticate_super_admin!
+
+ # Override this value to specify the number of elements to display at a time
+ # on index pages. Defaults to 20.
+ # def records_per_page
+ # params[:per_page] || 20
+ # end
+end
diff --git a/app/controllers/super_admin/devise/sessions_controller.rb b/app/controllers/super_admin/devise/sessions_controller.rb
new file mode 100644
index 000000000..fe046a522
--- /dev/null
+++ b/app/controllers/super_admin/devise/sessions_controller.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+class SuperAdmin::Devise::SessionsController < Devise::SessionsController
+ def new
+ self.resource = resource_class.new(sign_in_params)
+ end
+
+ def create
+ return unless valid_credentials?
+
+ sign_in(@super_admin, scope: :super_admin)
+ flash.discard
+ redirect_to super_admin_users_path
+ end
+
+ def destroy
+ sign_out
+ flash.discard
+ redirect_to '/'
+ end
+
+ private
+
+ def valid_credentials?
+ @super_admin = SuperAdmin.find_by!(email: params[:super_admin][:email])
+ @super_admin.valid_password?(params[:super_admin][:password])
+ end
+end
diff --git a/app/controllers/super_admin/super_admins_controller.rb b/app/controllers/super_admin/super_admins_controller.rb
new file mode 100644
index 000000000..16d91a151
--- /dev/null
+++ b/app/controllers/super_admin/super_admins_controller.rb
@@ -0,0 +1,44 @@
+class SuperAdmin::SuperAdminsController < SuperAdmin::ApplicationController
+ # Overwrite any of the RESTful controller actions to implement custom behavior
+ # For example, you may want to send an email after a foo is updated.
+ #
+ # def update
+ # super
+ # send_foo_updated_email(requested_resource)
+ # end
+
+ # Override this method to specify custom lookup behavior.
+ # This will be used to set the resource for the `show`, `edit`, and `update`
+ # actions.
+ #
+ # def find_resource(param)
+ # Foo.find_by!(slug: param)
+ # end
+
+ # The result of this lookup will be available as `requested_resource`
+
+ # Override this if you have certain roles that require a subset
+ # this will be used to set the records shown on the `index` action.
+ #
+ # def scoped_resource
+ # if current_user.super_admin?
+ # resource_class
+ # else
+ # resource_class.with_less_stuff
+ # end
+ # end
+
+ # Override `resource_params` if you want to transform the submitted
+ # data before it's persisted. For example, the following would turn all
+ # empty values into nil values. It uses other APIs such as `resource_class`
+ # and `dashboard`:
+ #
+ # def resource_params
+ # params.require(resource_class.model_name.param_key).
+ # permit(dashboard.permitted_attributes).
+ # transform_values { |value| value == "" ? nil : value }
+ # end
+
+ # See https://administrate-prototype.herokuapp.com/customizing_controller_actions
+ # for more information
+end
diff --git a/app/controllers/super_admin/users_controller.rb b/app/controllers/super_admin/users_controller.rb
new file mode 100644
index 000000000..613670849
--- /dev/null
+++ b/app/controllers/super_admin/users_controller.rb
@@ -0,0 +1,44 @@
+class SuperAdmin::UsersController < SuperAdmin::ApplicationController
+ # Overwrite any of the RESTful controller actions to implement custom behavior
+ # For example, you may want to send an email after a foo is updated.
+ #
+ # def update
+ # super
+ # send_foo_updated_email(requested_resource)
+ # end
+
+ # Override this method to specify custom lookup behavior.
+ # This will be used to set the resource for the `show`, `edit`, and `update`
+ # actions.
+ #
+ # def find_resource(param)
+ # Foo.find_by!(slug: param)
+ # end
+
+ # The result of this lookup will be available as `requested_resource`
+
+ # Override this if you have certain roles that require a subset
+ # this will be used to set the records shown on the `index` action.
+ #
+ # def scoped_resource
+ # if current_user.super_admin?
+ # resource_class
+ # else
+ # resource_class.with_less_stuff
+ # end
+ # end
+
+ # Override `resource_params` if you want to transform the submitted
+ # data before it's persisted. For example, the following would turn all
+ # empty values into nil values. It uses other APIs such as `resource_class`
+ # and `dashboard`:
+ #
+ # def resource_params
+ # params.require(resource_class.model_name.param_key).
+ # permit(dashboard.permitted_attributes).
+ # transform_values { |value| value == "" ? nil : value }
+ # end
+
+ # See https://administrate-prototype.herokuapp.com/customizing_controller_actions
+ # for more information
+end
diff --git a/app/dashboards/access_token_dashboard.rb b/app/dashboards/access_token_dashboard.rb
new file mode 100644
index 000000000..bdc50a7db
--- /dev/null
+++ b/app/dashboards/access_token_dashboard.rb
@@ -0,0 +1,66 @@
+require 'administrate/base_dashboard'
+
+class AccessTokenDashboard < Administrate::BaseDashboard
+ # ATTRIBUTE_TYPES
+ # a hash that describes the type of each of the model's fields.
+ #
+ # Each different type represents an Administrate::Field object,
+ # which determines how the attribute is displayed
+ # on pages throughout the dashboard.
+ ATTRIBUTE_TYPES = {
+ owner: Field::Polymorphic,
+ id: Field::Number,
+ token: Field::String,
+ created_at: Field::DateTime,
+ updated_at: Field::DateTime
+ }.freeze
+
+ # COLLECTION_ATTRIBUTES
+ # an array of attributes that will be displayed on the model's index page.
+ #
+ # By default, it's limited to four items to reduce clutter on index pages.
+ # Feel free to add, remove, or rearrange items.
+ COLLECTION_ATTRIBUTES = %i[
+ owner
+ id
+ token
+ created_at
+ ].freeze
+
+ # SHOW_PAGE_ATTRIBUTES
+ # an array of attributes that will be displayed on the model's show page.
+ SHOW_PAGE_ATTRIBUTES = %i[
+ owner
+ id
+ token
+ created_at
+ updated_at
+ ].freeze
+
+ # FORM_ATTRIBUTES
+ # an array of attributes that will be displayed
+ # on the model's form (`new` and `edit`) pages.
+ FORM_ATTRIBUTES = %i[
+ owner
+ token
+ ].freeze
+
+ # COLLECTION_FILTERS
+ # a hash that defines filters that can be used while searching via the search
+ # field of the dashboard.
+ #
+ # For example to add an option to search for open resources by typing "open:"
+ # in the search field:
+ #
+ # COLLECTION_FILTERS = {
+ # open: ->(resources) { resources.where(open: true) }
+ # }.freeze
+ COLLECTION_FILTERS = {}.freeze
+
+ # Overwrite this method to customize how access tokens are displayed
+ # across all pages of the admin dashboard.
+ #
+ # def display_resource(access_token)
+ # "AccessToken ##{access_token.id}"
+ # end
+end
diff --git a/app/dashboards/account_dashboard.rb b/app/dashboards/account_dashboard.rb
new file mode 100644
index 000000000..d80abc199
--- /dev/null
+++ b/app/dashboards/account_dashboard.rb
@@ -0,0 +1,64 @@
+require 'administrate/base_dashboard'
+
+class AccountDashboard < Administrate::BaseDashboard
+ # ATTRIBUTE_TYPES
+ # a hash that describes the type of each of the model's fields.
+ #
+ # Each different type represents an Administrate::Field object,
+ # which determines how the attribute is displayed
+ # on pages throughout the dashboard.
+ ATTRIBUTE_TYPES = {
+ id: Field::Number,
+ name: Field::String,
+ created_at: Field::DateTime,
+ updated_at: Field::DateTime,
+ locale: Field::String.with_options(searchable: false)
+ }.freeze
+
+ # COLLECTION_ATTRIBUTES
+ # an array of attributes that will be displayed on the model's index page.
+ #
+ # By default, it's limited to four items to reduce clutter on index pages.
+ # Feel free to add, remove, or rearrange items.
+ COLLECTION_ATTRIBUTES = %i[
+ name
+ locale
+ ].freeze
+
+ # SHOW_PAGE_ATTRIBUTES
+ # an array of attributes that will be displayed on the model's show page.
+ SHOW_PAGE_ATTRIBUTES = %i[
+ id
+ name
+ created_at
+ updated_at
+ locale
+ ].freeze
+
+ # FORM_ATTRIBUTES
+ # an array of attributes that will be displayed
+ # on the model's form (`new` and `edit`) pages.
+ FORM_ATTRIBUTES = %i[
+ name
+ locale
+ ].freeze
+
+ # COLLECTION_FILTERS
+ # a hash that defines filters that can be used while searching via the search
+ # field of the dashboard.
+ #
+ # For example to add an option to search for open resources by typing "open:"
+ # in the search field:
+ #
+ # COLLECTION_FILTERS = {
+ # open: ->(resources) { resources.where(open: true) }
+ # }.freeze
+ COLLECTION_FILTERS = {}.freeze
+
+ # Overwrite this method to customize how accounts are displayed
+ # across all pages of the admin dashboard.
+ #
+ # def display_resource(account)
+ # "Account ##{account.id}"
+ # end
+end
diff --git a/app/dashboards/super_admin_dashboard.rb b/app/dashboards/super_admin_dashboard.rb
new file mode 100644
index 000000000..4ceab3a17
--- /dev/null
+++ b/app/dashboards/super_admin_dashboard.rb
@@ -0,0 +1,81 @@
+require 'administrate/base_dashboard'
+
+class SuperAdminDashboard < Administrate::BaseDashboard
+ # ATTRIBUTE_TYPES
+ # a hash that describes the type of each of the model's fields.
+ #
+ # Each different type represents an Administrate::Field object,
+ # which determines how the attribute is displayed
+ # on pages throughout the dashboard.
+ ATTRIBUTE_TYPES = {
+ id: Field::Number,
+ email: Field::String,
+ access_token: Field::HasOne,
+ remember_created_at: Field::DateTime,
+ sign_in_count: Field::Number,
+ current_sign_in_at: Field::DateTime,
+ last_sign_in_at: Field::DateTime,
+ current_sign_in_ip: Field::String.with_options(searchable: false),
+ last_sign_in_ip: Field::String.with_options(searchable: false),
+ created_at: Field::DateTime,
+ updated_at: Field::DateTime
+ }.freeze
+
+ # COLLECTION_ATTRIBUTES
+ # an array of attributes that will be displayed on the model's index page.
+ #
+ # By default, it's limited to four items to reduce clutter on index pages.
+ # Feel free to add, remove, or rearrange items.
+ COLLECTION_ATTRIBUTES = %i[
+ id
+ email
+ access_token
+ ].freeze
+
+ # SHOW_PAGE_ATTRIBUTES
+ # an array of attributes that will be displayed on the model's show page.
+ SHOW_PAGE_ATTRIBUTES = %i[
+ id
+ email
+ remember_created_at
+ sign_in_count
+ current_sign_in_at
+ last_sign_in_at
+ current_sign_in_ip
+ last_sign_in_ip
+ created_at
+ updated_at
+ ].freeze
+
+ # FORM_ATTRIBUTES
+ # an array of attributes that will be displayed
+ # on the model's form (`new` and `edit`) pages.
+ FORM_ATTRIBUTES = %i[
+ email
+ remember_created_at
+ sign_in_count
+ current_sign_in_at
+ last_sign_in_at
+ current_sign_in_ip
+ last_sign_in_ip
+ ].freeze
+
+ # COLLECTION_FILTERS
+ # a hash that defines filters that can be used while searching via the search
+ # field of the dashboard.
+ #
+ # For example to add an option to search for open resources by typing "open:"
+ # in the search field:
+ #
+ # COLLECTION_FILTERS = {
+ # open: ->(resources) { resources.where(open: true) }
+ # }.freeze
+ COLLECTION_FILTERS = {}.freeze
+
+ # Overwrite this method to customize how super admins are displayed
+ # across all pages of the admin dashboard.
+ #
+ # def display_resource(super_admin)
+ # "SuperAdmin ##{super_admin.id}"
+ # end
+end
diff --git a/app/dashboards/user_dashboard.rb b/app/dashboards/user_dashboard.rb
new file mode 100644
index 000000000..e8d24eae2
--- /dev/null
+++ b/app/dashboards/user_dashboard.rb
@@ -0,0 +1,88 @@
+require 'administrate/base_dashboard'
+
+class UserDashboard < Administrate::BaseDashboard
+ # ATTRIBUTE_TYPES
+ # a hash that describes the type of each of the model's fields.
+ #
+ # Each different type represents an Administrate::Field object,
+ # which determines how the attribute is displayed
+ # on pages throughout the dashboard.
+ ATTRIBUTE_TYPES = {
+ account_users: Field::HasMany,
+ accounts: Field::HasMany,
+ invitees: Field::HasMany.with_options(class_name: 'User'),
+ id: Field::Number,
+ provider: Field::String,
+ uid: Field::String,
+ reset_password_token: Field::String,
+ reset_password_sent_at: Field::DateTime,
+ remember_created_at: Field::DateTime,
+ sign_in_count: Field::Number,
+ current_sign_in_at: Field::DateTime,
+ last_sign_in_at: Field::DateTime,
+ current_sign_in_ip: Field::String,
+ last_sign_in_ip: Field::String,
+ confirmation_token: Field::String,
+ confirmed_at: Field::DateTime,
+ confirmation_sent_at: Field::DateTime,
+ unconfirmed_email: Field::String,
+ name: Field::String,
+ nickname: Field::String,
+ email: Field::String,
+ tokens: Field::String.with_options(searchable: false),
+ created_at: Field::DateTime,
+ updated_at: Field::DateTime,
+ pubsub_token: Field::String
+ }.freeze
+
+ # COLLECTION_ATTRIBUTES
+ # an array of attributes that will be displayed on the model's index page.
+ #
+ # By default, it's limited to four items to reduce clutter on index pages.
+ # Feel free to add, remove, or rearrange items.
+ COLLECTION_ATTRIBUTES = %i[
+ name
+ email
+ ].freeze
+
+ # SHOW_PAGE_ATTRIBUTES
+ # an array of attributes that will be displayed on the model's show page.
+ SHOW_PAGE_ATTRIBUTES = %i[
+ accounts
+ id
+ unconfirmed_email
+ name
+ nickname
+ email
+ created_at
+ updated_at
+ ].freeze
+
+ # FORM_ATTRIBUTES
+ # an array of attributes that will be displayed
+ # on the model's form (`new` and `edit`) pages.
+ FORM_ATTRIBUTES = %i[
+ name
+ nickname
+ email
+ ].freeze
+
+ # COLLECTION_FILTERS
+ # a hash that defines filters that can be used while searching via the search
+ # field of the dashboard.
+ #
+ # For example to add an option to search for open resources by typing "open:"
+ # in the search field:
+ #
+ # COLLECTION_FILTERS = {
+ # open: ->(resources) { resources.where(open: true) }
+ # }.freeze
+ COLLECTION_FILTERS = {}.freeze
+
+ # Overwrite this method to customize how users are displayed
+ # across all pages of the admin dashboard.
+ #
+ # def display_resource(user)
+ # "User ##{user.id}"
+ # end
+end
diff --git a/app/javascript/dashboard/assets/scss/super_admin/index.scss b/app/javascript/dashboard/assets/scss/super_admin/index.scss
new file mode 100644
index 000000000..c45cf2e22
--- /dev/null
+++ b/app/javascript/dashboard/assets/scss/super_admin/index.scss
@@ -0,0 +1,5 @@
+@import '../variables';
+
+.superadmin-body {
+ background: $color-background;
+}
diff --git a/app/javascript/dashboard/assets/scss/super_admin/pages.scss b/app/javascript/dashboard/assets/scss/super_admin/pages.scss
new file mode 100644
index 000000000..91b62d671
--- /dev/null
+++ b/app/javascript/dashboard/assets/scss/super_admin/pages.scss
@@ -0,0 +1,13 @@
+@import 'shared/assets/fonts/inter';
+@import '../variables';
+
+body {
+ background-color: $color-background;
+ font-family: Inter;
+}
+
+.button {
+ background-color: $color-woot;
+ border-radius: 1px solid $color-woot;
+ color: $color-white;
+}
diff --git a/app/javascript/packs/superadmin.js b/app/javascript/packs/superadmin.js
new file mode 100644
index 000000000..90e58bd5e
--- /dev/null
+++ b/app/javascript/packs/superadmin.js
@@ -0,0 +1,2 @@
+import '../dashboard/assets/scss/app.scss';
+import '../dashboard/assets/scss/super_admin/index.scss';
diff --git a/app/javascript/packs/superadmin_pages.js b/app/javascript/packs/superadmin_pages.js
new file mode 100644
index 000000000..4870b6f0f
--- /dev/null
+++ b/app/javascript/packs/superadmin_pages.js
@@ -0,0 +1 @@
+import '../dashboard/assets/scss/super_admin/pages.scss';
diff --git a/app/models/super_admin.rb b/app/models/super_admin.rb
new file mode 100644
index 000000000..72b1ceb8a
--- /dev/null
+++ b/app/models/super_admin.rb
@@ -0,0 +1,27 @@
+# == Schema Information
+#
+# Table name: super_admins
+#
+# id :bigint not null, primary key
+# current_sign_in_at :datetime
+# current_sign_in_ip :inet
+# email :string default(""), not null
+# encrypted_password :string default(""), not null
+# last_sign_in_at :datetime
+# last_sign_in_ip :inet
+# remember_created_at :datetime
+# sign_in_count :integer default(0), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+# Indexes
+#
+# index_super_admins_on_email (email) UNIQUE
+#
+class SuperAdmin < ApplicationRecord
+ # Include default devise modules. Others available are:
+ # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
+ devise :database_authenticatable, :trackable, :rememberable, :validatable
+
+ include AccessTokenable
+end
diff --git a/app/views/super_admin/application/_navigation.html.erb b/app/views/super_admin/application/_navigation.html.erb
new file mode 100644
index 000000000..348309e7b
--- /dev/null
+++ b/app/views/super_admin/application/_navigation.html.erb
@@ -0,0 +1,27 @@
+<%#
+# Navigation
+
+This partial is used to display the navigation in Administrate.
+By default, the navigation contains navigation links
+for all resources in the admin dashboard,
+as defined by the routes in the `admin/` namespace
+%>
+
+<%= javascript_pack_tag 'superadmin_pages' %>
+<%= stylesheet_pack_tag 'superadmin_pages' %>
+
+
+
+ <%= link_to "Back to app", root_url, class: "button button--alt" %>
+ <%= link_to "Logout", super_admin_logout_url , class: "button button--alt" %>
+
+ <% Administrate::Namespace.new(namespace).resources.each do |resource| %>
+ <%= link_to(
+ display_resource_name(resource),
+ [namespace, resource_index_route_key(resource)],
+ class: "navigation__link navigation__link--#{nav_link_state(resource)}"
+ ) if valid_action? :index, resource %>
+ <% end %>
+
+ <%= link_to "Sidekiq", sidekiq_web_url , class: "button" %>
+
diff --git a/app/views/super_admin/devise/sessions/new.html.erb b/app/views/super_admin/devise/sessions/new.html.erb
new file mode 100644
index 000000000..294ee66ee
--- /dev/null
+++ b/app/views/super_admin/devise/sessions/new.html.erb
@@ -0,0 +1,43 @@
+
+
+
+ SuperAdmin | Chatwoot
+ <%= javascript_pack_tag 'superadmin' %>
+ <%= stylesheet_pack_tag 'superadmin' %>
+
+
+
+
+
+
+ Howdy, admin 👋
+
+
+
+
+ <%= form_for(resource, as: resource_name, url: '/super_admin/sign_in', html: { class: 'login-box column align-self-top'}) do |f| %>
+
+ <% end %>
+
+ © Chatwoot
+
+
+
+
+
+
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index ce0010637..80eb40433 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -6,7 +6,7 @@ Devise.setup do |config|
# confirmation, reset password and unlock tokens in the database.
# Devise will use the `secret_key_base` as its `secret_key`
# by default. You can change it below and use your own secret key.
- # config.secret_key = 'dff4665a082305d28b485d1d763d0d3e52e2577220eaa551836862a3dbca1aade309fe7ceed35180ac494cbc27bd2f5f84d45e4d19530598d1bd899dcbb115e1'
+ # config.secret_key = 'dff4665a082305d28b485d1d763d0d3e52e2577220eaa551836862a3dbca1aade309fe7ceed35180ac494cbc27bd2f5f84d45e1'
# ==> Mailer Configuration
# Configure the e-mail address which will be shown in Devise::Mailer,
@@ -220,15 +220,15 @@ Devise.setup do |config|
# Turn scoped views on. Before rendering "sessions/new", it will first check for
# "users/sessions/new". It's turned off by default because it's slower if you
# are using only default views.
- # config.scoped_views = false
+ config.scoped_views = true
# Configure the default scope given to Warden. By default it's the first
# devise role declared in your routes (usually :user).
- # config.default_scope = :user
+ config.default_scope = :user
# Set this configuration to false if you want /users/sign_out to sign out
# only the current scope. By default, Devise signs out all scopes.
- # config.sign_out_all_scopes = true
+ config.sign_out_all_scopes = true
# ==> Navigation configuration
# Lists the formats that should be treated as navigational. Formats like
diff --git a/config/routes.rb b/config/routes.rb
index 50331d25a..35f162e0d 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -168,20 +168,20 @@ Rails.application.routes.draw do
# Internal Monitoring Routes
require 'sidekiq/web'
- scope :monitoring do
- # Sidekiq should use basic auth in production environment
- if Rails.env.production?
- Sidekiq::Web.use Rack::Auth::Basic do |username, password|
- ENV['SIDEKIQ_AUTH_USERNAME'] &&
- ENV['SIDEKIQ_AUTH_PASSWORD'] &&
- ActiveSupport::SecurityUtils.secure_compare(::Digest::SHA256.hexdigest(username),
- ::Digest::SHA256.hexdigest(ENV['SIDEKIQ_AUTH_USERNAME'])) &&
- ActiveSupport::SecurityUtils.secure_compare(::Digest::SHA256.hexdigest(password),
- ::Digest::SHA256.hexdigest(ENV['SIDEKIQ_AUTH_PASSWORD']))
- end
- end
+ devise_for :super_admins, path: 'super_admin', controllers: { sessions: 'super_admin/devise/sessions' }
+ devise_scope :super_admin do
+ get 'super_admin/logout', to: 'super_admin/devise/sessions#destroy'
+ namespace :super_admin do
+ resources :users
+ resources :accounts
+ resources :super_admins
+ resources :access_tokens
- mount Sidekiq::Web, at: '/sidekiq'
+ root to: 'users#index'
+ end
+ authenticated :super_admin do
+ mount Sidekiq::Web => '/monitoring/sidekiq'
+ end
end
# ---------------------------------------------------------------------
diff --git a/db/migrate/20200410145519_devise_create_super_admins.rb b/db/migrate/20200410145519_devise_create_super_admins.rb
new file mode 100644
index 000000000..4a74847dc
--- /dev/null
+++ b/db/migrate/20200410145519_devise_create_super_admins.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+class DeviseCreateSuperAdmins < ActiveRecord::Migration[6.0]
+ def change
+ return if ActiveRecord::Base.connection.table_exists? 'super_admins'
+
+ create_table :super_admins do |t|
+ ## Database authenticatable
+ t.string :email, null: false, default: ''
+ t.string :encrypted_password, null: false, default: ''
+
+ ## Recoverable
+ # t.string :reset_password_token
+ # t.datetime :reset_password_sent_at
+
+ ## Rememberable
+ t.datetime :remember_created_at
+
+ ## Trackable
+ t.integer :sign_in_count, default: 0, null: false
+ t.datetime :current_sign_in_at
+ t.datetime :last_sign_in_at
+ t.inet :current_sign_in_ip
+ t.inet :last_sign_in_ip
+
+ ## Confirmable
+ # t.string :confirmation_token
+ # t.datetime :confirmed_at
+ # t.datetime :confirmation_sent_at
+ # t.string :unconfirmed_email # Only if using reconfirmable
+
+ ## Lockable
+ # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
+ # t.string :unlock_token # Only if unlock strategy is :email or :both
+ # t.datetime :locked_at
+
+ t.timestamps null: false
+ end
+
+ add_index :super_admins, :email, unique: true
+ # add_index :super_admins, :reset_password_token, unique: true
+ # add_index :super_admins, :confirmation_token, unique: true
+ # add_index :super_admins, :unlock_token, unique: true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 7fab7d066..6965c2d03 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -327,6 +327,20 @@ ActiveRecord::Schema.define(version: 2020_05_10_112339) do
t.boolean "payment_source_added", default: false
end
+ create_table "super_admins", force: :cascade do |t|
+ t.string "email", default: "", null: false
+ t.string "encrypted_password", default: "", null: false
+ t.datetime "remember_created_at"
+ t.integer "sign_in_count", default: 0, null: false
+ t.datetime "current_sign_in_at"
+ t.datetime "last_sign_in_at"
+ t.inet "current_sign_in_ip"
+ t.inet "last_sign_in_ip"
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.index ["email"], name: "index_super_admins_on_email", unique: true
+ end
+
create_table "taggings", id: :serial, force: :cascade do |t|
t.integer "tag_id"
t.string "taggable_type"
diff --git a/spec/controllers/api/v1/accounts/accounts_controller_spec.rb b/spec/controllers/api/v1/accounts/accounts_controller_spec.rb
index 70b6953a3..955cf1e72 100644
--- a/spec/controllers/api/v1/accounts/accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/accounts_controller_spec.rb
@@ -2,9 +2,10 @@ require 'rails_helper'
RSpec.describe 'Accounts API', type: :request do
describe 'POST /api/v1/accounts' do
+ let(:email) { Faker::Internet.email }
+
context 'when posting to accounts with correct parameters' do
let(:account_builder) { double }
- let(:email) { Faker::Internet.email }
let(:account) { create(:account) }
let(:user) { create(:user, email: email, account: account) }
@@ -22,7 +23,7 @@ RSpec.describe 'Accounts API', type: :request do
params: params,
as: :json
- expect(AccountBuilder).to have_received(:new).with(params)
+ expect(AccountBuilder).to have_received(:new).with(params.merge(confirmed: false))
expect(account_builder).to have_received(:perform)
expect(response.headers.keys).to include('access-token', 'token-type', 'client', 'expiry', 'uid')
end
@@ -36,16 +37,45 @@ RSpec.describe 'Accounts API', type: :request do
params: params,
as: :json
- expect(AccountBuilder).to have_received(:new).with(params)
+ expect(AccountBuilder).to have_received(:new).with(params.merge(confirmed: false))
+ expect(account_builder).to have_received(:perform)
+ expect(response).to have_http_status(:forbidden)
+ expect(response.body).to eq({ message: I18n.t('errors.signup.failed') }.to_json)
+ end
+
+ it 'ignores confirmed param when called with out super admin token' do
+ allow(account_builder).to receive(:perform).and_return(nil)
+
+ params = { account_name: 'test', email: email, confirmed: true }
+
+ post api_v1_accounts_url,
+ params: params,
+ as: :json
+
+ expect(AccountBuilder).to have_received(:new).with(params.merge(confirmed: false))
expect(account_builder).to have_received(:perform)
expect(response).to have_http_status(:forbidden)
expect(response.body).to eq({ message: I18n.t('errors.signup.failed') }.to_json)
end
end
- context 'when ENABLE_ACCOUNT_SIGNUP env variable is set to false' do
- let(:email) { Faker::Internet.email }
+ context 'when called with super admin token' do
+ let(:super_admin) { create(:super_admin) }
+ it 'calls account builder with confirmed true when confirmed param is passed' do
+ params = { account_name: 'test', email: email, confirmed: true }
+
+ post api_v1_accounts_url,
+ params: params,
+ headers: { api_access_token: super_admin.access_token.token },
+ as: :json
+
+ expect(User.find_by(email: email).confirmed?).to eq(true)
+ expect(response.headers.keys).to include('access-token', 'token-type', 'client', 'expiry', 'uid')
+ end
+ end
+
+ context 'when ENABLE_ACCOUNT_SIGNUP env variable is set to false' do
it 'responds 404 on requests' do
params = { account_name: 'test', email: email }
ENV['ENABLE_ACCOUNT_SIGNUP'] = 'false'
@@ -60,8 +90,6 @@ RSpec.describe 'Accounts API', type: :request do
end
context 'when ENABLE_ACCOUNT_SIGNUP env variable is set to api_only' do
- let(:email) { Faker::Internet.email }
-
it 'does not respond 404 on requests' do
params = { account_name: 'test', email: email }
ENV['ENABLE_ACCOUNT_SIGNUP'] = 'api_only'
diff --git a/spec/controllers/super_admin/access_tokens_controller_spec.rb b/spec/controllers/super_admin/access_tokens_controller_spec.rb
new file mode 100644
index 000000000..c1f38ea23
--- /dev/null
+++ b/spec/controllers/super_admin/access_tokens_controller_spec.rb
@@ -0,0 +1,24 @@
+require 'rails_helper'
+
+RSpec.describe 'Super Admin access tokens API', type: :request do
+ let(:super_admin) { create(:super_admin) }
+
+ describe 'GET /super_admin/access_tokens' do
+ context 'when it is an unauthenticated super admin' do
+ it 'returns unauthorized' do
+ get '/super_admin/'
+ expect(response).to have_http_status(:redirect)
+ end
+ end
+
+ context 'when it is an authenticated super admin' do
+ it 'shows the list of access tokens' do
+ sign_in super_admin
+ get '/super_admin/access_tokens'
+ expect(response).to have_http_status(:success)
+ expect(response.body).to include('New access token')
+ expect(response.body).to include(super_admin.access_token.token)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/super_admin/accounts_controller_spec.rb b/spec/controllers/super_admin/accounts_controller_spec.rb
new file mode 100644
index 000000000..f4da5ad3d
--- /dev/null
+++ b/spec/controllers/super_admin/accounts_controller_spec.rb
@@ -0,0 +1,26 @@
+require 'rails_helper'
+
+RSpec.describe 'Super Admin accounts API', type: :request do
+ let(:super_admin) { create(:super_admin) }
+
+ describe 'GET /super_admin/accounts' do
+ context 'when it is an unauthenticated user' do
+ it 'returns unauthorized' do
+ get '/super_admin/accounts'
+ expect(response).to have_http_status(:redirect)
+ end
+ end
+
+ context 'when it is an authenticated user' do
+ let!(:account) { create(:account) }
+
+ it 'shows the list of accounts' do
+ sign_in super_admin
+ get '/super_admin/accounts'
+ expect(response).to have_http_status(:success)
+ expect(response.body).to include('New account')
+ expect(response.body).to include(account.name)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/super_admin/super_admins_controller_spec.rb b/spec/controllers/super_admin/super_admins_controller_spec.rb
new file mode 100644
index 000000000..0255b520c
--- /dev/null
+++ b/spec/controllers/super_admin/super_admins_controller_spec.rb
@@ -0,0 +1,24 @@
+require 'rails_helper'
+
+RSpec.describe 'Super Admin super admins API', type: :request do
+ let(:super_admin) { create(:super_admin) }
+
+ describe 'GET /super_admin/users' do
+ context 'when it is an unauthenticated super admin' do
+ it 'returns unauthorized' do
+ get '/super_admin/super_admins'
+ expect(response).to have_http_status(:redirect)
+ end
+ end
+
+ context 'when it is an authenticated super admin' do
+ it 'shows the list of super admins' do
+ sign_in super_admin
+ get '/super_admin/super_admins'
+ expect(response).to have_http_status(:success)
+ expect(response.body).to include('New super admin')
+ expect(response.body).to include(super_admin.email)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/super_admin/users_controller_spec.rb b/spec/controllers/super_admin/users_controller_spec.rb
new file mode 100644
index 000000000..7a1385213
--- /dev/null
+++ b/spec/controllers/super_admin/users_controller_spec.rb
@@ -0,0 +1,26 @@
+require 'rails_helper'
+
+RSpec.describe 'Super Admin Users API', type: :request do
+ let(:super_admin) { create(:super_admin) }
+
+ describe 'GET /super_admin/users' do
+ context 'when it is an unauthenticated super admin' do
+ it 'returns unauthorized' do
+ get '/super_admin/'
+ expect(response).to have_http_status(:redirect)
+ end
+ end
+
+ context 'when it is an authenticated super admin' do
+ let!(:user) { create(:user) }
+
+ it 'shows the list of users' do
+ sign_in super_admin
+ get '/super_admin'
+ expect(response).to have_http_status(:success)
+ expect(response.body).to include('New user')
+ expect(response.body).to include(user.name)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/super_admin_controller_spec.rb b/spec/controllers/super_admin_controller_spec.rb
new file mode 100644
index 000000000..ea0697dd9
--- /dev/null
+++ b/spec/controllers/super_admin_controller_spec.rb
@@ -0,0 +1,46 @@
+require 'rails_helper'
+
+RSpec.describe 'Super Admin', type: :request do
+ let(:super_admin) { create(:super_admin) }
+
+ describe 'request to /super_admin' do
+ context 'when the super admin is unauthenticated' do
+ it 'redirects to signin page' do
+ get '/super_admin/'
+ expect(response).to have_http_status(:redirect)
+ expect(response.body).to include('sign_in')
+ end
+
+ it 'signs super admin in and out' do
+ sign_in super_admin
+ get '/super_admin'
+ expect(response).to have_http_status(:success)
+ expect(response.body).to include('New user')
+
+ sign_out super_admin
+ get '/super_admin'
+ expect(response).to have_http_status(:redirect)
+ end
+ end
+ end
+
+ describe 'request to /super_admin/sidekiq' do
+ context 'when the super admin is unauthenticated' do
+ it 'redirects to signin page' do
+ get '/monitoring/sidekiq'
+ expect(response).to have_http_status(:not_found)
+ expect(response.body).to include('sign_in')
+ end
+
+ it 'signs super admin in and out' do
+ sign_in super_admin
+ get '/monitoring/sidekiq'
+ expect(response).to have_http_status(:success)
+
+ sign_out super_admin
+ get '/monitoring/sidekiq'
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+ end
+end
diff --git a/spec/factories/super_admins.rb b/spec/factories/super_admins.rb
new file mode 100644
index 000000000..88e488f68
--- /dev/null
+++ b/spec/factories/super_admins.rb
@@ -0,0 +1,6 @@
+FactoryBot.define do
+ factory :super_admin do
+ email { "admin@#{SecureRandom.uuid}.com" }
+ password { 'password' }
+ end
+end
diff --git a/spec/models/super_admin_spec.rb b/spec/models/super_admin_spec.rb
new file mode 100644
index 000000000..e9a03b361
--- /dev/null
+++ b/spec/models/super_admin_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe SuperAdmin, type: :model do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 0c0d89421..ef39ac0ef 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -61,6 +61,8 @@ RSpec.configure do |config|
config.filter_rails_from_backtrace!
# arbitrary gems may also be filtered via:
# config.filter_gems_from_backtrace("gem name")
+
+ config.include Devise::Test::IntegrationHelpers, type: :request
end
Shoulda::Matchers.configure do |config|
From f819bc0f33e31bf248c16eef89c2751d08637e91 Mon Sep 17 00:00:00 2001
From: Pranav Raj S
Date: Tue, 12 May 2020 01:31:40 +0530
Subject: [PATCH 07/42] Chore: Use installation config in frontend (#847)
* Use installation config in widget
* Add configuration for installation in UI
* Add config for mailer
Co-authored-by: Sojan
---
.../dashboard/components/layout/Sidebar.vue | 9 ++---
.../dashboard/components/ui/Wizard.vue | 11 ++++---
.../dashboard/routes/auth/Signup.vue | 31 +++++++++++++----
.../dashboard/settings/agents/Index.vue | 13 ++++++--
.../settings/inbox/InboxChannels.vue | 29 +++++++++++++++-
.../routes/dashboard/settings/inbox/Index.vue | 13 ++++++--
.../settings/inbox/channels/Facebook.vue | 20 +++++++++--
.../dashboard/settings/integrations/Index.vue | 13 +++++---
.../settings/integrations/Webhook.vue | 12 ++++++-
.../dashboard/routes/login/Login.vue | 19 +++++++----
app/javascript/dashboard/store/index.js | 6 ++--
.../shared/mixins/globalConfigMixin.js | 7 ++++
app/javascript/shared/store/globalConfig.js | 33 +++++++++++++++++++
app/javascript/widget/components/Branding.vue | 25 ++++++++++++--
app/javascript/widget/store/index.js | 2 ++
app/mailers/application_mailer.rb | 5 +++
app/views/layouts/mailer.html.erb | 6 ++--
app/views/layouts/vueapp.html.erb | 15 +++++----
app/views/widgets/show.html.erb | 1 +
config/installation_config.yml | 16 +++++++--
public/brand-assets/logo.svg | 15 +++++++++
public/brand-assets/logo_thumbnail.svg | 12 +++++++
22 files changed, 264 insertions(+), 49 deletions(-)
create mode 100644 app/javascript/shared/mixins/globalConfigMixin.js
create mode 100644 app/javascript/shared/store/globalConfig.js
create mode 100644 public/brand-assets/logo.svg
create mode 100644 public/brand-assets/logo_thumbnail.svg
diff --git a/app/javascript/dashboard/components/layout/Sidebar.vue b/app/javascript/dashboard/components/layout/Sidebar.vue
index dfb9081d2..a38ebe2cf 100644
--- a/app/javascript/dashboard/components/layout/Sidebar.vue
+++ b/app/javascript/dashboard/components/layout/Sidebar.vue
@@ -2,7 +2,7 @@
-
+
- {{ $t('INBOX_MGMT.ADD.FB.HELP') }}
+
+ {{
+ useInstallationName(
+ $t('INBOX_MGMT.ADD.FB.HELP'),
+ globalConfig.installationName
+ )
+ }}
+
@@ -72,13 +84,14 @@ import { mapGetters } from 'vuex';
import ChannelApi from '../../../../../api/channels';
import PageHeader from '../../SettingsSubPageHeader';
import router from '../../../../index';
+import globalConfigMixin from 'shared/mixins/globalConfigMixin';
export default {
components: {
LoadingState,
PageHeader,
},
-
+ mixins: [globalConfigMixin],
data() {
return {
isCreating: false,
@@ -114,6 +127,7 @@ export default {
},
...mapGetters({
currentUser: 'getCurrentUser',
+ globalConfig: 'globalConfig/get',
}),
accountId() {
return this.currentUser.account_id;
diff --git a/app/javascript/dashboard/routes/dashboard/settings/integrations/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/integrations/Index.vue
index 4ebad6b2d..7eb506273 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/integrations/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/integrations/Index.vue
@@ -13,7 +13,12 @@
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.TITLE') }}
- {{ $t('INTEGRATION_SETTINGS.WEBHOOK.INTEGRATION_TXT') }}
+ {{
+ useInstallationName(
+ $t('INTEGRATION_SETTINGS.WEBHOOK.INTEGRATION_TXT'),
+ globalConfig.installationName
+ )
+ }}
@@ -33,20 +38,20 @@
-
+
diff --git a/app/javascript/widget/components/AgentMessageBubble.vue b/app/javascript/widget/components/AgentMessageBubble.vue
index cc2044980..1062614da 100755
--- a/app/javascript/widget/components/AgentMessageBubble.vue
+++ b/app/javascript/widget/components/AgentMessageBubble.vue
@@ -21,7 +21,7 @@
{
@@ -25,6 +26,34 @@ export const createTemporaryMessage = ({ attachments, content }) => {
};
};
+const getSenderName = message => (message.sender ? message.sender.name : '');
+
+const shouldShowAvatar = (message, nextMessage) => {
+ const currentSender = getSenderName(message);
+ const nextSender = getSenderName(nextMessage);
+
+ return (
+ currentSender !== nextSender ||
+ message.message_type !== nextMessage.message_type ||
+ isASubmittedFormMessage(nextMessage)
+ );
+};
+
+const groupConversationBySender = conversationsForADate =>
+ conversationsForADate.map((message, index) => {
+ let showAvatar = false;
+ const isLastMessage = index === conversationsForADate.length - 1;
+ if (isASubmittedFormMessage(message)) {
+ showAvatar = false;
+ } else if (isLastMessage) {
+ showAvatar = true;
+ } else {
+ const nextMessage = conversationsForADate[index + 1];
+ showAvatar = shouldShowAvatar(message, nextMessage);
+ }
+ return { showAvatar, ...message };
+ });
+
export const findUndeliveredMessage = (messageInbox, { content }) =>
Object.values(messageInbox).filter(
message => message.content === content && message.status === 'in_progress'
@@ -58,27 +87,10 @@ export const getters = {
Object.values(_state.conversations),
message => new DateHelper(message.created_at).format()
);
- return Object.keys(conversationGroupedByDate).map(date => {
- const messages = conversationGroupedByDate[date].map((message, index) => {
- let showAvatar = false;
- if (index === conversationGroupedByDate[date].length - 1) {
- showAvatar = true;
- } else {
- const nextMessage = conversationGroupedByDate[date][index + 1];
- const currentSender = message.sender ? message.sender.name : '';
- const nextSender = nextMessage.sender ? nextMessage.sender.name : '';
- showAvatar =
- currentSender !== nextSender ||
- message.message_type !== nextMessage.message_type;
- }
- return { showAvatar, ...message };
- });
-
- return {
- date,
- messages,
- };
- });
+ return Object.keys(conversationGroupedByDate).map(date => ({
+ date,
+ messages: groupConversationBySender(conversationGroupedByDate[date]),
+ }));
},
getIsFetchingList: _state => _state.uiFlags.isFetchingList,
};
diff --git a/app/javascript/widget/store/modules/specs/conversation/getters.spec.js b/app/javascript/widget/store/modules/specs/conversation/getters.spec.js
index 91bc7396b..99f1c983a 100644
--- a/app/javascript/widget/store/modules/specs/conversation/getters.spec.js
+++ b/app/javascript/widget/store/modules/specs/conversation/getters.spec.js
@@ -56,40 +56,41 @@ describe('#getters', () => {
expect(getters.getIsAgentTyping(state)).toEqual(false);
});
- it('uiFlags', () => {
- const state = {
- conversations: {
- 1: {
- id: 1,
- content: 'Thanks for the help',
- created_at: 1574075964,
- message_type: 0,
+ it('getGroupedConversation', () => {
+ expect(
+ getters.getGroupedConversation({
+ conversations: {
+ 1: {
+ id: 1,
+ content: 'Thanks for the help',
+ created_at: 1574075964,
+ message_type: 0,
+ },
+ 2: {
+ id: 2,
+ content: 'Yes, It makes sense',
+ created_at: 1574092218,
+ message_type: 0,
+ },
+ 3: {
+ id: 3,
+ content: 'Hey',
+ created_at: 1574092218,
+ message_type: 1,
+ },
+ 4: {
+ id: 4,
+ content: 'Hey',
+ created_at: 1576340623,
+ },
+ 5: {
+ id: 5,
+ content: 'How may I help you',
+ created_at: 1576340626,
+ },
},
- 2: {
- id: 2,
- content: 'Yes, It makes sense',
- created_at: 1574092218,
- message_type: 0,
- },
- 3: {
- id: 3,
- content: 'Hey',
- created_at: 1574092218,
- message_type: 1,
- },
- 4: {
- id: 4,
- content: 'Hey',
- created_at: 1576340623,
- },
- 5: {
- id: 5,
- content: 'How may I help you',
- created_at: 1576340626,
- },
- },
- };
- expect(getters.getGroupedConversation(state)).toEqual([
+ })
+ ).toEqual([
{
date: 'Nov 18, 2019',
messages: [
@@ -134,5 +135,131 @@ describe('#getters', () => {
],
},
]);
+
+ expect(
+ getters.getGroupedConversation({
+ conversations: {
+ 1: {
+ id: 1,
+ content: 'Thanks for the help',
+ created_at: 1574075964,
+ message_type: 0,
+ },
+ 2: {
+ id: 2,
+ content: 'Yes, It makes sense',
+ created_at: 1574092218,
+ message_type: 0,
+ },
+ 3: {
+ id: 3,
+ content: 'Hey',
+ created_at: 1574092218,
+ message_type: 1,
+ },
+ 4: {
+ id: 4,
+ content: 'Hey',
+ created_at: 1576340623,
+ },
+ 5: {
+ id: 5,
+ content: 'How may I help you',
+ created_at: 1576340626,
+ message_type: 2,
+ content_type: 'form',
+ content_attributes: {
+ submitted_values: [{ name: 'text', value: 'sample text' }],
+ },
+ },
+ 6: {
+ id: 6,
+ content: 'How may I help you',
+ created_at: 1576340626,
+ message_type: 2,
+ content_type: 'form',
+ },
+ 7: {
+ id: 7,
+ content: 'How may I help you',
+ created_at: 1576340626,
+ message_type: 2,
+ content_type: 'form',
+ content_attributes: {
+ submitted_values: [{ name: 'text', value: 'sample text' }],
+ },
+ },
+ },
+ })
+ ).toEqual([
+ {
+ date: 'Nov 18, 2019',
+ messages: [
+ {
+ id: 1,
+ content: 'Thanks for the help',
+ created_at: 1574075964,
+ showAvatar: false,
+ message_type: 0,
+ },
+ {
+ id: 2,
+ content: 'Yes, It makes sense',
+ created_at: 1574092218,
+ showAvatar: true,
+ message_type: 0,
+ },
+ {
+ id: 3,
+ content: 'Hey',
+ created_at: 1574092218,
+ showAvatar: true,
+ message_type: 1,
+ },
+ ],
+ },
+ {
+ date: 'Dec 14, 2019',
+ messages: [
+ {
+ id: 4,
+ content: 'Hey',
+ created_at: 1576340623,
+ showAvatar: true,
+ },
+ {
+ id: 5,
+ content: 'How may I help you',
+ created_at: 1576340626,
+ message_type: 2,
+ content_type: 'form',
+ content_attributes: {
+ submitted_values: [{ name: 'text', value: 'sample text' }],
+ },
+ showAvatar: false,
+ },
+ {
+ id: 6,
+ content: 'How may I help you',
+ created_at: 1576340626,
+ message_type: 2,
+ content_type: 'form',
+ showAvatar: true,
+ },
+ {
+ id: 7,
+ content: 'How may I help you',
+ created_at: 1576340626,
+ message_type: 2,
+ content_type: 'form',
+ content_attributes: {
+ submitted_values: [{ name: 'text', value: 'sample text' }],
+ },
+
+ showAvatar: false,
+ },
+ ],
+ },
+ ]);
});
});
From 6e92d9be9ef5b110d3b09dd1bdadf60704f323a3 Mon Sep 17 00:00:00 2001
From: Pranav Raj S
Date: Sun, 17 May 2020 23:44:50 +0530
Subject: [PATCH 19/42] Bug: Fix Facebook v7.0 API issues (#863)
* Fix v7.0 API issues
Co-authored-by: Sojan
---
Gemfile.lock | 30 +++++++++----------
.../widgets/conversation/ReplyBox.vue | 4 ++-
.../dashboard/i18n/locale/en/inboxMgmt.json | 8 ++++-
.../settings/inbox/channels/Facebook.vue | 23 +++++++-------
app/models/channel/facebook_page.rb | 15 +++++++++-
app/models/inbox.rb | 13 --------
6 files changed, 48 insertions(+), 45 deletions(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index cc6d481ee..7c3079978 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -96,16 +96,16 @@ GEM
autoprefixer-rails (9.7.6)
execjs
aws-eventstream (1.1.0)
- aws-partitions (1.310.0)
- aws-sdk-core (3.94.1)
+ aws-partitions (1.315.0)
+ aws-sdk-core (3.95.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
- aws-sdk-kms (1.30.0)
+ aws-sdk-kms (1.31.0)
aws-sdk-core (~> 3, >= 3.71.0)
aws-sigv4 (~> 1.1)
- aws-sdk-s3 (1.63.1)
+ aws-sdk-s3 (1.64.0)
aws-sdk-core (~> 3, >= 3.83.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
@@ -126,8 +126,8 @@ GEM
bindex (0.8.1)
bootsnap (1.4.6)
msgpack (~> 1.0)
- brakeman (4.8.1)
- browser (4.0.0)
+ brakeman (4.8.2)
+ browser (4.1.0)
builder (3.2.4)
bullet (6.1.0)
activesupport (>= 3.0.0)
@@ -194,7 +194,7 @@ GEM
foreman (0.87.1)
globalid (0.4.2)
activesupport (>= 4.2.0)
- google-api-client (0.38.0)
+ google-api-client (0.39.3)
addressable (~> 2.5, >= 2.5.1)
googleauth (~> 0.9)
httpclient (>= 2.8.1, < 3.0)
@@ -208,7 +208,7 @@ GEM
google-cloud-env (1.3.1)
faraday (>= 0.17.3, < 2.0)
google-cloud-errors (1.0.0)
- google-cloud-storage (1.26.0)
+ google-cloud-storage (1.26.1)
addressable (~> 2.5)
digest-crc (~> 0.4)
google-api-client (~> 0.33)
@@ -239,11 +239,10 @@ GEM
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
inflecto (0.0.2)
- jaro_winkler (1.5.4)
jbuilder (2.10.0)
activesupport (>= 5.0.0)
jmespath (1.4.0)
- jquery-rails (4.3.5)
+ jquery-rails (4.4.0)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
@@ -284,7 +283,7 @@ GEM
method_source (1.0.0)
mime-types (3.3.1)
mime-types-data (~> 3.2015)
- mime-types-data (3.2020.0425)
+ mime-types-data (3.2020.0512)
mimemagic (0.3.5)
mini_magick (4.10.1)
mini_mime (1.0.2)
@@ -312,7 +311,7 @@ GEM
method_source (~> 1.0)
pry-rails (0.3.9)
pry (>= 0.10.4)
- public_suffix (4.0.4)
+ public_suffix (4.0.5)
puma (4.3.3)
nio4r (~> 2.0)
pundit (2.1.0)
@@ -383,7 +382,7 @@ GEM
rexml (3.2.4)
rspec-core (3.9.2)
rspec-support (~> 3.9.3)
- rspec-expectations (3.9.1)
+ rspec-expectations (3.9.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.9.0)
rspec-mocks (3.9.1)
@@ -398,8 +397,7 @@ GEM
rspec-mocks (~> 3.9)
rspec-support (~> 3.9)
rspec-support (3.9.3)
- rubocop (0.82.0)
- jaro_winkler (~> 1.5.1)
+ rubocop (0.83.0)
parallel (~> 1.10)
parser (>= 2.7.0.1)
rainbow (>= 2.2.2, < 4.0)
@@ -471,7 +469,7 @@ GEM
faraday
inflecto
virtus
- telephone_number (1.4.6)
+ telephone_number (1.4.7)
thor (0.20.3)
thread_safe (0.3.6)
tilt (2.0.10)
diff --git a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue
index 4f73291b2..8a2a836ba 100644
--- a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue
+++ b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue
@@ -208,7 +208,9 @@ export default {
async sendMessage() {
const isMessageEmpty = !this.message.replace(/\n/g, '').length;
if (isMessageEmpty) return;
-
+ if (this.message.length > this.maxLength) {
+ return;
+ }
if (!this.showCannedResponsesList) {
try {
await this.$store.dispatch('sendMessage', {
diff --git a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json
index aa43c5ecb..2aa1c15c2 100644
--- a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json
+++ b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json
@@ -29,7 +29,13 @@
],
"ADD": {
"FB": {
- "HELP": "PS: By signing in, we only get access to your Page's messages. Your private messages can never be accessed by Chatwoot."
+ "HELP": "PS: By signing in, we only get access to your Page's messages. Your private messages can never be accessed by Chatwoot.",
+ "CHOOSE_PAGE": "Choose Page",
+ "CHOOSE_PLACEHOLDER": "Select a page from the list",
+ "INBOX_NAME": "Inbox Name",
+ "ADD_NAME": "Add a name for your inbox",
+ "PICK_NAME": "Pick A Name Your Inbox",
+ "PICK_A_VALUE": "Pick a value"
},
"TWITTER": {
"HELP": "To add your Twitter profile as a channel, you need to authenticate your Twitter Profile by clicking on 'Sign in with Twitter' "
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Facebook.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Facebook.vue
index 25634d026..282096616 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Facebook.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Facebook.vue
@@ -17,10 +17,7 @@
+
+
+
+
@@ -82,7 +111,7 @@ import SidebarItem from './SidebarItem';
import WootStatusBar from '../widgets/StatusBar';
import { frontendURL } from '../../helper/URLHelper';
import Thumbnail from '../widgets/Thumbnail';
-import sidemenuItems from '../../i18n/default-sidebar';
+import { getSidebarItems } from '../../i18n/default-sidebar';
export default {
components: {
@@ -100,6 +129,7 @@ export default {
data() {
return {
showOptionsMenu: false,
+ showAccountModal: false,
};
},
computed: {
@@ -109,15 +139,20 @@ export default {
globalConfig: 'globalConfig/get',
inboxes: 'inboxes/getInboxes',
subscriptionData: 'getSubscription',
+ accountId: 'getCurrentAccountId',
+ currentRole: 'getCurrentRole',
}),
+ sidemenuItems() {
+ return getSidebarItems(this.accountId);
+ },
accessibleMenuItems() {
// get all keys in menuGroup
- const groupKey = Object.keys(sidemenuItems);
+ const groupKey = Object.keys(this.sidemenuItems);
let menuItems = [];
// Iterate over menuGroup to find the correct group
for (let i = 0; i < groupKey.length; i += 1) {
- const groupItem = sidemenuItems[groupKey[i]];
+ const groupItem = this.sidemenuItems[groupKey[i]];
// Check if current route is included
const isRouteIncluded = groupItem.routes.includes(this.currentRoute);
if (isRouteIncluded) {
@@ -135,7 +170,7 @@ export default {
return this.$store.state.route.name;
},
shouldShowInboxes() {
- return sidemenuItems.common.routes.includes(this.currentRoute);
+ return this.sidemenuItems.common.routes.includes(this.currentRoute);
},
inboxSection() {
return {
@@ -177,9 +212,6 @@ export default {
trialMessage() {
return `${this.daysLeft} ${this.$t('APP_GLOBAL.TRIAL_MESSAGE')}`;
},
- accountId() {
- return this.currentUser.account_id;
- },
},
mounted() {
this.$store.dispatch('inboxes/get');
@@ -191,13 +223,14 @@ export default {
);
},
filterMenuItemsByRole(menuItems) {
- const { role } = this.currentUser;
- if (!role) {
+ if (!this.currentRole) {
return [];
}
return menuItems.filter(
menuItem =>
- window.roleWiseRoutes[role].indexOf(menuItem.toStateName) > -1
+ window.roleWiseRoutes[this.currentRole].indexOf(
+ menuItem.toStateName
+ ) > -1
);
},
logout() {
@@ -206,6 +239,80 @@ export default {
showOptions() {
this.showOptionsMenu = !this.showOptionsMenu;
},
+ changeAccount() {
+ this.showAccountModal = true;
+ },
+ onClose() {
+ this.showAccountModal = false;
+ },
},
};
+
+
diff --git a/app/javascript/dashboard/components/layout/SidebarItem.vue b/app/javascript/dashboard/components/layout/SidebarItem.vue
index 8e8297e01..35356a685 100644
--- a/app/javascript/dashboard/components/layout/SidebarItem.vue
+++ b/app/javascript/dashboard/components/layout/SidebarItem.vue
@@ -50,7 +50,7 @@
import { mapGetters } from 'vuex';
import router from '../../routes';
-import auth from '../../api/auth';
+import adminMixin from '../../mixins/isAdmin';
const INBOX_TYPES = {
WEB: 'Channel::WebWidget',
@@ -78,6 +78,7 @@ const getInboxClassByType = type => {
};
export default {
+ mixins: [adminMixin],
props: {
menuItem: {
type: Object,
@@ -119,7 +120,7 @@ export default {
router.push({ name: 'settings_inbox_new', params: { page: 'new' } });
},
showItem(item) {
- return auth.isAdmin() && item.newLink !== undefined;
+ return this.isAdmin && item.newLink !== undefined;
},
},
};
diff --git a/app/javascript/dashboard/components/widgets/conversation/EmptyState.vue b/app/javascript/dashboard/components/widgets/conversation/EmptyState.vue
index f354ecd0c..978c43965 100644
--- a/app/javascript/dashboard/components/widgets/conversation/EmptyState.vue
+++ b/app/javascript/dashboard/components/widgets/conversation/EmptyState.vue
@@ -9,7 +9,7 @@
-
+
{{ $t('CONVERSATION.NO_INBOX_1') }}
@@ -17,7 +17,7 @@
{{ $t('CONVERSATION.NO_INBOX_2') }}
-
+
{{ $t('CONVERSATION.NO_INBOX_AGENT') }}
diff --git a/app/javascript/dashboard/helper/actionCable.js b/app/javascript/dashboard/helper/actionCable.js
index 035b277c3..515674433 100644
--- a/app/javascript/dashboard/helper/actionCable.js
+++ b/app/javascript/dashboard/helper/actionCable.js
@@ -19,6 +19,10 @@ class ActionCableConnector extends BaseActionCableConnector {
};
}
+ isAValidEvent = data => {
+ return this.app.$store.getters.getCurrentAccountId === data.account_id;
+ };
+
onMessageUpdated = data => {
this.app.$store.dispatch('updateMessage', data);
};
diff --git a/app/javascript/dashboard/i18n/default-sidebar.js b/app/javascript/dashboard/i18n/default-sidebar.js
index 296f88381..f1823915c 100644
--- a/app/javascript/dashboard/i18n/default-sidebar.js
+++ b/app/javascript/dashboard/i18n/default-sidebar.js
@@ -1,10 +1,6 @@
import { frontendURL } from '../helper/URLHelper';
-import auth from '../api/auth';
-const user = auth.getCurrentUser() || {};
-const accountId = user.account_id;
-
-export default {
+export const getSidebarItems = accountId => ({
common: {
routes: [
'home',
@@ -106,13 +102,13 @@ export default {
toState: frontendURL(`accounts/${accountId}/settings/integrations`),
toStateName: 'settings_integrations',
},
- general_settings: {
+ general_settings_index: {
icon: 'ion-gear-a',
label: 'ACCOUNT_SETTINGS',
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/general`),
- toStateName: 'general_settings',
+ toStateName: 'general_settings_index',
},
},
},
-};
+});
diff --git a/app/javascript/dashboard/i18n/locale/en/settings.json b/app/javascript/dashboard/i18n/locale/en/settings.json
index 5ea423142..c1955d90d 100644
--- a/app/javascript/dashboard/i18n/locale/en/settings.json
+++ b/app/javascript/dashboard/i18n/locale/en/settings.json
@@ -66,6 +66,8 @@
}
},
"SIDEBAR_ITEMS": {
+ "CHANGE_ACCOUNTS": "Switch Account",
+ "SELECTOR_SUBTITLE": "Select an account from the following list",
"PROFILE_SETTINGS": "Profile Settings",
"LOGOUT": "Logout"
},
diff --git a/app/javascript/dashboard/i18n/locale/fr/settings.json b/app/javascript/dashboard/i18n/locale/fr/settings.json
index dcac42205..7aca5abb1 100644
--- a/app/javascript/dashboard/i18n/locale/fr/settings.json
+++ b/app/javascript/dashboard/i18n/locale/fr/settings.json
@@ -66,6 +66,8 @@
}
},
"SIDEBAR_ITEMS": {
+ "CHANGE_ACCOUNTS": "Changer de compte",
+ "SELECTOR_SUBTITLE": "Sélectionnez un compte dans la liste suivante",
"PROFILE_SETTINGS": "Paramètres de profil",
"LOGOUT": "Se déconnecter"
},
diff --git a/app/javascript/dashboard/i18n/locale/nl/settings.json b/app/javascript/dashboard/i18n/locale/nl/settings.json
index 3ff100afd..a5f604209 100644
--- a/app/javascript/dashboard/i18n/locale/nl/settings.json
+++ b/app/javascript/dashboard/i18n/locale/nl/settings.json
@@ -66,6 +66,8 @@
}
},
"SIDEBAR_ITEMS": {
+ "CHANGE_ACCOUNTS": "Verwissel van profiel",
+ "SELECTOR_SUBTITLE": "Selecteer een account in de volgende lijst",
"PROFILE_SETTINGS": "Profiel instellingen",
"LOGOUT": "Afmelden"
},
diff --git a/app/javascript/dashboard/mixins/isAdmin.js b/app/javascript/dashboard/mixins/isAdmin.js
index 50ba3a78e..996631916 100644
--- a/app/javascript/dashboard/mixins/isAdmin.js
+++ b/app/javascript/dashboard/mixins/isAdmin.js
@@ -1,9 +1,12 @@
-import Auth from '../api/auth';
+import { mapGetters } from 'vuex';
export default {
- methods: {
+ computed: {
+ ...mapGetters({
+ currentUserRole: 'getCurrentRole',
+ }),
isAdmin() {
- return Auth.isAdmin();
+ return this.currentUserRole === 'administrator';
},
},
};
diff --git a/app/javascript/dashboard/routes/dashboard/Dashboard.vue b/app/javascript/dashboard/routes/dashboard/Dashboard.vue
index 5ffe6cd1c..54bf35493 100644
--- a/app/javascript/dashboard/routes/dashboard/Dashboard.vue
+++ b/app/javascript/dashboard/routes/dashboard/Dashboard.vue
@@ -15,11 +15,6 @@ export default {
components: {
Sidebar,
},
- props: {
- mainViewComponent: String,
- sidebarMenu: String,
- page: String,
- },
data() {
return {
isSidebarOpen: false,
@@ -50,6 +45,7 @@ export default {
},
},
mounted() {
+ this.$store.dispatch('setCurrentAccountId', this.$route.params.accountId);
window.addEventListener('resize', this.handleResize);
this.handleResize();
bus.$on('sidemenu_icon_click', () => {
diff --git a/app/javascript/dashboard/routes/dashboard/settings/SettingsHeader.vue b/app/javascript/dashboard/routes/dashboard/settings/SettingsHeader.vue
index 76858695c..442d14360 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/SettingsHeader.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/SettingsHeader.vue
@@ -19,11 +19,13 @@
diff --git a/app/javascript/dashboard/routes/dashboard/settings/account/account.routes.js b/app/javascript/dashboard/routes/dashboard/settings/account/account.routes.js
index 6957af495..70aff7c36 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/account/account.routes.js
+++ b/app/javascript/dashboard/routes/dashboard/settings/account/account.routes.js
@@ -6,7 +6,6 @@ export default {
routes: [
{
path: frontendURL('accounts/:accountId/settings/general'),
- name: 'general_settings',
roles: ['administrator'],
component: SettingsContent,
props: {
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/Index.vue
index 5b0049b89..faead1f76 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/Index.vue
@@ -6,7 +6,7 @@
{{ $t('INBOX_MGMT.LIST.404') }}
{{ $t('SETTINGS.INBOXES.NEW_INBOX') }}
@@ -54,7 +54,7 @@
:to="addAccountScoping(`settings/inboxes/${item.id}`)"
>
{
- console.log(subscription);
if (!subscription) {
this.hasEnabledPushPermissions = false;
} else {
diff --git a/app/javascript/dashboard/routes/dashboard/settings/profile/profile.routes.js b/app/javascript/dashboard/routes/dashboard/settings/profile/profile.routes.js
index e79ed929a..55a33e2cc 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/profile/profile.routes.js
+++ b/app/javascript/dashboard/routes/dashboard/settings/profile/profile.routes.js
@@ -17,7 +17,7 @@ export default {
children: [
{
path: 'settings',
- name: 'general_settings_index',
+ name: 'profile_settings_index',
component: Index,
roles: ['administrator', 'agent'],
},
diff --git a/app/javascript/dashboard/routes/dashboard/settings/settings.routes.js b/app/javascript/dashboard/routes/dashboard/settings/settings.routes.js
index 99241394e..5907768cc 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/settings.routes.js
+++ b/app/javascript/dashboard/routes/dashboard/settings/settings.routes.js
@@ -1,6 +1,5 @@
import { frontendURL } from '../../../helper/URLHelper';
import agent from './agents/agent.routes';
-import Auth from '../../../api/auth';
import billing from './billing/billing.routes';
import canned from './canned/canned.routes';
import inbox from './inbox/inbox.routes';
@@ -8,6 +7,7 @@ import profile from './profile/profile.routes';
import reports from './reports/reports.routes';
import integrations from './integrations/integrations.routes';
import account from './account/account.routes';
+import store from '../../../store';
export default {
routes: [
@@ -16,7 +16,7 @@ export default {
name: 'settings_home',
roles: ['administrator', 'agent'],
redirect: () => {
- if (Auth.isAdmin()) {
+ if (store.getters.getCurrentRole === 'administrator') {
return frontendURL('accounts/:accountId/settings/agents');
}
return frontendURL('accounts/:accountId/settings/canned-response');
diff --git a/app/javascript/dashboard/store/modules/auth.js b/app/javascript/dashboard/store/modules/auth.js
index 493bd44e0..b4ea0ecba 100644
--- a/app/javascript/dashboard/store/modules/auth.js
+++ b/app/javascript/dashboard/store/modules/auth.js
@@ -22,6 +22,7 @@ const state = {
expiry: null,
},
},
+ currentAccountId: null,
};
// getters
@@ -34,6 +35,18 @@ export const getters = {
return _state.currentUser.id;
},
+ getCurrentAccountId(_state) {
+ return _state.currentAccountId;
+ },
+
+ getCurrentRole(_state) {
+ const { accounts = [] } = _state.currentUser;
+ const [currentAccount = {}] = accounts.filter(
+ account => account.id === _state.currentAccountId
+ );
+ return currentAccount.role;
+ },
+
getCurrentUser(_state) {
return _state.currentUser;
},
@@ -103,6 +116,10 @@ export const actions = {
// Ignore error
}
},
+
+ setCurrentAccountId({ commit }, accountId) {
+ commit(types.default.SET_CURRENT_ACCOUNT_ID, accountId);
+ },
};
// mutations
@@ -118,6 +135,9 @@ const mutations = {
Vue.set(_state, 'currentUser', currentUser);
},
+ [types.default.SET_CURRENT_ACCOUNT_ID](_state, accountId) {
+ Vue.set(_state, 'currentAccountId', Number(accountId));
+ },
};
export default {
diff --git a/app/javascript/dashboard/store/mutation-types.js b/app/javascript/dashboard/store/mutation-types.js
index d2df7aa7d..f9d7ea4ec 100755
--- a/app/javascript/dashboard/store/mutation-types.js
+++ b/app/javascript/dashboard/store/mutation-types.js
@@ -2,6 +2,8 @@ export default {
AUTHENTICATE: 'AUTHENTICATE',
CLEAR_USER: 'LOGOUT',
SET_CURRENT_USER: 'SET_CURRENT_USER',
+ SET_CURRENT_ACCOUNT_ID: 'SET_CURRENT_ACCOUNT_ID',
+
// Chat List
RECEIVE_CHAT_LIST: 'RECEIVE_CHAT_LIST',
SET_ALL_CONVERSATION: 'SET_ALL_CONVERSATION',
diff --git a/app/javascript/shared/helpers/BaseActionCableConnector.js b/app/javascript/shared/helpers/BaseActionCableConnector.js
index 703b6e74a..c601d4164 100644
--- a/app/javascript/shared/helpers/BaseActionCableConnector.js
+++ b/app/javascript/shared/helpers/BaseActionCableConnector.js
@@ -14,6 +14,7 @@ class BaseActionCableConnector {
);
this.app = app;
this.events = {};
+ this.isAValidEvent = () => true;
}
disconnect() {
@@ -21,8 +22,10 @@ class BaseActionCableConnector {
}
onReceived = ({ event, data } = {}) => {
- if (this.events[event] && typeof this.events[event] === 'function') {
- this.events[event](data);
+ if (this.isAValidEvent(data)) {
+ if (this.events[event] && typeof this.events[event] === 'function') {
+ this.events[event](data);
+ }
}
};
}
diff --git a/app/listeners/action_cable_listener.rb b/app/listeners/action_cable_listener.rb
index 4164c06ed..950e74caf 100644
--- a/app/listeners/action_cable_listener.rb
+++ b/app/listeners/action_cable_listener.rb
@@ -2,52 +2,55 @@ class ActionCableListener < BaseListener
include Events::Types
def conversation_created(event)
- conversation, account, timestamp = extract_conversation_and_account(event)
+ conversation, account = extract_conversation_and_account(event)
+ tokens = user_tokens(account, conversation.inbox.members)
- broadcast(user_tokens(account, conversation.inbox.members), CONVERSATION_CREATED, conversation.push_event_data)
+ broadcast(account, tokens, CONVERSATION_CREATED, conversation.push_event_data)
end
def conversation_read(event)
- conversation, account, timestamp = extract_conversation_and_account(event)
+ conversation, account = extract_conversation_and_account(event)
+ tokens = user_tokens(account, conversation.inbox.members)
- broadcast(user_tokens(account, conversation.inbox.members), CONVERSATION_READ, conversation.push_event_data)
+ broadcast(account, tokens, CONVERSATION_READ, conversation.push_event_data)
end
def message_created(event)
- message, account, timestamp = extract_message_and_account(event)
+ message, account = extract_message_and_account(event)
conversation = message.conversation
tokens = user_tokens(account, conversation.inbox.members) + contact_token(conversation.contact, message)
- broadcast(tokens, MESSAGE_CREATED, message.push_event_data)
+ broadcast(account, tokens, MESSAGE_CREATED, message.push_event_data)
end
def message_updated(event)
- message, account, timestamp = extract_message_and_account(event)
+ message, account = extract_message_and_account(event)
conversation = message.conversation
contact = conversation.contact
tokens = user_tokens(account, conversation.inbox.members) + contact_token(conversation.contact, message)
- broadcast(tokens, MESSAGE_UPDATED, message.push_event_data)
+ broadcast(account, tokens, MESSAGE_UPDATED, message.push_event_data)
end
def conversation_resolved(event)
- conversation, account, timestamp = extract_conversation_and_account(event)
+ conversation, account = extract_conversation_and_account(event)
tokens = user_tokens(account, conversation.inbox.members) + [conversation.contact&.pubsub_token]
- broadcast(tokens, CONVERSATION_RESOLVED, conversation.push_event_data)
+ broadcast(account, tokens, CONVERSATION_RESOLVED, conversation.push_event_data)
end
def conversation_opened(event)
- conversation, account, timestamp = extract_conversation_and_account(event)
+ conversation, account = extract_conversation_and_account(event)
tokens = user_tokens(account, conversation.inbox.members) + [conversation.contact&.pubsub_token]
- broadcast(tokens, CONVERSATION_OPENED, conversation.push_event_data)
+ broadcast(account, tokens, CONVERSATION_OPENED, conversation.push_event_data)
end
def conversation_lock_toggle(event)
- conversation, account, timestamp = extract_conversation_and_account(event)
+ conversation, account = extract_conversation_and_account(event)
+ tokens = user_tokens(account, conversation.inbox.members)
- broadcast(user_tokens(account, conversation.inbox.members), CONVERSATION_LOCK_TOGGLE, conversation.lock_event_data)
+ broadcast(account, tokens, CONVERSATION_LOCK_TOGGLE, conversation.lock_event_data)
end
def conversation_typing_on(event)
@@ -57,6 +60,7 @@ class ActionCableListener < BaseListener
tokens = typing_event_listener_tokens(account, conversation, user)
broadcast(
+ account,
tokens,
CONVERSATION_TYPING_ON,
conversation: conversation.push_event_data,
@@ -71,6 +75,7 @@ class ActionCableListener < BaseListener
tokens = typing_event_listener_tokens(account, conversation, user)
broadcast(
+ account,
tokens,
CONVERSATION_TYPING_OFF,
conversation: conversation.push_event_data,
@@ -79,21 +84,24 @@ class ActionCableListener < BaseListener
end
def assignee_changed(event)
- conversation, account, timestamp = extract_conversation_and_account(event)
+ conversation, account = extract_conversation_and_account(event)
+ tokens = user_tokens(account, conversation.inbox.members)
- broadcast(user_tokens(account, conversation.inbox.members), ASSIGNEE_CHANGED, conversation.push_event_data)
+ broadcast(account, tokens, ASSIGNEE_CHANGED, conversation.push_event_data)
end
def contact_created(event)
- contact, account, timestamp = extract_contact_and_account(event)
+ contact, account = extract_contact_and_account(event)
+ tokens = user_tokens(account, account.agents)
- broadcast(user_tokens(account, account.agents), CONTACT_CREATED, contact.push_event_data)
+ broadcast(account, tokens, CONTACT_CREATED, contact.push_event_data)
end
def contact_updated(event)
- contact, account, timestamp = extract_contact_and_account(event)
+ contact, account = extract_contact_and_account(event)
+ tokens = user_tokens(account, account.agents)
- broadcast(user_tokens(account, account.agents), CONTACT_UPDATED, contact.push_event_data)
+ broadcast(account, tokens, CONTACT_UPDATED, contact.push_event_data)
end
private
@@ -117,9 +125,9 @@ class ActionCableListener < BaseListener
[contact.pubsub_token]
end
- def broadcast(tokens, event_name, data)
+ def broadcast(account, tokens, event_name, data)
return if tokens.blank?
- ::ActionCableBroadcastJob.perform_later(tokens.uniq, event_name, data)
+ ::ActionCableBroadcastJob.perform_later(tokens.uniq, event_name, data.merge(account_id: account.id))
end
end
diff --git a/app/listeners/base_listener.rb b/app/listeners/base_listener.rb
index 5d800b456..d4326133a 100644
--- a/app/listeners/base_listener.rb
+++ b/app/listeners/base_listener.rb
@@ -3,16 +3,16 @@ class BaseListener
def extract_conversation_and_account(event)
conversation = event.data[:conversation]
- [conversation, conversation.account, event.timestamp]
+ [conversation, conversation.account]
end
def extract_message_and_account(event)
message = event.data[:message]
- [message, message.account, event.timestamp]
+ [message, message.account]
end
def extract_contact_and_account(event)
contact = event.data[:contact]
- [contact, contact.account, event.timestamp]
+ [contact, contact.account]
end
end
diff --git a/app/listeners/notification_listener.rb b/app/listeners/notification_listener.rb
index a8349993f..ab7e4cc3c 100644
--- a/app/listeners/notification_listener.rb
+++ b/app/listeners/notification_listener.rb
@@ -1,6 +1,6 @@
class NotificationListener < BaseListener
def conversation_created(event)
- conversation, account, _timestamp = extract_conversation_and_account(event)
+ conversation, account = extract_conversation_and_account(event)
return if conversation.bot?
conversation.inbox.members.each do |agent|
@@ -14,7 +14,7 @@ class NotificationListener < BaseListener
end
def assignee_changed(event)
- conversation, account, _timestamp = extract_conversation_and_account(event)
+ conversation, account = extract_conversation_and_account(event)
assignee = conversation.assignee
return unless conversation.notifiable_assignee_change?
return if conversation.bot?
diff --git a/app/listeners/reporting_listener.rb b/app/listeners/reporting_listener.rb
index 2f4e069e0..407dc47bc 100644
--- a/app/listeners/reporting_listener.rb
+++ b/app/listeners/reporting_listener.rb
@@ -1,11 +1,15 @@
class ReportingListener < BaseListener
def conversation_created(event)
- conversation, account, timestamp = extract_conversation_and_account(event)
+ conversation, account = extract_conversation_and_account(event)
+ timestamp = event.timestamp
+
::Reports::UpdateAccountIdentity.new(account, timestamp).incr_conversations_count
end
def conversation_resolved(event)
- conversation, account, timestamp = extract_conversation_and_account(event)
+ conversation, account = extract_conversation_and_account(event)
+ timestamp = event.timestamp
+
time_to_resolve = conversation.updated_at.to_i - conversation.created_at.to_i
if conversation.assignee.present?
@@ -19,7 +23,9 @@ class ReportingListener < BaseListener
end
def first_reply_created(event)
- message, account, timestamp = extract_message_and_account(event)
+ message, account = extract_message_and_account(event)
+ timestamp = event.timestamp
+
conversation = message.conversation
agent = conversation.assignee
first_response_time = message.created_at.to_i - conversation.created_at.to_i
@@ -28,7 +34,8 @@ class ReportingListener < BaseListener
end
def message_created(event)
- message, account, timestamp = extract_message_and_account(event)
+ message, account = extract_message_and_account(event)
+ timestamp = event.timestamp
return unless message.reportable?
diff --git a/app/models/account_user.rb b/app/models/account_user.rb
index c915a5042..8c482eb3c 100644
--- a/app/models/account_user.rb
+++ b/app/models/account_user.rb
@@ -3,6 +3,7 @@
# Table name: account_users
#
# id :bigint not null, primary key
+# active_at :datetime
# role :integer default("agent")
# created_at :datetime not null
# updated_at :datetime not null
@@ -23,6 +24,8 @@
#
class AccountUser < ApplicationRecord
+ include Events::Types
+
belongs_to :account
belongs_to :user
belongs_to :inviter, class_name: 'User', optional: true
@@ -30,8 +33,8 @@ class AccountUser < ApplicationRecord
enum role: { agent: 0, administrator: 1 }
accepts_nested_attributes_for :account
- after_create :create_notification_setting
- after_destroy :destroy_notification_setting
+ after_create :notify_creation, :create_notification_setting
+ after_destroy :notify_deletion, :destroy_notification_setting
validates :user_id, uniqueness: { scope: :account_id }
@@ -46,4 +49,14 @@ class AccountUser < ApplicationRecord
setting = user.notification_settings.find_by(account_id: account.id)
setting.destroy!
end
+
+ private
+
+ def notify_creation
+ Rails.configuration.dispatcher.dispatch(AGENT_ADDED, Time.zone.now, account: account)
+ end
+
+ def notify_deletion
+ Rails.configuration.dispatcher.dispatch(AGENT_REMOVED, Time.zone.now, account: account)
+ end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 35e3c006d..2476f586e 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -64,7 +64,7 @@ class User < ApplicationRecord
has_many :assigned_conversations, foreign_key: 'assignee_id', class_name: 'Conversation', dependent: :nullify
has_many :inbox_members, dependent: :destroy
- has_many :assigned_inboxes, through: :inbox_members, source: :inbox
+ has_many :inboxes, through: :inbox_members, source: :inbox
has_many :messages
has_many :invitees, through: :account_users, class_name: 'User', foreign_key: 'inviter_id', dependent: :nullify
@@ -74,9 +74,7 @@ class User < ApplicationRecord
before_validation :set_password_and_uid, on: :create
- after_create :notify_creation, :create_access_token
-
- after_destroy :notify_deletion
+ after_create :create_access_token
def send_devise_notification(notification, *args)
devise_mailer.send(notification, self, *args).deliver_later
@@ -86,30 +84,36 @@ class User < ApplicationRecord
self.uid = email
end
- def account_user
- # FIXME : temporary hack to transition over to multiple accounts per user
- # We should be fetching the current account user relationship here.
- account_users&.first
+ def active_account_user
+ account_users.order(active_at: :desc)&.first
+ end
+
+ def current_account_user
+ account_users.find_by(account_id: Current.account.id) if Current.account
end
def account
- account_user&.account
+ current_account_user&.account
+ end
+
+ def assigned_inboxes
+ inboxes.where(account_id: Current.account.id)
end
def administrator?
- account_user&.administrator?
+ current_account_user&.administrator?
end
def agent?
- account_user&.agent?
+ current_account_user&.agent?
end
def role
- account_user&.role
+ current_account_user&.role
end
def inviter
- account_user&.inviter
+ current_account_user&.inviter
end
def serializable_hash(options = nil)
@@ -118,14 +122,6 @@ class User < ApplicationRecord
serialized_user
end
- def notify_creation
- Rails.configuration.dispatcher.dispatch(AGENT_ADDED, Time.zone.now, account: account)
- end
-
- def notify_deletion
- Rails.configuration.dispatcher.dispatch(AGENT_REMOVED, Time.zone.now, account: account)
- end
-
def push_event_data
{
id: id,
diff --git a/app/policies/account_policy.rb b/app/policies/account_policy.rb
index 9577a3247..ec5799587 100644
--- a/app/policies/account_policy.rb
+++ b/app/policies/account_policy.rb
@@ -1,13 +1,13 @@
class AccountPolicy < ApplicationPolicy
def show?
- # FIXME : temporary hack to transition over to multiple accounts per user
- # We should be fetching the current account user relationship here.
- @user.administrator?
+ @account_user.administrator? || @account_user.agent?
end
def update?
- # FIXME : temporary hack to transition over to multiple accounts per user
- # We should be fetching the current account user relationship here.
- @user.administrator?
+ @account_user.administrator?
+ end
+
+ def update_active_at?
+ true
end
end
diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb
index b91c7f510..f08a47898 100644
--- a/app/policies/application_policy.rb
+++ b/app/policies/application_policy.rb
@@ -1,8 +1,11 @@
class ApplicationPolicy
- attr_reader :user, :record
+ attr_reader :user_context, :user, :record, :account, :account_user
- def initialize(user, record)
- @user = user
+ def initialize(user_context, record)
+ @user_context = user_context = user_context
+ @user = user_context[:user]
+ @account = user_context[:account]
+ @account_user = user_context[:account_user]
@record = record
end
@@ -35,14 +38,17 @@ class ApplicationPolicy
end
def scope
- Pundit.policy_scope!(user, record.class)
+ Pundit.policy_scope!(user_context, record.class)
end
class Scope
- attr_reader :user, :scope
+ attr_reader :user_context, :user, :scope, :account, :account_user
- def initialize(user, scope)
- @user = user
+ def initialize(user_context, scope)
+ @user_context = user_context = user_context
+ @user = user_context[:user]
+ @account = user_context[:account]
+ @account_user = user_context[:account_user]
@scope = scope
end
diff --git a/app/policies/contact_policy.rb b/app/policies/contact_policy.rb
index 629cbf1ba..dc1c3b7d1 100644
--- a/app/policies/contact_policy.rb
+++ b/app/policies/contact_policy.rb
@@ -1,14 +1,14 @@
class ContactPolicy < ApplicationPolicy
def index?
- @user.administrator?
+ true
end
def update?
- @user.administrator?
+ true
end
def show?
- @user.administrator?
+ true
end
def create?
diff --git a/app/policies/inbox_policy.rb b/app/policies/inbox_policy.rb
index 282ab7e49..230a4c131 100644
--- a/app/policies/inbox_policy.rb
+++ b/app/policies/inbox_policy.rb
@@ -1,16 +1,19 @@
class InboxPolicy < ApplicationPolicy
class Scope
- attr_reader :user, :scope
+ attr_reader :user_context, :user, :scope, :account, :account_user
- def initialize(user, scope)
- @user = user
+ def initialize(user_context, scope)
+ @user_context = user_context
+ @user = user_context[:user]
+ @account = user_context[:account]
+ @account_user = user_context[:account_user]
@scope = scope
end
def resolve
- if user.administrator?
+ if @account_user.administrator?
scope.all
- elsif user.agent?
+ elsif @account_user.agent?
user.assigned_inboxes
end
end
@@ -21,18 +24,18 @@ class InboxPolicy < ApplicationPolicy
end
def create?
- @user.administrator?
+ @account_user.administrator?
end
def update?
- @user.administrator?
+ @account_user.administrator?
end
def destroy?
- @user.administrator?
+ @account_user.administrator?
end
def set_agent_bot?
- @user.administrator?
+ @account_user.administrator?
end
end
diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb
index 5ac8803d1..b19874fd1 100644
--- a/app/policies/user_policy.rb
+++ b/app/policies/user_policy.rb
@@ -4,14 +4,14 @@ class UserPolicy < ApplicationPolicy
end
def create?
- @user.administrator?
+ @account_user.administrator?
end
def update?
- @user.administrator?
+ @account_user.administrator?
end
def destroy?
- @user.administrator?
+ @account_user.administrator?
end
end
diff --git a/app/policies/webhook_policy.rb b/app/policies/webhook_policy.rb
index 04814ee0f..ce8af2f37 100644
--- a/app/policies/webhook_policy.rb
+++ b/app/policies/webhook_policy.rb
@@ -1,17 +1,17 @@
class WebhookPolicy < ApplicationPolicy
def index?
- @user.administrator?
+ @account_user.administrator?
end
def update?
- @user.administrator?
+ @account_user.administrator?
end
def destroy?
- @user.administrator?
+ @account_user.administrator?
end
def create?
- @user.administrator?
+ @account_user.administrator?
end
end
diff --git a/app/views/api/v1/models/user.json.jbuilder b/app/views/api/v1/models/user.json.jbuilder
index 2d99937d0..d12a35657 100644
--- a/app/views/api/v1/models/user.json.jbuilder
+++ b/app/views/api/v1/models/user.json.jbuilder
@@ -4,9 +4,9 @@ json.uid resource.uid
json.name resource.name
json.nickname resource.nickname
json.email resource.email
-json.account_id resource.account.id
+json.account_id resource.current_account_user.account_id
json.pubsub_token resource.pubsub_token
-json.role resource.role
-json.inviter_id resource.account_user.inviter_id
+json.role resource.current_account_user.role
+json.inviter_id resource.current_account_user.inviter_id
json.confirmed resource.confirmed?
json.avatar_url resource.avatar_url
diff --git a/app/views/api/v1/profiles/update.json.jbuilder b/app/views/api/v1/profiles/update.json.jbuilder
index a1d99525a..f4eef3516 100644
--- a/app/views/api/v1/profiles/update.json.jbuilder
+++ b/app/views/api/v1/profiles/update.json.jbuilder
@@ -4,8 +4,6 @@ json.uid @user.uid
json.name @user.name
json.nickname @user.nickname
json.email @user.email
-json.account_id @user.account.id
json.pubsub_token @user.pubsub_token
-json.role @user.role
json.confirmed @user.confirmed?
json.avatar_url @user.avatar_url
diff --git a/app/views/devise/_auth.json.jbuilder b/app/views/devise/_auth.json.jbuilder
new file mode 100644
index 000000000..a5035d7a1
--- /dev/null
+++ b/app/views/devise/_auth.json.jbuilder
@@ -0,0 +1,22 @@
+json.data do
+ json.id resource.id
+ json.provider resource.provider
+ json.uid resource.uid
+ json.name resource.name
+ json.nickname resource.nickname
+ json.email resource.email
+ json.account_id resource.active_account_user.account_id
+ json.pubsub_token resource.pubsub_token
+ json.role resource.active_account_user.role
+ json.inviter_id resource.active_account_user.inviter_id
+ json.confirmed resource.confirmed?
+ json.avatar_url resource.avatar_url
+ json.accounts do
+ json.array! resource.account_users do |account_user|
+ json.id account_user.account_id
+ json.name account_user.account.name
+ json.active_at account_user.active_at
+ json.role account_user.role
+ end
+ end
+end
diff --git a/app/views/devise/auth.json.jbuilder b/app/views/devise/auth.json.jbuilder
deleted file mode 100644
index f7992e673..000000000
--- a/app/views/devise/auth.json.jbuilder
+++ /dev/null
@@ -1,14 +0,0 @@
-json.data do
- json.id resource.id
- json.provider resource.provider
- json.uid resource.uid
- json.name resource.name
- json.nickname resource.nickname
- json.email resource.email
- json.account_id resource.account.id
- json.pubsub_token resource.pubsub_token
- json.role resource.account_user.role
- json.inviter_id resource.account_user.inviter_id
- json.confirmed resource.confirmed?
- json.avatar_url resource.avatar_url
-end
diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb
index b4e92c7b6..c03e5dc3c 100644
--- a/app/views/devise/mailer/confirmation_instructions.html.erb
+++ b/app/views/devise/mailer/confirmation_instructions.html.erb
@@ -1,6 +1,6 @@
Welcome, <%= @resource.name %>!
-<% if @resource.inviter.present? %>
+<% if @resource&.inviter.present? %>
<%= @resource.inviter.name %>, with <%= @resource.inviter.account.name %>, has invited you to try out Chatwoot!
<% end %>
diff --git a/app/views/devise/token.json.jbuilder b/app/views/devise/token.json.jbuilder
index ec9d740aa..1de4594cd 100644
--- a/app/views/devise/token.json.jbuilder
+++ b/app/views/devise/token.json.jbuilder
@@ -1,18 +1,4 @@
json.payload do
json.success true
- json.data do
- json.id @resource.id
- json.provider @resource.provider
- json.uid @resource.uid
- json.name @resource.name
- json.nickname @resource.nickname
- json.email @resource.email
- json.account_id @resource.account.id
- json.pubsub_token @resource.pubsub_token
- json.role @resource.account_user.role
- json.inviter_id @resource.account_user.inviter_id
- json.confirmed @resource.confirmed?
- json.avatar_url @resource.avatar_url
- json.access_token @resource.access_token&.token
- end
+ json.partial! 'auth.json.jbuilder', resource: @resource
end
diff --git a/config/routes.rb b/config/routes.rb
index d2920ed00..31f90084e 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -23,6 +23,9 @@ Rails.application.routes.draw do
# ----------------------------------
# start of account scoped api routes
resources :accounts, only: [:create, :show, :update], module: :accounts do
+ member do
+ post :update_active_at
+ end
namespace :actions do
resource :contact_merge, only: [:create]
end
diff --git a/db/migrate/20200520125815_add_active_at_to_account_users.rb b/db/migrate/20200520125815_add_active_at_to_account_users.rb
new file mode 100644
index 000000000..b46557f53
--- /dev/null
+++ b/db/migrate/20200520125815_add_active_at_to_account_users.rb
@@ -0,0 +1,5 @@
+class AddActiveAtToAccountUsers < ActiveRecord::Migration[6.0]
+ def change
+ add_column :account_users, :active_at, :datetime, default: nil
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index f467a623a..1509a912a 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -34,6 +34,7 @@ ActiveRecord::Schema.define(version: 2020_05_22_115645) do
t.bigint "inviter_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
+ t.datetime "active_at"
t.index ["account_id", "user_id"], name: "uniq_user_id_per_account_id", unique: true
t.index ["account_id"], name: "index_account_users_on_account_id"
t.index ["user_id"], name: "index_account_users_on_user_id"
diff --git a/lib/current.rb b/lib/current.rb
index 9c1fcd9d0..71e69ead7 100644
--- a/lib/current.rb
+++ b/lib/current.rb
@@ -1,3 +1,5 @@
module Current
thread_mattr_accessor :user
+ thread_mattr_accessor :account
+ thread_mattr_accessor :account_user
end
diff --git a/spec/controllers/api/v1/accounts/accounts_controller_spec.rb b/spec/controllers/api/v1/accounts/accounts_controller_spec.rb
index 955cf1e72..30bffd9f2 100644
--- a/spec/controllers/api/v1/accounts/accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/accounts_controller_spec.rb
@@ -107,6 +107,7 @@ RSpec.describe 'Accounts API', type: :request do
describe 'GET /api/v1/accounts/{account.id}' do
let(:account) { create(:account) }
let(:agent) { create(:user, account: account, role: :agent) }
+ let(:user_without_access) { create(:user) }
let(:admin) { create(:user, account: account, role: :administrator) }
context 'when it is an unauthenticated user' do
@@ -119,9 +120,9 @@ RSpec.describe 'Accounts API', type: :request do
context 'when it is an unauthorized user' do
it 'returns unauthorized' do
get "/api/v1/accounts/#{account.id}",
- headers: agent.create_new_auth_token
+ headers: user_without_access.create_new_auth_token
- expect(response).to have_http_status(:unauthorized)
+ expect(response).to have_http_status(:not_found)
end
end
@@ -185,4 +186,29 @@ RSpec.describe 'Accounts API', type: :request do
end
end
end
+
+ describe 'POST /api/v1/accounts/{account.id}/update_active_at' do
+ let(:account) { create(:account) }
+ let(:agent) { create(:user, account: account, role: :agent) }
+
+ context 'when it is an unauthenticated user' do
+ it 'returns unauthorized' do
+ post "/api/v1/accounts/#{account.id}/update_active_at"
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+
+ context 'when it is an authenticated user' do
+ it 'modifies an account' do
+ expect(agent.account_users.first.active_at).to eq(nil)
+ post "/api/v1/accounts/#{account.id}/update_active_at",
+ params: {},
+ headers: agent.create_new_auth_token,
+ as: :json
+
+ expect(response).to have_http_status(:success)
+ expect(agent.account_users.first.active_at).not_to eq(nil)
+ end
+ end
+ end
end
diff --git a/spec/finders/conversation_finder_spec.rb b/spec/finders/conversation_finder_spec.rb
index 4ef0428a1..5a0a01403 100644
--- a/spec/finders/conversation_finder_spec.rb
+++ b/spec/finders/conversation_finder_spec.rb
@@ -15,6 +15,7 @@ describe ::ConversationFinder do
create(:conversation, account: account, inbox: inbox, assignee: user_1)
create(:conversation, account: account, inbox: inbox, assignee: user_1, status: 'resolved')
create(:conversation, account: account, inbox: inbox, assignee: user_2)
+ Current.account = account
end
describe '#perform' do
diff --git a/spec/listeners/action_cable_listener_spec.rb b/spec/listeners/action_cable_listener_spec.rb
index d53cf8c44..a70ff6675 100644
--- a/spec/listeners/action_cable_listener_spec.rb
+++ b/spec/listeners/action_cable_listener_spec.rb
@@ -24,7 +24,9 @@ describe ActionCableListener do
expect(conversation.inbox.reload.inbox_members.count).to eq(1)
expect(ActionCableBroadcastJob).to receive(:perform_later).with(
- [agent.pubsub_token, admin.pubsub_token, conversation.contact.pubsub_token], 'message.created', message.push_event_data
+ [agent.pubsub_token, admin.pubsub_token, conversation.contact.pubsub_token],
+ 'message.created',
+ message.push_event_data.merge(account_id: account.id)
)
listener.message_created(event)
end
@@ -40,7 +42,8 @@ describe ActionCableListener do
expect(ActionCableBroadcastJob).to receive(:perform_later).with(
[admin.pubsub_token, conversation.contact.pubsub_token],
'conversation.typing_on', conversation: conversation.push_event_data,
- user: agent.push_event_data
+ user: agent.push_event_data,
+ account_id: account.id
)
listener.conversation_typing_on(event)
end
@@ -56,7 +59,8 @@ describe ActionCableListener do
expect(ActionCableBroadcastJob).to receive(:perform_later).with(
[admin.pubsub_token, conversation.contact.pubsub_token],
'conversation.typing_off', conversation: conversation.push_event_data,
- user: agent.push_event_data
+ user: agent.push_event_data,
+ account_id: account.id
)
listener.conversation_typing_off(event)
end
diff --git a/spec/mailers/confirmation_instructions_spec.rb b/spec/mailers/confirmation_instructions_spec.rb
index 48b00fa6c..f6d908e2d 100644
--- a/spec/mailers/confirmation_instructions_spec.rb
+++ b/spec/mailers/confirmation_instructions_spec.rb
@@ -27,6 +27,7 @@ RSpec.describe 'Confirmation Instructions', type: :mailer do
let(:inviter_val) { create(:user, :administrator, skip_confirmation: true, account: account) }
it 'refers to the inviter and their account' do
+ Current.account = account
expect(mail.body).to match(
"#{CGI.escapeHTML(inviter_val.name)}, with #{CGI.escapeHTML(inviter_val.account.name)}, has invited you to try out Chatwoot!"
)
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 62cc750f3..0321fb93a 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -16,7 +16,6 @@ RSpec.describe User do
it { is_expected.to have_many(:assigned_conversations).class_name('Conversation').dependent(:nullify) }
it { is_expected.to have_many(:inbox_members).dependent(:destroy) }
it { is_expected.to have_many(:notification_settings).dependent(:destroy) }
- it { is_expected.to have_many(:assigned_inboxes).through(:inbox_members) }
it { is_expected.to have_many(:messages) }
it { is_expected.to have_many(:events) }
end
diff --git a/spec/policies/contact_policy_spec.rb b/spec/policies/contact_policy_spec.rb
index 01402ca59..371e455cd 100644
--- a/spec/policies/contact_policy_spec.rb
+++ b/spec/policies/contact_policy_spec.rb
@@ -11,23 +11,26 @@ RSpec.describe ContactPolicy, type: :policy do
let(:agent) { create(:user, account: account) }
let(:contact) { create(:contact) }
+ let(:administrator_context) { { user: administrator, account: account, account_user: account.account_users.first } }
+ let(:agent_context) { { user: agent, account: account, account_user: account.account_users.first } }
+
permissions :index?, :show?, :update? do
context 'when administrator' do
- it { expect(contact_policy).to permit(administrator, contact) }
+ it { expect(contact_policy).to permit(administrator_context, contact) }
end
context 'when agent' do
- it { expect(contact_policy).not_to permit(agent, contact) }
+ it { expect(contact_policy).to permit(agent_context, contact) }
end
end
permissions :create? do
context 'when administrator' do
- it { expect(contact_policy).to permit(administrator, contact) }
+ it { expect(contact_policy).to permit(administrator_context, contact) }
end
context 'when agent' do
- it { expect(contact_policy).to permit(agent, contact) }
+ it { expect(contact_policy).to permit(agent_context, contact) }
end
end
end
diff --git a/spec/policies/inbox_policy_spec.rb b/spec/policies/inbox_policy_spec.rb
index 298c6a154..978e81d76 100644
--- a/spec/policies/inbox_policy_spec.rb
+++ b/spec/policies/inbox_policy_spec.rb
@@ -10,24 +10,26 @@ RSpec.describe InboxPolicy, type: :policy do
let(:administrator) { create(:user, :administrator, account: account) }
let(:agent) { create(:user, account: account) }
let(:inbox) { create(:inbox) }
+ let(:administrator_context) { { user: administrator, account: account, account_user: account.account_users.first } }
+ let(:agent_context) { { user: agent, account: account, account_user: account.account_users.first } }
permissions :create?, :destroy?, :update?, :set_agent_bot? do
context 'when administrator' do
- it { expect(inbox_policy).to permit(administrator, inbox) }
+ it { expect(inbox_policy).to permit(administrator_context, inbox) }
end
context 'when agent' do
- it { expect(inbox_policy).not_to permit(agent, inbox) }
+ it { expect(inbox_policy).not_to permit(agent_context, inbox) }
end
end
permissions :index? do
context 'when administrator' do
- it { expect(inbox_policy).to permit(administrator, inbox) }
+ it { expect(inbox_policy).to permit(administrator_context, inbox) }
end
context 'when agent' do
- it { expect(inbox_policy).to permit(agent, inbox) }
+ it { expect(inbox_policy).to permit(agent_context, inbox) }
end
end
end
diff --git a/spec/policies/user_policy_spec.rb b/spec/policies/user_policy_spec.rb
index 05e8f3dc0..9cf2f18fa 100644
--- a/spec/policies/user_policy_spec.rb
+++ b/spec/policies/user_policy_spec.rb
@@ -10,24 +10,26 @@ RSpec.describe UserPolicy, type: :policy do
let(:administrator) { create(:user, :administrator, account: account) }
let(:agent) { create(:user, account: account) }
let(:user) { create(:user, account: account) }
+ let(:administrator_context) { { user: administrator, account: account, account_user: account.account_users.first } }
+ let(:agent_context) { { user: agent, account: account, account_user: account.account_users.first } }
permissions :create?, :update?, :destroy? do
context 'when administrator' do
- it { expect(user_policy).to permit(administrator, user) }
+ it { expect(user_policy).to permit(administrator_context, user) }
end
context 'when agent' do
- it { expect(user_policy).not_to permit(agent, user) }
+ it { expect(user_policy).not_to permit(agent_context, user) }
end
end
permissions :index? do
context 'when administrator' do
- it { expect(user_policy).to permit(administrator, user) }
+ it { expect(user_policy).to permit(administrator_context, user) }
end
context 'when agent' do
- it { expect(user_policy).to permit(agent, user) }
+ it { expect(user_policy).to permit(agent_context, user) }
end
end
end
From efc59bb43fa9e09dd08758f112c0c5bc5e4be53c Mon Sep 17 00:00:00 2001
From: Pranav Raj S
Date: Tue, 26 May 2020 23:36:42 +0530
Subject: [PATCH 31/42] Bug: Fix account_id in URLs (#894)
* Bug: Fix account_id in URLs
* Fix accountMixin specs
---
.../widgets/conversation/ConversationCard.vue | 7 ++--
.../dashboard/i18n/locale/ca/inboxMgmt.json | 2 +-
.../dashboard/i18n/locale/de/inboxMgmt.json | 2 +-
.../dashboard/i18n/locale/el/inboxMgmt.json | 2 +-
.../dashboard/i18n/locale/en/inboxMgmt.json | 2 +-
.../dashboard/i18n/locale/fr/inboxMgmt.json | 2 +-
.../dashboard/i18n/locale/ml/inboxMgmt.json | 2 +-
.../dashboard/i18n/locale/nl/inboxMgmt.json | 2 +-
.../dashboard/i18n/locale/pt/inboxMgmt.json | 2 +-
.../dashboard/i18n/locale/ro/inboxMgmt.json | 2 +-
app/javascript/dashboard/mixins/account.js | 8 ++---
.../dashboard/mixins/specs/account.spec.js | 33 +++++++++++--------
.../dashboard/settings/account/Index.vue | 13 ++++----
.../settings/inbox/channels/Facebook.vue | 6 ++--
14 files changed, 43 insertions(+), 42 deletions(-)
diff --git a/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue b/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue
index 4afe61c31..aa9af83b2 100644
--- a/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue
+++ b/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue
@@ -75,6 +75,7 @@ export default {
inboxesList: 'inboxes/getInboxes',
activeInbox: 'getSelectedInbox',
currentUser: 'getCurrentUser',
+ accountId: 'getCurrentAccountId',
}),
isActiveChat() {
@@ -97,11 +98,7 @@ export default {
methods: {
cardClick(chat) {
const { activeInbox } = this;
- const path = conversationUrl(
- this.currentUser.account_id,
- activeInbox,
- chat.id
- );
+ const path = conversationUrl(this.accountId, activeInbox, chat.id);
router.push({ path: frontendURL(path) });
},
extractMessageText(chatItem) {
diff --git a/app/javascript/dashboard/i18n/locale/ca/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/ca/inboxMgmt.json
index 5059cd491..c9c1f740d 100644
--- a/app/javascript/dashboard/i18n/locale/ca/inboxMgmt.json
+++ b/app/javascript/dashboard/i18n/locale/ca/inboxMgmt.json
@@ -1,7 +1,7 @@
{
"INBOX_MGMT": {
"HEADER": "Safates d'entrada",
- "SIDEBAR_TXT": "Safata d'entrada
Quan connecteu un lloc web o una pàgina de facebook a Chatwoot, es diu Safata d'entrada . Teniu bústies d'entrada il·limitades al vostre compte de Chatwoot.
Feu click a Afegir safata d'entrada per connectar-vos a un lloc web o a una pàgina de Facebook.
Al Tauler de control , pots veure totes les converses de totes les teves safates d'entrada en un sol lloc i respondre-les a la pestanya `Converses`.
També pots veure converses específiques per a una safata d’entrada si feu clic al nom de la safata d'entrada, al panell esquerre de la taula.
",
+ "SIDEBAR_TXT": "Safata d'entrada
Quan connecteu un lloc web o una pàgina de facebook a Chatwoot, es diu Safata d'entrada . Teniu bústies d'entrada il·limitades al vostre compte de Chatwoot.
Feu click a Afegir safata d'entrada per connectar-vos a un lloc web o a una pàgina de Facebook.
Al Tauler de control, pots veure totes les converses de totes les teves safates d'entrada en un sol lloc i respondre-les a la pestanya `Converses`.
També pots veure converses específiques per a una safata d’entrada si feu clic al nom de la safata d'entrada, al panell esquerre de la taula.
",
"LIST": {
"404": "No hi ha cap safata d'entrada connectat a aquest compte."
},
diff --git a/app/javascript/dashboard/i18n/locale/de/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/de/inboxMgmt.json
index 5f09f8095..fca1a7c44 100644
--- a/app/javascript/dashboard/i18n/locale/de/inboxMgmt.json
+++ b/app/javascript/dashboard/i18n/locale/de/inboxMgmt.json
@@ -1,7 +1,7 @@
{
"INBOX_MGMT": {
"HEADER": "Posteingänge",
- "SIDEBAR_TXT": " Posteingang
Wenn Sie eine Website oder eine Facebook-Seite mit Chatwoot verbinden, wird dies als Posteingang bezeichnet. Sie können unbegrenzt Posteingänge in Ihrem Chatwoot-Konto haben.
Klicken Sie auf Posteingang hinzufügen , um eine Website oder eine Facebook-Seite zu verbinden.
Im Dashboard können Sie alle Konversationen aus all Ihren Posteingängen an einem einzigen Ort anzeigen und unter 'Konversationen' darauf antworten `tab.
Sie können Konversationen auch für einen Posteingang anzeigen, indem Sie auf den Namen des Posteingangs im linken Bereich des Dashboards klicken.
",
+ "SIDEBAR_TXT": " Posteingang
Wenn Sie eine Website oder eine Facebook-Seite mit Chatwoot verbinden, wird dies als Posteingang bezeichnet. Sie können unbegrenzt Posteingänge in Ihrem Chatwoot-Konto haben.
Klicken Sie auf Posteingang hinzufügen , um eine Website oder eine Facebook-Seite zu verbinden.
Im Dashboard können Sie alle Konversationen aus all Ihren Posteingängen an einem einzigen Ort anzeigen und unter 'Konversationen' darauf antworten `tab.
Sie können Konversationen auch für einen Posteingang anzeigen, indem Sie auf den Namen des Posteingangs im linken Bereich des Dashboards klicken.
",
"LIST": {
"404": "Diesem Konto sind keine Posteingänge zugeordnet."
},
diff --git a/app/javascript/dashboard/i18n/locale/el/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/el/inboxMgmt.json
index fe29aaebb..0a18b005b 100644
--- a/app/javascript/dashboard/i18n/locale/el/inboxMgmt.json
+++ b/app/javascript/dashboard/i18n/locale/el/inboxMgmt.json
@@ -1,7 +1,7 @@
{
"INBOX_MGMT": {
"HEADER": "Κιβώτια Εισερχομένων",
- "SIDEBAR_TXT": "Κιβώτιο Εισερχομένων
Όταν συνδέετε σε μια Ιστοσελίδα ή μια σελίδα του Facebook με το Chatwoot, δημιουργείται ένα Κιβώτιο . Μπορείτε να έχετε απεριόριστα κιβώτια στον λογαριασμό σας στο Chatwoot.
Πατήστε στο Προσθήκη Κιβωτίου για να το συνδέσετε με μια ιστοσελίδα ή μια σελίδα στο Facebook.
Στον Πίνακα Ελέγχου , μπορείτε να δείτε τις συνομιλίες από όλα τα κιβώτια σε ένα μέρος και να απαντήσετε από την καρτέλα `Συνομιλίες`.
Μπορείτε επίσης να δείτε τις συνομιλίες από ένα κιβώτιο εισερχομένων πατώντας στο όνομά του στο αριστερό μέρος του πίνακα ελέγχου.
",
+ "SIDEBAR_TXT": "Κιβώτιο Εισερχομένων
Όταν συνδέετε σε μια Ιστοσελίδα ή μια σελίδα του Facebook με το Chatwoot, δημιουργείται ένα Κιβώτιο . Μπορείτε να έχετε απεριόριστα κιβώτια στον λογαριασμό σας στο Chatwoot.
Πατήστε στο Προσθήκη Κιβωτίου για να το συνδέσετε με μια ιστοσελίδα ή μια σελίδα στο Facebook.
Στον Πίνακα Ελέγχου, μπορείτε να δείτε τις συνομιλίες από όλα τα κιβώτια σε ένα μέρος και να απαντήσετε από την καρτέλα `Συνομιλίες`.
Μπορείτε επίσης να δείτε τις συνομιλίες από ένα κιβώτιο εισερχομένων πατώντας στο όνομά του στο αριστερό μέρος του πίνακα ελέγχου.
",
"LIST": {
"404": "Δεν υπάρχουν κιβώτια εισερχομένων σε αυτόν τον λογαριασμό."
},
diff --git a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json
index 2aa1c15c2..da8465626 100644
--- a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json
+++ b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json
@@ -1,7 +1,7 @@
{
"INBOX_MGMT": {
"HEADER": "Inboxes",
- "SIDEBAR_TXT": "Inbox
When you connect a website or a facebook Page to Chatwoot, it is called an Inbox . You can have unlimited inboxes in your Chatwoot account.
Click on Add Inbox to connect a website or a Facebook Page.
In the Dashboard , you can see all the conversations from all your inboxes in a single place and respond to them under the `Conversations` tab.
You can also see conversations specific to an inbox by clicking on the inbox name on the left pane of the dashboard.
",
+ "SIDEBAR_TXT": "Inbox
When you connect a website or a facebook Page to Chatwoot, it is called an Inbox . You can have unlimited inboxes in your Chatwoot account.
Click on Add Inbox to connect a website or a Facebook Page.
In the Dashboard, you can see all the conversations from all your inboxes in a single place and respond to them under the `Conversations` tab.
You can also see conversations specific to an inbox by clicking on the inbox name on the left pane of the dashboard.
",
"LIST": {
"404": "There are no inboxes attached to this account."
},
diff --git a/app/javascript/dashboard/i18n/locale/fr/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/fr/inboxMgmt.json
index 3a1fa2761..fe35bf261 100644
--- a/app/javascript/dashboard/i18n/locale/fr/inboxMgmt.json
+++ b/app/javascript/dashboard/i18n/locale/fr/inboxMgmt.json
@@ -1,7 +1,7 @@
{
"INBOX_MGMT": {
"HEADER": "Boîtes de réception",
- "SIDEBAR_TXT": "Boîte de réception
Lorsque vous connectez un site Web ou une page Facebook à Chatwoot, elle est appelée une Boîte de réception . Vous pouvez avoir des boîtes de réception illimitées dans votre compte Chatwoot.
Cliquez sur Ajouter Boîte de réception pour connecter un site Web ou une page Facebook.
Dans le tableau de bord , vous pouvez voir toutes les conversations de toutes vos boîtes de réception en un seul endroit et y répondre dans l'onglet `Conversations`.
Vous pouvez également voir les conversations spécifiques à une boîte de réception en cliquant sur le nom de la boîte de réception sur le volet gauche du tableau de bord.
",
+ "SIDEBAR_TXT": "Boîte de réception
Lorsque vous connectez un site Web ou une page Facebook à Chatwoot, elle est appelée une Boîte de réception . Vous pouvez avoir des boîtes de réception illimitées dans votre compte Chatwoot.
Cliquez sur Ajouter Boîte de réception pour connecter un site Web ou une page Facebook.
Dans le tableau de bord, vous pouvez voir toutes les conversations de toutes vos boîtes de réception en un seul endroit et y répondre dans l'onglet `Conversations`.
Vous pouvez également voir les conversations spécifiques à une boîte de réception en cliquant sur le nom de la boîte de réception sur le volet gauche du tableau de bord.
",
"LIST": {
"404": "Il n'y a aucune boîte de réception associée à ce compte."
},
diff --git a/app/javascript/dashboard/i18n/locale/ml/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/ml/inboxMgmt.json
index f9e98fba7..2f87a0f67 100644
--- a/app/javascript/dashboard/i18n/locale/ml/inboxMgmt.json
+++ b/app/javascript/dashboard/i18n/locale/ml/inboxMgmt.json
@@ -1,7 +1,7 @@
{
"INBOX_MGMT": {
"HEADER": "ഇൻബോക്സുകൾ",
- "SIDEBAR_TXT": " ഇൻബോക്സ്
ചാറ്റ് വൂട്ടിലേക്ക് നിങ്ങൾ ഒരു വെബ്സൈറ്റോ ഫേസ്ബുക്ക് പേജോ കണക്റ്റുചെയ്യുമ്പോൾ, അതിനെ ഇൻബോക്സ് എന്ന് വിളിക്കുന്നു. നിങ്ങളുടെ ചാറ്റ് വൂട്ട് അക്കൗണ്ടിൽ പരിധിയില്ലാത്ത ഇൻബോക്സുകൾ ഉണ്ടായിരിക്കാൻ കഴിയും.
ഒരു വെബ്സൈറ്റ് അല്ലെങ്കിൽ ഫേസ്ബുക് പേജ് ബന്ധിപ്പിക്കുന്നതിന് ഇൻബോക്സ് ചേർക്കുക ക്ലിക്കുചെയ്യുക.
ഡാഷ്ബോർഡ് , നിങ്ങളുടെ എല്ലാ ഇൻബോക്സുകളിൽ നിന്നുമുള്ള എല്ലാ സംഭാഷണങ്ങളും ഒരൊറ്റ സ്ഥലത്ത് കാണാനും `സംഭാഷണങ്ങൾ` ടാബിന് കീഴിൽ അവയോട് പ്രതികരിക്കാനും കഴിയും.
ഡാഷ്ബോർഡിന്റെ ഇടത് പാളിയിലെ ഇൻബോക്സ് ബട്ടണിൽ ക്ലിക്കുചെയ്ത് ഇൻബോക്സിൽ ഉള്ള സംഭാഷണങ്ങൾ കാണാൻ നിങ്ങൾക്കു സാധിക്കും .
",
+ "SIDEBAR_TXT": " ഇൻബോക്സ്
ചാറ്റ് വൂട്ടിലേക്ക് നിങ്ങൾ ഒരു വെബ്സൈറ്റോ ഫേസ്ബുക്ക് പേജോ കണക്റ്റുചെയ്യുമ്പോൾ, അതിനെ ഇൻബോക്സ് എന്ന് വിളിക്കുന്നു. നിങ്ങളുടെ ചാറ്റ് വൂട്ട് അക്കൗണ്ടിൽ പരിധിയില്ലാത്ത ഇൻബോക്സുകൾ ഉണ്ടായിരിക്കാൻ കഴിയും.
ഒരു വെബ്സൈറ്റ് അല്ലെങ്കിൽ ഫേസ്ബുക് പേജ് ബന്ധിപ്പിക്കുന്നതിന് ഇൻബോക്സ് ചേർക്കുക ക്ലിക്കുചെയ്യുക.
ഡാഷ്ബോർഡ്, നിങ്ങളുടെ എല്ലാ ഇൻബോക്സുകളിൽ നിന്നുമുള്ള എല്ലാ സംഭാഷണങ്ങളും ഒരൊറ്റ സ്ഥലത്ത് കാണാനും `സംഭാഷണങ്ങൾ` ടാബിന് കീഴിൽ അവയോട് പ്രതികരിക്കാനും കഴിയും.
ഡാഷ്ബോർഡിന്റെ ഇടത് പാളിയിലെ ഇൻബോക്സ് ബട്ടണിൽ ക്ലിക്കുചെയ്ത് ഇൻബോക്സിൽ ഉള്ള സംഭാഷണങ്ങൾ കാണാൻ നിങ്ങൾക്കു സാധിക്കും .
",
"LIST": {
"404": "ഈ അക്കൗണ്ടിലേക്കു ഇൻബോക്സുകളൊന്നും ബന്ധിപ്പിച്ചിട്ടില്ല."
},
diff --git a/app/javascript/dashboard/i18n/locale/nl/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/nl/inboxMgmt.json
index 9d3734621..981ca5ffc 100644
--- a/app/javascript/dashboard/i18n/locale/nl/inboxMgmt.json
+++ b/app/javascript/dashboard/i18n/locale/nl/inboxMgmt.json
@@ -1,7 +1,7 @@
{
"INBOX_MGMT": {
"HEADER": "Inboxen",
- "SIDEBAR_TXT": "Inboxen
Wanneer u een website of een Facebook-pagina verbindt met Chatwoot, het heet een inbox . U kunt een oneindig aantal inboxen hebben in uw Chatwoot account.
Klik op Voeg Postvak in toe om een website of een Facebook-pagina te verbinden.
in het Dashboard , u kunt alle gesprekken van al uw inboxen op één plek zien en erop reageren onder het `Conversations` tabblad.
U kunt ook gesprekken zien die specifiek zijn voor een inbox door te klikken op de naam van de inbox op het linkerpaneel van het dashboard.
",
+ "SIDEBAR_TXT": "Inboxen
Wanneer u een website of een Facebook-pagina verbindt met Chatwoot, het heet een inbox . U kunt een oneindig aantal inboxen hebben in uw Chatwoot account.
Klik op Voeg Postvak in toe om een website of een Facebook-pagina te verbinden.
in het Dashboard, u kunt alle gesprekken van al uw inboxen op één plek zien en erop reageren onder het `Conversations` tabblad.
U kunt ook gesprekken zien die specifiek zijn voor een inbox door te klikken op de naam van de inbox op het linkerpaneel van het dashboard.
",
"LIST": {
"404": "Er zijn geen inboxen aan dit account gekoppeld."
},
diff --git a/app/javascript/dashboard/i18n/locale/pt/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/pt/inboxMgmt.json
index acd204b5c..631470370 100644
--- a/app/javascript/dashboard/i18n/locale/pt/inboxMgmt.json
+++ b/app/javascript/dashboard/i18n/locale/pt/inboxMgmt.json
@@ -1,7 +1,7 @@
{
"INBOX_MGMT": {
"HEADER": "Caixas de Entrada",
- "SIDEBAR_TXT": "Caixa de entrada
Quando você conectar um site ou uma página de Facebook ao Chatwoot, é chamado de caixa de entrada . Você pode ter caixas de entrada ilimitadas na sua conta de Chatwoot.
Clique em Adicionar caixa de entrada para conectar um site ou uma página do Facebook.
No Painel , você pode ver todas as conversas de todas as suas caixas de entrada em um único lugar e responder a elas sob a guia `Conversations`.
Você também pode ver conversas específicas para uma caixa de entrada, clicando no nome da caixa de entrada no painel esquerdo do painel.
",
+ "SIDEBAR_TXT": "Caixa de entrada
Quando você conectar um site ou uma página de Facebook ao Chatwoot, é chamado de caixa de entrada . Você pode ter caixas de entrada ilimitadas na sua conta de Chatwoot.
Clique em Adicionar caixa de entrada para conectar um site ou uma página do Facebook.
No Painel, você pode ver todas as conversas de todas as suas caixas de entrada em um único lugar e responder a elas sob a guia `Conversations`.
Você também pode ver conversas específicas para uma caixa de entrada, clicando no nome da caixa de entrada no painel esquerdo do painel.
",
"LIST": {
"404": "Não há caixas de entrada anexadas a esta conta."
},
diff --git a/app/javascript/dashboard/i18n/locale/ro/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/ro/inboxMgmt.json
index 344436cc6..702a3b1d5 100644
--- a/app/javascript/dashboard/i18n/locale/ro/inboxMgmt.json
+++ b/app/javascript/dashboard/i18n/locale/ro/inboxMgmt.json
@@ -1,7 +1,7 @@
{
"INBOX_MGMT": {
"HEADER": "Căsuțe",
- "SIDEBAR_TXT": "Inbox
Când conectezi un site web sau o pagină de Facebook la Chatwoot, se numește Inbox . Poți avea inbox-uri nelimitate în contul tău din Chatwoot.
Faceţi clic pe Adăugaţi Inbox pentru a conecta un website sau o pagină Facebook.
În Panoul de control , poți vedea toate conversațiile din toate cutiile tale într-un singur loc și să le răspunzi sub fila `Conversations`.
De asemenea, poți vedea conversații specifice unei căsuțe poștale făcând clic pe numele inbox-ului din panoul stâng al tabloului de bord.
",
+ "SIDEBAR_TXT": "Inbox
Când conectezi un site web sau o pagină de Facebook la Chatwoot, se numește Inbox . Poți avea inbox-uri nelimitate în contul tău din Chatwoot.
Faceţi clic pe Adăugaţi Inbox pentru a conecta un website sau o pagină Facebook.
În Panoul de control, poți vedea toate conversațiile din toate cutiile tale într-un singur loc și să le răspunzi sub fila `Conversations`.
De asemenea, poți vedea conversații specifice unei căsuțe poștale făcând clic pe numele inbox-ului din panoul stâng al tabloului de bord.
",
"LIST": {
"404": "Nu există căsuțe poștale atașate acestui cont."
},
diff --git a/app/javascript/dashboard/mixins/account.js b/app/javascript/dashboard/mixins/account.js
index 8ce8d2095..9e4a6e605 100644
--- a/app/javascript/dashboard/mixins/account.js
+++ b/app/javascript/dashboard/mixins/account.js
@@ -1,10 +1,10 @@
-import auth from '../api/auth';
+import { mapGetters } from 'vuex';
export default {
computed: {
- accountId() {
- return auth.getCurrentUser().account_id;
- },
+ ...mapGetters({
+ accountId: 'getCurrentAccountId',
+ }),
},
methods: {
addAccountScoping(url) {
diff --git a/app/javascript/dashboard/mixins/specs/account.spec.js b/app/javascript/dashboard/mixins/specs/account.spec.js
index a0cbf0d0a..c918ae774 100644
--- a/app/javascript/dashboard/mixins/specs/account.spec.js
+++ b/app/javascript/dashboard/mixins/specs/account.spec.js
@@ -1,33 +1,40 @@
-import { createWrapper } from '@vue/test-utils';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
import accountMixin from '../account';
-import Vue from 'vue';
+import Vuex from 'vuex';
-jest.mock('../../api/auth', () => ({
- getCurrentUser: () => ({ account_id: 1 }),
-}));
+const localVue = createLocalVue();
+localVue.use(Vuex);
describe('accountMixin', () => {
- test('set accountId properly', () => {
+ let getters;
+ let store;
+
+ beforeEach(() => {
+ getters = {
+ getCurrentAccountId: () => 1,
+ };
+
+ store = new Vuex.Store({ getters });
+ });
+
+ it('set accountId properly', () => {
const Component = {
render() {},
title: 'TestComponent',
mixins: [accountMixin],
};
- const Constructor = Vue.extend(Component);
- const vm = new Constructor().$mount();
- const wrapper = createWrapper(vm);
+ const wrapper = shallowMount(Component, { store, localVue });
expect(wrapper.vm.accountId).toBe(1);
});
- test('returns current url', () => {
+ it('returns current url', () => {
const Component = {
render() {},
title: 'TestComponent',
mixins: [accountMixin],
};
- const Constructor = Vue.extend(Component);
- const vm = new Constructor().$mount();
- const wrapper = createWrapper(vm);
+
+ const wrapper = shallowMount(Component, { store, localVue });
expect(wrapper.vm.addAccountScoping('settings/inboxes/new')).toBe(
'/app/accounts/1/settings/inboxes/new'
);
diff --git a/app/javascript/dashboard/routes/dashboard/settings/account/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/account/Index.vue
index f4bac9d12..d8d86a052 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/account/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/account/Index.vue
@@ -94,12 +94,12 @@
import Vue from 'vue';
import { required } from 'vuelidate/lib/validators';
import { mapGetters } from 'vuex';
-import { accountIdFromPathname } from 'dashboard/helper/URLHelper';
import alertMixin from 'shared/mixins/alertMixin';
import configMixin from 'shared/mixins/configMixin';
+import accountMixin from '../../../../mixins/account';
export default {
- mixins: [alertMixin, configMixin],
+ mixins: [accountMixin, alertMixin, configMixin],
data() {
return {
id: '',
@@ -140,10 +140,7 @@ export default {
},
methods: {
async initializeAccount() {
- const { pathname } = window.location;
- const accountId = accountIdFromPathname(pathname);
-
- if (accountId) {
+ try {
await this.$store.dispatch('accounts/get');
const {
name,
@@ -153,7 +150,7 @@ export default {
support_email,
domain_emails_enabled,
features,
- } = this.getAccount(accountId);
+ } = this.getAccount(this.accountId);
Vue.config.lang = locale;
this.name = name;
@@ -163,6 +160,8 @@ export default {
this.supportEmail = support_email;
this.domainEmailsEnabled = domain_emails_enabled;
this.features = features;
+ } catch (error) {
+ // Ignore error
}
},
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Facebook.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Facebook.vue
index 282096616..bcd9cda9d 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Facebook.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Facebook.vue
@@ -82,13 +82,14 @@ import ChannelApi from '../../../../../api/channels';
import PageHeader from '../../SettingsSubPageHeader';
import router from '../../../../index';
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
+import accountMixin from '../../../../../mixins/account';
export default {
components: {
LoadingState,
PageHeader,
},
- mixins: [globalConfigMixin],
+ mixins: [globalConfigMixin, accountMixin],
data() {
return {
isCreating: false,
@@ -126,9 +127,6 @@ export default {
currentUser: 'getCurrentUser',
globalConfig: 'globalConfig/get',
}),
- accountId() {
- return this.currentUser.account_id;
- },
},
created() {
From a9d93b750c6af63ac7e63bbf767e1179acb78d1d Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 29 May 2020 11:14:27 +0530
Subject: [PATCH 32/42] Chore: Bump kaminari from 1.2.0 to 1.2.1 (#898)
Bumps [kaminari](https://github.com/kaminari/kaminari) from 1.2.0 to 1.2.1.
- [Release notes](https://github.com/kaminari/kaminari/releases)
- [Changelog](https://github.com/kaminari/kaminari/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kaminari/kaminari/compare/v1.2.0...v1.2.1)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
Gemfile.lock | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index ba424cac0..48e4e12a8 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -249,18 +249,18 @@ GEM
json (2.3.0)
json_pure (2.3.0)
jwt (2.2.1)
- kaminari (1.2.0)
+ kaminari (1.2.1)
activesupport (>= 4.1.0)
- kaminari-actionview (= 1.2.0)
- kaminari-activerecord (= 1.2.0)
- kaminari-core (= 1.2.0)
- kaminari-actionview (1.2.0)
+ kaminari-actionview (= 1.2.1)
+ kaminari-activerecord (= 1.2.1)
+ kaminari-core (= 1.2.1)
+ kaminari-actionview (1.2.1)
actionview
- kaminari-core (= 1.2.0)
- kaminari-activerecord (1.2.0)
+ kaminari-core (= 1.2.1)
+ kaminari-activerecord (1.2.1)
activerecord
- kaminari-core (= 1.2.0)
- kaminari-core (1.2.0)
+ kaminari-core (= 1.2.1)
+ kaminari-core (1.2.1)
koala (3.0.0)
addressable
faraday
From 10a1758261d96fbc2e0ad2474aa9185a6a777729 Mon Sep 17 00:00:00 2001
From: Ronald Walker
Date: Fri, 29 May 2020 00:07:18 -0700
Subject: [PATCH 33/42] Bug: Fix false positive in #toggleAgentTypingStatus
spec (#899)
---
.../widget/store/modules/specs/conversation/mutations.spec.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/javascript/widget/store/modules/specs/conversation/mutations.spec.js b/app/javascript/widget/store/modules/specs/conversation/mutations.spec.js
index 7241dbc2f..1ce076638 100644
--- a/app/javascript/widget/store/modules/specs/conversation/mutations.spec.js
+++ b/app/javascript/widget/store/modules/specs/conversation/mutations.spec.js
@@ -101,7 +101,7 @@ describe('#mutations', () => {
});
it('sets isAgentTyping flag to false', () => {
- const state = { uiFlags: { isAgentTyping: false } };
+ const state = { uiFlags: { isAgentTyping: true } };
mutations.toggleAgentTypingStatus(state, { status: 'off' });
expect(state.uiFlags.isAgentTyping).toEqual(false);
});
From ec197b077d6da658e21ea16f1e152da25c7c8f86 Mon Sep 17 00:00:00 2001
From: Pranav Raj S
Date: Fri, 29 May 2020 14:53:27 +0530
Subject: [PATCH 34/42] Chore: Add db migrate to Heroku release phase (#901)
---
Procfile | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Procfile b/Procfile
index 16a5cc0c1..34f447dda 100644
--- a/Procfile
+++ b/Procfile
@@ -1,2 +1,3 @@
+release: bundle exec rake db:migrate
web: bin/rails server -p $PORT -e $RAILS_ENV
-worker: bundle exec sidekiq -C config/sidekiq.yml
\ No newline at end of file
+worker: bundle exec sidekiq -C config/sidekiq.yml
From 47ec7ad7c917dbb5a3e5c0e81b2d629036605cc3 Mon Sep 17 00:00:00 2001
From: Pranav Raj S
Date: Sat, 30 May 2020 17:28:00 +0530
Subject: [PATCH 35/42] Feature: Ability to customise widget color (#903)
- Use Chrome style color-picker
---
.../dashboard/assets/scss/_woot.scss | 1 -
.../assets/scss/widgets/_colorpicker.scss | 13 ---
.../dashboard/components/SettingsSection.vue | 4 +-
app/javascript/dashboard/components/index.js | 2 +
.../components/widgets/ColorPicker.vue | 80 +++++++++++++++++++
.../dashboard/settings/inbox/Settings.vue | 60 ++++++--------
.../settings/inbox/channels/Website.vue | 24 +++---
.../shared/helpers/vuex/mutationHelpers.js | 4 +-
package.json | 2 +-
yarn.lock | 2 +-
10 files changed, 124 insertions(+), 68 deletions(-)
delete mode 100644 app/javascript/dashboard/assets/scss/widgets/_colorpicker.scss
create mode 100644 app/javascript/dashboard/components/widgets/ColorPicker.vue
diff --git a/app/javascript/dashboard/assets/scss/_woot.scss b/app/javascript/dashboard/assets/scss/_woot.scss
index 05d822b5a..947204ca6 100644
--- a/app/javascript/dashboard/assets/scss/_woot.scss
+++ b/app/javascript/dashboard/assets/scss/_woot.scss
@@ -5,7 +5,6 @@
@import 'foundation-custom';
@import 'widgets/billing';
@import 'widgets/buttons';
-@import 'widgets/colorpicker';
@import 'widgets/conv-header';
@import 'widgets/conversation-card';
@import 'widgets/conversation-view';
diff --git a/app/javascript/dashboard/assets/scss/widgets/_colorpicker.scss b/app/javascript/dashboard/assets/scss/widgets/_colorpicker.scss
deleted file mode 100644
index 9d761807a..000000000
--- a/app/javascript/dashboard/assets/scss/widgets/_colorpicker.scss
+++ /dev/null
@@ -1,13 +0,0 @@
-@import '~dashboard/assets/scss/variables';
-
-.widget-color--selector.vc-compact {
- border: 1px solid $color-border;
- box-shadow: none;
- margin-bottom: $space-normal;
- width: 356px;
-
- .vc-compact-color-item {
- height: 24px;
- width: 24px;
- }
-}
diff --git a/app/javascript/dashboard/components/SettingsSection.vue b/app/javascript/dashboard/components/SettingsSection.vue
index ebf79bf3e..adf6f9912 100644
--- a/app/javascript/dashboard/components/SettingsSection.vue
+++ b/app/javascript/dashboard/components/SettingsSection.vue
@@ -1,6 +1,6 @@
-
+
{{ title }}
@@ -8,7 +8,7 @@
{{ subTitle }}
-
diff --git a/app/javascript/dashboard/components/index.js b/app/javascript/dashboard/components/index.js
index 67a678d79..062ae2783 100644
--- a/app/javascript/dashboard/components/index.js
+++ b/app/javascript/dashboard/components/index.js
@@ -3,6 +3,7 @@
import Bar from './widgets/chart/BarChart';
import Code from './Code';
+import ColorPicker from './widgets/ColorPicker';
import DeleteModal from './widgets/modal/DeleteModal.vue';
import LoadingState from './widgets/LoadingState';
import Modal from './Modal';
@@ -17,6 +18,7 @@ import TabsItem from './ui/Tabs/TabsItem';
const WootUIKit = {
Bar,
Code,
+ ColorPicker,
DeleteModal,
LoadingState,
Modal,
diff --git a/app/javascript/dashboard/components/widgets/ColorPicker.vue b/app/javascript/dashboard/components/widgets/ColorPicker.vue
new file mode 100644
index 000000000..00597aa89
--- /dev/null
+++ b/app/javascript/dashboard/components/widgets/ColorPicker.vue
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue
index 990c19a01..b152a6364 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue
@@ -15,7 +15,7 @@
{{ $t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_NAME.LABEL') }}
{{ $t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.WIDGET_COLOR.LABEL') }}
-
+
+
+
+
+
+
+ {{ $t('INBOX_MGMT.SETTINGS_POPUP.AUTO_ASSIGNMENT') }}
+
+
+ {{ $t('INBOX_MGMT.EDIT.AUTO_ASSIGNMENT.ENABLED') }}
+
+
+ {{ $t('INBOX_MGMT.EDIT.AUTO_ASSIGNMENT.DISABLED') }}
+
+
+
+ {{ $t('INBOX_MGMT.SETTINGS_POPUP.AUTO_ASSIGNMENT_SUB_TEXT') }}
+
-
-
- {{ $t('INBOX_MGMT.SETTINGS_POPUP.AUTO_ASSIGNMENT') }}
-
-
- {{ $t('INBOX_MGMT.EDIT.AUTO_ASSIGNMENT.ENABLED') }}
-
-
- {{ $t('INBOX_MGMT.EDIT.AUTO_ASSIGNMENT.DISABLED') }}
-
-
-
- {{ $t('INBOX_MGMT.SETTINGS_POPUP.AUTO_ASSIGNMENT_SUB_TEXT') }}
-
-
-
-
-
+ />
@@ -150,8 +146,7 @@
:button-text="$t('INBOX_MGMT.SETTINGS_POPUP.UPDATE')"
:loading="isAgentListUpdating"
@click="updateAgents"
- >
-
+ />
@@ -196,13 +191,11 @@
/* global bus */
import { mapGetters } from 'vuex';
import { createMessengerScript } from 'dashboard/helper/scriptGenerator';
-import { Compact } from 'vue-color';
import configMixin from 'shared/mixins/configMixin';
import SettingsSection from '../../../../components/SettingsSection';
export default {
components: {
- Compact,
SettingsSection,
},
mixins: [configMixin],
@@ -212,6 +205,7 @@ export default {
autoAssignment: false,
isUpdating: false,
isAgentListUpdating: false,
+ selectedInboxName: '',
channelWebsiteUrl: '',
channelWelcomeTitle: '',
channelWelcomeTagline: '',
@@ -258,6 +252,7 @@ export default {
this.$store.dispatch('agents/get');
this.$store.dispatch('inboxes/get').then(() => {
this.fetchAttachedAgents();
+ this.selectedInboxName = this.inbox.name;
this.autoAssignment = this.inbox.enable_auto_assignment;
this.channelWebsiteUrl = this.inbox.website_url;
this.channelWelcomeTitle = this.inbox.welcome_title;
@@ -303,10 +298,10 @@ export default {
try {
await this.$store.dispatch('inboxes/updateInbox', {
id: this.currentInboxId,
- name: this.inboxName,
+ name: this.selectedInboxName,
enable_auto_assignment: this.autoAssignment,
channel: {
- widget_color: this.getWidgetColor(this.inbox.widget_color),
+ widget_color: this.inbox.widget_color,
website_url: this.channelWebsiteUrl,
welcome_title: this.channelWelcomeTitle,
welcome_tagline: this.channelWelcomeTagline,
@@ -318,11 +313,6 @@ export default {
this.showAlert(this.$t('INBOX_MGMT.EDIT.API.SUCCESS_MESSAGE'));
}
},
- getWidgetColor() {
- return typeof this.inbox.widget_color !== 'object'
- ? this.inbox.widget_color
- : this.inbox.widget_color.hex;
- },
},
validations: {
selectedAgents: {
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Website.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Website.vue
index e6d93ae41..1e955e1d7 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Website.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Website.vue
@@ -12,7 +12,7 @@