Compare commits

..
123 Commits
Author SHA1 Message Date
Sojan e04b37dfcf Merge branch 'release/1.3.0' 2020-04-06 22:58:20 +05:30
99eaf59509 Feature: Ability to set an account name (#667)
* Ability to change the account name 
* Ability to set a language to the account

Addresses: #667  #307  

Co-authored-by: Pranav Raj Sreepuram <pranavrajs@gmail.com>
2020-04-06 22:17:07 +05:30
Pranav Raj SandGitHub a1a81e3799 Feature: Twilio SMS Channel (#658)
Twilio SMS Channel
Fixes :  #350
2020-04-05 22:11:27 +05:30
Sojan JoseandGitHub 8e59564793 Chore: Update ruby version (#665) 2020-04-05 12:11:50 +05:30
cb22b396eb Feature: Website SDK (#653)
Add SDK functions

Co-authored-by: Sojan <sojan@pepalo.com>
2020-04-03 13:04:58 +05:30
7fcd2d0e85 Feature: Support file type messages on widget and dashboard (#659)
- Adds support for file upload

Co-authored-by: Pranav Raj Sreepuram <pranavrajs@gmail.com>
Co-authored-by: Sojan <sojan@pepalo.com>
2020-04-02 12:28:38 +05:30
Sony MathewandGitHub 0afa5c297f Bug: Fixed conversation reply mail issue when an attachment is sent (#655)
* When an attachment was sent in the reply mailer it was breaking due to the
truncating function applied on the last message content. Here the content was empty
and so it broke.
* Fixed this breakage
* Also addressed the issue that if the attachment was there the content was coming empty.
With these changes, made the content to have a link to the attachment.
2020-03-30 16:07:01 +05:30
Nithin David ThomasandGitHub 6c4e1fdaac Feature: Send images from widget 2020-03-30 12:15:06 +05:30
Subin T PandGitHub e56132c506 Non blocking event dispatch (#652)
- Performance improvements for event dispatch
2020-03-29 19:18:30 +05:30
Sojan JoseandGitHub bab9d663d2 Feature: API for updating account settings (#645)
* Feature: API for updating account settings

- API to update account locale
- API to update account name
- API to show account info
2020-03-29 12:16:31 +05:30
Pranav Raj SandGitHub 6c48f2e789 Bug: Update conversation urls in email (#649)
Update the conversation URLs in email templates.
2020-03-29 09:34:57 +05:30
50ebbc13b6 Chore: Add Twitter documentation (#648)
* Chore: Add Twitter documentation

Co-authored-by: Sojan <sojan@pepalo.com>
2020-03-28 11:51:42 +05:30
a3c2d4e5bd Chore: Add Facebook app set up documentation (#647)
Co-authored-by: Pranav Raj S <pranavrajs@gmail.com>
2020-03-28 11:43:02 +05:30
Sony MathewandGitHub 209e0a0fb4 [Feature] Change conversation ordering (#640)
* Update conversation's updated_at when a message is created/saved
* changed teh ordering of conversation to be based on `updated_at` rather than `created_at`
2020-03-24 23:34:22 +05:30
Pranav Raj Sreepuram 2eac97c705 Merge branch 'hotfix/1.2.4' into develop 2020-03-22 20:43:58 +05:30
Pranav Raj Sreepuram bf80a37cfb Merge branch 'hotfix/1.2.4' 2020-03-22 20:41:51 +05:30
Pranav Raj SandGitHub a94f14ca22 Bug: Disable logging to file on asset precompile (#637) 2020-03-22 20:38:49 +05:30
Nithin David ThomasandGitHub f7e5f1fabf Feature: Send attachments to widget user (#621) 2020-03-22 15:54:36 +05:30
Nithin David ThomasandGitHub fe70843fae Feature: Improve Dashboard UI (#630) 2020-03-22 11:44:40 +05:30
Pranav Raj SandGitHub 29bb2bff3a Security: Upgrade eslint, eslint-config-airbnb (#635) 2020-03-21 21:01:55 +05:30
Pranav Raj SandGitHub 4c40d738a5 Security: Upgrade rails to 6.0.2.2 (#636) 2020-03-21 20:54:04 +05:30
Pranav Raj SandGitHub 1c4566df66 Bug: Migrate messages while merging contact (#624)
Bug: Migrate messages while merging contact
2020-03-19 01:52:38 +05:30
Subin T PandGitHub 8f6f07177d Enhancement: Move reporting metrics to postgres (#606) 2020-03-18 16:53:35 +05:30
Sojan f69eb7e542 Merge branch 'hotfix/1.2.3' into develop 2020-03-18 01:53:24 +05:30
Sojan 9d170fc727 Merge branch 'hotfix/1.2.3' 2020-03-18 01:53:13 +05:30
Sojan 8d65e7db95 HotFix: Tune the chatwoot logs
Fixes : #616
2020-03-18 01:40:34 +05:30
Arief LuthfiandGitHub 71d48854b1 Chore: Refactor Facebook send reply service
Refactor Facebook send reply service
2020-03-17 21:09:20 +05:30
Pranav Raj SandGitHub 4d0f46176b Chore: Add web_widget_script in the Inboxes API (#615)
* Chore: Add web_widget_script in the Inboxes API
2020-03-16 12:32:34 +05:30
Nithin David ThomasandGitHub d8599c62dd Feature: Support options message type on widget and dashboard (#614) 2020-03-16 12:08:40 +05:30
ghosteathumanandGitHub bb47b5f086 Reduce long peer dependencies warning (#568)
Co-authored-by: ghosteathuman <46582963+ghosteathuman@users.noreply.github.com>
2020-03-12 22:00:58 +05:30
Nithin David ThomasandGitHub fb17d34b8a Feature: Support card options in widget and dashboard #594 (#609) 2020-03-12 21:23:52 +05:30
Pranav Raj SandGitHub 260e40831a Chore: Update Swagger API docs (#608) 2020-03-11 16:19:13 +05:30
a5b1e2b650 Feature: Access tokens for API access (#604)
Co-authored-by: Pranav Raj Sreepuram <pranavrajs@gmail.com>
2020-03-11 00:02:15 +05:30
19ab0fe108 Chore: Scope URLs with account_id (#601)
* Chore: Enable Users to create multiple accounts

Addresses: #402
- migrations to split roles and other attributes from users table
- make changes in code to accommodate this change

Co-authored-by: Pranav Raj Sreepuram <pranavrajs@gmail.com>
2020-03-09 23:27:10 +05:30
Pranav Raj Sreepuram 2a6670f0da Merge branch 'hotfix/1.2.2' into develop 2020-03-09 10:01:15 +05:30
Pranav Raj Sreepuram 91bccd3c6d Merge branch 'hotfix/1.2.2' 2020-03-09 10:00:42 +05:30
Pranav Raj Sreepuram ef6c695bcb Chore: Add additional_attributes to conversation seed 2020-03-09 10:00:11 +05:30
Pranav Raj Sreepuram 1db35caa56 Merge branch 'release/1.2.1' into develop 2020-03-08 22:15:58 +05:30
Pranav Raj Sreepuram 1273ea64d3 Merge branch 'release/1.2.1' 2020-03-08 22:15:04 +05:30
Pranav Raj SandGitHub 2a4fb7b056 Bug: Fix conversation not loading from the links in email (#602)
Bug: Load conversation from links
2020-03-08 22:08:25 +05:30
60dc564f37 Bug: Fix missing close button on mobile chat window (#600)
Co-authored-by: Pranav Raj Sreepuram <pranavrajs@gmail.com>
2020-03-07 23:39:41 +05:30
8b4df986bf Chore: Enable Users to create multiple accounts (#440)
Addresses: #402
- migrations to split roles and other attributes from users table
- make changes in code to accommodate this change

Co-authored-by: Sojan Jose <sojan@pepalo.com>
Co-authored-by: Pranav Raj Sreepuram <pranavrajs@gmail.com>
2020-03-07 12:18:16 +05:30
Sojan JoseandGitHub a8ac048716 Bug: Fix duplicate messages from Twitter DM and tweets (#599)
* Bug: Fix duplicate messages from Twitter DM and tweets
2020-03-06 01:47:37 +05:30
b2d5cc7b05 Feature: Introduce bots (#545)
Co-authored-by: Pranav Raj S <pranavrajs@gmail.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2020-03-06 01:43:12 +05:30
Sony MathewandGitHub fabc6c87af Chore: User Friendly URLs on the swagger docs (#590)
Made the swagger documentation urls short and readable.
2020-03-04 00:41:21 +05:30
Pranav Raj SandGitHub f76ba889dd Docs: Add webhook documentation (#588) 2020-03-03 18:52:30 +05:30
Pranav Raj SandGitHub 97ad512c21 Security: Upgrade puma to 4.3.3 (#586) 2020-03-03 13:00:54 +05:30
Nithin David ThomasandGitHub dfdb68e1c3 Enhancement: Improve UI on user preferences page (#584) 2020-03-03 12:39:05 +05:30
Sojan c772c0af03 Merge branch 'release/1.2.0' 2020-03-01 19:33:41 +05:30
Sojan JoseandGitHub cda65ea339 Feature: Conversation creation email notifications (#576)
* Clean up the mailers

* Disable assignment mailer if setting is turned off

* Email notifications on conversation create

* Specs
2020-03-01 19:06:13 +05:30
Pranav Raj SandGitHub d6237dfc59 Feature: Add UI to update email notification preferences (#579) 2020-03-01 17:47:08 +05:30
Pranav Raj SandGitHub 629c73d3ce Fix onBackDropClick (#578) 2020-03-01 11:23:55 +05:30
Tim LangeandGitHub bbf01037c6 Chore: Added tests for conversations messages controller (#575) 2020-03-01 04:56:30 +05:30
7f26b34b15 Feature: Add new notification settings for user (#569)
Added new notification settings API for user 

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2020-02-29 20:41:09 +05:30
dependabot[bot]andGitHub bbd9968d4b Chore(deps): Bump puma from 4.3.1 to 4.3.2 (#573)
Bumps [puma](https://github.com/puma/puma) from 4.3.1 to 4.3.2.
- [Release notes](https://github.com/puma/puma/releases)
- [Changelog](https://github.com/puma/puma/blob/master/History.md)
- [Commits](https://github.com/puma/puma/compare/v4.3.1...v4.3.2)

Signed-off-by: dependabot[bot] <support@github.com>
2020-02-29 18:06:27 +05:30
c119c6577b Feature: As a admin, I should be able to add webhooks to account (#572)
Co-authored-by: Pranav Raj S <pranavrajs@gmail.com>
2020-02-29 17:43:49 +05:30
Pranav Raj SandGitHub e8cf59c661 Feature: Alert widget user when message is received from agent (#571) 2020-02-29 11:27:02 +05:30
b05f843790 Chore: Making Account Signup Optional (#563)
Introduce new environment variable that lets you control account signups
ENABLE_ACCOUNT_SIGNUP :( true | false | api_only )
Fixes: #406

Co-authored-by: Pranav Raj S <pranavrajs@gmail.com>
2020-02-29 11:20:33 +05:30
Sony MathewandGitHub 0aad99ff0b Chore: Update the source of json_refs gem (#565) (#566)
At the time of using, `json_refs` gem did not have the ability to recursively resolve the references. So we had to fork this into one of our own and make the required changes. So this fork was used as the source for the gem.
Along with this, we had raised a PR in the original repo so as to push the recursive dereferencing changes upstream. This was merged in the gem's parent repo. So updated the source of this gem to the original parent repo source.
2020-02-27 01:19:26 +05:30
Pranav Raj SandGitHub 0740d4762f Enhancement: Paginate conversation calls in tabs (#560)
* Use conversationPage module for pagination

* Load more conversations

* Reset list if conversation status is changed

* Add specs to conversationPage

* Reset filter when page is re-mounted

* Update text

* Update text
2020-02-26 21:15:01 +05:30
Tim LangeandGitHub e5bc372a29 Chore: Added tests for callbacks_controller (#531)
* Chore: Added tests for callbacks_controller
2020-02-26 18:22:08 +05:30
74f8a35cfc Chore: Environment variable to force ssl (#561)
Fixes: #558

Co-authored-by: Pranav Raj S <pranavrajs@gmail.com>
2020-02-26 11:36:22 +05:30
1f158aa193 Chore(deps): Bump nokogiri from 1.10.7 to 1.10.8 (#564)
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.10.7 to 1.10.8.
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.10.7...v1.10.8)

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

Co-authored-by: Pranav Raj S <pranavrajs@gmail.com>
2020-02-26 09:59:38 +05:30
Subin T PandGitHub 7479b5db43 Feature: Support account/inbox specific webhooks (#562) 2020-02-26 09:44:24 +05:30
Sony MathewandGitHub 4d5e7b4adf Bug: Added Sidekiq to production docker compose (#555) (#557)
* Sidekiq service was missing from the production docker compose file. Added it.
2020-02-25 01:23:29 +05:30
Pranav Raj SandGitHub 55b001435b Bug: Fix yarn.lock discrepancies (#554) 2020-02-24 10:32:35 +05:30
Sony MathewandGitHub fc1fa579e9 Chore: Document Conversation APIs in swagger (#552)
* Added the documentation for the conversation APIs - 8 endpoints
* Added descriptions for the existing endpoints
* Added new resource models and updated some parts of the contact API
2020-02-24 00:51:31 +05:30
Sony MathewandGitHub c7a4550477 Updated Docker documentation
* Removed the links to docker hub
2020-02-23 23:11:11 +05:30
Nithin David ThomasandGitHub fd8e8c7c87 Enhancement: Add close button to the contact sidebar panel (#551) 2020-02-23 21:13:28 +05:30
Nithin David ThomasandGitHub 2d3212575d Enhancement: Hides available in widget interaction (#548) 2020-02-23 15:16:09 +05:30
Sony MathewandGitHub e2688d5176 Chore: Swagger Docs V1 (#546) 2020-02-23 12:54:29 +05:30
Nithin David ThomasandGitHub eea92bd0ac Bug: Add link cursor to widget close button (#544) 2020-02-22 20:20:43 +05:30
Pranav Raj SandGitHub 1261cc7783 Enhancement: Upgrade @babel/preset-env (#542) 2020-02-22 20:16:56 +05:30
a790dc640f Fix: Changed overflow scroll to overflow auto (#523)
Co-authored-by: Nithin David Thomas <webofnithin@gmail.com>
2020-02-22 19:15:45 +05:30
67f4f69e8a Bug: Fix error message when existing agent email is reused for a new agent (#538)
Co-authored-by: Pranav Raj S <pranavrajs@gmail.com>
2020-02-22 19:06:16 +05:30
Pranav Raj SandGitHub 6340eb3ae2 Security: Upgrade @rails/webpacker and jest (#540)
* Security: Upgrade @rails/webpacker and jest

* Upgrade jest from 24.9.0 to 25.1.0
* Upgrade @rails/webpacker from 4.0.7 to 4.2.2

* Update test to use 1 worker
2020-02-22 19:02:11 +05:30
Nithin David ThomasandGitHub 708d2688ff Bug: Fix URL text overflow (#537) 2020-02-22 15:32:15 +05:30
91f8b4a650 Bug: Show helper description for code in finish setup page (#532)
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2020-02-22 14:27:04 +05:30
Nithin David ThomasandGitHub 330bf87f08 Bug : Inbox Filter indicator is removed when a conversation is selected (#535) 2020-02-22 14:24:51 +05:30
Tim LangeandGitHub bc884ae184 Chore: Added tests for dashboard_controller (#529) 2020-02-21 00:43:14 +05:30
Nithin David ThomasandGitHub f7c8358549 Bug: Hide contact panel when inboxId is not present (#530) 2020-02-20 19:48:09 +05:30
Subin T PandGitHub d36e1b52f6 Enhancement: Add event name in webhook payload (#528)
* chore: Remove unwanted twitter gem
* Enhancement: Add event name in webhook payload
fixes #528 #516 #518
2020-02-20 16:19:42 +05:30
Tim LangeandGitHub 813cdd8aec Chore: Added tests for facebook_indicators_controller (#526) 2020-02-20 12:12:19 +05:30
b7e725a7cd Fix: Fixed code font not being displayed on windows (#525)
Co-authored-by: Nithin David Thomas <webofnithin@gmail.com>
2020-02-20 11:11:36 +05:30
9674e0bff4 Bug: Fix misalignment of back button (#524)
Co-authored-by: Nithin David Thomas <webofnithin@gmail.com>
2020-02-20 11:02:12 +05:30
4817f4a28c Bug: Fixed color of agent link in chat widget being white (#522)
Co-authored-by: Nithin David Thomas <webofnithin@gmail.com>
2020-02-20 10:59:43 +05:30
Sony MathewandGitHub 04c62417fa [491] Bug fix - Sidekiq Redis Auth issue (#527)
* When the the .env file has line with REDIS_PASSWORD set as empty, the value for this in the initializers comes as an empty string "".
* Fixed this in a way that, if it's empty string, then it's taken as `nil` value so that password is skipped
2020-02-20 10:52:26 +05:30
Tim LangeandGitHub 30e5edf6dc Feature: Add ability to disable auto assignment of conversations (#513) 2020-02-19 14:40:03 +05:30
Tim LangeandGitHub e0afb84502 Chore: Removed unused functions from home_controller (#512) 2020-02-19 13:51:22 +05:30
fbefb78817 Enhancement: Update the design of inbox listing in sidebar (#511)
Co-authored-by: Pranav Raj S <pranavrajs@gmail.com>
2020-02-19 10:30:34 +05:30
Vishnu NarayananandGitHub f00e1dcdc8 Chore: Update docker setup manual (#504)
* Chore: Update docker setup manual (#503)
2020-02-17 19:52:26 +05:30
Tim LangeandGitHub 60547b9fb3 Chore: Added tests for contacts_controller (#509)
- Tests for contacts controller
Addresses:  #37
2020-02-17 17:08:29 +05:30
Tim LangeandGitHub fc23a10a99 Chore: Added tests for conversations_controller (#508)
* Chore: Added tests for conversations_controller
* Chore: Remove get_messages function from conversations
2020-02-17 15:57:57 +05:30
Tim LangeandGitHub 44a3977f5c Chore: Added tests for inboxes_controller (#507) 2020-02-17 15:39:24 +05:30
Tim LangeandGitHub a2316d4d09 Chore: Added tests for subscriptions_controller (#483)
Added tests for subscriptions_controller
ref: #37
2020-02-17 07:51:45 +05:30
Pranav Raj SandGitHub c32d391261 Bug: Fix imports in ProfileSettings (#506) 2020-02-16 22:20:59 +05:30
Pranav Raj SandGitHub c4e2a84f65 Feature: Agent Profile Update with avatar (#449)
* Feature: Agent Profile Update with avatar
* Add Update Profile with name, avatar, email and password
2020-02-16 17:20:38 +05:30
e61ba95cf7 Feature: Add/Edit conversation labels (#488)
Co-authored-by: Pranav Raj S <pranavrajs@gmail.com>
2020-02-16 15:46:26 +05:30
Sojan JoseandGitHub 77473dc2aa Chore: Include avatar url in sign_in response (#501)
- include avatar url in sign_in response ( fixes #500 )
- fix circle ci builds
2020-02-15 22:57:48 +05:30
Subin T PandGitHub eb5185974a Monitoring: Add Scout APM (#487)
* Add scout APM
2020-02-15 04:22:36 +05:30
Subin T PandGitHub 919261d843 Feature: Webhooks (#489) 2020-02-14 23:19:17 +05:30
sony-mathew 79a847aeab Merge branch 'hotfix/1.1.3' into develop - Bug Fix : Adjusted the connection pool size and concurrency for Sidekiq 2020-02-11 22:59:07 +05:30
sony-mathew baf1a6e8da Merge branch 'hotfix/1.1.3' into develop - Bug Fix : Increased the connection pool size and concurrency for Sidekiq 2020-02-11 21:52:37 +05:30
Sojan 2023d5d42b Revert "Updates sidebar on dashboard"
This reverts commit 6186d14593.
2020-02-11 19:34:42 +05:30
Nithin David 6186d14593 Updates sidebar on dashboard 2020-02-11 19:21:55 +05:30
30f4c08143 Feature: SignIn with Twitter (#479)
* Add Twitter SignIn flow

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2020-02-11 14:27:38 +05:30
Pranav Raj SandGitHub 272c481464 Feature: Create conversations from Tweets (#470)
* Feature: Add tweets to conversations
2020-02-09 15:47:48 +05:30
Tim LangeandGitHub 3465ebefd1 Chore: Added tests for assignments_controller (#482) 2020-02-08 23:48:00 +05:30
Tim LangeandGitHub b9836309fb Chore: Added tests for agents_controller (#481) 2020-02-08 23:05:41 +05:30
Tim LangeandGitHub 6c46891ee1 Chore: Remove unnessecary check if object exists (#480) 2020-02-08 22:38:37 +05:30
Tim LangeandGitHub 67e41092e6 Chore: Added tests for inbox_members (#478) 2020-02-08 13:21:41 +05:30
Tim LangeandGitHub 1d269f4e8d Chore: Added tests for canned_responses (#477) 2020-02-08 11:58:40 +05:30
Tim LangeandGitHub 37289fbd63 Chore: Increase test coverage for conversations controller (#476) 2020-02-07 21:43:51 +05:30
Tim LangeandGitHub a30a8867a3 Chore: Increase test coverage for accounts controller (#475) 2020-02-07 16:41:49 +05:30
Tim LangeandGitHub 5210d201b6 Chore: Increase test coverage for widgets controller (#473) 2020-02-07 13:14:07 +05:30
Vishnu NarayananandGitHub 3eb5b3a0c1 docs: Update contribution guidelines (#471) (#474) 2020-02-07 12:05:09 +05:30
Tim LangeandGitHub 3fa7c605c3 Chore: Increase test coverage for widget messages (#472) 2020-02-07 00:51:47 +05:30
Pranav Raj SandGitHub 7b1fdc5fcd BugFix: Filter duplicate DMs in Twitter Integration (#468)
BugFix: Filter duplicate DMs in Twitter Integration
2020-02-05 18:50:37 +05:30
Pranav Raj SandGitHub 607fc25723 Bugfix: Fix copy in agent availability status (#466)
Copy update for agent status
2020-02-05 12:11:33 +05:45
83b0bb4062 Feature: As an end-user, I should be able to see the list of agents in the widget. (#461)
Co-authored-by: Pranav Raj S <pranavrajs@gmail.com>
2020-02-05 11:27:22 +05:30
33e0bd434b Chore: Update the copy used in signup page (#464)
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2020-02-03 20:12:37 +05:45
Sojan 002463aede Merge branch 'hotfix/1.1.2' into develop 2020-02-03 14:33:53 +05:45
Sojan 8d6f9cad05 Merge branch 'hotfix/1.1.1' into develop 2020-02-03 14:14:39 +05:45
605 changed files with 18882 additions and 4824 deletions
+8 -8
View File
@@ -7,13 +7,13 @@ defaults: &defaults
working_directory: ~/build
docker:
# specify the version you desire here
- image: circleci/ruby:2.6.5-node-browsers
- image: circleci/ruby:2.7.0-node-browsers
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
# documented at https://circleci.com/docs/2.0/circleci-images/
- image: circleci/postgres:9.4
- image: circleci/redis:5.0.7-alpine
- image: circleci/postgres:alpine
- image: circleci/redis:alpine
environment:
- CC_TEST_REPORTER_ID: b1b5c4447bf93f6f0b06a64756e35afd0810ea83649f03971cbf303b4449456f
@@ -95,24 +95,24 @@ jobs:
command: yarn run eslint
# Run rails tests
- run:
- run:
name: Run backend tests
command: |
bundle exec rspec $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)
./tmp/cc-test-reporter format-coverage -t simplecov -o tmp/codeclimate.backend.json coverage/backend/.resultset.json
- persist_to_workspace:
root: tmp
paths:
paths:
- codeclimate.backend.json
- run:
- run:
name: Run frontend tests
command: |
yarn test:coverage
./tmp/cc-test-reporter format-coverage -t lcov -o tmp/codeclimate.frontend.json buildreports/lcov.info
- persist_to_workspace:
root: tmp
paths:
paths:
- codeclimate.frontend.json
# collect reports
@@ -126,4 +126,4 @@ jobs:
name: Upload coverage results to Code Climate
command: |
./tmp/cc-test-reporter sum-coverage tmp/codeclimate.*.json -p 2 -o tmp/codeclimate.total.json
./tmp/cc-test-reporter upload-coverage -i tmp/codeclimate.total.json
./tmp/cc-test-reporter upload-coverage -i tmp/codeclimate.total.json
+4
View File
@@ -26,3 +26,7 @@ exclude_patterns:
- "node_modules/**/*"
- "lib/tasks/auto_annotate_models.rake"
- "app/test-matchers.js"
- "docs/*"
- "**/*.md"
- "**/*.yml"
- "app/javascript/dashboard/i18n/locale"
+26 -11
View File
@@ -1,4 +1,12 @@
SECRET_KEY_BASE=
# Force all access to the app over SSL, default is set to false
FORCE_SSL=
# This lets you control new sign ups on your chatwoot installation
# true : default option, allows sign ups
# false : disables all the end points related to sign ups
# api_only: disables the UI for signup, but you can create sign ups via the account apis
ENABLE_ACCOUNT_SIGNUP=
#redis config
REDIS_URL=redis://redis:6379
@@ -14,20 +22,11 @@ POSTGRES_PASSWORD=
RAILS_ENV=development
RAILS_MAX_THREADS=5
#fb app
FB_VERIFY_TOKEN=
FB_APP_SECRET=
FB_APP_ID=
#twitter app
TWITTER_CONSUMER_KEY=
TWITTER_CONSUMER_SECRET=
#mail
MAILER_SENDER_EMAIL=accounts@chatwoot.com
SMTP_PORT=1025
SMTP_DOMAIN=chatwoot.com
# if you are running docker-compose, set SMTP_ADDRESS value as "mailhog",
# if you are running docker-compose, set SMTP_ADDRESS value as "mailhog",
# else set the value as "localhost"
SMTP_ADDRESS=mailhog
SMTP_USERNAME=
@@ -48,10 +47,26 @@ AWS_REGION=
#sentry
SENTRY_DSN=
#Log settings
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=
FB_APP_SECRET=
FB_APP_ID=
#twitter
TWITTER_APP_ID=
TWITTER_CONSUMER_KEY=
TWITTER_CONSUMER_SECRET=
TWITTER_ENVIRONMENT=
#### This environment variables are only required in hosted version which has billing
ENABLE_BILLING=
@@ -59,4 +74,4 @@ ENABLE_BILLING=
CHARGEBEE_API_KEY=
CHARGEBEE_SITE=
CHARGEBEE_WEBHOOK_USERNAME=
CHARGEBEE_WEBHOOK_PASSWORD=
CHARGEBEE_WEBHOOK_PASSWORD=
+4 -2
View File
@@ -24,10 +24,12 @@ module.exports = {
'multiline': {
'max': 1,
'allowFirstLine': false
}
},
}],
'vue/html-self-closing': 'off',
"vue/no-v-html": 'off'
"vue/no-v-html": 'off',
'import/extensions': ['off']
},
settings: {
'import/resolver': {
+7 -1
View File
@@ -37,6 +37,10 @@ public/packs*
*.swo
*.un~
.jest-cache
#VS Code files
.vscode
# ignore jetbrains IDE files
.idea
@@ -48,4 +52,6 @@ coverage
# ignore packages
node_modules
package-lock.json
package-lock.json
*.dump
+1
View File
@@ -0,0 +1 @@
12.16.1
+56 -12
View File
@@ -20,14 +20,12 @@ Style/GlobalVars:
Exclude:
- 'config/initializers/redis.rb'
- 'lib/redis/alfred.rb'
- 'app/controllers/api/v1/webhooks_controller.rb'
- 'app/services/twitter/send_reply_service.rb'
- 'spec/services/twitter/send_reply_service_spec.rb'
Metrics/BlockLength:
Exclude:
- spec/**/*
- '**/routes.rb'
- 'config/environments/*'
- db/schema.rb
Rails/ApplicationController:
Exclude:
- 'app/controllers/api/v1/widget/messages_controller.rb'
@@ -41,14 +39,60 @@ Style/ClassAndModuleChildren:
RSpec/NestedGroups:
Enabled: true
Max: 4
RSpec/MessageSpies:
Enabled: false
Metrics/MethodLength:
Exclude:
- 'db/migrate/20161123131628_devise_token_auth_create_users.rb'
Rails/CreateTableWithTimestamps:
Exclude:
- 'db/migrate/20170207092002_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb'
Style/GuardClause:
Exclude:
- 'app/builders/account_builder.rb'
- 'app/models/attachment.rb'
- 'app/models/message.rb'
- 'lib/webhooks/chargebee.rb'
- 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb'
Metrics/AbcSize:
Exclude:
- 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb'
Metrics/CyclomaticComplexity:
Exclude:
- 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb'
Rails/ReversibleMigration:
Exclude:
- 'db/migrate/20161025070152_removechannelsfrommodels.rb'
- 'db/migrate/20161025070645_remchannel.rb'
- 'db/migrate/20161025070645_remchannel.rb'
- 'db/migrate/20161110102609_removeinboxid.rb'
- 'db/migrate/20170519091539_add_avatar_to_fb.rb'
- 'db/migrate/20191020085608_rename_old_tables.rb'
- 'db/migrate/20191126185833_update_user_invite_foreign_key.rb'
- 'db/migrate/20191130164019_add_template_type_to_messages.rb'
Rails/BulkChangeTable:
Exclude:
- 'db/migrate/20161025070152_removechannelsfrommodels.rb'
- 'db/migrate/20200121190901_create_account_users.rb'
- 'db/migrate/20170211092540_notnullableusers.rb'
- 'db/migrate/20170403095203_contactadder.rb'
- 'db/migrate/20170406104018_add_default_status_conv.rb'
- 'db/migrate/20170511134418_latlong.rb'
- 'db/migrate/20191027054756_create_contact_inboxes.rb'
- 'db/migrate/20191130164019_add_template_type_to_messages.rb'
Rails/UniqueValidationWithoutIndex:
Exclude:
- 'app/models/channel/twitter_profile.rb'
- 'app/models/webhook.rb'
AllCops:
Exclude:
- db/*
- bin/**/*
- db/**/*
- config/**/*
- public/**/*
- vendor/**/*
- node_modules/**/*
- lib/tasks/auto_annotate_models.rake
- config/environments/**/*
- 'bin/**/*'
- 'db/schema.rb'
- 'config/**/*'
- 'public/**/*'
- 'vendor/**/*'
- 'node_modules/**/*'
- 'lib/tasks/auto_annotate_models.rake'
- 'config/environments/**/*'
- 'tmp/**/*'
- 'storage/**/*'
-9
View File
@@ -282,15 +282,6 @@ Style/GlobalVars:
Exclude:
- 'lib/redis/alfred.rb'
# Offense count: 7
# Configuration parameters: MinBodyLength.
Style/GuardClause:
Exclude:
- 'app/builders/account_builder.rb'
- 'app/models/attachment.rb'
- 'app/models/message.rb'
- 'lib/webhooks/chargebee.rb'
# Offense count: 4
Style/IdenticalConditionalBranches:
Exclude:
+1 -1
View File
@@ -1 +1 @@
2.6.5
2.7.0
+274
View File
@@ -1,7 +1,281 @@
# Default application configuration that all configurations inherit from.
scss_files: '**/*.scss'
plugin_directories: ['.scss-linters']
# List of gem names to load custom linters from (make sure they are already
# installed)
plugin_gems: []
# Default severity of all linters.
severity: warning
linters:
BangFormat:
enabled: true
space_before_bang: true
space_after_bang: false
BemDepth:
enabled: false
max_elements: 1
BorderZero:
enabled: true
convention: zero # or `none`
ChainedClasses:
enabled: false
ColorKeyword:
enabled: true
ColorVariable:
enabled: true
Comment:
enabled: true
style: silent
DebugStatement:
enabled: true
DeclarationOrder:
enabled: true
DisableLinterReason:
enabled: false
DuplicateProperty:
enabled: true
ElsePlacement:
enabled: true
style: same_line # or 'new_line'
EmptyLineBetweenBlocks:
enabled: true
ignore_single_line_blocks: true
EmptyRule:
enabled: true
ExtendDirective:
enabled: false
FinalNewline:
enabled: true
present: true
HexLength:
enabled: true
style: short # or 'long'
HexNotation:
enabled: true
style: lowercase # or 'uppercase'
HexValidation:
enabled: true
IdSelector:
enabled: true
ImportantRule:
enabled: false
ImportPath:
enabled: true
leading_underscore: false
filename_extension: false
Indentation:
enabled: true
allow_non_nested_indentation: false
character: space # or 'tab'
width: 2
LeadingZero:
enabled: false
MergeableSelector:
enabled: true
force_nesting: true
NameFormat:
enabled: true
allow_leading_underscore: true
convention: hyphenated_lowercase # or 'camel_case', or 'snake_case', or a regex pattern
NestingDepth:
enabled: true
max_depth: 6
ignore_parent_selectors: false
PlaceholderInExtend:
enabled: true
PrivateNamingConvention:
enabled: false
prefix: _
PropertyCount:
enabled: false
include_nested: false
max_properties: 10
PropertySortOrder:
enabled: true
ignore_unspecified: false
min_properties: 2
separate_groups: false
PropertySpelling:
enabled: true
extra_properties: []
disabled_properties: []
PropertyUnits:
enabled: true
global: [
'ch',
'em',
'ex',
'rem', # Font-relative lengths
'cm',
'in',
'mm',
'pc',
'pt',
'px',
'q', # Absolute lengths
'vh',
'vw',
'vmin',
'vmax', # Viewport-percentage lengths
'fr', # Grid fractional lengths
'deg',
'grad',
'rad',
'turn', # Angle
'ms',
's', # Duration
'Hz',
'kHz', # Frequency
'dpi',
'dpcm',
'dppx', # Resolution
'%',
] # Other
properties: {}
PseudoElement:
enabled: true
QualifyingElement:
enabled: true
allow_element_with_attribute: false
allow_element_with_class: false
allow_element_with_id: false
SelectorDepth:
enabled: true
max_depth: 5
SelectorFormat:
enabled: false
Shorthand:
enabled: true
allowed_shorthands: [1, 2, 3, 4]
SingleLinePerProperty:
enabled: true
allow_single_line_rule_sets: true
SingleLinePerSelector:
enabled: true
SpaceAfterComma:
enabled: true
style: one_space # or 'no_space', or 'at_least_one_space'
SpaceAfterComment:
enabled: false
style: one_space # or 'no_space', or 'at_least_one_space'
allow_empty_comments: true
SpaceAfterPropertyColon:
enabled: true
style: one_space # or 'no_space', or 'at_least_one_space', or 'aligned'
SpaceAfterPropertyName:
enabled: true
SpaceAfterVariableColon:
enabled: false
style: one_space # or 'no_space', 'at_least_one_space' or 'one_space_or_newline'
SpaceAfterVariableName:
enabled: true
SpaceAroundOperator:
enabled: true
style: one_space # or 'at_least_one_space', or 'no_space'
SpaceBeforeBrace:
enabled: true
style: space # or 'new_line'
allow_single_line_padding: false
SpaceBetweenParens:
enabled: true
spaces: 0
StringQuotes:
enabled: true
style: single_quotes # or double_quotes
TrailingSemicolon:
enabled: true
TrailingWhitespace:
enabled: true
TrailingZero:
enabled: false
TransitionAll:
enabled: false
UnnecessaryMantissa:
enabled: false
UnnecessaryParentReference:
enabled: true
UrlFormat:
enabled: true
UrlQuotes:
enabled: true
VariableForProperty:
enabled: false
properties: []
VendorPrefix:
enabled: true
identifier_list: base
additional_identifiers: []
excluded_identifiers: []
ZeroUnit:
enabled: true
Compass::*:
enabled: false
exclude:
- 'app/javascript/widget/assets/scss/_reset.scss'
- 'app/javascript/widget/assets/scss/sdk.css'
+14 -5
View File
@@ -1,6 +1,6 @@
source 'https://rubygems.org'
ruby '2.6.5'
ruby '2.7.0'
##-- base gems for rails --##
gem 'rack-cors', require: 'rack/cors'
@@ -16,6 +16,7 @@ gem 'hashie'
gem 'jbuilder'
gem 'kaminari'
gem 'responders'
gem 'rest-client'
gem 'time_diff'
gem 'tzinfo-data'
gem 'valid_email2'
@@ -24,11 +25,12 @@ gem 'uglifier'
##-- for active storage --##
gem 'aws-sdk-s3', require: false
gem 'azure-storage', require: false
gem 'azure-storage-blob', require: false
gem 'google-cloud-storage', require: false
gem 'mini_magick'
##-- gems for database --#
gem 'groupdate'
gem 'pg'
gem 'redis'
gem 'redis-namespace'
@@ -60,10 +62,9 @@ gem 'chargebee'
##--- gems for channels ---##
gem 'facebook-messenger'
gem 'telegram-bot-ruby'
gem 'twitter'
gem 'twilio-ruby', '~> 5.32.0'
# twitty will handle subscription of twitter account events
gem 'twitty', git: 'https://github.com/chatwoot/twitty'
# facebook client
gem 'koala'
# Random name generator
@@ -72,19 +73,27 @@ gem 'haikunator'
##--- gems for debugging and error reporting ---##
# static analysis
gem 'brakeman'
gem 'scout_apm'
gem 'sentry-raven'
##-- background job processing --##
gem 'sidekiq'
##-- used for single column multiple binary flags in notification settings/feature flagging --##
gem 'flag_shih_tzu'
group :development do
gem 'annotate'
gem 'bullet'
gem 'letter_opener'
gem 'web-console'
# used in swagger build
gem 'json_refs', git: 'https://github.com/tzmfreedom/json_refs', ref: 'e32deb0'
end
group :development, :test do
# locking until https://github.com/codeclimate/test-reporter/issues/418 is resolved
gem 'action-cable-testing'
gem 'bundle-audit', require: false
gem 'byebug', platform: :mri
@@ -98,9 +107,9 @@ group :development, :test do
gem 'rubocop-performance', require: false
gem 'rubocop-rails', require: false
gem 'rubocop-rspec', require: false
gem 'scss_lint', require: false
gem 'seed_dump'
gem 'shoulda-matchers'
# locking until https://github.com/codeclimate/test-reporter/issues/418 is resolved
gem 'simplecov', '0.17.1', require: false
gem 'spring'
gem 'spring-watcher-listen'
+161 -150
View File
@@ -1,65 +1,73 @@
GIT
remote: https://github.com/chatwoot/twitty
revision: 58b4958d7f4a58eec8fe9543caedb232308253f6
revision: af4f3e45dca55e42c64f7741a1fedfaa94d36419
specs:
twitty (0.1.0)
oauth
GIT
remote: https://github.com/tzmfreedom/json_refs
revision: e32deb073ce9aef39bdd63556bffd7fe7c2a803d
ref: e32deb0
specs:
json_refs (0.1.2)
hana
GEM
remote: https://rubygems.org/
specs:
action-cable-testing (0.6.0)
action-cable-testing (0.6.1)
actioncable (>= 5.0)
actioncable (6.0.2.1)
actionpack (= 6.0.2.1)
actioncable (6.0.2.2)
actionpack (= 6.0.2.2)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (6.0.2.1)
actionpack (= 6.0.2.1)
activejob (= 6.0.2.1)
activerecord (= 6.0.2.1)
activestorage (= 6.0.2.1)
activesupport (= 6.0.2.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)
mail (>= 2.7.1)
actionmailer (6.0.2.1)
actionpack (= 6.0.2.1)
actionview (= 6.0.2.1)
activejob (= 6.0.2.1)
actionmailer (6.0.2.2)
actionpack (= 6.0.2.2)
actionview (= 6.0.2.2)
activejob (= 6.0.2.2)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (6.0.2.1)
actionview (= 6.0.2.1)
activesupport (= 6.0.2.1)
actionpack (6.0.2.2)
actionview (= 6.0.2.2)
activesupport (= 6.0.2.2)
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.1)
actionpack (= 6.0.2.1)
activerecord (= 6.0.2.1)
activestorage (= 6.0.2.1)
activesupport (= 6.0.2.1)
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)
nokogiri (>= 1.8.5)
actionview (6.0.2.1)
activesupport (= 6.0.2.1)
actionview (6.0.2.2)
activesupport (= 6.0.2.2)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
activejob (6.0.2.1)
activesupport (= 6.0.2.1)
activejob (6.0.2.2)
activesupport (= 6.0.2.2)
globalid (>= 0.3.6)
activemodel (6.0.2.1)
activesupport (= 6.0.2.1)
activerecord (6.0.2.1)
activemodel (= 6.0.2.1)
activesupport (= 6.0.2.1)
activestorage (6.0.2.1)
actionpack (= 6.0.2.1)
activejob (= 6.0.2.1)
activerecord (= 6.0.2.1)
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)
marcel (~> 0.3.1)
activesupport (6.0.2.1)
activesupport (6.0.2.2)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
@@ -69,47 +77,44 @@ GEM
activerecord (>= 5.0, < 6.1)
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
annotate (3.0.3)
annotate (3.1.1)
activerecord (>= 3.2, < 7.0)
rake (>= 10.4, < 14.0)
ast (2.4.0)
attr_extras (6.2.3)
aws-eventstream (1.0.3)
aws-partitions (1.269.0)
aws-sdk-core (3.89.1)
aws-partitions (1.294.0)
aws-sdk-core (3.92.0)
aws-eventstream (~> 1.0, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-kms (1.28.0)
aws-sdk-kms (1.30.0)
aws-sdk-core (~> 3, >= 3.71.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.60.1)
aws-sdk-s3 (1.61.2)
aws-sdk-core (~> 3, >= 3.83.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
aws-sigv4 (1.1.0)
aws-sigv4 (1.1.1)
aws-eventstream (~> 1.0, >= 1.0.2)
axiom-types (0.1.1)
descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1)
azure-core (0.1.15)
faraday (~> 0.9)
faraday_middleware (~> 0.10)
nokogiri (~> 1.6)
azure-storage (0.15.0.preview)
azure-core (~> 0.1)
faraday (~> 0.9)
faraday_middleware (~> 0.10)
nokogiri (~> 1.6, >= 1.6.8)
azure-storage-blob (2.0.0)
azure-storage-common (~> 2.0)
nokogiri (~> 1.10.4)
azure-storage-common (2.0.1)
faraday (~> 1.0)
faraday_middleware (~> 1.0.0.rc1)
nokogiri (~> 1.10.4)
bcrypt (3.1.13)
bindex (0.8.1)
bootsnap (1.4.5)
bootsnap (1.4.6)
msgpack (~> 1.0)
brakeman (4.7.2)
browser (3.0.3)
buftok (0.2.0)
brakeman (4.8.0)
browser (4.0.0)
builder (3.2.4)
bullet (6.1.0)
activesupport (>= 3.0.0)
@@ -120,13 +125,13 @@ GEM
bundler (>= 1.2.0, < 3)
thor (~> 0.18)
byebug (11.1.1)
chargebee (2.7.3)
chargebee (2.7.5)
json_pure (~> 2.1)
rest-client (>= 1.8, < 3.0)
coderay (1.1.2)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
concurrent-ruby (1.1.5)
concurrent-ruby (1.1.6)
connection_pool (2.2.2)
crass (1.0.6)
declarative (0.0.10)
@@ -144,7 +149,7 @@ GEM
devise (> 3.5.2, < 5)
rails (>= 4.2.0, < 6.1)
diff-lcs (1.3)
digest-crc (0.4.1)
digest-crc (0.5.1)
docile (1.3.2)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
@@ -158,22 +163,23 @@ GEM
facebook-messenger (1.4.1)
httparty (~> 0.13, >= 0.13.7)
rack (>= 1.4.5)
factory_bot (5.1.1)
factory_bot (5.1.2)
activesupport (>= 4.2.0)
factory_bot_rails (5.1.1)
factory_bot (~> 5.1.0)
railties (>= 4.2.0)
faker (2.10.1)
faker (2.11.0)
i18n (>= 1.6, < 2)
faraday (0.17.3)
faraday (1.0.1)
multipart-post (>= 1.2, < 3)
faraday_middleware (0.14.0)
faraday (>= 0.7.4, < 1.0)
faraday_middleware (1.0.0)
faraday (~> 1.0)
ffi (1.12.2)
foreman (0.87.0)
flag_shih_tzu (0.3.23)
foreman (0.87.1)
globalid (0.4.2)
activesupport (>= 4.2.0)
google-api-client (0.36.4)
google-api-client (0.37.2)
addressable (~> 2.5, >= 2.5.1)
googleauth (~> 0.9)
httpclient (>= 2.8.1, < 3.0)
@@ -184,8 +190,8 @@ GEM
google-cloud-core (1.5.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.3.0)
faraday (~> 0.11)
google-cloud-env (1.3.1)
faraday (>= 0.17.3, < 2.0)
google-cloud-errors (1.0.0)
google-cloud-storage (1.25.1)
addressable (~> 2.5)
@@ -194,26 +200,22 @@ GEM
google-cloud-core (~> 1.2)
googleauth (~> 0.9)
mini_mime (~> 1.0)
googleauth (0.10.0)
faraday (~> 0.12)
googleauth (0.11.0)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (~> 0.12)
groupdate (5.0.0)
activesupport (>= 5)
haikunator (1.1.0)
hana (1.3.5)
hashie (4.1.0)
http (3.3.0)
addressable (~> 2.3)
http-cookie (~> 1.0)
http-form_data (~> 2.0)
http_parser.rb (~> 0.6.0)
http-accept (1.7.0)
http-cookie (1.0.3)
domain_name (~> 0.5)
http-form_data (2.2.0)
http_parser.rb (0.6.0)
httparty (0.17.3)
httparty (0.18.0)
mime-types (~> 3.0)
multi_xml (>= 0.5.2)
httpclient (2.8.3)
@@ -222,11 +224,11 @@ GEM
ice_nine (0.11.2)
inflecto (0.0.2)
jaro_winkler (1.5.4)
jbuilder (2.9.1)
activesupport (>= 4.2.0)
jbuilder (2.10.0)
activesupport (>= 5.0.0)
jmespath (1.4.0)
json (2.3.0)
json_pure (2.2.0)
json_pure (2.3.0)
jwt (2.2.1)
kaminari (1.2.0)
activesupport (>= 4.1.0)
@@ -244,8 +246,8 @@ GEM
addressable
faraday
json (>= 1.8)
launchy (2.4.3)
addressable (~> 2.3)
launchy (2.5.0)
addressable (~> 2.7)
letter_opener (1.7.0)
launchy (~> 2.2)
listen (3.2.1)
@@ -259,9 +261,7 @@ GEM
marcel (0.3.3)
mimemagic (~> 0.3.2)
memoist (0.16.2)
memoizable (0.4.2)
thread_safe (~> 0.3, >= 0.3.1)
method_source (0.9.2)
method_source (1.0.0)
mime-types (3.3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2019.1009)
@@ -271,34 +271,33 @@ GEM
mini_portile2 (2.4.0)
minitest (5.14.0)
mock_redis (0.22.0)
msgpack (1.3.1)
msgpack (1.3.3)
multi_json (1.14.1)
multi_xml (0.6.0)
multipart-post (2.1.1)
naught (1.1.0)
netrc (0.11.0)
nightfury (1.0.1)
nio4r (2.5.2)
nokogiri (1.10.7)
nokogiri (1.10.9)
mini_portile2 (~> 2.4.0)
oauth (0.5.4)
orm_adapter (0.5.0)
os (1.0.1)
os (1.1.0)
parallel (1.19.1)
parser (2.7.0.2)
parser (2.7.1.0)
ast (~> 2.4.0)
pg (1.2.2)
pry (0.12.2)
coderay (~> 1.1.0)
method_source (~> 0.9.0)
pg (1.2.3)
pry (0.13.0)
coderay (~> 1.1)
method_source (~> 1.0)
pry-rails (0.3.9)
pry (>= 0.10.4)
public_suffix (4.0.3)
puma (4.3.1)
puma (4.3.3)
nio4r (~> 2.0)
pundit (2.1.0)
activesupport (>= 3.0.0)
rack (2.1.2)
rack (2.2.2)
rack-cache (1.11.0)
rack (>= 0.4)
rack-cors (1.1.1)
@@ -309,29 +308,29 @@ GEM
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails (6.0.2.1)
actioncable (= 6.0.2.1)
actionmailbox (= 6.0.2.1)
actionmailer (= 6.0.2.1)
actionpack (= 6.0.2.1)
actiontext (= 6.0.2.1)
actionview (= 6.0.2.1)
activejob (= 6.0.2.1)
activemodel (= 6.0.2.1)
activerecord (= 6.0.2.1)
activestorage (= 6.0.2.1)
activesupport (= 6.0.2.1)
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)
bundler (>= 1.3.0)
railties (= 6.0.2.1)
railties (= 6.0.2.2)
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.1)
actionpack (= 6.0.2.1)
activesupport (= 6.0.2.1)
railties (6.0.2.2)
actionpack (= 6.0.2.2)
activesupport (= 6.0.2.2)
method_source
rake (>= 0.8.7)
thor (>= 0.20.3, < 2.0)
@@ -346,7 +345,7 @@ GEM
redis-rack-cache (2.2.1)
rack-cache (>= 1.10, < 2)
redis-store (>= 1.6, < 2)
redis-store (1.8.1)
redis-store (1.8.2)
redis (>= 4, < 5)
representable (3.0.4)
declarative (< 0.1.0)
@@ -361,15 +360,16 @@ GEM
mime-types (>= 1.16, < 4.0)
netrc (~> 0.8)
retriable (3.1.2)
rexml (3.2.4)
rspec-core (3.9.1)
rspec-support (~> 3.9.1)
rspec-expectations (3.9.0)
rspec-expectations (3.9.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.9.0)
rspec-mocks (3.9.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.9.0)
rspec-rails (4.0.0.beta4)
rspec-rails (4.0.0)
actionpack (>= 4.2)
activesupport (>= 4.2)
railties (>= 4.2)
@@ -378,39 +378,50 @@ GEM
rspec-mocks (~> 3.9)
rspec-support (~> 3.9)
rspec-support (3.9.2)
rubocop (0.79.0)
rubocop (0.81.0)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
parser (>= 2.7.0.1)
rainbow (>= 2.2.2, < 4.0)
rexml
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 1.7)
unicode-display_width (>= 1.4.0, < 2.0)
rubocop-performance (1.5.2)
rubocop (>= 0.71.0)
rubocop-rails (2.4.2)
rubocop-rails (2.5.1)
activesupport
rack (>= 1.1)
rubocop (>= 0.72.0)
rubocop-rspec (1.37.1)
rubocop-rspec (1.38.1)
rubocop (>= 0.68.1)
ruby-progressbar (1.10.1)
sass (3.7.4)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
scout_apm (2.6.7)
parser
scss_lint (0.59.0)
sass (~> 3.5, >= 3.5.5)
seed_dump (3.3.1)
activerecord (>= 4)
activesupport (>= 4)
sentry-raven (2.13.0)
faraday (>= 0.7.6, < 1.0)
shoulda-matchers (4.2.0)
semantic_range (2.3.0)
sentry-raven (3.0.0)
faraday (>= 1.0)
shoulda-matchers (4.3.0)
activesupport (>= 4.2.0)
sidekiq (6.0.4)
sidekiq (6.0.6)
connection_pool (>= 2.2.2)
rack (>= 2.0.0)
rack (~> 2.0)
rack-protection (>= 2.0.0)
redis (>= 4.1.0)
signet (0.12.0)
signet (0.14.0)
addressable (~> 2.3)
faraday (~> 0.9)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simple_oauth (0.3.1)
simplecov (0.17.1)
docile (~> 1.1)
json (>= 1.8, < 3)
@@ -436,18 +447,11 @@ GEM
time_diff (0.3.0)
activesupport
i18n
twitter (6.2.0)
addressable (~> 2.3)
buftok (~> 0.2.0)
equalizer (~> 0.0.11)
http (~> 3.0)
http-form_data (~> 2.0)
http_parser.rb (~> 0.6.0)
memoizable (~> 0.4.0)
multipart-post (~> 2.0)
naught (~> 1.0)
simple_oauth (~> 0.3.0)
tzinfo (1.2.6)
twilio-ruby (5.32.0)
faraday (~> 1.0.0)
jwt (>= 1.5, <= 2.5)
nokogiri (>= 1.6, < 2.0)
tzinfo (1.2.7)
thread_safe (~> 0.1)
tzinfo-data (1.2019.3)
tzinfo (>= 1.0.0)
@@ -456,10 +460,10 @@ GEM
execjs (>= 0.3.0, < 3)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.6)
unicode-display_width (1.6.1)
unf_ext (0.0.7.7)
unicode-display_width (1.7.0)
uniform_notifier (1.13.0)
valid_email2 (3.1.3)
valid_email2 (3.2.1)
activemodel (>= 3.2)
mail (~> 2.5)
virtus (1.0.5)
@@ -474,15 +478,16 @@ GEM
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
railties (>= 6.0.0)
webpacker (4.2.2)
activesupport (>= 4.2)
webpacker (5.0.1)
activesupport (>= 5.2)
rack-proxy (>= 0.6.1)
railties (>= 4.2)
railties (>= 5.2)
semantic_range (>= 2.3.0)
websocket-driver (0.7.1)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.4)
wisper (2.0.0)
zeitwerk (2.2.2)
zeitwerk (2.3.0)
PLATFORMS
ruby
@@ -493,7 +498,7 @@ DEPENDENCIES
annotate
attr_extras
aws-sdk-s3
azure-storage
azure-storage-blob
bootsnap
brakeman
browser
@@ -507,11 +512,14 @@ DEPENDENCIES
facebook-messenger
factory_bot_rails
faker
flag_shih_tzu
foreman
google-cloud-storage
groupdate
haikunator
hashie
jbuilder
json_refs!
jwt
kaminari
koala
@@ -530,11 +538,14 @@ DEPENDENCIES
redis-namespace
redis-rack-cache
responders
rest-client
rspec-rails (~> 4.0.0.beta2)
rubocop
rubocop-performance
rubocop-rails
rubocop-rspec
scout_apm
scss_lint
seed_dump
sentry-raven
shoulda-matchers
@@ -544,7 +555,7 @@ DEPENDENCIES
spring-watcher-listen
telegram-bot-ruby
time_diff
twitter
twilio-ruby (~> 5.32.0)
twitty!
tzinfo-data
uglifier
@@ -554,7 +565,7 @@ DEPENDENCIES
wisper (= 2.0.0)
RUBY VERSION
ruby 2.6.5p114
ruby 2.7.0p0
BUNDLED WITH
2.0.2
2.1.2
+1 -7
View File
@@ -52,16 +52,10 @@ Follow this [link](https://www.chatwoot.com/docs/environment-variables) to under
## Docker
You can use our official Docker image from [https://hub.docker.com/r/chatwoot/chatwoot](https://hub.docker.com/r/chatwoot/chatwoot)
```bash
docker pull chatwoot/chatwoot
```
Follow our [docker development guide](https://www.chatwoot.com/docs/installation-guide-docker) to develop and debug the application using `docker-compose`.
Follow our [environment variables](https://www.chatwoot.com/docs/environment-variables/) guide to setup environment for Docker.
Follow our [docker development guide](https://www.chatwoot.com/docs/installation-guide-docker) to develop and debug the application using docker composer.
## Contributors ✨
Thanks goes to all these [wonderful people](https://www.chatwoot.com/docs/contributors):
+1
View File
@@ -0,0 +1 @@
module.exports = '';
+47
View File
@@ -0,0 +1,47 @@
class ContactIdentifyAction
pattr_initialize [:contact!, :params!]
def perform
ActiveRecord::Base.transaction do
@contact = merge_contact(existing_identified_contact, @contact) if merge_contacts?(existing_identified_contact, @contact)
@contact = merge_contact(existing_email_contact, @contact) if merge_contacts?(existing_email_contact, @contact)
update_contact
end
@contact
end
private
def account
@account ||= @contact.account
end
def existing_identified_contact
return if params[:identifier].blank?
@existing_identified_contact ||= Contact.where(account_id: account.id).find_by(identifier: params[:identifier])
end
def existing_email_contact
return if params[:email].blank?
@existing_email_contact ||= Contact.where(account_id: account.id).find_by(email: params[:email])
end
def merge_contacts?(existing_contact, _contact)
existing_contact && existing_contact.id != @contact.id
end
def update_contact
@contact.update!(params.slice(:name, :email, :identifier))
ContactAvatarJob.perform_later(@contact, params[:avatar_url]) if params[:avatar_url].present?
end
def merge_contact(base_contact, merge_contact)
ContactMergeAction.new(
account: account,
base_contact: base_contact,
mergee_contact: merge_contact
).perform
end
end
+6
View File
@@ -5,9 +5,11 @@ class ContactMergeAction
ActiveRecord::Base.transaction do
validate_contacts
merge_conversations
merge_messages
merge_contact_inboxes
remove_mergee_contact
end
@base_contact
end
private
@@ -26,6 +28,10 @@ class ContactMergeAction
Conversation.where(contact_id: @mergee_contact.id).update(contact_id: @base_contact.id)
end
def merge_messages
Message.where(contact_id: @mergee_contact.id).update(contact_id: @base_contact.id)
end
def merge_contact_inboxes
ContactInbox.where(contact_id: @mergee_contact.id).update(contact_id: @base_contact.id)
end
+13 -5
View File
@@ -42,18 +42,26 @@ class AccountBuilder
def create_and_link_user
password = Time.now.to_i
@user = @account.users.new(email: @email,
password: password,
password_confirmation: password,
role: User.roles['administrator'],
name: email_to_name(@email))
@user = User.new(email: @email,
password: password,
password_confirmation: password,
name: email_to_name(@email))
if @user.save!
link_user_to_account(@user, @account)
@user
else
raise UserErrors.new(errors: @user.errors)
end
end
def link_user_to_account(user, account)
AccountUser.create!(
account_id: account.id,
user_id: user.id,
role: AccountUser.roles['administrator']
)
end
def email_to_name(email)
name = email[/[^@]+/]
name.split('.').map(&:capitalize).join(' ')
+37
View File
@@ -0,0 +1,37 @@
class ContactBuilder
pattr_initialize [:source_id!, :inbox!, :contact_attributes!]
def perform
contact_inbox = inbox.contact_inboxes.find_by(source_id: source_id)
return contact_inbox if contact_inbox
build_contact
end
private
def account
@account ||= inbox.account
end
def build_contact
ActiveRecord::Base.transaction do
contact = account.contacts.create!(
name: contact_attributes[:name],
phone_number: contact_attributes[:phone_number],
email: contact_attributes[:email],
identifier: contact_attributes[:identifier],
additional_attributes: contact_attributes[:identifier]
)
contact_inbox = ::ContactInbox.create!(
contact_id: contact.id,
inbox_id: inbox.id,
source_id: source_id
)
::ContactAvatarJob.perform_later(contact, contact_attributes[:avatar_url]) if contact_attributes[:avatar_url]
contact_inbox
rescue StandardError => e
Rails.logger e
end
end
end
+3 -7
View File
@@ -1,8 +1,6 @@
require 'open-uri'
# This class creates both outgoing messages from chatwoot and echo outgoing messages based on the flag `outgoing_echo`
# Assumptions
# 1. Incase of an outgoing message which is echo, fb_id will NOT be nil,
# 1. Incase of an outgoing message which is echo, source_id will NOT be nil,
# based on this we are showing "not sent from chatwoot" message in frontend
# Hence there is no need to set user_id in message for outgoing echo messages.
@@ -36,9 +34,7 @@ class Messages::MessageBuilder
return if contact.present?
@contact = Contact.create!(contact_params.except(:remote_avatar_url))
avatar_resource = LocalResource.new(contact_params[:remote_avatar_url])
@contact.avatar.attach(io: avatar_resource.file, filename: avatar_resource.tmp_filename, content_type: avatar_resource.encoding)
ContactAvatarJob.perform_later(@contact, contact_params[:remote_avatar_url]) if contact_params[:remote_avatar_url]
@contact_inbox = ContactInbox.create(contact: contact, inbox: @inbox, source_id: @sender_id)
end
@@ -121,7 +117,7 @@ class Messages::MessageBuilder
inbox_id: conversation.inbox_id,
message_type: @message_type,
content: response.content,
fb_id: response.identifier
source_id: response.identifier
}
end
@@ -1,16 +1,27 @@
class Messages::Outgoing::NormalBuilder
include ::FileTypeHelper
attr_reader :message
def initialize(user, conversation, params)
@content = params[:message]
@private = ['1', 'true', 1, true].include? params[:private]
@private = params[:private] || false
@conversation = conversation
@user = user
@fb_id = params[:fb_id]
@attachment = params[:attachment]
end
def perform
@message = @conversation.messages.create!(message_params)
@message = @conversation.messages.build(message_params)
if @attachment
@message.attachment = Attachment.new(
account_id: message.account_id,
file_type: file_type(@attachment[:file]&.content_type)
)
@message.attachment.file.attach(@attachment[:file])
end
@message.save
@message
end
private
@@ -22,8 +33,8 @@ class Messages::Outgoing::NormalBuilder
message_type: :outgoing,
content: @content,
private: @private,
user_id: @user.id,
fb_id: @fb_id
user_id: @user&.id,
source_id: @fb_id
}
end
end
+110
View File
@@ -0,0 +1,110 @@
class V2::ReportBuilder
attr_reader :account, :params
def initialize(account, params)
@account = account
@params = params
end
def timeseries
send(params[:metric])
end
# For backward compatible with old report
def build
timeseries.each_with_object([]) do |p, arr|
arr << { value: p[1], timestamp: p[0].to_time.to_i }
end
end
def summary
{
conversations_count: conversations_count.values.sum,
incoming_messages_count: incoming_messages_count.values.sum,
outgoing_messages_count: outgoing_messages_count.values.sum,
avg_first_response_time: avg_first_response_time_summary,
avg_resolution_time: avg_resolution_time_summary,
resolutions_count: resolutions_count.values.sum
}
end
private
def scope
return account if params[:type].match?('account')
return inbox if params[:type].match?('inbox')
return user if params[:type].match?('agent')
end
def inbox
@inbox ||= account.inboxes.where(id: params[:id]).first
end
def user
@user ||= account.users.where(id: params[:id]).first
end
def conversations_count
scope.conversations
.group_by_day(:created_at, range: range, default_value: 0)
.count
end
def incoming_messages_count
scope.messages.unscoped.incoming
.group_by_day(:created_at, range: range, default_value: 0)
.count
end
def outgoing_messages_count
scope.messages.unscoped.outgoing
.group_by_day(:created_at, range: range, default_value: 0)
.count
end
def resolutions_count
scope.conversations
.resolved
.group_by_day(:created_at, range: range, default_value: 0)
.count
end
def avg_first_response_time
scope.events
.where(name: 'first_response')
.group_by_day(:created_at, range: range, default_value: 0)
.average(:value)
end
def avg_resolution_time
scope.events.where(name: 'conversation_resolved')
.group_by_day(:created_at, range: range, default_value: 0)
.average(:value)
end
def range
parse_date_time(params[:since])..parse_date_time(params[:until])
end
# Taking average of average is not too accurate
# https://en.wikipedia.org/wiki/Simpson's_paradox
# TODO: Will optimize this later
def avg_resolution_time_summary
return 0 if avg_resolution_time.values.empty?
(avg_resolution_time.values.sum / avg_resolution_time.values.length)
end
def avg_first_response_time_summary
return 0 if avg_first_response_time.values.empty?
(avg_first_response_time.values.sum / avg_first_response_time.values.length)
end
def parse_date_time(datetime)
return datetime if datetime.is_a?(DateTime)
return datetime.to_datetime if datetime.is_a?(Time) || datetime.is_a?(Date)
DateTime.strptime(datetime, '%s')
end
end
+12 -7
View File
@@ -1,16 +1,21 @@
class Api::BaseController < ApplicationController
include AccessTokenAuthHelper
respond_to :json
before_action :authenticate_user!
unless Rails.env.development?
rescue_from StandardError do |exception|
Raven.capture_exception(exception)
render json: { error: '500 error', message: exception.message }.to_json, status: 500
end
end
before_action :authenticate_access_token!, if: :authenticate_by_access_token?
before_action :validate_bot_access_token!, if: :authenticate_by_access_token?
before_action :authenticate_user!, unless: :authenticate_by_access_token?
private
def authenticate_by_access_token?
request.headers[:api_access_token].present?
end
def set_conversation
@conversation ||= current_account.conversations.find_by(display_id: params[:conversation_id])
end
def check_billing_enabled
raise ActionController::RoutingError, 'Not Found' unless ENV['BILLING_ENABLED']
end
end
@@ -1,9 +1,12 @@
class Api::V1::AccountsController < Api::BaseController
class Api::V1::Accounts::AccountsController < Api::BaseController
include AuthHelper
skip_before_action :verify_authenticity_token, only: [:create]
skip_before_action :authenticate_user!, :set_current_user, :check_subscription, :handle_with_exception,
only: [:create], raise: false
before_action :check_signup_enabled, only: [:create]
before_action :check_authorization, except: [:create]
before_action :fetch_account, except: [:create]
rescue_from CustomExceptions::Account::InvalidEmail,
CustomExceptions::Account::UserExists,
@@ -17,17 +20,35 @@ class Api::V1::AccountsController < Api::BaseController
).perform
if @user
send_auth_headers(@user)
render json: {
data: @user.token_validation_response
}
render 'devise/auth.json', locals: { resource: @user }
else
render_error_response(CustomExceptions::Account::SignupFailed.new({}))
end
end
def show
render 'api/v1/accounts/show.json'
end
def update
@account.update!(account_params.slice(:name, :locale))
end
private
def check_authorization
authorize(Account)
end
def fetch_account
@account = current_user.accounts.find(params[:id])
end
def account_params
params.permit(:account_name, :email)
params.permit(:account_name, :email, :name, :locale)
end
def check_signup_enabled
raise ActionController::RoutingError, 'Not Found' if ENV.fetch('ENABLE_ACCOUNT_SIGNUP', true) == 'false'
end
end
@@ -1,4 +1,4 @@
class Api::V1::Actions::ContactMergesController < Api::BaseController
class Api::V1::Accounts::Actions::ContactMergesController < Api::BaseController
before_action :set_base_contact, only: [:create]
before_action :set_mergee_contact, only: [:create]
@@ -0,0 +1,69 @@
class Api::V1::Accounts::AgentsController < Api::BaseController
before_action :fetch_agent, except: [:create, :index]
before_action :check_authorization
before_action :find_user, only: [:create]
before_action :create_user, only: [:create]
before_action :save_account_user, only: [:create]
def index
@agents = agents
end
def destroy
@agent.account_user.destroy
head :ok
end
def update
@agent.update!(agent_params.except(:role))
@agent.account_user.update!(role: agent_params[:role]) if agent_params[:role]
render 'api/v1/models/user.json', locals: { resource: @agent }
end
def create
render 'api/v1/models/user.json', locals: { resource: @user }
end
private
def check_authorization
authorize(User)
end
def fetch_agent
@agent = agents.find(params[:id])
end
def find_user
@user = User.find_by(email: new_agent_params[:email])
end
def create_user
return if @user
@user = User.create!(new_agent_params.slice(:email, :name, :password, :password_confirmation))
end
def save_account_user
AccountUser.create!(
account_id: current_account.id,
user_id: @user.id,
role: new_agent_params[:role],
inviter_id: current_user.id
)
end
def agent_params
params.require(:agent).permit(:email, :name, :role)
end
def new_agent_params
time = Time.now.to_i
params.require(:agent).permit(:email, :name, :role)
.merge!(password: time, password_confirmation: time, inviter: current_user)
end
def agents
@agents ||= current_account.users
end
end
@@ -1,8 +1,5 @@
require 'rest-client'
require 'telegram/bot'
class Api::V1::CallbacksController < ApplicationController
skip_before_action :verify_authenticity_token, only: [:register_facebook_page]
skip_before_action :authenticate_user!, only: [:register_facebook_page], raise: false
class Api::V1::Accounts::CallbacksController < Api::BaseController
before_action :inbox, only: [:reauthorize_page]
def register_facebook_page
user_access_token = params[:user_access_token]
@@ -19,21 +16,19 @@ class Api::V1::CallbacksController < ApplicationController
render json: inbox
end
def get_facebook_pages
def facebook_pages
@page_details = mark_already_existing_facebook_pages(fb_object.get_connections('me', 'accounts'))
end
# get params[:inbox_id], current_account, params[:omniauth_token]
def reauthorize_page
if @inbox&.first&.facebook?
if @inbox&.facebook?
fb_page_id = @inbox.channel.page_id
page_details = fb_object.get_connections('me', 'accounts')
(page_details || []).each do |page_detail|
if fb_page_id == page_detail['id'] # found the page which has to be reauthorised
update_fb_page(fb_page_id, page_detail['access_token'])
head :ok
end
if (page_detail = (page_details || []).detect { |page| fb_page_id == page['id'] })
update_fb_page(fb_page_id, page_detail['access_token'])
return head :ok
end
end
@@ -46,18 +41,13 @@ class Api::V1::CallbacksController < ApplicationController
@inbox = current_account.inboxes.find_by(id: params[:inbox_id])
end
def update_fb_page
if fb_page(fb_page_id)
fb_page.update_attributes!(
user_access_token: @user_access_token, page_access_token: access_token
)
head :ok
else
head :unprocessable_entity
end
def update_fb_page(fb_page_id, access_token)
get_fb_page(fb_page_id)&.update!(
user_access_token: @user_access_token, page_access_token: access_token
)
end
def fb_page(fb_page_id)
def get_fb_page(fb_page_id)
current_account.facebook_pages.find_by(page_id: fb_page_id)
end
@@ -68,20 +58,26 @@ class Api::V1::CallbacksController < ApplicationController
def long_lived_token(omniauth_token)
koala = Koala::Facebook::OAuth.new(ENV['FB_APP_ID'], ENV['FB_APP_SECRET'])
long_lived_token = koala.exchange_access_token_info(omniauth_token)['access_token']
koala.exchange_access_token_info(omniauth_token)['access_token']
rescue StandardError => e
Rails.logger e
end
def mark_already_existing_facebook_pages(data)
return [] if data.empty?
data.inject([]) do |result, page_detail|
current_account.facebook_pages.exists?(page_id: page_detail['id']) ? page_detail.merge!(exists: true) : page_detail.merge!(exists: false)
page_detail[:exists] = current_account.facebook_pages.exists?(page_id: page_detail['id']) ? true : false
result << page_detail
end
end
def set_avatar(facebook_channel, page_id)
avatar_resource = LocalResource.new(get_avatar_url(page_id))
uri = get_avatar_url(page_id)
return unless uri
avatar_resource = LocalResource.new(uri)
facebook_channel.avatar.attach(io: avatar_resource.file, filename: avatar_resource.tmp_filename, content_type: avatar_resource.encoding)
end
@@ -94,12 +90,12 @@ class Api::V1::CallbacksController < ApplicationController
response = uri.open(redirect: false)
rescue OpenURI::HTTPRedirect => e
uri = e.uri # assigned from the "Location" response header
retry if (tries -= 1) > 0
retry if (tries -= 1).positive?
raise
end
pic_url = response.base_uri.to_s
Rails.logger.info(pic_url)
rescue StandardError => e
Rails.logger.debug "Rescued: #{e.inspect}"
pic_url = nil
end
pic_url
@@ -1,4 +1,4 @@
class Api::V1::CannedResponsesController < Api::BaseController
class Api::V1::Accounts::CannedResponsesController < Api::BaseController
before_action :fetch_canned_response, only: [:update, :destroy]
def index
@@ -0,0 +1,50 @@
class Api::V1::Accounts::Channels::TwilioChannelsController < Api::BaseController
before_action :authorize_request
def create
authenticate_twilio
build_inbox
setup_webhooks
rescue Twilio::REST::TwilioError => e
render_could_not_create_error(e.message)
rescue StandardError => e
render_could_not_create_error(e.message)
end
private
def authorize_request
authorize ::Inbox
end
def authenticate_twilio
client = Twilio::REST::Client.new(permitted_params[:account_sid], permitted_params[:auth_token])
client.messages.list(limit: 1)
end
def setup_webhooks
::Twilio::WebhookSetupService.new(inbox: @inbox).perform
end
def build_inbox
ActiveRecord::Base.transaction do
twilio_sms = current_account.twilio_sms.create(
account_sid: permitted_params[:account_sid],
auth_token: permitted_params[:auth_token],
phone_number: permitted_params[:phone_number]
)
@inbox = current_account.inboxes.create(
name: permitted_params[:name],
channel: twilio_sms
)
rescue StandardError => e
render_could_not_create_error(e.message)
end
end
def permitted_params
params.require(:twilio_channel).permit(
:account_id, :phone_number, :account_sid, :auth_token, :name
)
end
end
@@ -1,4 +1,4 @@
class Api::V1::Contacts::ConversationsController < Api::BaseController
class Api::V1::Accounts::Contacts::ConversationsController < Api::BaseController
def index
@conversations = current_account.conversations.includes(
:assignee, :contact, :inbox
@@ -1,4 +1,4 @@
class Api::V1::ContactsController < Api::BaseController
class Api::V1::Accounts::ContactsController < Api::BaseController
protect_from_forgery with: :null_session
before_action :check_authorization
@@ -1,7 +1,8 @@
class Api::V1::Conversations::AssignmentsController < Api::BaseController
class Api::V1::Accounts::Conversations::AssignmentsController < Api::BaseController
before_action :set_conversation, only: [:create]
def create # assign agent to a conversation
# assign agent to a conversation
def create
# if params[:assignee_id] is not a valid id, it will set to nil, hence unassigning the conversation
assignee = current_account.users.find_by(id: params[:assignee_id])
@conversation.update_assignee(assignee)
@@ -1,4 +1,4 @@
class Api::V1::Conversations::LabelsController < Api::BaseController
class Api::V1::Accounts::Conversations::LabelsController < Api::BaseController
before_action :set_conversation, only: [:create, :index]
def create
@@ -6,7 +6,8 @@ class Api::V1::Conversations::LabelsController < Api::BaseController
@labels = @conversation.label_list
end
def index # all labels of the current conversation
# all labels of the current conversation
def index
@labels = @conversation.label_list
end
end
@@ -0,0 +1,18 @@
class Api::V1::Accounts::Conversations::MessagesController < Api::BaseController
before_action :set_conversation, only: [:index, :create]
def index
@messages = message_finder.perform
end
def create
mb = Messages::Outgoing::NormalBuilder.new(current_user, @conversation, params)
@message = mb.perform
end
private
def message_finder
@message_finder ||= MessageFinder.new(@conversation, params)
end
end
@@ -0,0 +1,35 @@
class Api::V1::Accounts::ConversationsController < Api::BaseController
before_action :conversation, except: [:index]
def index
result = conversation_finder.perform
@conversations = result[:conversations]
@conversations_count = result[:count]
end
def show; end
def toggle_status
@status = @conversation.toggle_status
end
def update_last_seen
@conversation.agent_last_seen_at = parsed_last_seen_at
@conversation.save!
head :ok
end
private
def parsed_last_seen_at
DateTime.strptime(params[:agent_last_seen_at].to_s, '%s')
end
def conversation
@conversation ||= current_account.conversations.find_by(display_id: params[:id])
end
def conversation_finder
@conversation_finder ||= ConversationFinder.new(current_user, params)
end
end
@@ -1,4 +1,4 @@
class Api::V1::FacebookIndicatorsController < Api::BaseController
class Api::V1::Accounts::FacebookIndicatorsController < Api::BaseController
before_action :set_access_token
around_action :handle_with_exception
@@ -26,6 +26,7 @@ class Api::V1::FacebookIndicatorsController < Api::BaseController
def handle_with_exception
yield
rescue Facebook::Messenger::Error => e
Rails.logger.debug "Rescued: #{e.inspect}"
true
end
@@ -1,20 +1,14 @@
class Api::V1::InboxMembersController < Api::BaseController
class Api::V1::Accounts::InboxMembersController < Api::BaseController
before_action :fetch_inbox, only: [:create, :show]
before_action :current_agents_ids, only: [:create]
def create
# update also done via same action
if @inbox
begin
update_agents_list
head :ok
rescue StandardError => e
Rails.logger.debug "Rescued: #{e.inspect}"
render_could_not_create_error('Could not add agents to inbox')
end
else
render_not_found_error('Agents or inbox not found')
end
update_agents_list
head :ok
rescue StandardError => e
Rails.logger.debug "Rescued: #{e.inspect}"
render_could_not_create_error('Could not add agents to inbox')
end
def show
@@ -1,11 +1,15 @@
class Api::V1::InboxesController < Api::BaseController
class Api::V1::Accounts::InboxesController < Api::BaseController
before_action :check_authorization
before_action :fetch_inbox, only: [:destroy]
before_action :fetch_inbox, only: [:destroy, :update]
def index
@inboxes = policy_scope(current_account.inboxes)
end
def update
@inbox.update(inbox_update_params)
end
def destroy
@inbox.destroy
head :ok
@@ -20,4 +24,8 @@ class Api::V1::InboxesController < Api::BaseController
def check_authorization
authorize(Inbox)
end
def inbox_update_params
params.require(:inbox).permit(:enable_auto_assignment)
end
end
@@ -1,5 +1,6 @@
class Api::V1::LabelsController < Api::BaseController
def index # list all labels in account
class Api::V1::Accounts::LabelsController < Api::BaseController
# list all labels in account
def index
@labels = current_account.all_conversation_tags
end
@@ -0,0 +1,29 @@
class Api::V1::Accounts::NotificationSettingsController < Api::BaseController
before_action :set_user, :load_notification_setting
def show; end
def update
update_flags
@notification_setting.save!
render action: 'show'
end
private
def set_user
@user = current_user
end
def load_notification_setting
@notification_setting = @user.notification_settings.find_by(account_id: current_account.id)
end
def notification_setting_params
params.require(:notification_settings).permit(selected_email_flags: [])
end
def update_flags
@notification_setting.selected_email_flags = notification_setting_params[:selected_email_flags]
end
end
@@ -1,4 +1,4 @@
class Api::V1::ReportsController < Api::BaseController
class Api::V1::Accounts::ReportsController < Api::BaseController
include CustomExceptions::Report
include Constants::Report
@@ -36,10 +36,6 @@ class Api::V1::ReportsController < Api::BaseController
current_user.account
end
def agent
@agent ||= current_account.users.find(params[:agent_id])
end
def account_summary_metrics
summary_metrics(ACCOUNT_METRICS, :account_summary_params, AVG_ACCOUNT_METRICS)
end
@@ -51,18 +47,18 @@ class Api::V1::ReportsController < Api::BaseController
def summary_metrics(metrics, calc_function, avg_metrics)
metrics.each_with_object({}) do |metric, result|
data = ReportBuilder.new(current_account, send(calc_function, metric)).build
if avg_metrics.include?(metric)
sum = data.inject(0) { |sum, hash| sum + hash[:value].to_i }
sum /= data.length unless sum.zero?
else
sum = data.inject(0) { |sum, hash| sum + hash[:value].to_i }
end
result[metric] = sum
result[metric] = calculate_metric(data, metric, avg_metrics)
end
end
def calculate_metric(data, metric, avg_metrics)
sum = data.inject(0) { |val, hash| val + hash[:value].to_i }
if avg_metrics.include?(metric)
sum /= data.length unless sum.zero?
end
sum
end
def account_summary_params(metric)
{
metric: metric.to_s,
@@ -1,6 +1,8 @@
class Api::V1::SubscriptionsController < ApplicationController
class Api::V1::Accounts::SubscriptionsController < Api::BaseController
skip_before_action :check_subscription
before_action :check_billing_enabled
def index
render json: current_account.subscription_data
end
@@ -0,0 +1,36 @@
class Api::V1::Accounts::WebhooksController < Api::BaseController
before_action :check_authorization
before_action :fetch_webhook, only: [:update, :destroy]
def index
@webhooks = current_account.webhooks
end
def create
@webhook = current_account.webhooks.new(webhook_params)
@webhook.save!
end
def update
@webhook.update!(webhook_params)
end
def destroy
@webhook.destroy
head :ok
end
private
def webhook_params
params.require(:webhook).permit(:inbox_id, :url)
end
def fetch_webhook
@webhook = current_account.webhooks.find(params[:id])
end
def check_authorization
authorize(Webhook)
end
end
@@ -1,4 +1,4 @@
class Api::V1::Widget::InboxesController < Api::BaseController
class Api::V1::Accounts::Widget::InboxesController < Api::BaseController
before_action :authorize_request
before_action :set_web_widget_channel, only: [:update]
before_action :set_inbox, only: [:update]
@@ -1,52 +0,0 @@
class Api::V1::AgentsController < Api::BaseController
before_action :fetch_agent, except: [:create, :index]
before_action :check_authorization
before_action :build_agent, only: [:create]
def index
@agents = agents
end
def destroy
@agent.destroy
head :ok
end
def update
@agent.update!(agent_params)
render json: @agent
end
def create
@agent.save!
render json: @agent
end
private
def check_authorization
authorize(User)
end
def fetch_agent
@agent = agents.find(params[:id])
end
def build_agent
@agent = agents.new(new_agent_params)
end
def agent_params
params.require(:agent).permit(:email, :name, :role)
end
def new_agent_params
time = Time.now.to_i
params.require(:agent).permit(:email, :name, :role)
.merge!(password: time, password_confirmation: time, inviter: current_user)
end
def agents
@agents ||= current_account.users
end
end
@@ -1,8 +0,0 @@
class Api::V1::Conversations::MessagesController < Api::BaseController
before_action :set_conversation, only: [:create]
def create
mb = Messages::Outgoing::NormalBuilder.new(current_user, @conversation, params)
@message = mb.perform
end
end
@@ -1,52 +0,0 @@
class Api::V1::ConversationsController < Api::BaseController
before_action :set_conversation, except: [:index, :get_messages]
# TODO: move this to public controller
skip_before_action :authenticate_user!, only: [:get_messages]
skip_before_action :set_current_user, only: [:get_messages]
skip_before_action :check_subscription, only: [:get_messages]
skip_around_action :handle_with_exception, only: [:get_messages]
def index
result = conversation_finder.perform
@conversations = result[:conversations]
@conversations_count = result[:count]
end
def show
@messages = messages_finder.perform
end
def toggle_status
@status = @conversation.toggle_status
end
def update_last_seen
@conversation.agent_last_seen_at = parsed_last_seen_at
@conversation.save!
head :ok
end
def get_messages
@conversation = Conversation.find(params[:id])
@messages = messages_finder.perform
end
private
def parsed_last_seen_at
DateTime.strptime(params[:agent_last_seen_at].to_s, '%s')
end
def set_conversation
@conversation ||= current_account.conversations.find_by(display_id: params[:id])
end
def conversation_finder
@conversation_finder ||= ConversationFinder.new(current_user, params)
end
def messages_finder
@message_finder ||= MessageFinder.new(@conversation, params)
end
end
@@ -1,5 +1,5 @@
class Api::V1::ProfilesController < Api::BaseController
before_action :fetch_user
before_action :set_user
def show
render json: @user
@@ -7,12 +7,11 @@ class Api::V1::ProfilesController < Api::BaseController
def update
@user.update!(profile_params)
render json: @user
end
private
def fetch_user
def set_user
@user = current_user
end
@@ -4,6 +4,8 @@ class Api::V1::WebhooksController < ApplicationController
skip_before_action :check_subscription
before_action :login_from_basic_auth, only: [:chargebee]
before_action :check_billing_enabled, only: [:chargebee]
def chargebee
chargebee_consumer.consume
head :ok
@@ -13,7 +15,7 @@ class Api::V1::WebhooksController < ApplicationController
end
def twitter_crc
render json: { response_token: "sha256=#{$twitter.generate_crc(params[:crc_token])}" }
render json: { response_token: "sha256=#{twitter_client.generate_crc(params[:crc_token])}" }
end
def twitter_events
@@ -26,6 +28,12 @@ class Api::V1::WebhooksController < ApplicationController
private
def twitter_client
Twitty::Facade.new do |config|
config.consumer_secret = ENV.fetch('TWITTER_CONSUMER_SECRET', nil)
end
end
def login_from_basic_auth
authenticate_or_request_with_http_basic do |username, password|
username == ENV['CHARGEBEE_WEBHOOK_USERNAME'] && password == ENV['CHARGEBEE_WEBHOOK_PASSWORD']
@@ -0,0 +1,18 @@
class Api::V1::Widget::ContactsController < Api::V1::Widget::BaseController
before_action :set_web_widget
before_action :set_contact
def update
contact_identify_action = ContactIdentifyAction.new(
contact: @contact,
params: permitted_params.to_h.deep_symbolize_keys
)
render json: contact_identify_action.perform
end
private
def permitted_params
params.permit(:website_token, :identifier, :email, :name, :avatar_url)
end
end
@@ -0,0 +1,24 @@
class Api::V1::Widget::LabelsController < Api::V1::Widget::BaseController
before_action :set_web_widget
before_action :set_contact
def create
conversation.label_list.add(permitted_params[:label])
conversation.save!
head :no_content
end
def destroy
conversation.label_list.remove(permitted_params[:id])
conversation.save!
head :no_content
end
private
def permitted_params
params.permit(:id, :label, :website_token)
end
end
@@ -10,20 +10,29 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
def create
@message = conversation.messages.new(message_params)
build_attachment
@message.save!
render json: @message
end
def update
@message.update!(input_submitted_email: contact_email)
update_contact(contact_email)
head :no_content
rescue StandardError => e
render json: { error: @contact.errors, message: e.message }.to_json, status: 500
end
private
def build_attachment
return if params[:message][:attachment].blank?
@message.attachment = Attachment.new(
account_id: @message.account_id,
file_type: helpers.file_type(params[:message][:attachment][:file]&.content_type)
)
@message.attachment.file.attach(params[:message][:attachment][:file])
end
def set_conversation
@conversation = ::Conversation.create!(conversation_params) if conversation.nil?
end
@@ -31,9 +40,10 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
def message_params
{
account_id: conversation.account_id,
contact_id: @contact.id,
content: permitted_params[:message][:content],
inbox_id: conversation.inbox_id,
message_type: :incoming,
content: permitted_params[:message][:content]
message_type: :incoming
}
end
@@ -85,7 +95,11 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
def update_contact(email)
contact_with_email = @account.contacts.find_by(email: email)
if contact_with_email
::ContactMergeAction.new(account: @account, base_contact: contact_with_email, mergee_contact: @contact).perform
@contact = ::ContactMergeAction.new(
account: @account,
base_contact: contact_with_email,
mergee_contact: @contact
).perform
else
@contact.update!(
email: email,
@@ -0,0 +1,39 @@
class Api::V2::Accounts::ReportsController < Api::BaseController
def account
builder = V2::ReportBuilder.new(current_account, account_report_params)
data = builder.build
render json: data
end
def account_summary
render json: account_summary_metrics
end
private
def current_account
current_user.account
end
def account_summary_params
{
type: :account,
since: params[:since],
until: params[:until]
}
end
def account_report_params
{
metric: params[:metric],
type: :account,
since: params[:since],
until: params[:until]
}
end
def account_summary_metrics
builder = V2::ReportBuilder.new(current_account, account_summary_params)
builder.summary
end
end
+20 -2
View File
@@ -14,7 +14,25 @@ class ApplicationController < ActionController::Base
private
def current_account
@_ ||= current_user.account
@_ ||= find_current_account
end
def find_current_account
account = Account.find(params[:account_id])
if current_user
account_accessible_for_user?(account)
elsif @resource&.is_a?(AgentBot)
account_accessible_for_bot?(account)
end
account
end
def account_accessible_for_user?(account)
render_unauthorized('You are not authorized to access this account') unless account.account_users.find_by(user_id: current_user.id)
end
def account_accessible_for_bot?(account)
render_unauthorized('You are not authorized to access this account') unless @resource.agent_bot_inboxes.find_by(account_id: account.id)
end
def handle_with_exception
@@ -67,7 +85,7 @@ class ApplicationController < ActionController::Base
def check_subscription
# This block is left over from the initial version of chatwoot
# We might reuse this later in the hosted version of chatwoot.
return unless ENV['BILLING_ENABLED']
return if !ENV['BILLING_ENABLED'] || !current_user
if current_subscription.trial? && current_subscription.expiry < Date.current
render json: { error: 'Trial Expired' }, status: :trial_expired
@@ -0,0 +1,25 @@
module AccessTokenAuthHelper
BOT_ACCESSIBLE_ENDPOINTS = {
'api/v1/accounts/conversations' => ['toggle_status'],
'api/v1/accounts/conversations/messages' => ['create']
}.freeze
def authenticate_access_token!
access_token = AccessToken.find_by(token: request.headers[:api_access_token])
render_unauthorized('Invalid Access Token') && return unless access_token
token_owner = access_token.owner
@resource = token_owner
end
def validate_bot_access_token!
return if current_user.is_a?(User)
return if agent_bot_accessible?
render_unauthorized('Access to this endpoint is not authorized for bots')
end
def agent_bot_accessible?
BOT_ACCESSIBLE_ENDPOINTS.fetch(params[:controller], []).include?(params[:action])
end
end
@@ -11,9 +11,7 @@ class DeviseOverrides::PasswordsController < Devise::PasswordsController
@recoverable = User.find_by(reset_password_token: reset_password_token)
if @recoverable && reset_password_and_confirmation(@recoverable)
send_auth_headers(@recoverable)
render json: {
data: @recoverable.token_validation_response
}
render 'devise/auth.json', locals: { resource: @recoverable }
else
render json: { "message": 'Invalid token', "redirect_url": '/' }, status: 422
end
@@ -2,4 +2,8 @@ class DeviseOverrides::SessionsController < ::DeviseTokenAuth::SessionsControlle
# Prevent session parameter from being passed
# Unpermitted parameter: session
wrap_parameters format: []
def render_create_success
render 'devise/auth.json', locals: { resource: @resource }
end
end
+1 -5
View File
@@ -1,13 +1,9 @@
require 'rest-client'
require 'telegram/bot'
class HomeController < ApplicationController
skip_before_action :verify_authenticity_token, only: [:telegram]
skip_before_action :authenticate_user!, only: [:telegram], raise: false
skip_before_action :set_current_user
skip_before_action :check_subscription
def index; end
def status
head :ok
end
end
+18
View File
@@ -0,0 +1,18 @@
class SwaggerController < ApplicationController
def respond
if Rails.env.development? || Rails.env.test?
render inline: File.read(Rails.root.join('swagger', derived_path))
else
head 404
end
end
private
def derived_path
params[:path] ||= 'index.html'
path = params[:path]
path << ".#{params[:format]}" unless path.ends_with?(params[:format].to_s)
path
end
end
@@ -0,0 +1,29 @@
class Twilio::CallbackController < ApplicationController
def create
::Twilio::IncomingMessageService.new(params: permitted_params).perform
head :no_content
end
private
def permitted_params
params.permit(
:ApiVersion,
:SmsSid,
:From,
:ToState,
:ToZip,
:AccountSid,
:MessageSid,
:FromCountry,
:ToCity,
:FromCity,
:To,
:FromZip,
:Body,
:ToCountry,
:FromState
)
end
end
@@ -0,0 +1,30 @@
class Twitter::AuthorizationsController < Twitter::BaseController
def create
@response = twitter_client.request_oauth_token(url: twitter_callback_url)
if @response.status == '200'
::Redis::Alfred.setex(oauth_token, account.id)
redirect_to oauth_authorize_endpoint(oauth_token)
else
redirect_to app_new_twitter_inbox_url
end
end
private
def oauth_token
parsed_body['oauth_token']
end
def user
@user ||= User.find_by(id: params[:user_id])
end
def account
@account ||= user.account
end
def oauth_authorize_endpoint(oauth_token)
"#{twitter_api_base_url}/oauth/authorize?oauth_token=#{oauth_token}"
end
end
@@ -0,0 +1,24 @@
class Twitter::BaseController < ApplicationController
private
def parsed_body
@parsed_body ||= Rack::Utils.parse_nested_query(@response.raw_response.body)
end
def host
ENV.fetch('FRONTEND_URL', '')
end
def twitter_client
Twitty::Facade.new do |config|
config.consumer_key = ENV.fetch('TWITTER_CONSUMER_KEY', nil)
config.consumer_secret = ENV.fetch('TWITTER_CONSUMER_SECRET', nil)
config.base_url = twitter_api_base_url
config.environment = ENV.fetch('TWITTER_ENVIRONMENT', '')
end
end
def twitter_api_base_url
'https://api.twitter.com'
end
end
@@ -0,0 +1,57 @@
class Twitter::CallbacksController < Twitter::BaseController
def show
return redirect_to twitter_app_redirect_url if permitted_params[:denied]
@response = twitter_client.access_token(
oauth_token: permitted_params[:oauth_token],
oauth_verifier: permitted_params[:oauth_verifier]
)
if @response.status == '200'
inbox = build_inbox
::Redis::Alfred.delete(permitted_params[:oauth_token])
::Twitter::WebhookSubscribeService.new(inbox_id: inbox.id).perform
redirect_to app_twitter_inbox_agents_url(account_id: account.id, inbox_id: inbox.id)
else
redirect_to twitter_app_redirect_url
end
end
private
def parsed_body
@parsed_body ||= Rack::Utils.parse_nested_query(@response.raw_response.body)
end
def account_id
::Redis::Alfred.get(permitted_params[:oauth_token])
end
def account
@account ||= Account.find_by!(id: account_id)
end
def twitter_app_redirect_url
app_new_twitter_inbox_url(account_id: account.id)
end
def build_inbox
ActiveRecord::Base.transaction do
twitter_profile = account.twitter_profiles.create(
twitter_access_token: parsed_body['oauth_token'],
twitter_access_token_secret: parsed_body['oauth_token_secret'],
profile_id: parsed_body['user_id'],
name: parsed_body['screen_name']
)
account.inboxes.create(
name: parsed_body['screen_name'],
channel: twitter_profile
)
rescue StandardError => e
Rails.logger e
end
end
def permitted_params
params.permit(:oauth_token, :oauth_verifier, :denied)
end
end
+1 -3
View File
@@ -4,9 +4,7 @@ class WidgetsController < ActionController::Base
before_action :set_contact
before_action :build_contact
def index
render
end
def index; end
private
+6 -1
View File
@@ -1,11 +1,16 @@
class AsyncDispatcher < BaseDispatcher
def dispatch(event_name, timestamp, data)
EventDispatcherJob.perform_later(event_name, timestamp, data)
end
def publish_event(event_name, timestamp, data)
event_object = Events::Base.new(event_name, timestamp, data)
publish(event_object.method_name, event_object)
end
def listeners
listeners = [ReportingListener.instance]
listeners = [AgentBotListener.instance, EmailNotificationListener.instance, ReportingListener.instance, WebhookListener.instance]
listeners << EventListener.instance
listeners << SubscriptionListener.instance if ENV['BILLING_ENABLED']
listeners
end
+5 -12
View File
@@ -1,23 +1,18 @@
class ConversationFinder
attr_reader :current_user, :current_account, :params
ASSIGNEE_TYPES = { me: 0, unassigned: 1, all: 2 }.freeze
ASSIGNEE_TYPES_BY_ID = ASSIGNEE_TYPES.invert
ASSIGNEE_TYPES_BY_ID.default = :me
DEFAULT_STATUS = 'open'.freeze
# assumptions
# inbox_id if not given, take from all conversations, else specific to inbox
# assignee_type if not given, take 'me'
# assignee_type if not given, take 'all'
# conversation_status if not given, take 'open'
# response of this class will be of type
# {conversations: [array of conversations], count: {open: count, resolved: count}}
# params
# assignee_type_id, inbox_id, :status
# assignee_type, inbox_id, :status
def initialize(current_user, params)
@current_user = current_user
@@ -62,7 +57,7 @@ class ConversationFinder
end
def set_assignee_type
@assignee_type_id = ASSIGNEE_TYPES[ASSIGNEE_TYPES_BY_ID[params[:assignee_type_id].to_i]]
@assignee_type = params[:assignee_type]
end
def find_all_conversations
@@ -72,12 +67,10 @@ class ConversationFinder
end
def filter_by_assignee_type
if @assignee_type_id == ASSIGNEE_TYPES[:me]
if @assignee_type == 'me'
@conversations = @conversations.assigned_to(current_user)
elsif @assignee_type_id == ASSIGNEE_TYPES[:unassigned]
elsif @assignee_type == 'unassigned'
@conversations = @conversations.unassigned
elsif @assignee_type_id == ASSIGNEE_TYPES[:all]
@conversations
end
@conversations
end
-2
View File
@@ -1,2 +0,0 @@
module Api::V1::WebhooksHelper
end
+14
View File
@@ -0,0 +1,14 @@
module FileTypeHelper
def file_type(content_type)
return :image if [
'image/jpeg',
'image/png',
'image/svg+xml',
'image/gif',
'image/tiff',
'image/bmp'
].include?(content_type)
:file
end
end
+24 -2
View File
@@ -8,7 +8,10 @@
</template>
<script>
import Vue from 'vue';
import { mapGetters } from 'vuex';
import WootSnackbarBox from './components/SnackbarContainer';
import { accountIdFromPathname } from './helper/URLHelper';
export default {
name: 'App',
@@ -17,9 +20,28 @@ export default {
WootSnackbarBox,
},
computed: {
...mapGetters({
getAccount: 'accounts/getAccount',
}),
},
mounted() {
this.$store.dispatch('set_user');
this.$store.dispatch('validityCheck');
this.$store.dispatch('setUser');
this.initializeAccount();
},
methods: {
async initializeAccount() {
const { pathname } = window.location;
const accountId = accountIdFromPathname(pathname);
if (accountId) {
await this.$store.dispatch('accounts/get');
const { locale } = this.getAccount(accountId);
Vue.config.lang = locale;
}
},
},
};
</script>
+18 -2
View File
@@ -3,9 +3,25 @@
const API_VERSION = `/api/v1`;
class ApiClient {
constructor(url) {
constructor(resource, options = {}) {
this.apiVersion = API_VERSION;
this.url = `${this.apiVersion}/${url}`;
this.options = options;
this.resource = resource;
}
get url() {
let url = this.apiVersion;
if (this.options.accountScoped) {
const isInsideAccountScopedURLs = window.location.pathname.includes(
'/app/accounts'
);
if (isInsideAccountScopedURLs) {
const accountId = window.location.pathname.split('/')[3];
url = `${url}/accounts/${accountId}`;
}
}
return `${url}/${this.resource}`;
}
get() {
+9
View File
@@ -0,0 +1,9 @@
import ApiClient from './ApiClient';
class AccountAPI extends ApiClient {
constructor() {
super('', { accountScoped: true });
}
}
export default new AccountAPI();
+1 -1
View File
@@ -2,7 +2,7 @@ import ApiClient from './ApiClient';
class Agents extends ApiClient {
constructor() {
super('agents');
super('agents', { accountScoped: true });
}
}
+21 -41
View File
@@ -1,29 +1,10 @@
/* eslint no-console: 0 */
/* global axios */
/* eslint no-undef: "error" */
/* eslint-env browser */
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true }] */
import moment from 'moment';
import Cookies from 'js-cookie';
import endPoints from './endPoints';
import { frontendURL } from '../helper/URLHelper';
const setAuthCredentials = response => {
const expiryDate = moment.unix(response.headers.expiry);
Cookies.set('auth_data', response.headers, {
expires: expiryDate.diff(moment(), 'days'),
});
Cookies.set('user', response.data.data, {
expires: expiryDate.diff(moment(), 'days'),
});
};
const clearCookiesOnLogout = () => {
Cookies.remove('auth_data');
Cookies.remove('user');
window.location = frontendURL('login');
};
import { setAuthCredentials, clearCookiesOnLogout } from '../store/utils/api';
export default {
login(creds) {
@@ -60,20 +41,7 @@ export default {
},
validityCheck() {
const urlData = endPoints('validityCheck');
const fetchPromise = new Promise((resolve, reject) => {
axios
.get(urlData.url)
.then(response => {
resolve(response);
})
.catch(error => {
if (error.response.status === 401) {
clearCookiesOnLogout();
}
reject(error);
});
});
return fetchPromise;
return axios.get(urlData.url);
},
logout() {
const urlData = endPoints('logout');
@@ -136,13 +104,7 @@ export default {
password,
})
.then(response => {
const expiryDate = moment.unix(response.headers.expiry);
Cookies.set('auth_data', response.headers, {
expires: expiryDate.diff(moment(), 'days'),
});
Cookies.set('user', response.data.data, {
expires: expiryDate.diff(moment(), 'days'),
});
setAuthCredentials(response);
resolve(response);
})
.catch(error => {
@@ -155,4 +117,22 @@ export default {
const urlData = endPoints('resetPassword');
return axios.post(urlData.url, { email });
},
profileUpdate({ name, email, password, password_confirmation, avatar }) {
const formData = new FormData();
if (name) {
formData.append('profile[name]', name);
}
if (email) {
formData.append('profile[email]', email);
}
if (password && password_confirmation) {
formData.append('profile[password]', password);
formData.append('profile[password_confirmation]', password_confirmation);
}
if (avatar) {
formData.append('profile[avatar]', avatar);
}
return axios.put(endPoints('profileUpdate').url, formData);
},
};
@@ -4,7 +4,7 @@ import ApiClient from './ApiClient';
class CannedResponse extends ApiClient {
constructor() {
super('canned_responses');
super('canned_responses', { accountScoped: true });
}
get({ searchKey }) {
@@ -3,7 +3,7 @@ import ApiClient from '../ApiClient';
class FBChannel extends ApiClient {
constructor() {
super('facebook_indicators');
super('facebook_indicators', { accountScoped: true });
}
markSeen({ inboxId, contactId }) {
@@ -22,7 +22,7 @@ class FBChannel extends ApiClient {
create(params) {
return axios.post(
`${this.apiVersion}/callbacks/register_facebook_page`,
`${this.url.replace(this.resource, '')}callbacks/register_facebook_page`,
params
);
}
@@ -0,0 +1,9 @@
import ApiClient from '../ApiClient';
class TwilioChannel extends ApiClient {
constructor() {
super('channels/twilio_channel', { accountScoped: true });
}
}
export default new TwilioChannel();
@@ -2,7 +2,7 @@ import ApiClient from '../ApiClient';
class WebChannel extends ApiClient {
constructor() {
super('widget/inboxes');
super('widget/inboxes', { accountScoped: true });
}
}
+2 -2
View File
@@ -5,9 +5,9 @@
import endPoints from './endPoints';
export default {
fetchFacebookPages(token) {
fetchFacebookPages(token, accountId) {
const urlData = endPoints('fetchFacebookPages');
urlData.params.omniauth_token = token;
return axios.post(urlData.url, urlData.params);
return axios.post(urlData.url(accountId), urlData.params);
},
};
+1 -1
View File
@@ -3,7 +3,7 @@ import ApiClient from './ApiClient';
class ContactAPI extends ApiClient {
constructor() {
super('contacts');
super('contacts', { accountScoped: true });
}
getConversations(contactId) {
@@ -3,15 +3,15 @@ import ApiClient from './ApiClient';
class ConversationApi extends ApiClient {
constructor() {
super('conversations');
super('conversations', { accountScoped: true });
}
getLabels(conversationID) {
return axios.get(`${this.url}/${conversationID}/labels`);
}
createLabels(conversationID) {
return axios.get(`${this.url}/${conversationID}/labels`);
updateLabels(conversationID, labels) {
return axios.post(`${this.url}/${conversationID}/labels`, { labels });
}
}
+6 -14
View File
@@ -10,6 +10,9 @@ const endPoints = {
validityCheck: {
url: '/auth/validate_token',
},
profileUpdate: {
url: '/api/v1/profile',
},
logout: {
url: 'auth/sign_out',
},
@@ -25,23 +28,12 @@ const endPoints = {
},
fetchFacebookPages: {
url: 'api/v1/callbacks/get_facebook_pages.json',
url(accountId) {
return `api/v1/accounts/${accountId}/callbacks/facebook_pages.json`;
},
params: { omniauth_token: '' },
},
reports: {
account(metric, from, to) {
return {
url: `/api/v1/reports/account?metric=${metric}&since=${from}&to=${to}`,
};
},
accountSummary(accountId, from, to) {
return {
url: `/api/v1/reports/${accountId}/account_summary?since=${from}&to=${to}`,
};
},
},
subscriptions: {
get() {
return {
@@ -3,15 +3,16 @@ import ApiClient from '../ApiClient';
class ConversationApi extends ApiClient {
constructor() {
super('conversations');
super('conversations', { accountScoped: true });
}
get({ inboxId, status, assigneeType }) {
get({ inboxId, status, assigneeType, page }) {
return axios.get(this.url, {
params: {
inbox_id: inboxId,
status,
assignee_type_id: assigneeType,
assignee_type: assigneeType,
page,
},
});
}
+12 -2
View File
@@ -4,7 +4,7 @@ import ApiClient from '../ApiClient';
class MessageApi extends ApiClient {
constructor() {
super('conversations');
super('conversations', { accountScoped: true });
}
create({ conversationId, message, private: isPrivate }) {
@@ -15,10 +15,20 @@ class MessageApi extends ApiClient {
}
getPreviousMessages({ conversationId, before }) {
return axios.get(`${this.url}/${conversationId}`, {
return axios.get(`${this.url}/${conversationId}/messages`, {
params: { before },
});
}
sendAttachment([conversationId, { file }]) {
const formData = new FormData();
formData.append('attachment[file]', file);
return axios({
method: 'post',
url: `${this.url}/${conversationId}/messages`,
data: formData,
});
}
}
export default new MessageApi();
+1 -1
View File
@@ -3,7 +3,7 @@ import ApiClient from './ApiClient';
class InboxMembers extends ApiClient {
constructor() {
super('inbox_members');
super('inbox_members', { accountScoped: true });
}
create({ inboxId, agentList }) {
+1 -1
View File
@@ -2,7 +2,7 @@ import ApiClient from './ApiClient';
class Inboxes extends ApiClient {
constructor() {
super('inboxes');
super('inboxes', { accountScoped: true });
}
}
+19 -11
View File
@@ -1,14 +1,22 @@
/* global axios */
import ApiClient from './ApiClient';
import endPoints from './endPoints';
class ReportsAPI extends ApiClient {
constructor() {
super('reports', { accountScoped: true });
}
export default {
getAccountReports(metric, from, to) {
const { url } = endPoints('reports').account(metric, from, to);
return axios.get(url);
},
getAccountSummary(accountId, from, to) {
const urlData = endPoints('reports').accountSummary(accountId, from, to);
return axios.get(urlData.url);
},
};
getAccountReports(metric, since, until) {
return axios.get(`${this.url}/account`, {
params: { metric, since, until },
});
}
getAccountSummary(accountId, since, until) {
return axios.get(`${this.url}/${accountId}/account_summary`, {
params: { since, until },
});
}
}
export default new ReportsAPI();
@@ -0,0 +1,15 @@
import fbChannel from '../../channel/fbChannel';
import ApiClient from '../../ApiClient';
describe('#FBChannel', () => {
it('creates correct instance', () => {
expect(fbChannel).toBeInstanceOf(ApiClient);
expect(fbChannel).toHaveProperty('get');
expect(fbChannel).toHaveProperty('show');
expect(fbChannel).toHaveProperty('create');
expect(fbChannel).toHaveProperty('update');
expect(fbChannel).toHaveProperty('delete');
expect(fbChannel).toHaveProperty('markSeen');
expect(fbChannel).toHaveProperty('toggleTyping');
});
});
@@ -10,6 +10,6 @@ describe('#ConversationApi', () => {
expect(conversations).toHaveProperty('update');
expect(conversations).toHaveProperty('delete');
expect(conversations).toHaveProperty('getLabels');
expect(conversations).toHaveProperty('createLabels');
expect(conversations).toHaveProperty('updateLabels');
});
});
@@ -0,0 +1,13 @@
import userNotificationSettings from '../userNotificationSettings';
import ApiClient from '../ApiClient';
describe('#AgentAPI', () => {
it('creates correct instance', () => {
expect(userNotificationSettings).toBeInstanceOf(ApiClient);
expect(userNotificationSettings).toHaveProperty('get');
expect(userNotificationSettings).toHaveProperty('show');
expect(userNotificationSettings).toHaveProperty('create');
expect(userNotificationSettings).toHaveProperty('update');
expect(userNotificationSettings).toHaveProperty('delete');
});
});
@@ -0,0 +1,14 @@
/* global axios */
import ApiClient from './ApiClient';
class UserNotificationSettings extends ApiClient {
constructor() {
super('notification_settings', { accountScoped: true });
}
update(params) {
return axios.patch(`${this.url}`, params);
}
}
export default new UserNotificationSettings();
+9
View File
@@ -0,0 +1,9 @@
import ApiClient from './ApiClient';
class WebHooks extends ApiClient {
constructor() {
super('webhooks', { accountScoped: true });
}
}
export default new WebHooks();
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<path style="fill:#4D4D4D;" d="M188.287,512c-41.473,0-75.213-33.74-75.213-75.213V246.75c0-4.142,3.358-7.5,7.5-7.5
s7.5,3.358,7.5,7.5v190.037c0,33.202,27.011,60.213,60.213,60.213c16.082,0,31.204-6.266,42.582-17.644
c11.37-11.37,17.631-26.488,17.631-42.569V75.213C248.5,33.74,282.24,0,323.713,0c20.088,0,38.978,7.826,53.189,22.037
c14.203,14.202,22.024,33.087,22.024,53.176V256c0,4.142-3.358,7.5-7.5,7.5s-7.5-3.358-7.5-7.5V75.213
c0-16.082-6.261-31.2-17.63-42.569C354.918,21.266,339.794,15,323.713,15C290.511,15,263.5,42.011,263.5,75.213v361.574
c0,20.088-7.822,38.973-22.024,53.176C227.265,504.174,208.376,512,188.287,512z"/>
<g>
<rect x="113.07" y="246.75" style="fill:#3B3B3B;" width="15" height="26.875"/>
<rect x="383.93" y="235.31" style="fill:#3B3B3B;" width="15" height="26.875"/>
</g>
<rect x="361.9" y="385" style="fill:#CCCCCC;" width="57.983" height="39.944"/>
<rect x="361.9" y="385" style="fill:#ADADAD;" width="57.983" height="22.19"/>
<path style="fill:#A6E2E3;" d="M432.802,298.678v86.977c0,3.616-2.932,6.548-6.548,6.548h-70.721c-3.617,0-6.548-2.932-6.548-6.548
v-87.746c0-23.439,19.239-42.39,42.803-41.899C414.709,256.486,432.802,275.751,432.802,298.678z"/>
<rect x="92.11" y="87.06" style="fill:#CCCCCC;" width="57.983" height="36.28"/>
<rect x="92.11" y="105.43" style="fill:#ADADAD;" width="57.983" height="17.907"/>
<path style="fill:#FFA638;" d="M163.015,126.345v86.977c0,22.927-18.093,42.191-41.015,42.668
c-23.564,0.49-42.803-18.461-42.803-41.899v-87.746c0-3.616,2.932-6.548,6.548-6.548l0,0h70.721l0,0
C160.083,119.797,163.015,122.729,163.015,126.345z"/>
<path style="fill:#7CCBCC;" d="M391.787,256.009c-5.066-0.105-9.93,0.693-14.447,2.236c0.396-0.081,0.781-0.166,1.142-0.257
c2.982-0.755,5.201-0.896,7.513-0.85c18.954,0.395,34.375,16.494,34.375,35.888v86.981c0,3.614-2.93,6.544-6.544,6.544H355.53
c-3.614,0-6.544-2.93-6.544-6.544l0,0v5.648c0,3.616,2.932,6.548,6.548,6.548h70.721c3.617,0,6.548-2.932,6.548-6.548v-86.977
C432.802,275.751,414.709,256.486,391.787,256.009z"/>
<path style="fill:#EB7100;" d="M79.527,217.153l-0.23-0.322c0.081,1.261,0.209,2.509,0.399,3.737
C79.586,219.444,79.527,218.305,79.527,217.153z"/>
<path style="fill:#ED8300;" d="M156.467,119.797h-11.188c2.613,0.858,4.502,3.314,4.502,6.215v90.372
c0,19.395-15.42,35.494-34.375,35.888c-0.251,0.005-0.503,0.008-0.753,0.008c-9.651,0-18.405-3.914-24.76-10.236
c7.856,8.766,19.342,14.212,32.106,13.947c22.922-0.477,41.015-19.741,41.015-42.668v-86.977
C163.015,122.728,160.083,119.797,156.467,119.797z"/>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

@@ -28,7 +28,7 @@
code {
border: 0;
font-family: 'Monaco';
font-family: 'Monaco', Verdana;
font-size: $font-size-mini;
&.hljs {
@@ -41,29 +41,34 @@
// 36. Tooltip
// 37. Top Bar
@import "~foundation-sites/scss/util/util";
@import '~foundation-sites/scss/util/util';
// 1. Global
// ---------
$global-font-size: 10px;
$global-width: 100%;
$global-lineheight: 1.5;
$foundation-palette: (
primary: $color-woot,
$foundation-palette: (primary: $color-woot,
secondary: #777,
success: #13ce66,
warning: #ffc82c,
alert: #ff4949
);
alert: #ff4949);
$light-gray: #c0ccda;
$medium-gray: #8492a6;
$dark-gray: $color-gray;
$black: #000000;
$black: #000;
$white: #fff;
$body-background: $white;
$body-font-color: $color-body;
$body-font-family: 'Inter', -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI",
Roboto, "Helvetica Neue", Arial, sans-serif;
$body-font-family: 'Inter',
-apple-system,
system-ui,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
"Helvetica Neue",
Arial,
sans-serif;
$body-antialiased: true;
$global-margin: $space-one;
$global-padding: $space-one;
@@ -79,13 +84,11 @@ $print-transparent-backgrounds: true;
// 2. Breakpoints
// --------------
$breakpoints: (
small: 0,
$breakpoints: (small: 0,
medium: 640px,
large: 1024px,
xlarge: 1200px,
xxlarge: 1440px
);
xxlarge: 1440px);
$print-breakpoint: large;
$breakpoint-classes: (small medium large);
@@ -94,10 +97,8 @@ $breakpoint-classes: (small medium large);
$grid-row-width: $global-width;
$grid-column-count: 12;
$grid-column-gutter: (
small: $zero,
medium: $zero
);
$grid-column-gutter: (small: $zero,
medium: $zero);
$grid-column-align-edge: true;
$block-grid-max: 8;
@@ -105,54 +106,24 @@ $block-grid-max: 8;
// ------------------
$header-font-family: $body-font-family;
$header-font-weight: $global-weight-normal;
$header-font-weight: $font-weight-medium;
$header-font-style: normal;
$font-family-monospace: $body-font-family;
$header-color: $color-heading;
$header-lineheight: 1.4;
$header-margin-bottom: 0.5rem;
$header-styles: (
small: (
"h1": (
"font-size": 24
),
"h2": (
"font-size": 20
),
"h3": (
"font-size": 19
),
"h4": (
"font-size": 18
),
"h5": (
"font-size": 17
),
"h6": (
"font-size": 16
)
),
medium: (
"h1": (
"font-size": 48
),
"h2": (
"font-size": 40
),
"h3": (
"font-size": 31
),
"h4": (
"font-size": 25
),
"h5": (
"font-size": 20
),
"h6": (
"font-size": 16
)
)
);
$header-styles: (small: ("h1": ("font-size": 24),
"h2": ("font-size": 20),
"h3": ("font-size": 19),
"h4": ("font-size": 18),
"h5": ("font-size": 17),
"h6": ("font-size": 16)),
medium: ("h1": ("font-size": 48),
"h2": ("font-size": 40),
"h3": ("font-size": 31),
"h4": ("font-size": 25),
"h5": ("font-size": 20),
"h6": ("font-size": 16)));
$header-text-rendering: optimizeLegibility;
$small-font-size: 80%;
$header-small-font-color: $medium-gray;
@@ -186,7 +157,7 @@ $blockquote-padding: rem-calc(9 20 0 19);
$blockquote-border: 1px solid $medium-gray;
$cite-font-size: rem-calc(13);
$cite-color: $dark-gray;
$cite-pseudo-content: "\2014 \0020";
$cite-pseudo-content: '\2014 \0020';
$keystroke-font: $font-family-monospace;
$keystroke-color: $black;
$keystroke-background: $light-gray;
@@ -272,24 +243,23 @@ $button-background-hover: scale-color($button-background, $lightness: -15%);
$button-color: $white;
$button-color-alt: $white;
$button-radius: $global-radius;
$button-sizes: (
tiny: $font-size-micro,
$button-sizes: (tiny: $font-size-micro,
small: $font-size-mini,
default: $font-size-default,
large: $font-size-large
);
large: $font-size-large);
$button-palette: $foundation-palette;
$button-opacity-disabled: 0.25;
$button-background-hover-lightness: -20%;
$button-hollow-hover-lightness: -50%;
$button-transition: background-color 0.25s ease-out, color 0.25s ease-out;
$button-transition: background-color 0.25s ease-out,
color 0.25s ease-out;
// 12. Button Group
// ----------------
$buttongroup-margin: 1rem;
$buttongroup-spacing: 1px;
$buttongroup-child-selector: ".button";
$buttongroup-child-selector: '.button';
$buttongroup-expand-max: 6;
$buttongroup-radius-on-each: true;
@@ -322,18 +292,12 @@ $card-margin: $global-margin;
// ----------------
$closebutton-position: right top;
$closebutton-offset-horizontal: (
small: 0.66rem,
medium: 1rem
);
$closebutton-offset-vertical: (
small: 0.33em,
medium: 0.5rem
);
$closebutton-size: (
small: 1.5em,
medium: 2em
);
$closebutton-offset-horizontal: (small: 0.66rem,
medium: 1rem);
$closebutton-offset-vertical: (small: 0.33em,
medium: 0.5rem);
$closebutton-size: (small: 1.5em,
medium: 2em);
$closebutton-lineheight: 1;
$closebutton-color: $dark-gray;
$closebutton-color-hover: $black;
@@ -356,11 +320,9 @@ $dropdown-border: 1px solid $medium-gray;
$dropdown-font-size: 1rem;
$dropdown-width: 300px;
$dropdown-radius: $global-radius;
$dropdown-sizes: (
tiny: 100px,
$dropdown-sizes: (tiny: 100px,
small: 200px,
large: 400px
);
large: 400px);
// 18. Dropdown Menu
// -----------------
@@ -455,12 +417,10 @@ $meter-fill-bad: $alert-color;
// 24. Off-canvas
// --------------
$offcanvas-sizes: (
small: 23rem,
$offcanvas-sizes: (small: 23rem,
medium: 23rem,
);
$offcanvas-vertical-sizes: (
small: 23rem,
$offcanvas-vertical-sizes: (small: 23rem,
medium: 23rem,
);
$offcanvas-background: $light-gray;
@@ -472,7 +432,7 @@ $offcanvas-transition-length: 0.5s;
$offcanvas-transition-timing: ease;
$offcanvas-fixed-reveal: true;
$offcanvas-exit-background: rgba($white, 0.25);
$maincontent-class: "off-canvas-content";
$maincontent-class: 'off-canvas-content';
// 25. Orbit
// ---------
@@ -520,10 +480,8 @@ $progress-radius: $global-radius;
// --------------------
$responsive-embed-margin-bottom: rem-calc(16);
$responsive-embed-ratios: (
default: 4 by 3,
widescreen: 16 by 9
);
$responsive-embed-ratios: (default: 4 by 3,
widescreen: 16 by 9);
// 29. Reveal
// ----------
@@ -576,10 +534,8 @@ $table-border: 1px solid transparent;
$table-padding: rem-calc(8 10 10);
$table-hover-scale: 2%;
$table-row-hover: darken($table-background, $table-hover-scale);
$table-row-stripe-hover: darken(
$table-background,
$table-color-scale + $table-hover-scale
);
$table-row-stripe-hover: darken($table-background,
$table-color-scale + $table-hover-scale);
$table-is-striped: false;
$table-striped-background: smart-scale($table-background, $table-color-scale);
$table-stripe: even;
@@ -3,8 +3,8 @@
}
.flex-center {
display: flex;
@include flex-align(center, middle);
display: flex;
}
.bottom-space-fix {
@@ -17,42 +17,43 @@
.spinner {
@include color-spinner();
position: relative;
display: inline-block;
width: $space-medium;
height: $space-medium;
padding: $zero $space-medium;
position: relative;
vertical-align: middle;
width: $space-medium;
&.message {
padding: $space-normal;
top: 0;
left: 0;
margin: 0 auto;
margin-top: $space-slab;
@include elegent-shadow;
background: $color-white;
border-radius: $space-large;
@include elegent-shadow;
left: 0;
margin: $space-slab 0 auto;
padding: $space-normal;
top: 0;
&:before {
margin-top: -$space-slab;
&::before {
margin-left: -$space-slab;
margin-top: -$space-slab;
}
}
&.small {
width: $space-normal;
height: $space-normal;
width: $space-normal;
&:before {
width: $space-normal;
&::before {
height: $space-normal;
margin-top: -$space-small;
width: $space-normal;
}
}
}
input, textarea {
input,
textarea,
select {
border-radius: 4px !important;
}
@@ -35,15 +35,17 @@ body {
flex-direction: column;
@include margin($zero);
@include padding($space-normal);
overflow-y: scroll;
overflow-y: auto;
}
.content-box {
overflow: scroll;
overflow: auto;
@include padding($space-normal);
}
.back-button {
@include flex;
align-items: center;
color: $color-woot;
font-size: $font-size-default;
font-weight: $font-weight-normal;
@@ -1,3 +1,8 @@
@import '~widget/assets/scss/mixins';
$elegant-shadow-color: rgba(49, 49, 93, 0.15);
$spinner-before-border-color: rgba(255, 255, 255, 0.7);
//borders
@mixin border-nil() {
border-color: transparent;
@@ -77,8 +82,8 @@
&:active,
&:hover,
&:focus {
box-shadow: none;
border-color: transparent;
box-shadow: none;
}
}
@@ -117,7 +122,6 @@
// full height
@mixin full-height() {
height: 100%;
// COmmenting because unneccessary scroll is apprearing on some pages eg: settings/agents / inboxes
}
@mixin round-corner() {
@@ -125,21 +129,20 @@
}
@mixin scroll-on-hover() {
transition: all .4s $ease-in-out-cubic;
overflow: hidden;
&:hover {
overflow-y: scroll;
overflow-y: auto;
}
}
@mixin horizontal-scroll() {
overflow-y: scroll;
overflow-y: auto;
}
@mixin elegent-shadow() {
box-shadow: 0 10px 25px 0 rgba(49,49,93,0.15);
box-shadow: 0 10px 25px 0 $elegant-shadow-color;
}
@mixin elegant-card() {
@@ -154,20 +157,20 @@
}
}
&:before {
content: '';
&::before {
animation: spinner .9s linear infinite;
border: 2px solid $spinner-before-border-color;
border-radius: 50%;
border-top-color: lighten($color-woot, 10%);
box-sizing: border-box;
content: '';
height: $space-medium;
left: 50%;
margin-left: -$space-one;
margin-top: -$space-one;
position: absolute;
top: 50%;
left: 50%;
width: $space-medium;
height: $space-medium;
margin-top: -$space-one;
margin-left: -$space-one;
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.7);
border-top-color: lighten($color-woot, 10%);
animation: spinner .9s linear infinite;
}
}
@@ -181,41 +184,41 @@
// .element{
// @include arrow(top, #000, 50px);
// }
@mixin arrow($direction, $color, $size){
display: block;
height: 0;
width: 0;
content: '';
@mixin arrow($direction, $color, $size) {
display: block;
height: 0;
width: 0;
content: '';
@if $direction == 'top' {
border-left: $size solid transparent;
border-right: $size solid transparent;
border-bottom: $size solid $color;
} @else if $direction == 'right' {
border-top: $size solid transparent;
border-bottom: $size solid transparent;
border-left: $size solid $color;
} @else if $direction == 'bottom' {
border-top: $size solid $color;
border-right: $size solid transparent;
border-left: $size solid transparent;
} @else if $direction == 'left' {
border-top: $size solid transparent;
border-right: $size solid $color;
border-bottom: $size solid transparent;
} @else if $direction == 'top-left' {
border-top: $size solid $color;
border-right: $size solid transparent;
} @else if $direction == 'top-right' {
border-top: $size solid $color;
border-left: $size solid transparent;
} @else if $direction == 'bottom-left' {
border-bottom: $size solid $color;
border-right: $size solid transparent;
} @else if $direction == 'bottom-right' {
border-bottom: $size solid $color;
border-left: $size solid transparent;
}
@if $direction == 'top' {
border-bottom: $size solid $color;
border-left: $size solid transparent;
border-right: $size solid transparent;
} @else if $direction == 'right' {
border-bottom: $size solid transparent;
border-left: $size solid $color;
border-top: $size solid transparent;
} @else if $direction == 'bottom' {
border-left: $size solid transparent;
border-right: $size solid transparent;
border-top: $size solid $color;
} @else if $direction == 'left' {
border-bottom: $size solid transparent;
border-right: $size solid $color;
border-top: $size solid transparent;
} @else if $direction == 'top-left' {
border-right: $size solid transparent;
border-top: $size solid $color;
} @else if $direction == 'top-right' {
border-left: $size solid transparent;
border-top: $size solid $color;
} @else if $direction == 'bottom-left' {
border-bottom: $size solid $color;
border-right: $size solid transparent;
} @else if $direction == 'bottom-right' {
border-bottom: $size solid $color;
border-left: $size solid transparent;
}
}
@mixin text-ellipsis {
@@ -18,10 +18,14 @@
font-size: $font-size-small;
}
.text-muted {
color: $color-gray;
}
a {
font-size: $font-size-small;
}
p {
font-size: $font-size-small;
}
}
@@ -12,7 +12,7 @@ $font-size-mega: 3.4rem;
$font-size-giga: 4.0rem;
// spaces
$zero: 0rem;
$zero: 0;
$space-micro: 0.2rem;
$space-smaller: 0.4rem;
$space-small: 0.8rem;
@@ -42,16 +42,27 @@ $woot-logo-padding: $space-large $space-two;
// Colors
$color-woot: #1f93ff;
$color-gray: #6E6F73;
$color-light-gray: #999A9B;
$color-border: #E0E6ED;
$color-gray: #6e6f73;
$color-light-gray: #999a9b;
$color-border: #e0e6ed;
$color-border-light: #f0f4f5;
$color-background: #EFF2F7;
$color-background-light: #F9FAFC;
$color-white: #FFF;
$color-body: #3C4858;
$color-heading: #1F2D3D;
$color-extra-light-blue: #F5F7F9;
$color-background: #eff2f7;
$color-background-light: #f9fafc;
$color-white: #fff;
$color-body: #3c4858;
$color-heading: #1f2d3d;
$color-extra-light-blue: #f5f7f9;
$primary-color: $color-woot;
$secondary-color: #ff5216;
$success-color: #13ce66;
$warning-color: #ffc82c;
$alert-color: #ff4949;
// Color-palettes
$color-primary-light: #c7e3ff;
$color-primary-dark: darken($color-woot, 20%);
// Thumbnail
$thumbnail-radius: 4rem;
@@ -81,3 +92,6 @@ $swift-ease-out: all $swift-ease-out-duration $swift-ease-out-timing-function !d
// Ionicons
$ionicons-font-path: '~ionicons/fonts';
// Transitions
$transition-ease-in: all 0.250s ease-in;

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