Merge branch 'develop' into feature/conversation-refactor
This commit is contained in:
commit
08175617af
66 changed files with 803 additions and 363 deletions
32
app/builders/notification_builder.rb
Normal file
32
app/builders/notification_builder.rb
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
class NotificationBuilder
|
||||||
|
pattr_initialize [:notification_type!, :user!, :account!, :primary_actor!]
|
||||||
|
|
||||||
|
def perform
|
||||||
|
return unless user_subscribed_to_notification?
|
||||||
|
|
||||||
|
build_notification
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def secondary_actor
|
||||||
|
Current.user
|
||||||
|
end
|
||||||
|
|
||||||
|
def user_subscribed_to_notification?
|
||||||
|
notification_setting = user.notification_settings.find_by(account_id: account.id)
|
||||||
|
return true if notification_setting.public_send("email_#{notification_type}?")
|
||||||
|
return true if notification_setting.public_send("push_#{notification_type}?")
|
||||||
|
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_notification
|
||||||
|
user.notifications.create!(
|
||||||
|
notification_type: notification_type,
|
||||||
|
account: account,
|
||||||
|
primary_actor: primary_actor,
|
||||||
|
secondary_actor: secondary_actor
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,11 +4,6 @@ class Api::V1::Accounts::ContactsController < Api::BaseController
|
||||||
before_action :check_authorization
|
before_action :check_authorization
|
||||||
before_action :fetch_contact, only: [:show, :update]
|
before_action :fetch_contact, only: [:show, :update]
|
||||||
|
|
||||||
skip_before_action :authenticate_user!, only: [:create]
|
|
||||||
skip_before_action :set_current_user, only: [:create]
|
|
||||||
skip_before_action :check_subscription, only: [:create]
|
|
||||||
skip_around_action :handle_with_exception, only: [:create]
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@contacts = current_account.contacts
|
@contacts = current_account.contacts
|
||||||
end
|
end
|
||||||
|
|
21
app/controllers/api/v1/accounts/notifications_controller.rb
Normal file
21
app/controllers/api/v1/accounts/notifications_controller.rb
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
class Api::V1::Accounts::NotificationsController < Api::BaseController
|
||||||
|
protect_from_forgery with: :null_session
|
||||||
|
|
||||||
|
before_action :fetch_notification, only: [:update]
|
||||||
|
|
||||||
|
def index
|
||||||
|
@notifications = current_user.notifications.where(account_id: current_account.id)
|
||||||
|
render json: @notifications
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@notification.update(read_at: DateTime.now.utc)
|
||||||
|
render json: @notification
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def fetch_notification
|
||||||
|
@notification = current_user.notifications.find(params[:id])
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,18 @@
|
||||||
|
class Api::V1::NotificationSubscriptionsController < Api::BaseController
|
||||||
|
before_action :set_user
|
||||||
|
|
||||||
|
def create
|
||||||
|
notification_subscription = @user.notification_subscriptions.create(notification_subscription_params)
|
||||||
|
render json: notification_subscription
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_user
|
||||||
|
@user = current_user
|
||||||
|
end
|
||||||
|
|
||||||
|
def notification_subscription_params
|
||||||
|
params.require(:notification_subscription).permit(:subscription_type, subscription_attributes: {})
|
||||||
|
end
|
||||||
|
end
|
|
@ -9,8 +9,7 @@ class AsyncDispatcher < BaseDispatcher
|
||||||
end
|
end
|
||||||
|
|
||||||
def listeners
|
def listeners
|
||||||
listeners = [EmailNotificationListener.instance, ReportingListener.instance, WebhookListener.instance]
|
listeners = [EventListener.instance, ReportingListener.instance, WebhookListener.instance]
|
||||||
listeners << EventListener.instance
|
|
||||||
listeners << SubscriptionListener.instance if ENV['BILLING_ENABLED']
|
listeners << SubscriptionListener.instance if ENV['BILLING_ENABLED']
|
||||||
listeners
|
listeners
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,6 +5,6 @@ class SyncDispatcher < BaseDispatcher
|
||||||
end
|
end
|
||||||
|
|
||||||
def listeners
|
def listeners
|
||||||
[ActionCableListener.instance, AgentBotListener.instance]
|
[ActionCableListener.instance, AgentBotListener.instance, NotificationListener.instance]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -49,10 +49,10 @@ $global-font-size: 10px;
|
||||||
$global-width: 100%;
|
$global-width: 100%;
|
||||||
$global-lineheight: 1.5;
|
$global-lineheight: 1.5;
|
||||||
$foundation-palette: (primary: $color-woot,
|
$foundation-palette: (primary: $color-woot,
|
||||||
secondary: #777,
|
secondary: #35c5ff,
|
||||||
success: #13ce66,
|
success: #44ce4b,
|
||||||
warning: #ffc82c,
|
warning: #ffc532,
|
||||||
alert: #ff4949);
|
alert: #ff382d);
|
||||||
$light-gray: #c0ccda;
|
$light-gray: #c0ccda;
|
||||||
$medium-gray: #8492a6;
|
$medium-gray: #8492a6;
|
||||||
$dark-gray: $color-gray;
|
$dark-gray: $color-gray;
|
||||||
|
@ -127,7 +127,7 @@ $header-styles: (small: ("h1": ("font-size": 24),
|
||||||
$header-text-rendering: optimizeLegibility;
|
$header-text-rendering: optimizeLegibility;
|
||||||
$small-font-size: 80%;
|
$small-font-size: 80%;
|
||||||
$header-small-font-color: $medium-gray;
|
$header-small-font-color: $medium-gray;
|
||||||
$paragraph-lineheight: 1.6;
|
$paragraph-lineheight: 1.45;
|
||||||
$paragraph-margin-bottom: 1rem;
|
$paragraph-margin-bottom: 1rem;
|
||||||
$paragraph-text-rendering: optimizeLegibility;
|
$paragraph-text-rendering: optimizeLegibility;
|
||||||
$code-color: $black;
|
$code-color: $black;
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
background: $color-white;
|
background: $color-white;
|
||||||
border-radius: $space-large;
|
border-radius: $space-large;
|
||||||
left: 0;
|
left: 0;
|
||||||
margin: $space-slab 0 auto;
|
margin: $space-slab auto;
|
||||||
padding: $space-normal;
|
padding: $space-normal;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,8 @@ $color-gray: #6e6f73;
|
||||||
$color-light-gray: #999a9b;
|
$color-light-gray: #999a9b;
|
||||||
$color-border: #e0e6ed;
|
$color-border: #e0e6ed;
|
||||||
$color-border-light: #f0f4f5;
|
$color-border-light: #f0f4f5;
|
||||||
$color-background: #eff2f7;
|
$color-background: #f4f6fb;
|
||||||
|
$color-border-dark: #cad0d4;
|
||||||
$color-background-light: #f9fafc;
|
$color-background-light: #f9fafc;
|
||||||
$color-white: #fff;
|
$color-white: #fff;
|
||||||
$color-body: #3c4858;
|
$color-body: #3c4858;
|
||||||
|
@ -54,11 +55,10 @@ $color-heading: #1f2d3d;
|
||||||
$color-extra-light-blue: #f5f7f9;
|
$color-extra-light-blue: #f5f7f9;
|
||||||
|
|
||||||
$primary-color: $color-woot;
|
$primary-color: $color-woot;
|
||||||
$secondary-color: #ff5216;
|
$secondary-color: #35c5ff;
|
||||||
$success-color: #13ce66;
|
$success-color: #44ce4b;
|
||||||
$warning-color: #ffc82c;
|
$warning-color: #ffc532;
|
||||||
$alert-color: #ff4949;
|
$alert-color: #ff382d;
|
||||||
|
|
||||||
// Color-palettes
|
// Color-palettes
|
||||||
|
|
||||||
$color-primary-light: #c7e3ff;
|
$color-primary-light: #c7e3ff;
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
@include padding($space-normal $space-two $zero);
|
@include padding($space-normal $space-two $zero);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conversation header - Light BG
|
// Conversation header - Light BG
|
||||||
.settings-header {
|
.settings-header {
|
||||||
@include padding($space-small $space-normal);
|
@include padding($space-small $space-normal);
|
||||||
|
@ -14,6 +15,7 @@
|
||||||
@include border-normal-bottom;
|
@include border-normal-bottom;
|
||||||
height: $header-height;
|
height: $header-height;
|
||||||
min-height: $header-height;
|
min-height: $header-height;
|
||||||
|
|
||||||
// Resolve Button
|
// Resolve Button
|
||||||
.button {
|
.button {
|
||||||
@include margin(0);
|
@include margin(0);
|
||||||
|
@ -83,7 +85,7 @@
|
||||||
background: $color-woot;
|
background: $color-woot;
|
||||||
}
|
}
|
||||||
|
|
||||||
& + .item {
|
&+.item {
|
||||||
&::before {
|
&::before {
|
||||||
background: $color-woot;
|
background: $color-woot;
|
||||||
}
|
}
|
||||||
|
@ -112,7 +114,7 @@
|
||||||
background: $color-border;
|
background: $color-border;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
color: $color-white;
|
color: $color-white;
|
||||||
font-size: $font-size-small;
|
font-size: $font-size-micro;
|
||||||
font-weight: $font-weight-medium;
|
font-weight: $font-weight-medium;
|
||||||
height: $space-normal;
|
height: $space-normal;
|
||||||
left: $space-normal;
|
left: $space-normal;
|
||||||
|
@ -161,6 +163,7 @@
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@include background-gray;
|
@include background-gray;
|
||||||
|
|
||||||
.arrow {
|
.arrow {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateX($space-small);
|
transform: translateX($space-small);
|
||||||
|
@ -228,7 +231,7 @@
|
||||||
@include padding($space-medium);
|
@include padding($space-medium);
|
||||||
}
|
}
|
||||||
|
|
||||||
> a > img {
|
>a>img {
|
||||||
width: $space-larger * 5;
|
width: $space-larger * 5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
.integrations-wrap {
|
.integrations-wrap {
|
||||||
.integration {
|
.integration {
|
||||||
background: $color-white;
|
background: $color-white;
|
||||||
border: 2px solid $color-border;
|
border: 1px solid $color-border;
|
||||||
border-radius: $space-slab;
|
border-radius: $space-smaller;
|
||||||
padding: $space-normal;
|
padding: $space-normal;
|
||||||
|
|
||||||
.integration--image {
|
.integration--image {
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
.user--name {
|
.user--name {
|
||||||
@include margin(0);
|
@include margin(0);
|
||||||
font-size: $font-size-medium;
|
font-size: $font-size-medium;
|
||||||
|
line-height: 1.3;
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,6 +66,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.resolve--button {
|
.button.resolve--button {
|
||||||
|
width: 13.2rem;
|
||||||
|
|
||||||
>.icon {
|
>.icon {
|
||||||
font-size: $font-size-default;
|
font-size: $font-size-default;
|
||||||
padding-right: $space-small;
|
padding-right: $space-small;
|
||||||
|
|
|
@ -2,11 +2,13 @@
|
||||||
@include flex;
|
@include flex;
|
||||||
@include flex-shrink;
|
@include flex-shrink;
|
||||||
@include padding($space-normal $zero $zero $space-normal);
|
@include padding($space-normal $zero $zero $space-normal);
|
||||||
|
border-left: 4px solid transparent;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background: $color-background;
|
background: $color-background;
|
||||||
|
border-left-color: $color-woot;
|
||||||
}
|
}
|
||||||
|
|
||||||
.conversation--details {
|
.conversation--details {
|
||||||
|
@ -64,7 +66,7 @@
|
||||||
background: darken($success-color, 3%);
|
background: darken($success-color, 3%);
|
||||||
color: $color-white;
|
color: $color-white;
|
||||||
display: none;
|
display: none;
|
||||||
font-size: $font-size-mini;
|
font-size: $font-size-micro;
|
||||||
font-weight: $font-weight-medium;
|
font-weight: $font-weight-medium;
|
||||||
height: $unread-size;
|
height: $unread-size;
|
||||||
line-height: $unread-size;
|
line-height: $unread-size;
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
@mixin bubble-with-tyes {
|
@mixin bubble-with-types {
|
||||||
@include padding($space-smaller $space-one);
|
@include padding($space-one $space-normal);
|
||||||
@include margin($zero);
|
@include margin($zero);
|
||||||
background: $color-primary-light;
|
background: $color-woot;
|
||||||
border-radius: $space-small;
|
border-radius: $space-one;
|
||||||
color: $color-heading;
|
color: $color-white;
|
||||||
font-size: $font-size-small;
|
font-size: $font-size-small;
|
||||||
|
font-weight: $font-weight-normal;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
|
@ -15,6 +16,17 @@
|
||||||
|
|
||||||
.message-text__wrap {
|
.message-text__wrap {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
.time {
|
||||||
|
color: $color-primary-light;
|
||||||
|
display: block;
|
||||||
|
font-size: $font-size-micro;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
color: $color-white;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-text {
|
.message-text {
|
||||||
|
@ -51,8 +63,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
$color-black: #000;
|
background-image: linear-gradient(-180deg, transparent 3%, $color-heading 130%);
|
||||||
background-image: linear-gradient(-180deg, transparent 3%, $color-black 70%);
|
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
content: '';
|
content: '';
|
||||||
height: 20%;
|
height: 20%;
|
||||||
|
@ -94,6 +105,7 @@
|
||||||
|
|
||||||
.load-more-conversations {
|
.load-more-conversations {
|
||||||
font-size: $font-size-small;
|
font-size: $font-size-small;
|
||||||
|
margin: 0;
|
||||||
padding: $space-normal;
|
padding: $space-normal;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
@ -122,10 +134,10 @@
|
||||||
|
|
||||||
.status--filter {
|
.status--filter {
|
||||||
@include padding($zero null $zero $space-normal);
|
@include padding($zero null $zero $space-normal);
|
||||||
@include border-light;
|
|
||||||
@include round-corner;
|
@include round-corner;
|
||||||
@include margin($space-smaller $space-slab $zero $zero);
|
@include margin($space-smaller $space-slab $zero $zero);
|
||||||
background-color: $color-background;
|
background-color: $color-background-light;
|
||||||
|
border: 1px solid $color-border;
|
||||||
float: right;
|
float: right;
|
||||||
font-size: $font-size-mini;
|
font-size: $font-size-mini;
|
||||||
height: $space-medium;
|
height: $space-medium;
|
||||||
|
@ -192,162 +204,192 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin-bottom: $space-small;
|
margin-bottom: $space-small;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
li {
|
.conversation-panel>li {
|
||||||
@include flex;
|
@include flex;
|
||||||
@include flex-shrink;
|
@include flex-shrink;
|
||||||
@include margin($zero $zero $space-smaller);
|
@include margin($zero $zero $space-micro);
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: $space-small;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.unread--toast {
|
||||||
|
span {
|
||||||
|
@include elegant-card;
|
||||||
|
@include round-corner;
|
||||||
|
background: $color-woot;
|
||||||
|
color: $color-white;
|
||||||
|
font-size: $font-size-mini;
|
||||||
|
font-weight: $font-weight-medium;
|
||||||
|
margin: $space-one auto;
|
||||||
|
padding: $space-smaller $space-two;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:last-child {
|
.bubble {
|
||||||
margin-bottom: $space-small;
|
@include bubble-with-types;
|
||||||
|
max-width: 50rem;
|
||||||
|
text-align: left;
|
||||||
|
word-wrap: break-word;
|
||||||
|
|
||||||
|
.aplayer {
|
||||||
|
box-shadow: none;
|
||||||
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.unread--toast {
|
&.left {
|
||||||
span {
|
|
||||||
@include elegant-card;
|
.bubble {
|
||||||
@include round-corner;
|
@include border-normal;
|
||||||
background: $color-woot;
|
background: $white;
|
||||||
color: $color-white;
|
border-bottom-left-radius: $space-smaller;
|
||||||
font-size: $font-size-mini;
|
border-top-left-radius: $space-smaller;
|
||||||
font-weight: $font-weight-medium;
|
color: $color-body;
|
||||||
margin: $space-one auto;
|
margin-right: auto;
|
||||||
padding: $space-smaller $space-two;
|
|
||||||
|
.time {
|
||||||
|
color: $color-light-gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.image .time {
|
||||||
|
color: $color-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
color: $color-primary-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file {
|
||||||
|
.text-block-title {
|
||||||
|
color: $color-body;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-wrap {
|
||||||
|
color: $color-woot;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download {
|
||||||
|
color: $color-primary-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+.right {
|
||||||
|
margin-top: $space-one;
|
||||||
|
|
||||||
|
.bubble {
|
||||||
|
border-top-right-radius: $space-one;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&.right {
|
||||||
|
@include flex-align(right, null);
|
||||||
|
|
||||||
|
.wrap {
|
||||||
|
margin-right: $space-normal;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bubble {
|
.bubble {
|
||||||
@include bubble-with-tyes;
|
border-bottom-right-radius: $space-smaller;
|
||||||
max-width: 50rem;
|
border-top-right-radius: $space-smaller;
|
||||||
text-align: left;
|
margin-left: auto;
|
||||||
word-wrap: break-word;
|
|
||||||
|
|
||||||
.aplayer {
|
&.is-private {
|
||||||
box-shadow: none;
|
background: lighten($warning-color, 32%);
|
||||||
font-family: inherit;
|
border: 1px solid $color-border;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.left {
|
|
||||||
.bubble {
|
|
||||||
background: $white;
|
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
border-top-left-radius: 0;
|
|
||||||
color: $color-heading;
|
color: $color-heading;
|
||||||
margin-right: auto;
|
padding-right: $space-large;
|
||||||
}
|
position: relative;
|
||||||
|
|
||||||
+.right {
|
&::before {
|
||||||
margin-top: $space-one;
|
bottom: 0;
|
||||||
|
color: $medium-gray;
|
||||||
|
position: absolute;
|
||||||
|
right: $space-one;
|
||||||
|
top: $space-smaller + $space-micro;
|
||||||
|
}
|
||||||
|
|
||||||
.bubble {
|
.time {
|
||||||
border-top-right-radius: $space-small;
|
color: $color-light-gray;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.right {
|
+.left {
|
||||||
@include flex-align(right, null);
|
margin-top: $space-one;
|
||||||
|
|
||||||
.wrap {
|
|
||||||
margin-right: $space-normal;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bubble {
|
.bubble {
|
||||||
border-bottom-right-radius: 0;
|
border-top-left-radius: $space-one;
|
||||||
border-top-right-radius: 0;
|
|
||||||
margin-left: auto;
|
|
||||||
|
|
||||||
&.is-private {
|
|
||||||
background: lighten($warning-color, 32%);
|
|
||||||
color: $color-heading;
|
|
||||||
padding-right: $space-large;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
bottom: 0;
|
|
||||||
color: $medium-gray;
|
|
||||||
position: absolute;
|
|
||||||
right: $space-one;
|
|
||||||
top: $space-smaller + $space-micro;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
+.left {
|
|
||||||
margin-top: $space-one;
|
|
||||||
|
|
||||||
.bubble {
|
|
||||||
border-top-left-radius: $space-small;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.wrap {
|
.wrap {
|
||||||
@include margin($zero $space-normal);
|
@include margin($zero $space-normal);
|
||||||
max-width: 69%;
|
max-width: 69%;
|
||||||
|
|
||||||
.sender--name {
|
.sender--name {
|
||||||
font-size: $font-size-mini;
|
font-size: $font-size-mini;
|
||||||
margin-bottom: $space-smaller;
|
margin-bottom: $space-smaller;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender--thumbnail {
|
||||||
|
@include round-corner();
|
||||||
|
height: $space-slab;
|
||||||
|
margin-right: $space-one;
|
||||||
|
margin-top: $space-micro;
|
||||||
|
width: $space-slab;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-wrap {
|
||||||
|
@include flex;
|
||||||
|
@include margin($space-small auto);
|
||||||
|
@include padding($space-small $space-normal);
|
||||||
|
@include flex-align($x: center, $y: null);
|
||||||
|
background: lighten($warning-color, 32%);
|
||||||
|
border: 1px solid lighten($warning-color, 22%);
|
||||||
|
border-radius: $space-smaller;
|
||||||
|
font-size: $font-size-small;
|
||||||
|
|
||||||
.activity-wrap {
|
p {
|
||||||
@include flex;
|
color: $color-heading;
|
||||||
@include margin($space-small auto);
|
margin-bottom: $zero;
|
||||||
@include padding($space-smaller $space-normal);
|
|
||||||
@include flex-align($x: center, $y: null);
|
|
||||||
background: lighten($warning-color, 32%);
|
|
||||||
border: 1px solid lighten($warning-color, 26%);
|
|
||||||
border-radius: $space-smaller;
|
|
||||||
font-size: $font-size-small;
|
|
||||||
|
|
||||||
p {
|
.ion-person {
|
||||||
color: $color-heading;
|
color: $color-body;
|
||||||
margin-bottom: $zero;
|
font-size: $font-size-default;
|
||||||
|
margin-right: $space-small;
|
||||||
.ion-person {
|
position: relative;
|
||||||
color: $color-body;
|
top: $space-micro;
|
||||||
font-size: $font-size-default;
|
|
||||||
margin-right: $space-small;
|
|
||||||
position: relative;
|
|
||||||
top: $space-micro;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-text__wrap {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-text {
|
|
||||||
&::after {
|
|
||||||
content: ' \00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0';
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.time {
|
.message-text__wrap {
|
||||||
color: $medium-gray;
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-text {
|
||||||
|
&::after {
|
||||||
|
content: ' \00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0';
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.time {
|
.time {
|
||||||
bottom: -$space-micro;
|
color: $medium-gray;
|
||||||
color: $color-gray;
|
|
||||||
float: right;
|
|
||||||
font-size: $font-size-micro;
|
font-size: $font-size-micro;
|
||||||
font-style: italic;
|
|
||||||
margin-left: $space-slab;
|
margin-left: $space-slab;
|
||||||
right: -$space-micro;
|
|
||||||
text-align: right;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,22 +42,26 @@
|
||||||
font-size: $font-size-default;
|
font-size: $font-size-default;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
padding: $space-slab;
|
|
||||||
height: $space-larger;
|
|
||||||
font-size: $font-size-default;
|
font-size: $font-size-default;
|
||||||
|
height: $space-larger;
|
||||||
|
padding: $space-slab;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
font-size: $font-size-small;
|
font-size: $font-size-small;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
height: $space-larger;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sigin__footer {
|
.sigin__footer {
|
||||||
font-size: $font-size-default;
|
font-size: $font-size-default;
|
||||||
padding: $space-medium;
|
padding: $space-medium;
|
||||||
|
|
||||||
> a {
|
>a {
|
||||||
font-weight: $font-weight-bold;
|
font-weight: $font-weight-bold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
margin-bottom: $space-micro;
|
margin-bottom: $space-micro;
|
||||||
margin-top: $space-micro;
|
margin-top: $space-micro;
|
||||||
|
|
||||||
>.inbox-icon {
|
.inbox-icon {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: $space-micro;
|
margin-right: $space-micro;
|
||||||
min-width: $space-normal;
|
min-width: $space-normal;
|
||||||
|
|
|
@ -1,33 +1,32 @@
|
||||||
.ui-snackbar-container {
|
.ui-snackbar-container {
|
||||||
position: absolute;
|
left: 0;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 40rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
z-index: 9999;
|
position: absolute;
|
||||||
top: $space-normal;
|
right: 0;
|
||||||
left: $space-normal;
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
top: $space-normal;
|
||||||
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-snackbar {
|
.ui-snackbar {
|
||||||
text-align: left;
|
@include padding($space-slab $space-medium);
|
||||||
|
@include shadow;
|
||||||
|
background-color: $woot-snackbar-bg;
|
||||||
|
border-radius: $space-smaller;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
min-width: 24rem;
|
margin-bottom: $space-small;
|
||||||
max-width: 40rem;
|
max-width: 40rem;
|
||||||
min-height: 3rem;
|
min-height: 3rem;
|
||||||
background-color: $woot-snackbar-bg;
|
min-width: 24rem;
|
||||||
@include padding($space-slab $space-medium);
|
text-align: left;
|
||||||
@include border-top-radius($space-micro);
|
|
||||||
@include border-right-radius($space-micro);
|
|
||||||
@include border-bottom-radius($space-micro);
|
|
||||||
@include border-left-radius($space-micro);
|
|
||||||
margin-bottom: $space-small;
|
|
||||||
|
|
||||||
// box-shadow: 0 1px 3px alpha(black, 0.12), 0 1px 2px alpha(black, 0.24);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-snackbar-text {
|
.ui-snackbar-text {
|
||||||
font-size: $font-size-small;
|
|
||||||
color: $color-white;
|
color: $color-white;
|
||||||
|
font-size: $font-size-small;
|
||||||
|
font-weight: $font-weight-medium;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-snackbar-action {
|
.ui-snackbar-action {
|
||||||
|
@ -35,12 +34,12 @@
|
||||||
padding-left: 3rem;
|
padding-left: 3rem;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
@include margin(0);
|
||||||
|
@include padding(0);
|
||||||
background: none;
|
background: none;
|
||||||
border: 0;
|
border: 0;
|
||||||
color: $woot-snackbar-button;
|
color: $woot-snackbar-button;
|
||||||
font-size: $font-size-small;
|
font-size: $font-size-small;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@include margin(0);
|
|
||||||
@include padding(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<transition-group name="toast-fade" tag="div" class="ui-snackbar-container">
|
<transition-group name="toast-fade" tag="div" class="ui-snackbar-container">
|
||||||
<woot-snackbar :message="snackMessage" v-for="snackMessage in snackMessages" v-bind:key="snackMessage" />
|
<woot-snackbar
|
||||||
|
v-for="snackMessage in snackMessages"
|
||||||
|
:key="snackMessage"
|
||||||
|
:message="snackMessage"
|
||||||
|
/>
|
||||||
</transition-group>
|
</transition-group>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -9,8 +13,12 @@
|
||||||
import WootSnackbar from './Snackbar';
|
import WootSnackbar from './Snackbar';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
WootSnackbar,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
duration: {
|
duration: {
|
||||||
|
type: Number,
|
||||||
default: 2500,
|
default: 2500,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -22,16 +30,12 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
bus.$on('newToastMessage', (message) => {
|
bus.$on('newToastMessage', message => {
|
||||||
this.snackMessages.push(message);
|
this.snackMessages.push(message);
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
this.snackMessages.splice(0, 1);
|
this.snackMessages.splice(0, 1);
|
||||||
}, this.duration);
|
}, this.duration);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
components: {
|
|
||||||
WootSnackbar,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -44,12 +44,12 @@ export default {
|
||||||
.file {
|
.file {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
padding: $space-normal;
|
padding: $space-smaller 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
.icon-wrap {
|
.icon-wrap {
|
||||||
font-size: $font-size-giga;
|
font-size: $font-size-giga;
|
||||||
color: $color-woot;
|
color: $color-white;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
margin-left: $space-smaller;
|
margin-left: $space-smaller;
|
||||||
margin-right: $space-slab;
|
margin-right: $space-slab;
|
||||||
|
@ -57,15 +57,22 @@ export default {
|
||||||
|
|
||||||
.text-block-title {
|
.text-block-title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
color: $color-white;
|
||||||
|
font-weight: $font-weight-bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
color: $color-primary-light;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meta {
|
.meta {
|
||||||
padding-right: $space-two;
|
padding-right: $space-two;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.time {
|
||||||
|
min-width: $space-larger;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<span class="message-text__wrap">
|
<span class="message-text__wrap">
|
||||||
<span class="time">{{ readableTime }}</span>
|
|
||||||
<span v-html="message"></span>
|
<span v-html="message"></span>
|
||||||
|
<span class="time">{{ readableTime }}</span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,6 @@
|
||||||
},
|
},
|
||||||
"FORGOT_PASSWORD": "Forgot your password?",
|
"FORGOT_PASSWORD": "Forgot your password?",
|
||||||
"CREATE_NEW_ACCOUNT": "Create new account",
|
"CREATE_NEW_ACCOUNT": "Create new account",
|
||||||
"SUBMIT": "Sign In"
|
"SUBMIT": "Login"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,7 +146,7 @@ export default {
|
||||||
return `${platformName || ''} ${platformVersion || ''}`;
|
return `${platformName || ''} ${platformVersion || ''}`;
|
||||||
},
|
},
|
||||||
contactId() {
|
contactId() {
|
||||||
return this.currentConversationMetaData.contact_id;
|
return this.currentConversationMetaData.contact?.id;
|
||||||
},
|
},
|
||||||
contact() {
|
contact() {
|
||||||
return this.$store.getters['contacts/getContact'](this.contactId);
|
return this.$store.getters['contacts/getContact'](this.contactId);
|
||||||
|
@ -155,16 +155,12 @@ export default {
|
||||||
watch: {
|
watch: {
|
||||||
contactId(newContactId, prevContactId) {
|
contactId(newContactId, prevContactId) {
|
||||||
if (newContactId && newContactId !== prevContactId) {
|
if (newContactId && newContactId !== prevContactId) {
|
||||||
this.$store.dispatch('contacts/show', {
|
this.$store.dispatch('contacts/show', { id: newContactId });
|
||||||
id: this.currentConversationMetaData.contact_id,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$store.dispatch('contacts/show', {
|
this.$store.dispatch('contacts/show', { id: this.contactId });
|
||||||
id: this.currentConversationMetaData.contact_id,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onPanelToggle() {
|
onPanelToggle() {
|
||||||
|
@ -189,7 +185,7 @@ export default {
|
||||||
|
|
||||||
.close-button {
|
.close-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: $space-slab;
|
right: $space-normal;
|
||||||
top: $space-slab;
|
top: $space-slab;
|
||||||
font-size: $font-size-default;
|
font-size: $font-size-default;
|
||||||
color: $color-heading;
|
color: $color-heading;
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
v-model="selectedNotifications"
|
v-model="selectedNotifications"
|
||||||
class="email-notification--checkbox"
|
class="email-notification--checkbox"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
value="conversation_creation"
|
value="email_conversation_creation"
|
||||||
@input="handleInput"
|
@input="handleInput"
|
||||||
/>
|
/>
|
||||||
<label for="conversation_creation">
|
<label for="conversation_creation">
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
v-model="selectedNotifications"
|
v-model="selectedNotifications"
|
||||||
class="email-notification--checkbox"
|
class="email-notification--checkbox"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
value="conversation_assignment"
|
value="email_conversation_assignment"
|
||||||
@input="handleInput"
|
@input="handleInput"
|
||||||
/>
|
/>
|
||||||
<label for="conversation_assignment">
|
<label for="conversation_assignment">
|
||||||
|
|
|
@ -16,7 +16,7 @@ class MessageFormatter {
|
||||||
return this.message.replace(
|
return this.message.replace(
|
||||||
urlRegex,
|
urlRegex,
|
||||||
url =>
|
url =>
|
||||||
`<a rel="noreferrer noopener nofollow" href="${url}" target="_blank">${url}</a>`
|
`<a rel="noreferrer noopener nofollow" href="${url}" class="link" target="_blank">${url}</a>`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ describe('#MessageFormatter', () => {
|
||||||
const message =
|
const message =
|
||||||
'Chatwoot is an opensource tool\nSee more at https://www.chatwoot.com';
|
'Chatwoot is an opensource tool\nSee more at https://www.chatwoot.com';
|
||||||
expect(new MessageFormatter(message).formattedMessage).toEqual(
|
expect(new MessageFormatter(message).formattedMessage).toEqual(
|
||||||
'Chatwoot is an opensource tool<br>See more at <a rel="noreferrer noopener nofollow" href="https://www.chatwoot.com" target="_blank">https://www.chatwoot.com</a>'
|
'Chatwoot is an opensource tool<br>See more at <a rel="noreferrer noopener nofollow" href="https://www.chatwoot.com" class="link" target="_blank">https://www.chatwoot.com</a>'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,7 +22,10 @@ $input-height: $space-two * 2;
|
||||||
outline: none;
|
outline: none;
|
||||||
padding: $space-smaller;
|
padding: $space-smaller;
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: background .2s, border .2s, box-shadow .2s, color .2s;
|
transition: background .2s,
|
||||||
|
border .2s,
|
||||||
|
box-shadow .2s,
|
||||||
|
color .2s;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
|
@ -37,7 +40,7 @@ $input-height: $space-two * 2;
|
||||||
&.small {
|
&.small {
|
||||||
font-size: $font-size-small;
|
font-size: $font-size-small;
|
||||||
height: $space-large;
|
height: $space-large;
|
||||||
padding: $space-small $space-slab;
|
padding: $space-small $space-one;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.default {
|
&.default {
|
||||||
|
|
|
@ -52,12 +52,13 @@ $color-light-gray: #999a9b;
|
||||||
$color-border: #e0e6ed;
|
$color-border: #e0e6ed;
|
||||||
$color-border-transparent: rgba(224, 230, 237, 0.5);
|
$color-border-transparent: rgba(224, 230, 237, 0.5);
|
||||||
$color-border-light: #f0f4f5;
|
$color-border-light: #f0f4f5;
|
||||||
$color-background: #ecf3f9;
|
$color-border-dark: #cad0d4;
|
||||||
|
$color-background: #f4f6fb;
|
||||||
$color-background-light: #fafafa;
|
$color-background-light: #fafafa;
|
||||||
$color-white: #fff;
|
$color-white: #fff;
|
||||||
$color-body: #3c4858;
|
$color-body: #3c4858;
|
||||||
$color-heading: #1f2d3d;
|
$color-heading: #1f2d3d;
|
||||||
$color-error: #ff4949;
|
$color-error: #ff382d;
|
||||||
|
|
||||||
|
|
||||||
// Thumbnail
|
// Thumbnail
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="agent-bubble">
|
<div
|
||||||
|
class="agent-message-wrap"
|
||||||
|
:class="{ 'has-response': hasRecordedResponse }"
|
||||||
|
>
|
||||||
<div class="agent-message">
|
<div class="agent-message">
|
||||||
<div class="avatar-wrap">
|
<div class="avatar-wrap">
|
||||||
<thumbnail
|
<thumbnail
|
||||||
|
@ -108,7 +111,7 @@ export default {
|
||||||
},
|
},
|
||||||
avatarUrl() {
|
avatarUrl() {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
const BotImage = require('dashboard/assets/images/chatwoot_bot.png')
|
const BotImage = require('dashboard/assets/images/chatwoot_bot.png');
|
||||||
if (this.message.message_type === MESSAGE_TYPE.TEMPLATE) {
|
if (this.message.message_type === MESSAGE_TYPE.TEMPLATE) {
|
||||||
return BotImage;
|
return BotImage;
|
||||||
}
|
}
|
||||||
|
@ -146,17 +149,6 @@ export default {
|
||||||
@import '~widget/assets/scss/variables.scss';
|
@import '~widget/assets/scss/variables.scss';
|
||||||
|
|
||||||
.conversation-wrap {
|
.conversation-wrap {
|
||||||
.agent-bubble {
|
|
||||||
margin-bottom: $space-micro;
|
|
||||||
& + .agent-bubble {
|
|
||||||
.agent-message {
|
|
||||||
.chat-bubble {
|
|
||||||
border-top-left-radius: $space-smaller;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-message {
|
.agent-message {
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -165,10 +157,6 @@ export default {
|
||||||
margin: 0 0 $space-micro $space-small;
|
margin: 0 0 $space-micro $space-small;
|
||||||
max-width: 88%;
|
max-width: 88%;
|
||||||
|
|
||||||
& + .user-message {
|
|
||||||
margin-top: $space-one;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-wrap {
|
.avatar-wrap {
|
||||||
height: $space-medium;
|
height: $space-medium;
|
||||||
width: $space-medium;
|
width: $space-medium;
|
||||||
|
@ -199,5 +187,26 @@ export default {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.agent-message-wrap {
|
||||||
|
+ .agent-message-wrap {
|
||||||
|
margin-top: $space-micro;
|
||||||
|
|
||||||
|
.agent-message .chat-bubble {
|
||||||
|
border-top-left-radius: $space-smaller;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+ .user-message-wrap {
|
||||||
|
margin-top: $space-normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.has-response + .user-message-wrap {
|
||||||
|
margin-top: $space-micro;
|
||||||
|
.chat-bubble {
|
||||||
|
border-top-right-radius: $space-smaller;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -124,6 +124,11 @@ export default {
|
||||||
background: $color-white;
|
background: $color-white;
|
||||||
border-bottom-left-radius: $space-smaller;
|
border-bottom-left-radius: $space-smaller;
|
||||||
color: $color-body;
|
color: $color-body;
|
||||||
|
|
||||||
|
.link {
|
||||||
|
word-break: break-word;
|
||||||
|
color: $color-woot;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -64,7 +64,7 @@ export default {
|
||||||
font-size: $font-size-default;
|
font-size: $font-size-default;
|
||||||
font-weight: $font-weight-medium;
|
font-weight: $font-weight-medium;
|
||||||
color: $color-white;
|
color: $color-white;
|
||||||
padding: $space-one $space-normal $space-one $space-small;
|
padding: $space-small $space-normal $space-small $space-small;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
|
@ -102,7 +102,7 @@ export default {
|
||||||
|
|
||||||
.conversation-wrap {
|
.conversation-wrap {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: $space-large $space-small $zero $space-small;
|
padding: $space-large $space-small $space-small $space-small;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message--loader {
|
.message--loader {
|
||||||
|
|
|
@ -59,11 +59,11 @@ export default {
|
||||||
.file {
|
.file {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
padding: $space-one $space-slab;
|
padding: $space-slab;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
.icon-wrap {
|
.icon-wrap {
|
||||||
font-size: $font-size-bigger;
|
font-size: $font-size-mega;
|
||||||
color: $color-woot;
|
color: $color-woot;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
margin-left: $space-smaller;
|
margin-left: $space-smaller;
|
||||||
|
@ -72,11 +72,14 @@ export default {
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-weight: $font-weight-medium;
|
font-weight: $font-weight-medium;
|
||||||
font-size: $font-size-small;
|
font-size: $font-size-default;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.download {
|
.download {
|
||||||
|
color: $color-woot;
|
||||||
|
font-weight: $font-weight-medium;
|
||||||
|
font-size: $font-size-mini;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: $font-size-small;
|
font-size: $font-size-small;
|
||||||
|
|
|
@ -29,11 +29,10 @@ export default {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
$color-black: #000;
|
|
||||||
background-image: linear-gradient(
|
background-image: linear-gradient(
|
||||||
-180deg,
|
-180deg,
|
||||||
transparent 3%,
|
transparent 3%,
|
||||||
$color-black 70%
|
$color-heading 130%
|
||||||
);
|
);
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
content: '';
|
content: '';
|
||||||
|
@ -55,7 +54,7 @@ export default {
|
||||||
bottom: $space-smaller;
|
bottom: $space-smaller;
|
||||||
color: $color-white;
|
color: $color-white;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: $space-small;
|
right: $space-slab;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,26 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="user-message">
|
<div class="user-message-wrap">
|
||||||
<div class="message-wrap" :class="{ 'in-progress': isInProgress }">
|
<div class="user-message">
|
||||||
<UserMessageBubble
|
<div class="message-wrap" :class="{ 'in-progress': isInProgress }">
|
||||||
v-if="showTextBubble"
|
<UserMessageBubble
|
||||||
:message="message.content"
|
v-if="showTextBubble"
|
||||||
:status="message.status"
|
:message="message.content"
|
||||||
/>
|
:status="message.status"
|
||||||
<div v-if="hasAttachments" class="chat-bubble has-attachment user">
|
/>
|
||||||
<div v-for="attachment in message.attachments" :key="attachment.id">
|
<div v-if="hasAttachments" class="chat-bubble has-attachment user">
|
||||||
<file-bubble
|
<div v-for="attachment in message.attachments" :key="attachment.id">
|
||||||
v-if="attachment.file_type !== 'image'"
|
<file-bubble
|
||||||
:url="attachment.data_url"
|
v-if="attachment.file_type !== 'image'"
|
||||||
:is-in-progress="isInProgress"
|
:url="attachment.data_url"
|
||||||
/>
|
:is-in-progress="isInProgress"
|
||||||
<image-bubble
|
/>
|
||||||
v-else
|
<image-bubble
|
||||||
:url="attachment.data_url"
|
v-else
|
||||||
:thumb="attachment.thumb_url"
|
:url="attachment.data_url"
|
||||||
:readable-time="readableTime"
|
:thumb="attachment.thumb_url"
|
||||||
/>
|
:readable-time="readableTime"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -80,16 +82,6 @@ export default {
|
||||||
max-width: 85%;
|
max-width: 85%;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
|
||||||
& + .user-message {
|
|
||||||
margin-bottom: $space-micro;
|
|
||||||
.chat-bubble {
|
|
||||||
border-top-right-radius: $space-smaller;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
& + .agent-message {
|
|
||||||
margin-top: $space-normal;
|
|
||||||
margin-bottom: $space-micro;
|
|
||||||
}
|
|
||||||
.message-wrap {
|
.message-wrap {
|
||||||
margin-right: $space-small;
|
margin-right: $space-small;
|
||||||
}
|
}
|
||||||
|
@ -103,13 +95,28 @@ export default {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user.has-attachment {
|
.user.has-attachment {
|
||||||
.icon-wrap {
|
.icon-wrap {
|
||||||
color: $color-white;
|
color: $color-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.download {
|
.download {
|
||||||
opacity: 0.8;
|
color: $color-white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-message-wrap {
|
||||||
|
+ .user-message-wrap {
|
||||||
|
margin-top: $space-micro;
|
||||||
|
|
||||||
|
.user-message .chat-bubble {
|
||||||
|
border-top-right-radius: $space-smaller;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+ .agent-message-wrap {
|
||||||
|
margin-top: $space-normal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ export default {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: $font-size-default;
|
font-size: $font-size-default;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
padding: $space-small $space-normal;
|
padding: $space-slab $space-normal $space-slab $space-normal;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
> a {
|
> a {
|
||||||
|
|
|
@ -86,7 +86,7 @@ export default {
|
||||||
|
|
||||||
.header-wrap {
|
.header-wrap {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
border-radius: $space-normal;
|
border-radius: $space-normal $space-normal $space-small $space-small;
|
||||||
background: white;
|
background: white;
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
@include shadow-large;
|
@include shadow-large;
|
||||||
|
|
|
@ -4,86 +4,88 @@ class ActionCableListener < BaseListener
|
||||||
def conversation_created(event)
|
def conversation_created(event)
|
||||||
conversation, account, timestamp = extract_conversation_and_account(event)
|
conversation, account, timestamp = extract_conversation_and_account(event)
|
||||||
|
|
||||||
send_to_agents(account, conversation.inbox.members, CONVERSATION_CREATED, conversation.push_event_data)
|
broadcast(user_tokens(account, conversation.inbox.members), CONVERSATION_CREATED, conversation.push_event_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
def conversation_read(event)
|
def conversation_read(event)
|
||||||
conversation, account, timestamp = extract_conversation_and_account(event)
|
conversation, account, timestamp = extract_conversation_and_account(event)
|
||||||
|
|
||||||
send_to_agents(account, conversation.inbox.members, CONVERSATION_READ, conversation.push_event_data)
|
broadcast(user_tokens(account, conversation.inbox.members), CONVERSATION_READ, conversation.push_event_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
def message_created(event)
|
def message_created(event)
|
||||||
message, account, timestamp = extract_message_and_account(event)
|
message, account, timestamp = extract_message_and_account(event)
|
||||||
conversation = message.conversation
|
conversation = message.conversation
|
||||||
|
tokens = user_tokens(account, conversation.inbox.members) + contact_token(conversation.contact, message)
|
||||||
|
|
||||||
send_to_agents(account, conversation.inbox.members, MESSAGE_CREATED, message.push_event_data)
|
broadcast(tokens, MESSAGE_CREATED, message.push_event_data)
|
||||||
send_to_contact(conversation.contact, MESSAGE_CREATED, message)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def message_updated(event)
|
def message_updated(event)
|
||||||
message, account, timestamp = extract_message_and_account(event)
|
message, account, timestamp = extract_message_and_account(event)
|
||||||
conversation = message.conversation
|
conversation = message.conversation
|
||||||
contact = conversation.contact
|
contact = conversation.contact
|
||||||
|
tokens = user_tokens(account, conversation.inbox.members) + contact_token(conversation.contact, message)
|
||||||
|
|
||||||
send_to_agents(account, conversation.inbox.members, MESSAGE_UPDATED, message.push_event_data)
|
broadcast(tokens, MESSAGE_UPDATED, message.push_event_data)
|
||||||
send_to_contact(contact, MESSAGE_UPDATED, message)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def conversation_resolved(event)
|
def conversation_resolved(event)
|
||||||
conversation, account, timestamp = extract_conversation_and_account(event)
|
conversation, account, timestamp = extract_conversation_and_account(event)
|
||||||
|
|
||||||
send_to_agents(account, conversation.inbox.members, CONVERSATION_RESOLVED, conversation.push_event_data)
|
broadcast(user_tokens(account, conversation.inbox.members), CONVERSATION_RESOLVED, conversation.push_event_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
def conversation_opened(event)
|
def conversation_opened(event)
|
||||||
conversation, account, timestamp = extract_conversation_and_account(event)
|
conversation, account, timestamp = extract_conversation_and_account(event)
|
||||||
|
|
||||||
send_to_agents(account, conversation.inbox.members, CONVERSATION_OPENED, conversation.push_event_data)
|
broadcast(user_tokens(account, conversation.inbox.members), CONVERSATION_OPENED, conversation.push_event_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
def conversation_lock_toggle(event)
|
def conversation_lock_toggle(event)
|
||||||
conversation, account, timestamp = extract_conversation_and_account(event)
|
conversation, account, timestamp = extract_conversation_and_account(event)
|
||||||
|
|
||||||
send_to_agents(account, conversation.inbox.members, CONVERSATION_LOCK_TOGGLE, conversation.lock_event_data)
|
broadcast(user_tokens(account, conversation.inbox.members), CONVERSATION_LOCK_TOGGLE, conversation.lock_event_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
def assignee_changed(event)
|
def assignee_changed(event)
|
||||||
conversation, account, timestamp = extract_conversation_and_account(event)
|
conversation, account, timestamp = extract_conversation_and_account(event)
|
||||||
|
|
||||||
send_to_agents(account, conversation.inbox.members, ASSIGNEE_CHANGED, conversation.push_event_data)
|
broadcast(user_tokens(account, conversation.inbox.members), ASSIGNEE_CHANGED, conversation.push_event_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
def contact_created(event)
|
def contact_created(event)
|
||||||
contact, account, timestamp = extract_contact_and_account(event)
|
contact, account, timestamp = extract_contact_and_account(event)
|
||||||
|
|
||||||
send_to_agents(account, account.agents, CONTACT_CREATED, contact.push_event_data)
|
broadcast(user_tokens(account, account.agents), CONTACT_CREATED, contact.push_event_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
def contact_updated(event)
|
def contact_updated(event)
|
||||||
contact, account, timestamp = extract_contact_and_account(event)
|
contact, account, timestamp = extract_contact_and_account(event)
|
||||||
|
|
||||||
send_to_agents(account, account.agents, CONTACT_UPDATED, contact.push_event_data)
|
broadcast(user_tokens(account, account.agents), CONTACT_UPDATED, contact.push_event_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def send_to_agents(account, agents, event_name, data)
|
def user_tokens(account, agents)
|
||||||
agent_tokens = agents.pluck(:pubsub_token)
|
agent_tokens = agents.pluck(:pubsub_token)
|
||||||
admin_tokens = account.administrators.pluck(:pubsub_token)
|
admin_tokens = account.administrators.pluck(:pubsub_token)
|
||||||
|
|
||||||
pubsub_tokens = (agent_tokens + admin_tokens).uniq
|
pubsub_tokens = (agent_tokens + admin_tokens).uniq
|
||||||
|
pubsub_tokens
|
||||||
return if pubsub_tokens.blank?
|
|
||||||
|
|
||||||
::ActionCableBroadcastJob.perform_later(pubsub_tokens, event_name, data)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_to_contact(contact, event_name, message)
|
def contact_token(contact, message)
|
||||||
return if message.private?
|
return [] if message.private?
|
||||||
return if message.activity?
|
return [] if message.activity?
|
||||||
return if contact.nil?
|
return [] if contact.nil?
|
||||||
|
|
||||||
::ActionCableBroadcastJob.perform_later([contact.pubsub_token], event_name, message.push_event_data)
|
[contact.pubsub_token]
|
||||||
|
end
|
||||||
|
|
||||||
|
def broadcast(tokens, event_name, data)
|
||||||
|
return if tokens.blank?
|
||||||
|
|
||||||
|
::ActionCableBroadcastJob.perform_later(tokens.uniq, event_name, data)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
class EmailNotificationListener < BaseListener
|
|
||||||
def conversation_created(event)
|
|
||||||
conversation, _account, _timestamp = extract_conversation_and_account(event)
|
|
||||||
return if conversation.bot?
|
|
||||||
|
|
||||||
conversation.inbox.members.each do |agent|
|
|
||||||
next unless agent.notification_settings.find_by(account_id: conversation.account_id).conversation_creation?
|
|
||||||
|
|
||||||
AgentNotifications::ConversationNotificationsMailer.conversation_created(conversation, agent).deliver_later
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
29
app/listeners/notification_listener.rb
Normal file
29
app/listeners/notification_listener.rb
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
class NotificationListener < BaseListener
|
||||||
|
def conversation_created(event)
|
||||||
|
conversation, account, _timestamp = extract_conversation_and_account(event)
|
||||||
|
return if conversation.bot?
|
||||||
|
|
||||||
|
conversation.inbox.members.each do |agent|
|
||||||
|
NotificationBuilder.new(
|
||||||
|
notification_type: 'conversation_creation',
|
||||||
|
user: agent,
|
||||||
|
account: account,
|
||||||
|
primary_actor: conversation
|
||||||
|
).perform
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def assignee_changed(event)
|
||||||
|
conversation, account, _timestamp = extract_conversation_and_account(event)
|
||||||
|
assignee = conversation.assignee
|
||||||
|
return unless conversation.notifiable_assignee_change?
|
||||||
|
return if conversation.bot?
|
||||||
|
|
||||||
|
NotificationBuilder.new(
|
||||||
|
notification_type: 'conversation_assignment',
|
||||||
|
user: assignee,
|
||||||
|
account: account,
|
||||||
|
primary_actor: conversation
|
||||||
|
).perform
|
||||||
|
end
|
||||||
|
end
|
|
@ -2,7 +2,7 @@ class AgentNotifications::ConversationNotificationsMailer < ApplicationMailer
|
||||||
default from: ENV.fetch('MAILER_SENDER_EMAIL', 'accounts@chatwoot.com')
|
default from: ENV.fetch('MAILER_SENDER_EMAIL', 'accounts@chatwoot.com')
|
||||||
layout 'mailer'
|
layout 'mailer'
|
||||||
|
|
||||||
def conversation_created(conversation, agent)
|
def conversation_creation(conversation, agent)
|
||||||
return unless smtp_config_set_or_development?
|
return unless smtp_config_set_or_development?
|
||||||
|
|
||||||
@agent = agent
|
@agent = agent
|
||||||
|
@ -11,7 +11,7 @@ class AgentNotifications::ConversationNotificationsMailer < ApplicationMailer
|
||||||
mail(to: @agent.email, subject: subject)
|
mail(to: @agent.email, subject: subject)
|
||||||
end
|
end
|
||||||
|
|
||||||
def conversation_assigned(conversation, agent)
|
def conversation_assignment(conversation, agent)
|
||||||
return unless smtp_config_set_or_development?
|
return unless smtp_config_set_or_development?
|
||||||
|
|
||||||
@agent = agent
|
@agent = agent
|
||||||
|
|
|
@ -37,7 +37,8 @@ class AccountUser < ApplicationRecord
|
||||||
|
|
||||||
def create_notification_setting
|
def create_notification_setting
|
||||||
setting = user.notification_settings.new(account_id: account.id)
|
setting = user.notification_settings.new(account_id: account.id)
|
||||||
setting.selected_email_flags = [:conversation_assignment]
|
setting.selected_email_flags = [:email_conversation_assignment]
|
||||||
|
setting.selected_push_flags = [:push_conversation_assignment]
|
||||||
setting.save!
|
setting.save!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ class Conversation < ApplicationRecord
|
||||||
|
|
||||||
before_create :set_bot_conversation
|
before_create :set_bot_conversation
|
||||||
|
|
||||||
after_update :notify_status_change, :create_activity, :send_email_notification_to_assignee
|
after_update :notify_status_change, :create_activity
|
||||||
|
|
||||||
after_create :notify_conversation_creation, :run_round_robin
|
after_create :notify_conversation_creation, :run_round_robin
|
||||||
|
|
||||||
|
@ -105,16 +105,6 @@ class Conversation < ApplicationRecord
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_bot_conversation
|
|
||||||
self.status = :bot if inbox.agent_bot_inbox&.active?
|
|
||||||
end
|
|
||||||
|
|
||||||
def notify_conversation_creation
|
|
||||||
dispatcher_dispatch(CONVERSATION_CREATED)
|
|
||||||
end
|
|
||||||
|
|
||||||
def notifiable_assignee_change?
|
def notifiable_assignee_change?
|
||||||
return false if self_assign?(assignee_id)
|
return false if self_assign?(assignee_id)
|
||||||
return false unless saved_change_to_assignee_id?
|
return false unless saved_change_to_assignee_id?
|
||||||
|
@ -123,12 +113,14 @@ class Conversation < ApplicationRecord
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_email_notification_to_assignee
|
private
|
||||||
return unless notifiable_assignee_change?
|
|
||||||
return if assignee.notification_settings.find_by(account_id: account_id).not_conversation_assignment?
|
|
||||||
return if bot?
|
|
||||||
|
|
||||||
AgentNotifications::ConversationNotificationsMailer.conversation_assigned(self, assignee).deliver_later
|
def set_bot_conversation
|
||||||
|
self.status = :bot if inbox.agent_bot_inbox&.active?
|
||||||
|
end
|
||||||
|
|
||||||
|
def notify_conversation_creation
|
||||||
|
dispatcher_dispatch(CONVERSATION_CREATED)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self_assign?(assignee_id)
|
def self_assign?(assignee_id)
|
||||||
|
|
|
@ -75,7 +75,7 @@ class Message < ApplicationRecord
|
||||||
:notify_via_mail
|
:notify_via_mail
|
||||||
|
|
||||||
# we need to wait for the active storage attachments to be available
|
# we need to wait for the active storage attachments to be available
|
||||||
after_create_commit :dispatch_event, :send_reply
|
after_create_commit :dispatch_create_events, :send_reply
|
||||||
|
|
||||||
after_update :dispatch_update_event
|
after_update :dispatch_update_event
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ class Message < ApplicationRecord
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def dispatch_event
|
def dispatch_create_events
|
||||||
Rails.configuration.dispatcher.dispatch(MESSAGE_CREATED, Time.zone.now, message: self)
|
Rails.configuration.dispatcher.dispatch(MESSAGE_CREATED, Time.zone.now, message: self)
|
||||||
|
|
||||||
if outgoing? && conversation.messages.outgoing.count == 1
|
if outgoing? && conversation.messages.outgoing.count == 1
|
||||||
|
|
47
app/models/notification.rb
Normal file
47
app/models/notification.rb
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: notifications
|
||||||
|
#
|
||||||
|
# id :bigint not null, primary key
|
||||||
|
# notification_type :integer not null
|
||||||
|
# primary_actor_type :string not null
|
||||||
|
# read_at :datetime
|
||||||
|
# secondary_actor_type :string
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
# account_id :bigint not null
|
||||||
|
# primary_actor_id :bigint not null
|
||||||
|
# secondary_actor_id :bigint
|
||||||
|
# user_id :bigint not null
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_notifications_on_account_id (account_id)
|
||||||
|
# index_notifications_on_user_id (user_id)
|
||||||
|
# uniq_primary_actor_per_account_notifications (primary_actor_type,primary_actor_id)
|
||||||
|
# uniq_secondary_actor_per_account_notifications (secondary_actor_type,secondary_actor_id)
|
||||||
|
#
|
||||||
|
|
||||||
|
class Notification < ApplicationRecord
|
||||||
|
belongs_to :account
|
||||||
|
belongs_to :user
|
||||||
|
|
||||||
|
belongs_to :primary_actor, polymorphic: true
|
||||||
|
belongs_to :secondary_actor, polymorphic: true, optional: true
|
||||||
|
|
||||||
|
NOTIFICATION_TYPES = {
|
||||||
|
conversation_creation: 1,
|
||||||
|
conversation_assignment: 2
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
enum notification_type: NOTIFICATION_TYPES
|
||||||
|
|
||||||
|
after_create_commit :process_notification_delivery
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def process_notification_delivery
|
||||||
|
Notification::EmailNotificationService.new(notification: self).perform
|
||||||
|
# Notification::PushNotificationService.new(notification: self).perform
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,6 +4,7 @@
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# email_flags :integer default(0), not null
|
# email_flags :integer default(0), not null
|
||||||
|
# push_flags :integer default(0), not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# account_id :integer
|
# account_id :integer
|
||||||
|
@ -25,10 +26,9 @@ class NotificationSetting < ApplicationRecord
|
||||||
flag_query_mode: :bit_operator
|
flag_query_mode: :bit_operator
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
EMAIL_NOTIFICATION_FLAGS = {
|
EMAIL_NOTIFICATION_FLAGS = ::Notification::NOTIFICATION_TYPES.transform_keys { |key| "email_#{key}".to_sym }.invert.freeze
|
||||||
1 => :conversation_creation,
|
PUSH_NOTIFICATION_FLAGS = ::Notification::NOTIFICATION_TYPES.transform_keys { |key| "push_#{key}".to_sym }.invert.freeze
|
||||||
2 => :conversation_assignment
|
|
||||||
}.freeze
|
|
||||||
|
|
||||||
has_flags EMAIL_NOTIFICATION_FLAGS.merge(column: 'email_flags').merge(DEFAULT_QUERY_SETTING)
|
has_flags EMAIL_NOTIFICATION_FLAGS.merge(column: 'email_flags').merge(DEFAULT_QUERY_SETTING)
|
||||||
|
has_flags PUSH_NOTIFICATION_FLAGS.merge(column: 'push_flags').merge(DEFAULT_QUERY_SETTING)
|
||||||
end
|
end
|
||||||
|
|
26
app/models/notification_subscription.rb
Normal file
26
app/models/notification_subscription.rb
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: notification_subscriptions
|
||||||
|
#
|
||||||
|
# id :bigint not null, primary key
|
||||||
|
# subscription_attributes :jsonb not null
|
||||||
|
# subscription_type :integer not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
# user_id :bigint not null
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_notification_subscriptions_on_user_id (user_id)
|
||||||
|
#
|
||||||
|
|
||||||
|
class NotificationSubscription < ApplicationRecord
|
||||||
|
belongs_to :user
|
||||||
|
|
||||||
|
SUBSCRIPTION_TYPES = {
|
||||||
|
browser_push: 1,
|
||||||
|
gcm: 2
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
enum subscription_type: SUBSCRIPTION_TYPES
|
||||||
|
end
|
|
@ -67,7 +67,10 @@ class User < ApplicationRecord
|
||||||
has_many :assigned_inboxes, through: :inbox_members, source: :inbox
|
has_many :assigned_inboxes, through: :inbox_members, source: :inbox
|
||||||
has_many :messages
|
has_many :messages
|
||||||
has_many :invitees, through: :account_users, class_name: 'User', foreign_key: 'inviter_id', dependent: :nullify
|
has_many :invitees, through: :account_users, class_name: 'User', foreign_key: 'inviter_id', dependent: :nullify
|
||||||
|
|
||||||
|
has_many :notifications, dependent: :destroy
|
||||||
has_many :notification_settings, dependent: :destroy
|
has_many :notification_settings, dependent: :destroy
|
||||||
|
has_many :notification_subscriptions, dependent: :destroy
|
||||||
|
|
||||||
before_validation :set_password_and_uid, on: :create
|
before_validation :set_password_and_uid, on: :create
|
||||||
|
|
||||||
|
@ -119,12 +122,6 @@ class User < ApplicationRecord
|
||||||
Rails.configuration.dispatcher.dispatch(AGENT_ADDED, Time.zone.now, account: account)
|
Rails.configuration.dispatcher.dispatch(AGENT_ADDED, Time.zone.now, account: account)
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_notification_setting
|
|
||||||
setting = notification_settings.new(account_id: account.id)
|
|
||||||
setting.selected_email_flags = [:conversation_assignment]
|
|
||||||
setting.save!
|
|
||||||
end
|
|
||||||
|
|
||||||
def notify_deletion
|
def notify_deletion
|
||||||
Rails.configuration.dispatcher.dispatch(AGENT_REMOVED, Time.zone.now, account: account)
|
Rails.configuration.dispatcher.dispatch(AGENT_REMOVED, Time.zone.now, account: account)
|
||||||
end
|
end
|
||||||
|
|
20
app/services/notification/email_notification_service.rb
Normal file
20
app/services/notification/email_notification_service.rb
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
class Notification::EmailNotificationService
|
||||||
|
pattr_initialize [:notification!]
|
||||||
|
|
||||||
|
def perform
|
||||||
|
return unless user_subscribed_to_notification?
|
||||||
|
|
||||||
|
# TODO : Clean up whatever happening over here
|
||||||
|
AgentNotifications::ConversationNotificationsMailer.public_send(notification
|
||||||
|
.notification_type.to_s, notification.primary_actor, notification.user).deliver_later
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def user_subscribed_to_notification?
|
||||||
|
notification_setting = notification.user.notification_settings.find_by(account_id: notification.account.id)
|
||||||
|
return true if notification_setting.public_send("email_#{notification.notification_type}?")
|
||||||
|
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
17
app/services/notification/push_notification_service.rb
Normal file
17
app/services/notification/push_notification_service.rb
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
class Notification::PushNotificationService
|
||||||
|
pattr_initialize [:notification!]
|
||||||
|
|
||||||
|
def perform
|
||||||
|
return unless user_subscribed_to_notification?
|
||||||
|
# TODO: implement the push delivery logic here
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def user_subscribed_to_notification?
|
||||||
|
notification_setting = notification.user.notification_settings.find_by(account_id: notification.account.id)
|
||||||
|
return true if notification_setting.public_send("push_#{notification.notification_type}?")
|
||||||
|
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,7 +1,9 @@
|
||||||
json.meta do
|
json.meta do
|
||||||
json.labels @conversation.label_list
|
json.labels @conversation.label_list
|
||||||
json.additional_attributes @conversation.additional_attributes
|
json.additional_attributes @conversation.additional_attributes
|
||||||
json.contact_id @conversation.contact_id
|
json.contact @conversation.contact.push_event_data
|
||||||
|
json.assignee @conversation.assignee.push_event_data if @conversation.assignee.present?
|
||||||
|
json.agent_last_seen_at @conversation.agent_last_seen_at
|
||||||
end
|
end
|
||||||
|
|
||||||
json.payload do
|
json.payload do
|
||||||
|
|
|
@ -76,6 +76,7 @@ Rails.application.routes.draw do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
resources :notifications, only: [:index, :update]
|
||||||
resource :notification_settings, only: [:show, :update]
|
resource :notification_settings, only: [:show, :update]
|
||||||
|
|
||||||
resources :reports, only: [] do
|
resources :reports, only: [] do
|
||||||
|
@ -103,6 +104,7 @@ Rails.application.routes.draw do
|
||||||
# ----------------------------------
|
# ----------------------------------
|
||||||
|
|
||||||
resource :profile, only: [:show, :update]
|
resource :profile, only: [:show, :update]
|
||||||
|
resource :notification_subscriptions, only: [:create]
|
||||||
|
|
||||||
resources :agent_bots, only: [:index]
|
resources :agent_bots, only: [:index]
|
||||||
|
|
||||||
|
|
34
db/migrate/20200422130153_create_notifications.rb
Normal file
34
db/migrate/20200422130153_create_notifications.rb
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
class CreateNotifications < ActiveRecord::Migration[6.0]
|
||||||
|
def change
|
||||||
|
create_table :notifications do |t|
|
||||||
|
t.references :account, index: true, null: false
|
||||||
|
t.references :user, index: true, null: false
|
||||||
|
t.integer :notification_type, null: false
|
||||||
|
t.references :primary_actor, polymorphic: true, null: false, index: { name: 'uniq_primary_actor_per_account_notifications' }
|
||||||
|
t.references :secondary_actor, polymorphic: true, index: { name: 'uniq_secondary_actor_per_account_notifications' }
|
||||||
|
t.timestamp :read_at, default: nil
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table :notification_subscriptions do |t|
|
||||||
|
t.references :user, index: true, null: false
|
||||||
|
t.integer :subscription_type, null: false
|
||||||
|
t.jsonb :subscription_attributes, null: false, default: '{}'
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
add_column :notification_settings, :push_flags, :integer, default: 0, null: false
|
||||||
|
add_push_settings_to_users
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_push_settings_to_users
|
||||||
|
::User.find_in_batches do |users_batch|
|
||||||
|
users_batch.each do |user|
|
||||||
|
user_notification_setting = user.notification_settings.first
|
||||||
|
user_notification_setting.push_conversation_assignment = true
|
||||||
|
user_notification_setting.save!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
27
db/schema.rb
27
db/schema.rb
|
@ -273,9 +273,36 @@ ActiveRecord::Schema.define(version: 2020_04_29_082655) do
|
||||||
t.integer "email_flags", default: 0, null: false
|
t.integer "email_flags", default: 0, null: false
|
||||||
t.datetime "created_at", precision: 6, null: false
|
t.datetime "created_at", precision: 6, null: false
|
||||||
t.datetime "updated_at", precision: 6, null: false
|
t.datetime "updated_at", precision: 6, null: false
|
||||||
|
t.integer "push_flags", default: 0, null: false
|
||||||
t.index ["account_id", "user_id"], name: "by_account_user", unique: true
|
t.index ["account_id", "user_id"], name: "by_account_user", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "notification_subscriptions", force: :cascade do |t|
|
||||||
|
t.bigint "user_id", null: false
|
||||||
|
t.integer "subscription_type", null: false
|
||||||
|
t.jsonb "subscription_attributes", default: "{}", null: false
|
||||||
|
t.datetime "created_at", precision: 6, null: false
|
||||||
|
t.datetime "updated_at", precision: 6, null: false
|
||||||
|
t.index ["user_id"], name: "index_notification_subscriptions_on_user_id"
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table "notifications", force: :cascade do |t|
|
||||||
|
t.bigint "account_id", null: false
|
||||||
|
t.bigint "user_id", null: false
|
||||||
|
t.integer "notification_type", null: false
|
||||||
|
t.string "primary_actor_type", null: false
|
||||||
|
t.bigint "primary_actor_id", null: false
|
||||||
|
t.string "secondary_actor_type"
|
||||||
|
t.bigint "secondary_actor_id"
|
||||||
|
t.datetime "read_at"
|
||||||
|
t.datetime "created_at", precision: 6, null: false
|
||||||
|
t.datetime "updated_at", precision: 6, null: false
|
||||||
|
t.index ["account_id"], name: "index_notifications_on_account_id"
|
||||||
|
t.index ["primary_actor_type", "primary_actor_id"], name: "uniq_primary_actor_per_account_notifications"
|
||||||
|
t.index ["secondary_actor_type", "secondary_actor_id"], name: "uniq_secondary_actor_per_account_notifications"
|
||||||
|
t.index ["user_id"], name: "index_notifications_on_user_id"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "subscriptions", id: :serial, force: :cascade do |t|
|
create_table "subscriptions", id: :serial, force: :cascade do |t|
|
||||||
t.string "pricing_version"
|
t.string "pricing_version"
|
||||||
t.integer "account_id"
|
t.integer "account_id"
|
||||||
|
|
|
@ -56,10 +56,10 @@ RSpec.describe 'Contacts API', type: :request do
|
||||||
let(:valid_params) { { contact: { account_id: account.id } } }
|
let(:valid_params) { { contact: { account_id: account.id } } }
|
||||||
|
|
||||||
context 'when it is an unauthenticated user' do
|
context 'when it is an unauthenticated user' do
|
||||||
it 'creates the contact' do
|
it 'returns unauthorized' do
|
||||||
expect { post "/api/v1/accounts/#{account.id}/contacts", params: valid_params }.to change(Contact, :count).by(1)
|
expect { post "/api/v1/accounts/#{account.id}/contacts", params: valid_params }.to change(Contact, :count).by(0)
|
||||||
|
|
||||||
expect(response).to have_http_status(:success)
|
expect(response).to have_http_status(:unauthorized)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -116,7 +116,7 @@ RSpec.describe 'Conversation Messages API', type: :request do
|
||||||
as: :json
|
as: :json
|
||||||
|
|
||||||
expect(response).to have_http_status(:success)
|
expect(response).to have_http_status(:success)
|
||||||
expect(JSON.parse(response.body, symbolize_names: true)[:meta][:contact_id]).to eq(conversation.contact_id)
|
expect(JSON.parse(response.body, symbolize_names: true)[:meta][:contact][:id]).to eq(conversation.contact_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -42,7 +42,7 @@ RSpec.describe 'Notification Settings API', type: :request do
|
||||||
|
|
||||||
it 'updates the email related notification flags' do
|
it 'updates the email related notification flags' do
|
||||||
put "/api/v1/accounts/#{account.id}/notification_settings",
|
put "/api/v1/accounts/#{account.id}/notification_settings",
|
||||||
params: { notification_settings: { selected_email_flags: ['conversation_assignment'] } },
|
params: { notification_settings: { selected_email_flags: ['email_conversation_assignment'] } },
|
||||||
headers: agent.create_new_auth_token,
|
headers: agent.create_new_auth_token,
|
||||||
as: :json
|
as: :json
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ RSpec.describe 'Notification Settings API', type: :request do
|
||||||
agent.reload
|
agent.reload
|
||||||
expect(json_response['user_id']).to eq(agent.id)
|
expect(json_response['user_id']).to eq(agent.id)
|
||||||
expect(json_response['account_id']).to eq(account.id)
|
expect(json_response['account_id']).to eq(account.id)
|
||||||
expect(json_response['selected_email_flags']).to eq(['conversation_assignment'])
|
expect(json_response['selected_email_flags']).to eq(['email_conversation_assignment'])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Notifications API', type: :request do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
|
||||||
|
describe 'GET /api/v1/accounts/{account.id}/notifications' do
|
||||||
|
context 'when it is an unauthenticated user' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
get "/api/v1/accounts/#{account.id}/notifications"
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an authenticated user' do
|
||||||
|
let(:admin) { create(:user, account: account, role: :administrator) }
|
||||||
|
let!(:notification) { create(:notification, account: account, user: admin) }
|
||||||
|
|
||||||
|
it 'returns all notifications' do
|
||||||
|
get "/api/v1/accounts/#{account.id}/notifications",
|
||||||
|
headers: admin.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
expect(response.body).to include(notification.notification_type)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'PATCH /api/v1/accounts/{account.id}/notifications/:id' do
|
||||||
|
let(:admin) { create(:user, account: account, role: :administrator) }
|
||||||
|
let!(:notification) { create(:notification, account: account, user: admin) }
|
||||||
|
|
||||||
|
context 'when it is an unauthenticated user' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
put "/api/v1/accounts/#{account.id}/notifications/#{notification.id}",
|
||||||
|
params: { read_at: true }
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an authenticated user' do
|
||||||
|
let(:admin) { create(:user, account: account, role: :administrator) }
|
||||||
|
|
||||||
|
it 'updates the notification read at' do
|
||||||
|
patch "/api/v1/accounts/#{account.id}/notifications/#{notification.id}",
|
||||||
|
headers: admin.create_new_auth_token,
|
||||||
|
params: { read_at: true },
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
expect(notification.reload.read_at).not_to eq('')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,31 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Notifications Subscriptions API', type: :request do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
|
||||||
|
describe 'POST /api/v1/notification_subscriptions' do
|
||||||
|
context 'when it is an unauthenticated user' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
post '/api/v1/notification_subscriptions'
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an authenticated user' do
|
||||||
|
let(:agent) { create(:user, account: account, role: :agent) }
|
||||||
|
|
||||||
|
it 'creates a notification subscriptions' do
|
||||||
|
post '/api/v1/notification_subscriptions',
|
||||||
|
params: { notification_subscription: { subscription_type: 'browser_push', 'subscription_attributes': { test: 'test' } } },
|
||||||
|
headers: agent.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
json_response = JSON.parse(response.body)
|
||||||
|
expect(json_response['subscription_type']).to eq('browser_push')
|
||||||
|
expect(json_response['subscription_attributes']).to eq({ 'test' => 'test' })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
10
spec/factories/notifications.rb
Normal file
10
spec/factories/notifications.rb
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
FactoryBot.define do
|
||||||
|
factory :notification do
|
||||||
|
primary_actor { create(:conversation, account: account) }
|
||||||
|
notification_type { 'conversation_assignment' }
|
||||||
|
user
|
||||||
|
account
|
||||||
|
end
|
||||||
|
end
|
|
@ -6,11 +6,6 @@ describe ActionCableListener do
|
||||||
let!(:inbox) { create(:inbox, account: account) }
|
let!(:inbox) { create(:inbox, account: account) }
|
||||||
let!(:agent) { create(:user, account: account, role: :agent) }
|
let!(:agent) { create(:user, account: account, role: :agent) }
|
||||||
let!(:conversation) { create(:conversation, account: account, inbox: inbox, assignee: agent) }
|
let!(:conversation) { create(:conversation, account: account, inbox: inbox, assignee: agent) }
|
||||||
let!(:message) do
|
|
||||||
create(:message, message_type: 'outgoing',
|
|
||||||
account: account, inbox: inbox, conversation: conversation)
|
|
||||||
end
|
|
||||||
let!(:event) { Events::Base.new(event_name, Time.zone.now, message: message) }
|
|
||||||
|
|
||||||
before do
|
before do
|
||||||
create(:inbox_member, inbox: inbox, user: agent)
|
create(:inbox_member, inbox: inbox, user: agent)
|
||||||
|
@ -18,13 +13,18 @@ describe ActionCableListener do
|
||||||
|
|
||||||
describe '#message_created' do
|
describe '#message_created' do
|
||||||
let(:event_name) { :'message.created' }
|
let(:event_name) { :'message.created' }
|
||||||
|
let!(:message) do
|
||||||
|
create(:message, message_type: 'outgoing',
|
||||||
|
account: account, inbox: inbox, conversation: conversation)
|
||||||
|
end
|
||||||
|
let!(:event) { Events::Base.new(event_name, Time.zone.now, message: message) }
|
||||||
|
|
||||||
it 'sends message to account admins, inbox agents and the contact' do
|
it 'sends message to account admins, inbox agents and the contact' do
|
||||||
|
# HACK: to reload conversation inbox members
|
||||||
|
expect(conversation.inbox.reload.inbox_members.count).to eq(1)
|
||||||
|
|
||||||
expect(ActionCableBroadcastJob).to receive(:perform_later).with(
|
expect(ActionCableBroadcastJob).to receive(:perform_later).with(
|
||||||
[agent.pubsub_token, admin.pubsub_token], 'message.created', message.push_event_data
|
[agent.pubsub_token, admin.pubsub_token, conversation.contact.pubsub_token], 'message.created', message.push_event_data
|
||||||
)
|
|
||||||
expect(ActionCableBroadcastJob).to receive(:perform_later).with(
|
|
||||||
[conversation.contact.pubsub_token], 'message.created', message.push_event_data
|
|
||||||
)
|
)
|
||||||
listener.message_created(event)
|
listener.message_created(event)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
describe EmailNotificationListener do
|
describe NotificationListener do
|
||||||
let(:listener) { described_class.instance }
|
let(:listener) { described_class.instance }
|
||||||
let!(:account) { create(:account) }
|
let!(:account) { create(:account) }
|
||||||
let!(:user) { create(:user, account: account) }
|
let!(:user) { create(:user, account: account) }
|
||||||
|
@ -13,14 +13,14 @@ describe EmailNotificationListener do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
creation_mailer = double
|
creation_mailer = double
|
||||||
allow(AgentNotifications::ConversationNotificationsMailer).to receive(:conversation_created).and_return(creation_mailer)
|
allow(AgentNotifications::ConversationNotificationsMailer).to receive(:conversation_creation).and_return(creation_mailer)
|
||||||
allow(creation_mailer).to receive(:deliver_later).and_return(true)
|
allow(creation_mailer).to receive(:deliver_later).and_return(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when conversation is created' do
|
context 'when conversation is created' do
|
||||||
it 'sends email to inbox members who have notifications turned on' do
|
it 'sends email to inbox members who have notifications turned on' do
|
||||||
notification_setting = agent_with_notification.notification_settings.first
|
notification_setting = agent_with_notification.notification_settings.first
|
||||||
notification_setting.selected_email_flags = [:conversation_creation]
|
notification_setting.selected_email_flags = [:email_conversation_creation]
|
||||||
notification_setting.save!
|
notification_setting.save!
|
||||||
|
|
||||||
create(:inbox_member, user: agent_with_notification, inbox: inbox)
|
create(:inbox_member, user: agent_with_notification, inbox: inbox)
|
||||||
|
@ -29,7 +29,7 @@ describe EmailNotificationListener do
|
||||||
event = Events::Base.new(event_name, Time.zone.now, conversation: conversation)
|
event = Events::Base.new(event_name, Time.zone.now, conversation: conversation)
|
||||||
|
|
||||||
listener.conversation_created(event)
|
listener.conversation_created(event)
|
||||||
expect(AgentNotifications::ConversationNotificationsMailer).to have_received(:conversation_created)
|
expect(AgentNotifications::ConversationNotificationsMailer).to have_received(:conversation_creation)
|
||||||
.with(conversation, agent_with_notification)
|
.with(conversation, agent_with_notification)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ describe EmailNotificationListener do
|
||||||
event = Events::Base.new(event_name, Time.zone.now, conversation: conversation)
|
event = Events::Base.new(event_name, Time.zone.now, conversation: conversation)
|
||||||
|
|
||||||
listener.conversation_created(event)
|
listener.conversation_created(event)
|
||||||
expect(AgentNotifications::ConversationNotificationsMailer).not_to have_received(:conversation_created)
|
expect(AgentNotifications::ConversationNotificationsMailer).not_to have_received(:conversation_creation)
|
||||||
.with(conversation, agent_with_out_notification)
|
.with(conversation, agent_with_out_notification)
|
||||||
end
|
end
|
||||||
end
|
end
|
|
@ -12,8 +12,8 @@ RSpec.describe AgentNotifications::ConversationNotificationsMailer, type: :maile
|
||||||
allow(class_instance).to receive(:smtp_config_set_or_development?).and_return(true)
|
allow(class_instance).to receive(:smtp_config_set_or_development?).and_return(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'conversation_created' do
|
describe 'conversation_creation' do
|
||||||
let(:mail) { described_class.conversation_created(conversation, agent).deliver_now }
|
let(:mail) { described_class.conversation_creation(conversation, agent).deliver_now }
|
||||||
|
|
||||||
it 'renders the subject' do
|
it 'renders the subject' do
|
||||||
expect(mail.subject).to eq("#{agent.name}, A new conversation [ID - #{conversation
|
expect(mail.subject).to eq("#{agent.name}, A new conversation [ID - #{conversation
|
||||||
|
@ -25,8 +25,8 @@ RSpec.describe AgentNotifications::ConversationNotificationsMailer, type: :maile
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'conversation_assigned' do
|
describe 'conversation_assignment' do
|
||||||
let(:mail) { described_class.conversation_assigned(conversation, agent).deliver_now }
|
let(:mail) { described_class.conversation_assignment(conversation, agent).deliver_now }
|
||||||
|
|
||||||
it 'renders the subject' do
|
it 'renders the subject' do
|
||||||
expect(mail.subject).to eq("#{agent.name}, A new conversation [ID - #{conversation.display_id}] has been assigned to you.")
|
expect(mail.subject).to eq("#{agent.name}, A new conversation [ID - #{conversation.display_id}] has been assigned to you.")
|
||||||
|
|
|
@ -9,8 +9,8 @@ RSpec.describe User do
|
||||||
it 'gets created with the right default settings' do
|
it 'gets created with the right default settings' do
|
||||||
expect(account_user.user.notification_settings).not_to eq(nil)
|
expect(account_user.user.notification_settings).not_to eq(nil)
|
||||||
|
|
||||||
expect(account_user.user.notification_settings.first.conversation_creation?).to eq(false)
|
expect(account_user.user.notification_settings.first.email_conversation_creation?).to eq(false)
|
||||||
expect(account_user.user.notification_settings.first.conversation_assignment?).to eq(true)
|
expect(account_user.user.notification_settings.first.email_conversation_assignment?).to eq(true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -39,8 +39,6 @@ RSpec.describe Conversation, type: :model do
|
||||||
new_assignee
|
new_assignee
|
||||||
|
|
||||||
allow(Rails.configuration.dispatcher).to receive(:dispatch)
|
allow(Rails.configuration.dispatcher).to receive(:dispatch)
|
||||||
allow(AgentNotifications::ConversationNotificationsMailer).to receive(:conversation_assigned).and_return(assignment_mailer)
|
|
||||||
allow(assignment_mailer).to receive(:deliver_later)
|
|
||||||
Current.user = old_assignee
|
Current.user = old_assignee
|
||||||
|
|
||||||
conversation.update(
|
conversation.update(
|
||||||
|
@ -61,11 +59,6 @@ RSpec.describe Conversation, type: :model do
|
||||||
.with(described_class::CONVERSATION_LOCK_TOGGLE, kind_of(Time), conversation: conversation)
|
.with(described_class::CONVERSATION_LOCK_TOGGLE, kind_of(Time), conversation: conversation)
|
||||||
expect(Rails.configuration.dispatcher).to have_received(:dispatch)
|
expect(Rails.configuration.dispatcher).to have_received(:dispatch)
|
||||||
.with(described_class::ASSIGNEE_CHANGED, kind_of(Time), conversation: conversation)
|
.with(described_class::ASSIGNEE_CHANGED, kind_of(Time), conversation: conversation)
|
||||||
|
|
||||||
# send_email_notification_to_assignee
|
|
||||||
expect(AgentNotifications::ConversationNotificationsMailer).to have_received(:conversation_assigned).with(conversation, new_assignee)
|
|
||||||
|
|
||||||
expect(assignment_mailer).to have_received(:deliver_later) if ENV.fetch('SMTP_ADDRESS', nil).present?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates conversation activities' do
|
it 'creates conversation activities' do
|
||||||
|
@ -129,15 +122,28 @@ RSpec.describe Conversation, type: :model do
|
||||||
expect(conversation.reload.assignee).to eq(agent)
|
expect(conversation.reload.assignee).to eq(agent)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'send assignment mailer' do
|
||||||
|
allow(AgentNotifications::ConversationNotificationsMailer).to receive(:conversation_assignment).and_return(assignment_mailer)
|
||||||
|
allow(assignment_mailer).to receive(:deliver_later)
|
||||||
|
|
||||||
|
Current.user = conversation.assignee
|
||||||
|
|
||||||
|
expect(update_assignee).to eq(true)
|
||||||
|
# send_email_notification_to_assignee
|
||||||
|
expect(AgentNotifications::ConversationNotificationsMailer).to have_received(:conversation_assignment).with(conversation, agent)
|
||||||
|
|
||||||
|
expect(assignment_mailer).to have_received(:deliver_later) if ENV.fetch('SMTP_ADDRESS', nil).present?
|
||||||
|
end
|
||||||
|
|
||||||
it 'does not send assignment mailer if notification setting is turned off' do
|
it 'does not send assignment mailer if notification setting is turned off' do
|
||||||
allow(AgentNotifications::ConversationNotificationsMailer).to receive(:conversation_assigned).and_return(assignment_mailer)
|
allow(AgentNotifications::ConversationNotificationsMailer).to receive(:conversation_assignment).and_return(assignment_mailer)
|
||||||
|
|
||||||
notification_setting = agent.notification_settings.first
|
notification_setting = agent.notification_settings.first
|
||||||
notification_setting.unselect_all_email_flags
|
notification_setting.unselect_all_email_flags
|
||||||
notification_setting.save!
|
notification_setting.save!
|
||||||
|
|
||||||
expect(update_assignee).to eq(true)
|
expect(update_assignee).to eq(true)
|
||||||
expect(AgentNotifications::ConversationNotificationsMailer).not_to have_received(:conversation_assigned).with(conversation, agent)
|
expect(AgentNotifications::ConversationNotificationsMailer).not_to have_received(:conversation_assignment).with(conversation, agent)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue