Merge branch 'develop' into fix/scroll-reply-to
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
class Api::V1::Accounts::NotificationsController < Api::V1::Accounts::BaseController
|
||||
RESULTS_PER_PAGE = 15
|
||||
include DateRangeHelper
|
||||
|
||||
before_action :fetch_notification, only: [:update, :destroy]
|
||||
before_action :fetch_notification, only: [:update, :destroy, :snooze]
|
||||
before_action :set_primary_actor, only: [:read_all]
|
||||
before_action :set_current_page, only: [:index]
|
||||
|
||||
@@ -38,6 +39,11 @@ class Api::V1::Accounts::NotificationsController < Api::V1::Accounts::BaseContro
|
||||
render json: @unread_count
|
||||
end
|
||||
|
||||
def snooze
|
||||
@notification.update(snoozed_until: parse_date_time(params[:snoozed_until].to_s)) if params[:snoozed_until]
|
||||
render json: @notification
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_primary_actor
|
||||
|
||||
@@ -144,6 +144,7 @@ import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
import {
|
||||
ALLOWED_FILE_TYPES,
|
||||
ALLOWED_FILE_TYPES_FOR_TWILIO_WHATSAPP,
|
||||
ALLOWED_FILE_TYPES_FOR_LINE,
|
||||
} from 'shared/constants/messages';
|
||||
import VideoCallButton from '../VideoCallButton.vue';
|
||||
import AIAssistanceButton from '../AIAssistanceButton.vue';
|
||||
@@ -270,6 +271,9 @@ export default {
|
||||
return this.showFileUpload || this.isNote;
|
||||
},
|
||||
showAudioRecorderButton() {
|
||||
if (this.isALineChannel) {
|
||||
return false;
|
||||
}
|
||||
// Disable audio recorder for safari browser as recording is not supported
|
||||
const isSafari = /^((?!chrome|android|crios|fxios).)*safari/i.test(
|
||||
navigator.userAgent
|
||||
@@ -291,6 +295,9 @@ export default {
|
||||
if (this.isATwilioWhatsAppChannel) {
|
||||
return ALLOWED_FILE_TYPES_FOR_TWILIO_WHATSAPP;
|
||||
}
|
||||
if (this.isALineChannel) {
|
||||
return ALLOWED_FILE_TYPES_FOR_LINE;
|
||||
}
|
||||
return ALLOWED_FILE_TYPES;
|
||||
},
|
||||
enableDragAndDrop() {
|
||||
|
||||
@@ -393,7 +393,8 @@ export default {
|
||||
this.isAPIInbox ||
|
||||
this.isAnEmailChannel ||
|
||||
this.isASmsInbox ||
|
||||
this.isATelegramChannel
|
||||
this.isATelegramChannel ||
|
||||
this.isALineChannel
|
||||
);
|
||||
},
|
||||
replyButtonLabel() {
|
||||
|
||||
@@ -55,6 +55,8 @@ export const ALLOWED_FILE_TYPES_FOR_TWILIO_WHATSAPP =
|
||||
'audio/mpeg, audio/opus, audio/ogg, audio/amr,' +
|
||||
'video/mp4,' +
|
||||
'application/pdf,';
|
||||
// https://developers.line.biz/en/reference/messaging-api/#image-message, https://developers.line.biz/en/reference/messaging-api/#video-message
|
||||
export const ALLOWED_FILE_TYPES_FOR_LINE = 'image/png, image/jpeg,video/mp4';
|
||||
|
||||
export const CSAT_RATINGS = [
|
||||
{
|
||||
|
||||
@@ -7,6 +7,12 @@ class ActionCableListener < BaseListener
|
||||
broadcast(account, tokens, NOTIFICATION_CREATED, { notification: notification.push_event_data, unread_count: unread_count, count: count })
|
||||
end
|
||||
|
||||
def notification_deleted(event)
|
||||
notification, account, unread_count, count = extract_notification_and_account(event)
|
||||
tokens = [event.data[:notification].user.pubsub_token]
|
||||
broadcast(account, tokens, NOTIFICATION_DELETED, notification: notification, unread_count: unread_count, count: count)
|
||||
end
|
||||
|
||||
def account_cache_invalidated(event)
|
||||
account = event.data[:account]
|
||||
tokens = user_tokens(account, account.agents)
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
# primary_actor_type :string not null
|
||||
# read_at :datetime
|
||||
# secondary_actor_type :string
|
||||
# snoozed_until :datetime
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :bigint not null
|
||||
@@ -41,6 +42,7 @@ class Notification < ApplicationRecord
|
||||
enum notification_type: NOTIFICATION_TYPES
|
||||
|
||||
after_create_commit :process_notification_delivery, :dispatch_create_event
|
||||
after_destroy_commit :dispatch_destroy_event
|
||||
|
||||
# TODO: Get rid of default scope
|
||||
# https://stackoverflow.com/a/1834250/939299
|
||||
@@ -135,4 +137,8 @@ class Notification < ApplicationRecord
|
||||
def dispatch_create_event
|
||||
Rails.configuration.dispatcher.dispatch(NOTIFICATION_CREATED, Time.zone.now, notification: self)
|
||||
end
|
||||
|
||||
def dispatch_destroy_event
|
||||
Rails.configuration.dispatcher.dispatch(NOTIFICATION_DELETED, Time.zone.now, notification: self)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,7 +6,8 @@ class Line::SendOnLineService < Base::SendOnChannelService
|
||||
end
|
||||
|
||||
def perform_reply
|
||||
response = channel.client.push_message(message.conversation.contact_inbox.source_id, [{ type: 'text', text: message.content }])
|
||||
response = channel.client.push_message(message.conversation.contact_inbox.source_id, build_payload)
|
||||
|
||||
return if response.blank?
|
||||
|
||||
parsed_json = JSON.parse(response.body)
|
||||
@@ -20,6 +21,37 @@ class Line::SendOnLineService < Base::SendOnChannelService
|
||||
end
|
||||
end
|
||||
|
||||
def build_payload
|
||||
if message.content && message.attachments.any?
|
||||
[text_message, *attachments]
|
||||
elsif message.content.nil? && message.attachments.any?
|
||||
attachments
|
||||
else
|
||||
text_message
|
||||
end
|
||||
end
|
||||
|
||||
def attachments
|
||||
message.attachments.map do |attachment|
|
||||
# Support only image and video for now, https://developers.line.biz/en/reference/messaging-api/#image-message
|
||||
next unless attachment.file_type == 'image' || attachment.file_type == 'video'
|
||||
|
||||
{
|
||||
type: attachment.file_type,
|
||||
originalContentUrl: attachment.download_url,
|
||||
previewImageUrl: attachment.download_url
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# https://developers.line.biz/en/reference/messaging-api/#text-message
|
||||
def text_message
|
||||
{
|
||||
type: 'text',
|
||||
text: message.content
|
||||
}
|
||||
end
|
||||
|
||||
# https://developers.line.biz/en/reference/messaging-api/#error-responses
|
||||
def external_error(error)
|
||||
# Message containing information about the error. See https://developers.line.biz/en/reference/messaging-api/#error-messages
|
||||
|
||||
@@ -171,6 +171,9 @@ Rails.application.routes.draw do
|
||||
post :read_all
|
||||
get :unread_count
|
||||
end
|
||||
member do
|
||||
post :snooze
|
||||
end
|
||||
end
|
||||
resource :notification_settings, only: [:show, :update]
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
class AddSnoozedUntilToNotifications < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_column :notifications, :snoozed_until, :datetime
|
||||
end
|
||||
end
|
||||
+2
-1
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.0].define(version: 2023_11_14_111614) do
|
||||
ActiveRecord::Schema[7.0].define(version: 2023_11_29_091149) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pg_stat_statements"
|
||||
enable_extension "pg_trgm"
|
||||
@@ -729,6 +729,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_11_14_111614) do
|
||||
t.datetime "read_at", precision: nil
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.datetime "snoozed_until"
|
||||
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"
|
||||
|
||||
+1
-1
@@ -104,7 +104,7 @@ RUN apk update && apk add --no-cache \
|
||||
&& gem install bundler
|
||||
|
||||
RUN if [ "$RAILS_ENV" != "production" ]; then \
|
||||
apk add --no-cache nodejs yarn; \
|
||||
apk add --no-cache nodejs-current yarn; \
|
||||
fi
|
||||
|
||||
COPY --from=pre-builder /gems/ /gems/
|
||||
|
||||
@@ -48,6 +48,7 @@ module Events::Types
|
||||
|
||||
# notification events
|
||||
NOTIFICATION_CREATED = 'notification.created'
|
||||
NOTIFICATION_DELETED = 'notification.deleted'
|
||||
|
||||
# agent events
|
||||
AGENT_ADDED = 'agent.added'
|
||||
|
||||
@@ -153,4 +153,32 @@ RSpec.describe 'Notifications API', type: :request do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PATCH /api/v1/accounts/{account.id}/notifications/:id/snooze' 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
|
||||
post "/api/v1/accounts/#{account.id}/notifications/#{notification.id}/snooze",
|
||||
params: { snoozed_until: DateTime.now.utc + 1.day }
|
||||
|
||||
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 snoozed until' do
|
||||
post "/api/v1/accounts/#{account.id}/notifications/#{notification.id}/snooze",
|
||||
headers: admin.create_new_auth_token,
|
||||
params: { snoozed_until: DateTime.now.utc + 1.day },
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(notification.reload.snoozed_until).not_to eq('')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -131,6 +131,27 @@ describe ActionCableListener do
|
||||
end
|
||||
end
|
||||
|
||||
describe '#notification_deleted' do
|
||||
let(:event_name) { :'notification.deleted' }
|
||||
let!(:notification) { create(:notification, account: account, user: agent) }
|
||||
let!(:event) { Events::Base.new(event_name, Time.zone.now, notification: notification) }
|
||||
|
||||
it 'sends message to account admins, inbox agents' do
|
||||
expect(ActionCableBroadcastJob).to receive(:perform_later).with(
|
||||
[agent.pubsub_token],
|
||||
'notification.deleted',
|
||||
{
|
||||
account_id: notification.account_id,
|
||||
notification: notification,
|
||||
unread_count: 1,
|
||||
count: 1
|
||||
}
|
||||
)
|
||||
|
||||
listener.notification_deleted(event)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#conversation_updated' do
|
||||
let(:event_name) { :'conversation.updated' }
|
||||
let!(:event) { Events::Base.new(event_name, Time.zone.now, conversation: conversation, user: agent, is_private: false) }
|
||||
|
||||
@@ -134,7 +134,7 @@ Hey @John, @Alisha Peter can you check this ticket?"
|
||||
end
|
||||
end
|
||||
|
||||
context 'when primary actory is deleted' do
|
||||
context 'when primary actor is deleted' do
|
||||
let!(:conversation) { create(:conversation) }
|
||||
|
||||
it 'clears notifications' do
|
||||
|
||||
@@ -92,5 +92,57 @@ describe Line::SendOnLineService do
|
||||
expect(message.status).to eq('delivered')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with message attachments' do
|
||||
it 'sends the message with text and attachments' do
|
||||
attachment = message.attachments.new(account_id: message.account_id, file_type: :image)
|
||||
attachment.file.attach(io: Rails.root.join('spec/assets/avatar.png').open, filename: 'avatar.png', content_type: 'image/png')
|
||||
expected_url_regex = %r{rails/active_storage/disk/[a-zA-Z0-9=_\-+]+/avatar\.png}
|
||||
|
||||
expect(line_client).to receive(:push_message).with(
|
||||
message.conversation.contact_inbox.source_id,
|
||||
[
|
||||
{ type: 'text', text: message.content },
|
||||
{
|
||||
type: 'image',
|
||||
originalContentUrl: match(expected_url_regex),
|
||||
previewImageUrl: match(expected_url_regex)
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
described_class.new(message: message).perform
|
||||
end
|
||||
|
||||
it 'sends the message with attachments only' do
|
||||
attachment = message.attachments.new(account_id: message.account_id, file_type: :image)
|
||||
attachment.file.attach(io: Rails.root.join('spec/assets/avatar.png').open, filename: 'avatar.png', content_type: 'image/png')
|
||||
message.update!(content: nil)
|
||||
expected_url_regex = %r{rails/active_storage/disk/[a-zA-Z0-9=_\-+]+/avatar\.png}
|
||||
|
||||
expect(line_client).to receive(:push_message).with(
|
||||
message.conversation.contact_inbox.source_id,
|
||||
[
|
||||
{
|
||||
type: 'image',
|
||||
originalContentUrl: match(expected_url_regex),
|
||||
previewImageUrl: match(expected_url_regex)
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
described_class.new(message: message).perform
|
||||
end
|
||||
|
||||
it 'sends the message with text only' do
|
||||
message.attachments.destroy_all
|
||||
expect(line_client).to receive(:push_message).with(
|
||||
message.conversation.contact_inbox.source_id,
|
||||
{ type: 'text', text: message.content }
|
||||
)
|
||||
|
||||
described_class.new(message: message).perform
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user