Merge branch 'develop' into fix/scroll-reply-to

This commit is contained in:
Muhsin Keloth
2023-12-05 09:48:50 +05:30
committed by GitHub
16 changed files with 177 additions and 6 deletions
@@ -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 = [
{
+6
View File
@@ -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)
+6
View File
@@ -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
+33 -1
View File
@@ -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
+3
View File
@@ -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
View File
@@ -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
View File
@@ -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/
+1
View File
@@ -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) }
+1 -1
View File
@@ -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