diff --git a/.rubocop.yml b/.rubocop.yml index 34d5b0bda..e0c98cdc4 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -26,6 +26,8 @@ Style/FrozenStringLiteralComment: Enabled: false Style/SymbolArray: Enabled: false +Style/OpenStructUse: + Enabled: false Style/OptionalBooleanParameter: Exclude: - 'app/services/email_templates/db_resolver_service.rb' @@ -68,14 +70,20 @@ Rails/ApplicationController: - 'app/controllers/platform_controller.rb' - 'app/controllers/public_controller.rb' - 'app/controllers/survey/responses_controller.rb' +Rails/CompactBlank: + Enabled: false Rails/EnvironmentVariableAccess: Enabled: false Rails/TimeZoneAssignment: Enabled: false +Rails/RedundantPresenceValidationOnBelongsTo: + Enabled: false Style/ClassAndModuleChildren: EnforcedStyle: compact Exclude: - 'config/application.rb' +Style/MapToHash: + Enabled: false RSpec/NestedGroups: Enabled: true Max: 4 @@ -83,6 +91,8 @@ RSpec/MessageSpies: Enabled: false RSpec/StubbedMock: Enabled: false +RSpec/FactoryBot/SyntaxMethods: + Enabled: false Naming/VariableNumber: Enabled: false Metrics/MethodLength: @@ -119,6 +129,7 @@ Rails/ReversibleMigration: - 'db/migrate/20191020085608_rename_old_tables.rb' - 'db/migrate/20191126185833_update_user_invite_foreign_key.rb' - 'db/migrate/20191130164019_add_template_type_to_messages.rb' + - 'db/migrate/20210513083044_remove_not_null_from_webhook_url_channel_api.rb' Rails/BulkChangeTable: Exclude: - 'db/migrate/20161025070152_removechannelsfrommodels.rb' diff --git a/Gemfile.lock b/Gemfile.lock index 4df118291..5e2abbac6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -56,8 +56,8 @@ GEM activerecord (6.1.4.6) activemodel (= 6.1.4.6) activesupport (= 6.1.4.6) - activerecord-import (1.2.0) - activerecord (>= 3.2) + activerecord-import (1.3.0) + activerecord (>= 4.2) activestorage (6.1.4.6) actionpack (= 6.1.4.6) activejob (= 6.1.4.6) @@ -71,11 +71,11 @@ GEM minitest (>= 5.1) tzinfo (~> 2.0) zeitwerk (~> 2.3) - acts-as-taggable-on (8.1.0) - activerecord (>= 5.0, < 6.2) + acts-as-taggable-on (9.0.1) + activerecord (>= 6.0, < 7.1) addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) - administrate (0.16.0) + administrate (0.17.0) actionpack (>= 5.0) actionview (>= 5.0) activerecord (>= 5.0) @@ -85,46 +85,46 @@ GEM momentjs-rails (~> 2.8) sassc-rails (~> 2.1) selectize-rails (~> 0.6) - annotate (3.1.1) - activerecord (>= 3.2, < 7.0) + annotate (3.2.0) + activerecord (>= 3.2, < 8.0) rake (>= 10.4, < 14.0) ast (2.4.2) - attr_extras (6.2.4) + attr_extras (6.2.5) aws-eventstream (1.2.0) - aws-partitions (1.513.0) - aws-sdk-core (3.121.1) + aws-partitions (1.556.0) + aws-sdk-core (3.126.2) aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.239.0) + aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.49.0) - aws-sdk-core (~> 3, >= 3.120.0) + aws-sdk-kms (1.54.0) + aws-sdk-core (~> 3, >= 3.126.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.103.0) - aws-sdk-core (~> 3, >= 3.120.0) + aws-sdk-s3 (1.112.0) + aws-sdk-core (~> 3, >= 3.126.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) aws-sigv4 (1.4.0) aws-eventstream (~> 1, >= 1.0.2) - azure-storage-blob (2.0.1) + azure-storage-blob (2.0.3) azure-storage-common (~> 2.0) - nokogiri (~> 1.11.0.rc2) - azure-storage-common (2.0.2) + nokogiri (~> 1, >= 1.10.8) + azure-storage-common (2.0.4) faraday (~> 1.0) - faraday_middleware (~> 1.0.0.rc1) + faraday_middleware (~> 1.0, >= 1.0.0.rc1) net-http-persistent (~> 4.0) - nokogiri (~> 1.11.0.rc2) + nokogiri (~> 1, >= 1.10.8) barnes (0.0.9) multi_json (~> 1) statsd-ruby (~> 1.1) bcrypt (3.1.16) bindex (0.8.1) - bootsnap (1.9.1) - msgpack (~> 1.0) - brakeman (5.1.1) + bootsnap (1.10.3) + msgpack (~> 1.2) + brakeman (5.2.1) browser (5.3.1) builder (3.2.4) - bullet (6.1.5) + bullet (7.0.1) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) bundle-audit (0.1.0) @@ -141,7 +141,7 @@ GEM crack (0.4.5) rexml crass (1.0.6) - cypress-on-rails (1.11.0) + cypress-on-rails (1.12.1) rack database_cleaner (2.0.1) database_cleaner-active_record (~> 2.0.0) @@ -151,11 +151,12 @@ GEM database_cleaner-core (2.0.1) datetime_picker_rails (0.0.7) momentjs-rails (>= 2.8.1) - ddtrace (0.53.0) - ffi (~> 1.0) + ddtrace (0.54.2) + debase-ruby_core_source (<= 0.10.14) msgpack + debase-ruby_core_source (0.10.14) declarative (0.0.20) - devise (4.8.0) + devise (4.8.1) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) @@ -165,7 +166,7 @@ GEM bcrypt (~> 3.0) devise (> 3.5.2, < 5) rails (>= 4.2.0, < 6.2) - diff-lcs (1.4.4) + diff-lcs (1.5.0) digest-crc (0.6.4) rake (>= 12.0.0, < 14.0.0) docile (1.4.0) @@ -175,14 +176,14 @@ GEM dotenv-rails (2.7.6) dotenv (= 2.7.6) railties (>= 3.2) - down (5.2.4) + down (5.3.0) addressable (~> 2.8) - ecma-re-validator (0.3.0) - regexp_parser (~> 2.0) + ecma-re-validator (0.4.0) + regexp_parser (~> 2.2) email_reply_trimmer (0.1.13) erubi (1.10.0) erubis (2.7.0) - et-orbi (1.2.5) + et-orbi (1.2.6) tzinfo execjs (2.8.1) facebook-messenger (2.0.1) @@ -197,11 +198,11 @@ GEM i18n (>= 1.6, < 2) faraday (1.0.1) multipart-post (>= 1.2, < 3) - faraday_middleware (1.0.0) + faraday_middleware (1.2.0) faraday (~> 1.0) - fcm (1.0.3) + fcm (1.0.5) faraday (~> 1) - ffi (1.15.4) + ffi (1.15.5) flag_shih_tzu (0.3.23) flay (2.12.1) erubis (~> 2.7.0) @@ -218,11 +219,11 @@ GEM googleapis-common-protos-types (>= 1.0.4, < 2.0) googleauth (~> 0.9) grpc (~> 1.25) - geocoder (1.7.0) - gli (2.20.1) + geocoder (1.7.3) + gli (2.21.0) globalid (1.0.0) activesupport (>= 5.0) - google-apis-core (0.4.1) + google-apis-core (0.4.2) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) httpclient (>= 2.8.1, < 3.a) @@ -231,9 +232,9 @@ GEM retriable (>= 2.0, < 4.a) rexml webrick - google-apis-iamcredentials_v1 (0.7.0) + google-apis-iamcredentials_v1 (0.10.0) google-apis-core (>= 0.4, < 2.a) - google-apis-storage_v1 (0.8.0) + google-apis-storage_v1 (0.11.0) google-apis-core (>= 0.4, < 2.a) google-cloud-core (1.6.0) google-cloud-env (~> 1.0) @@ -247,22 +248,22 @@ GEM google-cloud-env (1.5.0) faraday (>= 0.17.3, < 2.0) google-cloud-errors (1.2.0) - google-cloud-storage (1.34.1) - addressable (~> 2.5) + google-cloud-storage (1.36.1) + addressable (~> 2.8) digest-crc (~> 0.4) google-apis-iamcredentials_v1 (~> 0.1) google-apis-storage_v1 (~> 0.1) google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - google-protobuf (3.19.2) - google-protobuf (3.19.2-x86_64-darwin) - google-protobuf (3.19.2-x86_64-linux) + google-protobuf (3.19.4) + google-protobuf (3.19.4-x86_64-darwin) + google-protobuf (3.19.4-x86_64-linux) googleapis-common-protos (1.3.12) google-protobuf (~> 3.14) googleapis-common-protos-types (~> 1.2) grpc (~> 1.27) - googleapis-common-protos-types (1.2.0) + googleapis-common-protos-types (1.3.0) google-protobuf (~> 3.14) googleauth (0.17.1) faraday (>= 0.17.3, < 2.0) @@ -271,25 +272,25 @@ GEM multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (~> 0.15) - groupdate (5.2.2) - activesupport (>= 5) - grpc (1.41.0) - google-protobuf (~> 3.17) + groupdate (6.0.1) + activesupport (>= 5.2) + grpc (1.43.1) + google-protobuf (~> 3.18) googleapis-common-protos-types (~> 1.0) - grpc (1.41.0-universal-darwin) - google-protobuf (~> 3.17) + grpc (1.43.1-universal-darwin) + google-protobuf (~> 3.18) googleapis-common-protos-types (~> 1.0) - grpc (1.41.0-x86_64-linux) - google-protobuf (~> 3.17) + grpc (1.43.1-x86_64-linux) + google-protobuf (~> 3.18) googleapis-common-protos-types (~> 1.0) haikunator (1.1.1) - hairtrigger (0.2.24) - activerecord (>= 5.0, < 7) + hairtrigger (0.2.25) + activerecord (>= 5.0, < 8) ruby2ruby (~> 2.4) ruby_parser (~> 3.10) hana (1.3.7) hashdiff (1.0.1) - hashie (4.1.0) + hashie (5.0.0) hkdf (0.3.0) html2text (0.2.1) nokogiri (~> 1.6) @@ -300,50 +301,52 @@ GEM mime-types (~> 3.0) multi_xml (>= 0.5.2) httpclient (2.8.3) - i18n (1.9.1) + i18n (1.10.0) concurrent-ruby (~> 1.0) image_processing (1.12.1) mini_magick (>= 4.9.5, < 5) ruby-vips (>= 2.0.17, < 3) - jbuilder (2.11.2) + jbuilder (2.11.5) + actionview (>= 5.0.0) activesupport (>= 5.0.0) - jmespath (1.4.0) + jmespath (1.6.0) jquery-rails (4.4.0) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - json (2.5.1) - json_refs (0.1.6) + json (2.6.1) + json_refs (0.1.7) hana - json_schemer (0.2.18) + json_schemer (0.2.19) ecma-re-validator (~> 0.3) hana (~> 1.3) regexp_parser (~> 2.0) uri_template (~> 0.7) jwt (2.3.0) - kaminari (1.2.1) + kaminari (1.2.2) activesupport (>= 4.1.0) - kaminari-actionview (= 1.2.1) - kaminari-activerecord (= 1.2.1) - kaminari-core (= 1.2.1) - kaminari-actionview (1.2.1) + kaminari-actionview (= 1.2.2) + kaminari-activerecord (= 1.2.2) + kaminari-core (= 1.2.2) + kaminari-actionview (1.2.2) actionview - kaminari-core (= 1.2.1) - kaminari-activerecord (1.2.1) + kaminari-core (= 1.2.2) + kaminari-activerecord (1.2.2) activerecord - kaminari-core (= 1.2.1) - kaminari-core (1.2.1) - koala (3.0.0) + kaminari-core (= 1.2.2) + kaminari-core (1.2.2) + koala (3.1.0) addressable - faraday + faraday (< 2) json (>= 1.8) + rexml launchy (2.5.0) addressable (~> 2.7) letter_opener (1.7.0) launchy (~> 2.2) - line-bot-api (1.22.0) + line-bot-api (1.23.0) liquid (5.1.0) - listen (3.7.0) + listen (3.7.1) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) loofah (2.14.0) @@ -355,43 +358,43 @@ GEM maxminddb (0.1.22) memoist (0.16.2) method_source (1.0.0) - mime-types (3.3.1) + mime-types (3.4.1) mime-types-data (~> 3.2015) - mime-types-data (3.2021.0901) + mime-types-data (3.2022.0105) mini_magick (4.11.0) mini_mime (1.1.2) - mini_portile2 (2.5.3) + mini_portile2 (2.8.0) minitest (5.15.0) - mock_redis (0.29.0) + mock_redis (0.30.0) ruby2_keywords - momentjs-rails (2.20.1) + momentjs-rails (2.29.1.1) railties (>= 3.1) - msgpack (1.4.2) + msgpack (1.4.5) multi_json (1.15.0) multi_xml (0.6.0) multipart-post (2.1.1) net-http-persistent (4.0.1) connection_pool (~> 2.2) netrc (0.11.0) - newrelic_rpm (8.0.0) + newrelic_rpm (8.4.0) nio4r (2.5.8) - nokogiri (1.11.7) - mini_portile2 (~> 2.5.0) + nokogiri (1.13.3) + mini_portile2 (~> 2.8.0) racc (~> 1.4) - nokogiri (1.11.7-arm64-darwin) + nokogiri (1.13.3-arm64-darwin) racc (~> 1.4) - nokogiri (1.11.7-x86_64-darwin) + nokogiri (1.13.3-x86_64-darwin) racc (~> 1.4) - nokogiri (1.11.7-x86_64-linux) + nokogiri (1.13.3-x86_64-linux) racc (~> 1.4) - oauth (0.5.6) + oauth (0.5.8) orm_adapter (0.5.0) - os (1.1.1) + os (1.1.4) parallel (1.21.0) - parser (3.0.2.0) + parser (3.1.1.0) ast (~> 2.4.1) path_expander (1.1.0) - pg (1.2.3) + pg (1.3.2) procore-sift (0.16.0) rails (> 4.2.0) pry (0.14.1) @@ -402,16 +405,16 @@ GEM public_suffix (4.0.6) puma (5.6.2) nio4r (~> 2.0) - pundit (2.1.1) + pundit (2.2.0) activesupport (>= 3.0.0) raabro (1.4.0) racc (1.6.0) rack (2.2.3) - rack-attack (6.5.0) + rack-attack (6.6.0) rack (>= 1.0, < 3) rack-cors (1.1.1) rack (>= 2.0.0) - rack-proxy (0.7.0) + rack-proxy (0.7.2) rack rack-test (1.1.0) rack (>= 1.0, < 3) @@ -442,15 +445,15 @@ GEM method_source rake (>= 0.13) thor (~> 1.0) - rainbow (3.0.0) + rainbow (3.1.1) rake (13.0.6) - rb-fsevent (0.11.0) + rb-fsevent (0.11.1) rb-inotify (0.10.1) ffi (~> 1.0) - redis (4.5.1) + redis (4.6.0) redis-namespace (1.8.1) redis (>= 3.0.4) - regexp_parser (2.1.1) + regexp_parser (2.2.1) representable (3.1.1) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) @@ -465,19 +468,19 @@ GEM netrc (~> 0.8) retriable (3.1.2) rexml (3.2.5) - rspec (3.10.0) - rspec-core (~> 3.10.0) - rspec-expectations (~> 3.10.0) - rspec-mocks (~> 3.10.0) - rspec-core (3.10.1) - rspec-support (~> 3.10.0) - rspec-expectations (3.10.1) + rspec (3.11.0) + rspec-core (~> 3.11.0) + rspec-expectations (~> 3.11.0) + rspec-mocks (~> 3.11.0) + rspec-core (3.11.0) + rspec-support (~> 3.11.0) + rspec-expectations (3.11.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.10.0) - rspec-mocks (3.10.2) + rspec-support (~> 3.11.0) + rspec-mocks (3.11.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.10.0) - rspec-rails (5.0.2) + rspec-support (~> 3.11.0) + rspec-rails (5.0.3) actionpack (>= 5.2) activesupport (>= 5.2) railties (>= 5.2) @@ -485,36 +488,36 @@ GEM rspec-expectations (~> 3.10) rspec-mocks (~> 3.10) rspec-support (~> 3.10) - rspec-support (3.10.2) - rubocop (1.22.1) + rspec-support (3.11.0) + rubocop (1.25.1) parallel (~> 1.10) - parser (>= 3.0.0.0) + parser (>= 3.1.0.0) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml - rubocop-ast (>= 1.12.0, < 2.0) + rubocop-ast (>= 1.15.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.12.0) - parser (>= 3.0.1.1) - rubocop-performance (1.11.5) + rubocop-ast (1.16.0) + parser (>= 3.1.1.0) + rubocop-performance (1.13.2) rubocop (>= 1.7.0, < 2.0) rubocop-ast (>= 0.4.0) - rubocop-rails (2.12.3) + rubocop-rails (2.13.2) activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.7.0, < 2.0) - rubocop-rspec (2.5.0) + rubocop-rspec (2.8.0) rubocop (~> 1.19) ruby-progressbar (1.11.0) - ruby-vips (2.1.3) + ruby-vips (2.1.4) ffi (~> 1.12) ruby2_keywords (0.0.5) ruby2ruby (2.4.4) ruby_parser (~> 3.1) sexp_processor (~> 4.6) - ruby_parser (3.17.0) - sexp_processor (~> 4.15, >= 4.15.1) + ruby_parser (3.18.1) + sexp_processor (~> 4.16) sassc (2.4.0) ffi (~> 1.9) sassc-rails (2.1.2) @@ -523,30 +526,28 @@ GEM sprockets (> 3.0) sprockets-rails tilt - scout_apm (4.1.2) + scout_apm (5.1.1) parser seed_dump (3.3.1) activerecord (>= 4) activesupport (>= 4) selectize-rails (0.12.6) semantic_range (3.0.0) - sentry-rails (4.7.3) + sentry-rails (5.1.0) railties (>= 5.0) - sentry-ruby-core (~> 4.7.0) - sentry-ruby (4.7.3) + sentry-ruby-core (~> 5.1.0) + sentry-ruby (5.1.0) concurrent-ruby (~> 1.0, >= 1.0.2) - faraday (>= 1.0) - sentry-ruby-core (= 4.7.3) - sentry-ruby-core (4.7.3) + sentry-ruby-core (= 5.1.0) + sentry-ruby-core (5.1.0) concurrent-ruby - faraday - sentry-sidekiq (4.7.3) - sentry-ruby-core (~> 4.7.0) + sentry-sidekiq (5.1.0) + sentry-ruby-core (~> 5.1.0) sidekiq (>= 3.0) - sexp_processor (4.15.3) - shoulda-matchers (5.0.0) + sexp_processor (4.16.0) + shoulda-matchers (5.1.0) activesupport (>= 5.2.0) - sidekiq (6.4.0) + sidekiq (6.4.1) connection_pool (>= 2.2.2) rack (~> 2.0) redis (>= 4.2.0) @@ -563,7 +564,7 @@ GEM json (>= 1.8, < 3) simplecov-html (~> 0.10.0) simplecov-html (0.10.2) - slack-ruby-client (0.17.0) + slack-ruby-client (1.0.0) faraday (>= 1.0) faraday_middleware gli @@ -582,13 +583,13 @@ GEM sprockets (>= 3.0.0) squasher (0.6.2) statsd-ruby (1.5.0) - telephone_number (1.4.12) + telephone_number (1.4.13) thor (1.2.1) tilt (2.0.10) time_diff (0.3.0) activesupport i18n - trailblazer-option (0.1.1) + trailblazer-option (0.1.2) twilio-ruby (5.32.0) faraday (~> 1.0.0) jwt (>= 1.5, <= 2.5) @@ -597,7 +598,7 @@ GEM oauth tzinfo (2.0.4) concurrent-ruby (~> 1.0) - tzinfo-data (1.2021.3) + tzinfo-data (1.2021.5) tzinfo (>= 1.0.0) uber (0.1.0) uglifier (4.2.0) @@ -608,12 +609,12 @@ GEM unicode-display_width (2.1.0) uniform_notifier (1.14.2) uri_template (0.7.0) - valid_email2 (4.0.0) + valid_email2 (4.0.3) activemodel (>= 3.2) mail (~> 2.5) warden (1.2.9) rack (>= 2.0.9) - web-console (4.1.0) + web-console (4.2.0) actionview (>= 6.0.0) activemodel (>= 6.0.0) bindex (>= 0.4.0) diff --git a/app/builders/v2/report_builder.rb b/app/builders/v2/report_builder.rb index c2900c7e6..7f0dd470c 100644 --- a/app/builders/v2/report_builder.rb +++ b/app/builders/v2/report_builder.rb @@ -7,6 +7,9 @@ class V2::ReportBuilder def initialize(account, params) @account = account @params = params + + timezone_offset = (params[:timezone_offset] || 0).to_f + @timezone = ActiveSupport::TimeZone[timezone_offset]&.name end def timeseries @@ -64,60 +67,58 @@ class V2::ReportBuilder @team ||= account.teams.find(params[:id]) end + def get_grouped_values(object_scope) + object_scope.group_by_period( + params[:group_by] || DEFAULT_GROUP_BY, + :created_at, + default_value: 0, + range: range, + permit: %w[day week month year], + time_zone: @timezone + ) + end + def conversations_count - scope.conversations - .group_by_period(params[:group_by] || DEFAULT_GROUP_BY, - :created_at, range: range, default_value: 0, permit: %w[day week month year]) - .count + (get_grouped_values scope.conversations).count end def incoming_messages_count - scope.messages.incoming.unscope(:order) - .group_by_period(params[:group_by] || DEFAULT_GROUP_BY, - :created_at, range: range, default_value: 0, permit: %w[day week month year]) - .count + (get_grouped_values scope.messages.incoming.unscope(:order)).count end def outgoing_messages_count - scope.messages.outgoing.unscope(:order) - .group_by_period(params[:group_by] || DEFAULT_GROUP_BY, - :created_at, range: range, default_value: 0, permit: %w[day week month year]) - .count + (get_grouped_values scope.messages.outgoing.unscope(:order)).count end def resolutions_count - scope.conversations - .resolved - .group_by_period(params[:group_by] || DEFAULT_GROUP_BY, - :created_at, range: range, default_value: 0, permit: %w[day week month year]) - .count + (get_grouped_values scope.conversations.resolved).count end def avg_first_response_time - scope.events - .where(name: 'first_response') - .group_by_day(:created_at, range: range, default_value: 0) - .average(:value) + (get_grouped_values scope.events.where(name: 'first_response')).average(:value) end def avg_resolution_time - scope.events.where(name: 'conversation_resolved') - .group_by_day(:created_at, range: range, default_value: 0) - .average(:value) + (get_grouped_values scope.events.where(name: 'conversation_resolved')).average(:value) end - # Taking average of average is not too accurate - # https://en.wikipedia.org/wiki/Simpson's_paradox - # TODO: Will optimize this later def avg_resolution_time_summary - return 0 if avg_resolution_time.values.empty? + avg_rt = scope.events + .where(name: 'conversation_resolved', created_at: range) + .average(:value) - (avg_resolution_time.values.sum / avg_resolution_time.values.length) + return 0 if avg_rt.blank? + + avg_rt end def avg_first_response_time_summary - return 0 if avg_first_response_time.values.empty? + avg_frt = scope.events + .where(name: 'first_response', created_at: range) + .average(:value) - (avg_first_response_time.values.sum / avg_first_response_time.values.length) + return 0 if avg_frt.blank? + + avg_frt end end diff --git a/app/controllers/api/v1/accounts/bulk_actions_controller.rb b/app/controllers/api/v1/accounts/bulk_actions_controller.rb new file mode 100644 index 000000000..832f5a49b --- /dev/null +++ b/app/controllers/api/v1/accounts/bulk_actions_controller.rb @@ -0,0 +1,26 @@ +class Api::V1::Accounts::BulkActionsController < Api::V1::Accounts::BaseController + before_action :type_matches? + + def create + if type_matches? + ::BulkActionsJob.perform_later( + account: @current_account, + user: current_user, + params: permitted_params + ) + head :ok + else + render json: { success: false }, status: :unprocessable_entity + end + end + + private + + def type_matches? + ['Conversation'].include?(params[:type]) + end + + def permitted_params + params.permit(:type, ids: [], fields: [:status, :assignee_id, :team_id], labels: [add: [], remove: []]) + end +end diff --git a/app/controllers/api/v1/accounts/inboxes_controller.rb b/app/controllers/api/v1/accounts/inboxes_controller.rb index e0c963d25..2bda5c07a 100644 --- a/app/controllers/api/v1/accounts/inboxes_controller.rb +++ b/app/controllers/api/v1/accounts/inboxes_controller.rb @@ -138,7 +138,7 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController end def get_channel_attributes(channel_type) - if channel_type.constantize.const_defined?('EDITABLE_ATTRS') + if channel_type.constantize.const_defined?(:EDITABLE_ATTRS) channel_type.constantize::EDITABLE_ATTRS.presence else [] diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index f1755b921..e75d3f852 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -4,6 +4,7 @@ class Api::V1::AccountsController < Api::BaseController skip_before_action :authenticate_user!, :set_current_user, :handle_with_exception, only: [:create], raise: false before_action :check_signup_enabled, only: [:create] + before_action :validate_captcha, only: [:create] before_action :fetch_account, except: [:create] before_action :check_authorization, except: [:create] @@ -58,6 +59,10 @@ class Api::V1::AccountsController < Api::BaseController raise ActionController::RoutingError, 'Not Found' if GlobalConfigService.load('ENABLE_ACCOUNT_SIGNUP', 'false') == 'false' end + def validate_captcha + raise ActionController::InvalidAuthenticityToken, 'Invalid Captcha' unless ChatwootCaptcha.new(params[:h_captcha_client_response]).valid? + end + def pundit_user { user: current_user, diff --git a/app/controllers/api/v2/accounts/reports_controller.rb b/app/controllers/api/v2/accounts/reports_controller.rb index 9801d760d..a83611676 100644 --- a/app/controllers/api/v2/accounts/reports_controller.rb +++ b/app/controllers/api/v2/accounts/reports_controller.rb @@ -58,7 +58,8 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController since: params[:since], until: params[:until], id: params[:id], - group_by: params[:group_by] + group_by: params[:group_by], + timezone_offset: params[:timezone_offset] } end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 52c6c87af..2bb8e9847 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -26,7 +26,10 @@ class DashboardController < ActionController::Base 'API_CHANNEL_THUMBNAIL', 'ANALYTICS_TOKEN', 'ANALYTICS_HOST', - 'DIRECT_UPLOADS_ENABLED' + 'DIRECT_UPLOADS_ENABLED', + 'HCAPTCHA_SITE_KEY', + 'LOGOUT_REDIRECT_LINK', + 'DISABLE_USER_PROFILE_UPDATE' ).merge(app_config) end diff --git a/app/controllers/widget_tests_controller.rb b/app/controllers/widget_tests_controller.rb index 22a8c0da8..fff47d907 100644 --- a/app/controllers/widget_tests_controller.rb +++ b/app/controllers/widget_tests_controller.rb @@ -1,5 +1,8 @@ class WidgetTestsController < ActionController::Base - before_action :set_web_widget + before_action :ensure_web_widget + before_action :ensure_widget_position + before_action :ensure_widget_type + before_action :ensure_widget_style def index render @@ -7,7 +10,24 @@ class WidgetTestsController < ActionController::Base private - def set_web_widget - @web_widget = Channel::WebWidget.first + def ensure_widget_style + @widget_style = params[:widget_style] || 'standard' + end + + def ensure_widget_position + @widget_position = params[:position] || 'left' + end + + def ensure_widget_type + @widget_type = params[:type] || 'expanded_bubble' + end + + def inbox_id + @inbox_id ||= params[:inbox_id] || Channel::WebWidget.first.inbox.id + end + + def ensure_web_widget + @inbox = Inbox.find(inbox_id) + @web_widget = @inbox.channel end end diff --git a/app/javascript/dashboard/api/auth.js b/app/javascript/dashboard/api/auth.js index 7079614c1..18ec9e811 100644 --- a/app/javascript/dashboard/api/auth.js +++ b/app/javascript/dashboard/api/auth.js @@ -30,6 +30,7 @@ export default { user_full_name: creds.fullName.trim(), email: creds.email, password: creds.password, + h_captcha_client_response: creds.hCaptchaClientResponse, }) .then(response => { setAuthCredentials(response); diff --git a/app/javascript/dashboard/api/reports.js b/app/javascript/dashboard/api/reports.js index dbb2bf08d..90f8b34ea 100644 --- a/app/javascript/dashboard/api/reports.js +++ b/app/javascript/dashboard/api/reports.js @@ -1,6 +1,8 @@ /* global axios */ import ApiClient from './ApiClient'; +const getTimeOffset = () => -new Date().getTimezoneOffset() / 60; + class ReportsAPI extends ApiClient { constructor() { super('reports', { accountScoped: true, apiVersion: 'v2' }); @@ -8,13 +10,27 @@ class ReportsAPI extends ApiClient { getReports(metric, since, until, type = 'account', id, group_by) { return axios.get(`${this.url}`, { - params: { metric, since, until, type, id, group_by }, + params: { + metric, + since, + until, + type, + id, + group_by, + timezone_offset: getTimeOffset(), + }, }); } getSummary(since, until, type = 'account', id, group_by) { return axios.get(`${this.url}/summary`, { - params: { since, until, type, id, group_by }, + params: { + since, + until, + type, + id, + group_by, + }, }); } diff --git a/app/javascript/dashboard/api/specs/reports.spec.js b/app/javascript/dashboard/api/specs/reports.spec.js index f3b66996a..efde84fe4 100644 --- a/app/javascript/dashboard/api/specs/reports.spec.js +++ b/app/javascript/dashboard/api/specs/reports.spec.js @@ -27,6 +27,7 @@ describe('#Reports API', () => { since: 1621103400, until: 1621621800, type: 'account', + timezone_offset: -0, }, }); }); diff --git a/app/javascript/dashboard/assets/scss/_utility-helpers.scss b/app/javascript/dashboard/assets/scss/_utility-helpers.scss index 7b528d1c2..8a85dc6a5 100644 --- a/app/javascript/dashboard/assets/scss/_utility-helpers.scss +++ b/app/javascript/dashboard/assets/scss/_utility-helpers.scss @@ -54,3 +54,9 @@ .text-y-800 { color: var(--y-800); } + +.text-ellipsis { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/app/javascript/dashboard/components/ChatList.vue b/app/javascript/dashboard/components/ChatList.vue index 4173ed438..deb0276bf 100644 --- a/app/javascript/dashboard/components/ChatList.vue +++ b/app/javascript/dashboard/components/ChatList.vue @@ -219,7 +219,7 @@ export default { folders: 'customViews/getCustomViews', }), hasAppliedFilters() { - return this.appliedFilters.length; + return this.appliedFilters.length !== 0; }, hasActiveFolders() { return this.activeFolder && this.foldersId !== 0; @@ -460,7 +460,8 @@ export default { if (this.hasActiveFolders) { const payload = this.activeFolder.query; this.fetchSavedFilteredConversations(payload); - } else { + } + if (this.hasAppliedFilters) { this.fetchFilteredConversations(this.appliedFilters); } }, diff --git a/app/javascript/dashboard/components/NetworkNotification.vue b/app/javascript/dashboard/components/NetworkNotification.vue index deb5931c4..c716731d5 100644 --- a/app/javascript/dashboard/components/NetworkNotification.vue +++ b/app/javascript/dashboard/components/NetworkNotification.vue @@ -30,6 +30,7 @@ + diff --git a/app/javascript/dashboard/components/layout/sidebarComponents/Secondary.vue b/app/javascript/dashboard/components/layout/sidebarComponents/Secondary.vue index da14cf295..6222490e4 100644 --- a/app/javascript/dashboard/components/layout/sidebarComponents/Secondary.vue +++ b/app/javascript/dashboard/components/layout/sidebarComponents/Secondary.vue @@ -1,5 +1,6 @@