Merge branch 'develop' into feat/CW-3082

This commit is contained in:
Sivin Varghese
2024-03-27 13:21:29 +05:30
committed by GitHub
15 changed files with 259 additions and 55 deletions
-6
View File
@@ -16,7 +16,6 @@ class AgentBuilder
def perform
ActiveRecord::Base.transaction do
@user = find_or_create_user
send_confirmation_if_required
create_account_user
end
@user
@@ -34,11 +33,6 @@ class AgentBuilder
User.create!(email: email, name: name, password: temp_password, password_confirmation: temp_password)
end
# Sends confirmation instructions if the user is persisted and not confirmed.
def send_confirmation_if_required
@user.send_confirmation_instructions if user_needs_confirmation?
end
# Checks if the user needs confirmation.
# @return [Boolean] true if the user is persisted and not confirmed, false otherwise.
def user_needs_confirmation?
@@ -0,0 +1,103 @@
<template>
<div
class="flex items-center px-2 truncate border min-w-fit border-slate-75 dark:border-slate-700"
:class="showExtendedInfo ? 'py-[5px] rounded-lg' : 'py-0.5 gap-1 rounded'"
>
<div
class="flex items-center gap-1"
:class="
showExtendedInfo &&
'ltr:pr-1.5 rtl:pl-1.5 ltr:border-r rtl:border-l border-solid border-slate-75 dark:border-slate-700'
"
>
<fluent-icon
size="14"
:icon="slaStatus.icon"
type="outline"
:icon-lib="isSlaMissed ? 'lucide' : 'fluent'"
class="flex-shrink-0"
:class="slaTextStyles"
/>
<span
v-if="showExtendedInfo"
class="text-xs font-medium"
:class="slaTextStyles"
>
{{ slaStatusText }}
</span>
</div>
<span
class="text-xs font-medium"
:class="[slaTextStyles, showExtendedInfo && 'ltr:pl-1.5 rtl:pr-1.5']"
>
{{ slaStatus.threshold }}
</span>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import { evaluateSLAStatus } from '../helpers/SLAHelper';
// const REFRESH_INTERVAL = 60000;
export default {
props: {
chat: {
type: Object,
default: () => ({}),
},
showExtendedInfo: {
type: Boolean,
default: false,
},
},
data() {
return {
timer: null,
slaStatus: {},
};
},
computed: {
...mapGetters({
activeSLA: 'sla/getSLAById',
}),
slaPolicyId() {
return this.chat?.sla_policy_id;
},
sla() {
if (!this.slaPolicyId) return null;
return this.activeSLA(this.slaPolicyId);
},
isSlaMissed() {
return this.slaStatus?.isSlaMissed;
},
slaTextStyles() {
return this.isSlaMissed
? 'text-red-400 dark:text-red-300'
: 'text-yellow-600 dark:text-yellow-500';
},
slaStatusText() {
const upperCaseType = this.slaStatus?.type?.toUpperCase(); // FRT, NRT, or RT
const statusKey = this.isSlaMissed ? 'BREACH' : 'DUE';
return this.$t(`CONVERSATION.HEADER.SLA_STATUS.${upperCaseType}`, {
status: this.$t(`CONVERSATION.HEADER.SLA_STATUS.${statusKey}`),
});
},
},
watch: {
chat() {
this.updateSlaStatus();
},
},
mounted() {
this.updateSlaStatus();
},
methods: {
updateSlaStatus() {
this.slaStatus = evaluateSLAStatus(this.sla, this.chat);
},
},
};
</script>
@@ -64,7 +64,14 @@
"SNOOZED_UNTIL": "Snoozed until",
"SNOOZED_UNTIL_TOMORROW": "Snoozed until tomorrow",
"SNOOZED_UNTIL_NEXT_WEEK": "Snoozed until next week",
"SNOOZED_UNTIL_NEXT_REPLY": "Snoozed until next reply"
"SNOOZED_UNTIL_NEXT_REPLY": "Snoozed until next reply",
"SLA_STATUS": {
"FRT": "FRT {status}",
"NRT": "NRT {status}",
"RT": "RT {status}",
"BREACH": "breach",
"DUE": "due"
}
},
"RESOLVE_DROPDOWN": {
"MARK_PENDING": "Mark as pending",
-1
View File
@@ -78,7 +78,6 @@ Vue.component('multiselect', Multiselect);
Vue.component('woot-switch', WootSwitch);
Vue.component('woot-wizard', WootWizard);
Vue.component('fluent-icon', FluentIcon);
Vue.directive('resize', resizeDirective);
const i18nConfig = new VueI18n({
+1 -1
View File
@@ -6,7 +6,7 @@
# additional_attributes :jsonb
# agent_last_seen_at :datetime
# assignee_last_seen_at :datetime
# cached_label_list :string
# cached_label_list :text
# contact_last_seen_at :datetime
# custom_attributes :jsonb
# first_reply_created_at :datetime
+9
View File
@@ -106,6 +106,15 @@ en:
avg_resolution_time: Avg resolution time
conversation_traffic_csv:
timezone: Timezone
sla_csv:
conversation_id: Conversation ID
sla_policy_breached: SLA Policy
assignee: Assignee
team: Team
inbox: Inbox
labels: Labels
conversation_link: Link to the Conversation
breached_events: Breached Events
default_group_by: day
csat:
headers:
+1
View File
@@ -147,6 +147,7 @@ Rails.application.routes.draw do
resources :applied_slas, only: [:index] do
collection do
get :metrics
get :download
end
end
resources :custom_attribute_definitions, only: [:index, :show, :create, :update, :destroy]
@@ -0,0 +1,32 @@
class ConvertCachedLabelListToText < ActiveRecord::Migration[7.0]
def up
change_column :conversations, :cached_label_list, :text
end
def down
# This might cause data loss if the text is longer than 255 characters
# lets start by truncating the data to 255 characters
Conversation.where('LENGTH(cached_label_list) > 255').find_in_batches do |conversation_batch|
Conversation.transaction do
conversation_batch.each do |conversation|
conversation.update!(cached_label_list: truncate_list(conversation.cached_label_list))
end
end
end
change_column :conversations, :cached_label_list, :string
end
private
# Truncate the list to 255 characters or less
# by removing the last element until the length is less than 255
def truncate_list(label_list)
labels = label_list.split(',')
# we add the `labels.length - 1` to account for the commas
labels.pop while (labels.join(',').length + labels.length - 1) > 255
labels.join(',')
end
end
+2 -2
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: 2024_03_19_062553) do
ActiveRecord::Schema[7.0].define(version: 2024_03_22_071629) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_stat_statements"
enable_extension "pg_trgm"
@@ -472,7 +472,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_03_19_062553) do
t.integer "priority"
t.bigint "sla_policy_id"
t.datetime "waiting_since"
t.string "cached_label_list"
t.text "cached_label_list"
t.index ["account_id", "display_id"], name: "index_conversations_on_account_id_and_display_id", unique: true
t.index ["account_id", "id"], name: "index_conversations_on_id_and_account_id"
t.index ["account_id", "inbox_id", "status", "assignee_id"], name: "conv_acid_inbid_stat_asgnid_idx"
@@ -4,7 +4,7 @@ class Api::V1::Accounts::AppliedSlasController < Api::V1::Accounts::EnterpriseAc
RESULTS_PER_PAGE = 25
before_action :set_applied_slas, only: [:index, :metrics]
before_action :set_applied_slas, only: [:index, :metrics, :download]
before_action :set_current_page, only: [:index]
before_action :paginate_slas, only: [:index]
before_action :check_admin_authorization?
@@ -19,8 +19,22 @@ class Api::V1::Accounts::AppliedSlasController < Api::V1::Accounts::EnterpriseAc
@hit_rate = hit_rate
end
def download
@breached_slas = breached_slas
response.headers['Content-Type'] = 'text/csv'
response.headers['Content-Disposition'] = 'attachment; filename=breached_conversation.csv'
render layout: false, formats: [:csv]
end
private
def breached_slas
@applied_slas.includes(:sla_policy).joins(:conversation)
.where.not(conversations: { status: :resolved })
.where(applied_slas: { sla_status: :missed })
end
def total_applied_slas
@total_applied_slas ||= @applied_slas.count
end
@@ -0,0 +1,26 @@
<% headers = [
I18n.t('reports.sla_csv.conversation_id'),
I18n.t('reports.sla_csv.sla_policy_breached'),
I18n.t('reports.sla_csv.assignee'),
I18n.t('reports.sla_csv.team'),
I18n.t('reports.sla_csv.inbox'),
I18n.t('reports.sla_csv.labels'),
I18n.t('reports.sla_csv.conversation_link'),
I18n.t('reports.sla_csv.breached_events')
] %>
<%= CSV.generate_line headers %>
<% @breached_slas.each do |sla| %>
<% breached_events = sla.sla_events.map(&:event_type).join(', ') %>
<% conversation = sla.conversation %>
<%= CSV.generate_line([
conversation.display_id,
sla.sla_policy.name,
conversation.assignee&.name,
conversation.team&.name,
conversation.inbox&.name,
conversation.cached_label_list,
app_account_conversation_url(account_id: conversation.account_id, id: conversation.display_id),
breached_events
]) %>
<% end %>
+1 -1
View File
@@ -63,7 +63,7 @@
"libphonenumber-js": "^1.10.24",
"logrocket": "^3.0.1",
"logrocket-vuex": "^0.0.3",
"markdown-it": "^13.0.1",
"markdown-it": "^13.0.2",
"markdown-it-link-attributes": "^4.0.1",
"md5": "^2.3.0",
"ninja-keys": "^1.2.2",
-16
View File
@@ -67,21 +67,5 @@ RSpec.describe AgentBuilder, type: :model do
expect(user.encrypted_password).not_to be_empty
end
end
context 'with confirmation required' do
let(:unconfirmed_user) { create(:user, email: email) }
before do
unconfirmed_user.confirmed_at = nil
unconfirmed_user.save(validate: false)
allow(unconfirmed_user).to receive(:confirmed?).and_return(false)
end
it 'sends confirmation instructions' do
user = agent_builder.perform
expect(user).to receive(:send_confirmation_instructions)
agent_builder.send(:send_confirmation_if_required)
end
end
end
end
@@ -104,6 +104,36 @@ RSpec.describe 'Applied SLAs API', type: :request do
end
end
describe 'GET /api/v1/accounts/{account.id}/applied_slas/download' do
context 'when it is an unauthenticated user' do
it 'returns unauthorized' do
get "/api/v1/accounts/#{account.id}/applied_slas/download"
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an authenticated user' do
it 'returns a CSV file with breached conversations' do
create(:applied_sla, sla_policy: sla_policy1, conversation: conversation1, sla_status: 'missed')
create(:applied_sla, sla_policy: sla_policy1, conversation: conversation2, sla_status: 'missed')
conversation1.update(status: 'open')
conversation2.update(status: 'resolved')
get "/api/v1/accounts/#{account.id}/applied_slas/download",
headers: administrator.create_new_auth_token
expect(response).to have_http_status(:success)
expect(response.headers['Content-Type']).to eq('text/csv')
expect(response.headers['Content-Disposition']).to include('attachment; filename=breached_conversation.csv')
csv_data = CSV.parse(response.body)
csv_data.reject! { |row| row.all?(&:nil?) }
expect(csv_data.size).to eq(2)
expect(csv_data[1][0].to_i).to eq(conversation1.display_id)
end
end
end
describe 'GET /api/v1/accounts/{account.id}/applied_slas' do
context 'when it is an unauthenticated user' do
it 'returns unauthorized' do
+31 -26
View File
@@ -8140,13 +8140,13 @@ bn.js@^5.2.1:
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70"
integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==
body-parser@1.20.1:
version "1.20.1"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668"
integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==
body-parser@1.20.2:
version "1.20.2"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd"
integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==
dependencies:
bytes "3.1.2"
content-type "~1.0.4"
content-type "~1.0.5"
debug "2.6.9"
depd "2.0.0"
destroy "1.2.0"
@@ -8154,7 +8154,7 @@ body-parser@1.20.1:
iconv-lite "0.4.24"
on-finished "2.4.1"
qs "6.11.0"
raw-body "2.5.1"
raw-body "2.5.2"
type-is "~1.6.18"
unpipe "1.0.0"
@@ -9129,6 +9129,11 @@ content-type@~1.0.4:
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
content-type@~1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442"
@@ -9146,10 +9151,10 @@ cookie-signature@1.0.6:
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
cookie@0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
cookie@0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051"
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
copy-concurrently@^1.0.0:
version "1.0.5"
@@ -11054,16 +11059,16 @@ expect@^29.0.0, expect@^29.7.0:
jest-util "^29.7.0"
express@^4.17.1:
version "4.18.2"
resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59"
integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==
version "4.19.2"
resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465"
integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==
dependencies:
accepts "~1.3.8"
array-flatten "1.1.1"
body-parser "1.20.1"
body-parser "1.20.2"
content-disposition "0.5.4"
content-type "~1.0.4"
cookie "0.5.0"
cookie "0.6.0"
cookie-signature "1.0.6"
debug "2.6.9"
depd "2.0.0"
@@ -11407,9 +11412,9 @@ flush-write-stream@^1.0.0:
readable-stream "^2.3.6"
follow-redirects@^1.0.0, follow-redirects@^1.15.0:
version "1.15.3"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a"
integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==
version "1.15.6"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
for-each@^0.3.3:
version "0.3.3"
@@ -14551,10 +14556,10 @@ markdown-it@^10.0.0:
mdurl "^1.0.1"
uc.micro "^1.0.5"
markdown-it@^13.0.1:
version "13.0.1"
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-13.0.1.tgz#c6ecc431cacf1a5da531423fc6a42807814af430"
integrity sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==
markdown-it@^13.0.2:
version "13.0.2"
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-13.0.2.tgz#1bc22e23379a6952e5d56217fbed881e0c94d536"
integrity sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==
dependencies:
argparse "^2.0.1"
entities "~3.0.1"
@@ -17578,10 +17583,10 @@ range-parser@^1.2.1, range-parser@~1.2.1:
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
raw-body@2.5.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857"
integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==
raw-body@2.5.2:
version "2.5.2"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a"
integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==
dependencies:
bytes "3.1.2"
http-errors "2.0.0"