Fixes #5839 - Ticket Summary - Improve UX when an unexpected structure is returned by the LLM.
Co-authored-by: Martin Gruner <mg@zammad.com> Co-authored-by: Dusan Vuckovic <dv@zammad.com>
This commit is contained in:
@@ -172,42 +172,30 @@ class App.SidebarTicketSummary extends App.Controller
|
||||
|
||||
return if !@elSidebar
|
||||
|
||||
invalidSummary = @invalidSummary()
|
||||
|
||||
summarization = $(App.view('ticket_zoom/sidebar_ticket_summary')(
|
||||
data: @summaryData
|
||||
invalidSummary: invalidSummary
|
||||
structure: @getAvailableDisplayStructure()
|
||||
data: @summaryData
|
||||
structure: @getAvailableDisplayStructure()
|
||||
))
|
||||
|
||||
summarization
|
||||
.on('click', '.js-retry', @retrySummarization)
|
||||
|
||||
@stopStripeAnimation() if not @isSummarizationLoading(invalidSummary)
|
||||
@stopStripeAnimation() if not @isSummarizationLoading()
|
||||
|
||||
if not invalidSummary
|
||||
@feedbackWidget = new App.AIFeedbackWidget(
|
||||
el: summarization.find('.js-aiFeedback')
|
||||
runId: @summaryData?.analytics?.run_id
|
||||
hasProvidedFeedback: @summaryData?.analytics?.usage?.user_has_provided_feedback
|
||||
regenerateCallback: @loadSummarization
|
||||
)
|
||||
@feedbackWidget = new App.AIFeedbackWidget(
|
||||
el: summarization.find('.js-aiFeedback')
|
||||
runId: @summaryData?.analytics?.run_id
|
||||
hasProvidedFeedback: @summaryData?.analytics?.usage?.user_has_provided_feedback
|
||||
regenerateCallback: @loadSummarization
|
||||
)
|
||||
|
||||
@hasUsage = not _.isNull(@summaryData?.analytics?.usage)
|
||||
@markAsRead() if @sidebarItem()?.name is @parentSidebar.currentTab and not @hasUsage
|
||||
@hasUsage = not _.isNull(@summaryData?.analytics?.usage)
|
||||
@markAsRead() if @sidebarItem()?.name is @parentSidebar.currentTab and not @hasUsage
|
||||
|
||||
@elSidebar.html(summarization)
|
||||
|
||||
invalidSummary: =>
|
||||
# In case the summary result does not follow expected structure (at least in some part), we consider it as invalid.
|
||||
@summaryData?.result and not _.some(@getAvailableDisplayStructure(), (item) =>
|
||||
key = item.value
|
||||
return _.some(key, (k) => @summaryData?.result[k]?) if _.isArray(key)
|
||||
@summaryData?.result[key]?
|
||||
)
|
||||
|
||||
isSummarizationLoading: (invalidSummary) =>
|
||||
not @summaryData?.error and App.Config.get('ai_provider') and not invalidSummary and not @summaryData?.result
|
||||
isSummarizationLoading: =>
|
||||
not @summaryData?.error and App.Config.get('ai_provider') and not @summaryData?.result
|
||||
|
||||
retrySummarization: (e) =>
|
||||
@preventDefaultAndStopPropagation(e)
|
||||
|
||||
@@ -13,10 +13,6 @@
|
||||
<div class="alert alert--danger">
|
||||
<%- @T('No AI provider is currently set up. Please contact your administrator.') %>
|
||||
</div>
|
||||
<% else if @invalidSummary: %>
|
||||
<div class="alert alert--info">
|
||||
<%- @T('There is not enough content yet to summarize this ticket.') %>
|
||||
</div>
|
||||
<% else if @data?.result: %>
|
||||
<% for item in @structure: %>
|
||||
<% values = [] %>
|
||||
|
||||
@@ -43,27 +43,11 @@ const hasProvidedFeedback = computed(() => !!props.analyticsMeta?.usage?.userHas
|
||||
|
||||
const hasRecentlyRated = shallowRef(false)
|
||||
|
||||
const noSummaryPossible = computed(() => {
|
||||
const { summary } = props
|
||||
|
||||
if (!summary) return false
|
||||
|
||||
return props.summaryHeadings.every((section) => {
|
||||
if (Array.isArray(section.key)) return section.key.every((key) => !summary[key]?.length)
|
||||
return !summary[section.key]?.length
|
||||
})
|
||||
})
|
||||
|
||||
const titleClass = computed(() => {
|
||||
let titleClass =
|
||||
'ai-stripe before:-bottom-3 before:absolute relative before:left:0 before:right-0'
|
||||
|
||||
if (
|
||||
props.isProviderConfigured &&
|
||||
!errorMessage.value &&
|
||||
!noSummaryPossible.value &&
|
||||
!props.summary
|
||||
)
|
||||
if (props.isProviderConfigured && !errorMessage.value && !props.summary)
|
||||
titleClass += ' animate-ai-stripe'
|
||||
|
||||
return titleClass
|
||||
@@ -109,11 +93,6 @@ const titleClass = computed(() => {
|
||||
}}</CommonButton>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="noSummaryPossible">
|
||||
<CommonAlert variant="info">
|
||||
{{ $t('There is not enough content yet to summarize this ticket.') }}
|
||||
</CommonAlert>
|
||||
</template>
|
||||
<template v-else-if="summary">
|
||||
<template v-for="item in summaryHeadings" :key="item.key">
|
||||
<article
|
||||
|
||||
@@ -9,7 +9,6 @@ import { mockRouterHooks } from '#tests/support/mock-vue-router.ts'
|
||||
import type { TicketById } from '#shared/entities/ticket/types.ts'
|
||||
import { createDummyArticle } from '#shared/entities/ticket-article/__tests__/mocks/ticket-articles.ts'
|
||||
import { createDummyTicket } from '#shared/entities/ticket-article/__tests__/mocks/ticket.ts'
|
||||
import { EnumTicketSummaryGeneration } from '#shared/graphql/types.ts'
|
||||
import { convertToGraphQLId } from '#shared/graphql/utils.ts'
|
||||
|
||||
import { pluginFiles } from '#desktop/pages/ticket/components/TicketSidebar/plugins/index.ts'
|
||||
@@ -274,38 +273,6 @@ describe('TicketSidebarSummary', () => {
|
||||
expect(await wrapper.findByText('Any feedback on this result?')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows info that summary is too short to be generated', async () => {
|
||||
mockTicketAiAssistanceSummarizeMutation({
|
||||
ticketAIAssistanceSummarize: {
|
||||
summary: {
|
||||
customerRequest: null,
|
||||
conversationSummary: null,
|
||||
openQuestions: null,
|
||||
upcomingEvents: null,
|
||||
customerMood: null,
|
||||
customerEmotion: null,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
mockApplicationConfig({
|
||||
ai_provider: true,
|
||||
ai_assistance_ticket_summary: true,
|
||||
ai_assistance_ticket_summary_config: {
|
||||
open_questions: true,
|
||||
upcoming_events: true,
|
||||
customer_sentiment: true,
|
||||
generate_on: EnumTicketSummaryGeneration.OnTicketDetailOpening,
|
||||
},
|
||||
})
|
||||
|
||||
const wrapper = renderRenderTicketSidebarSummary()
|
||||
|
||||
expect(
|
||||
await wrapper.findByText('There is not enough content yet to summarize this ticket.'),
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows skeleton loader when summary is not ready', async () => {
|
||||
mockTicketAiAssistanceSummarizeMutation({
|
||||
ticketAIAssistanceSummarize: {
|
||||
|
||||
@@ -726,6 +726,10 @@ msgstr ""
|
||||
msgid "AI provider token is not set"
|
||||
msgstr ""
|
||||
|
||||
#: lib/ai/service/ticket_summarize.rb:59
|
||||
msgid "AI service result is missing expected keys"
|
||||
msgstr ""
|
||||
|
||||
#: lib/excel_sheet/ai/analytics/with_usage.rb:4
|
||||
msgid "AI usage and feedback log"
|
||||
msgstr ""
|
||||
@@ -769,7 +773,7 @@ msgstr ""
|
||||
#: lib/ai/provider/mistral.rb:95
|
||||
#: lib/ai/provider/ollama.rb:89
|
||||
#: lib/ai/provider/open_ai.rb:100
|
||||
#: lib/ai/provider/zammad_ai.rb:62
|
||||
#: lib/ai/provider/zammad_ai.rb:70
|
||||
msgid "API server not accessible"
|
||||
msgstr ""
|
||||
|
||||
@@ -2403,7 +2407,7 @@ msgid "Basic Settings"
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/views/ai/ai_feedback_widget.jst.eco:2
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarSummary/TicketSidebarSummaryContent.vue:141
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarSummary/TicketSidebarSummaryContent.vue:120
|
||||
msgid "Be sure to check AI-generated content for accuracy."
|
||||
msgstr ""
|
||||
|
||||
@@ -10602,7 +10606,7 @@ msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/controllers/_ai/ai_agent.coffee:90
|
||||
#: app/assets/javascripts/app/controllers/_channel/whatsapp.coffee:112
|
||||
#: app/assets/javascripts/app/controllers/webhook.coffee:162
|
||||
#: app/assets/javascripts/app/controllers/webhook.coffee:165
|
||||
#: app/assets/javascripts/app/controllers/widget/two_factor_configuration/modal/password_check.coffee:2
|
||||
#: app/assets/javascripts/app/controllers/widget/two_factor_configuration/modal/security_keys.coffee:120
|
||||
#: app/assets/javascripts/app/views/clues/clues_content.jst.eco:19
|
||||
@@ -10652,7 +10656,7 @@ msgid "No AI agent available, please create a new one or activate an existing on
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/views/ticket_zoom/sidebar_ticket_summary.jst.eco:14
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarSummary/TicketSidebarSummaryContent.vue:86
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarSummary/TicketSidebarSummaryContent.vue:70
|
||||
msgid "No AI provider is currently set up. Please contact your administrator."
|
||||
msgstr ""
|
||||
|
||||
@@ -12179,7 +12183,7 @@ msgid "Please enter a text."
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/controllers/_ai/ai_agent.coffee:409
|
||||
#: app/assets/javascripts/app/controllers/webhook.coffee:131
|
||||
#: app/assets/javascripts/app/controllers/webhook.coffee:134
|
||||
msgid "Please enter a valid JSON string."
|
||||
msgstr ""
|
||||
|
||||
@@ -12339,7 +12343,7 @@ msgstr ""
|
||||
msgid "Pre-defined Webhook"
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/controllers/webhook.coffee:286
|
||||
#: app/assets/javascripts/app/controllers/webhook.coffee:291
|
||||
msgid "Pre-defined webhook for %s."
|
||||
msgstr ""
|
||||
|
||||
@@ -13223,7 +13227,7 @@ msgstr ""
|
||||
#: app/frontend/apps/desktop/components/TwoFactor/TwoFactorConfiguration/TwoFactorConfigurationSecurityKeys.vue:64
|
||||
#: app/frontend/apps/desktop/pages/authentication/components/LoginTwoFactor.vue:169
|
||||
#: app/frontend/apps/desktop/pages/authentication/views/AdminPasswordAuth.vue:131
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarSummary/TicketSidebarSummaryContent.vue:108
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarSummary/TicketSidebarSummaryContent.vue:92
|
||||
#: app/frontend/apps/mobile/pages/authentication/components/LoginTwoFactor.vue:172
|
||||
msgid "Retry"
|
||||
msgstr ""
|
||||
@@ -15124,8 +15128,8 @@ msgstr ""
|
||||
msgid "Summary Services"
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/views/ticket_zoom/sidebar_ticket_summary.jst.eco:50
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarSummary/TicketSidebarSummaryContent.vue:160
|
||||
#: app/assets/javascripts/app/views/ticket_zoom/sidebar_ticket_summary.jst.eco:46
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarSummary/TicketSidebarSummaryContent.vue:139
|
||||
msgid "Summary is being generated…"
|
||||
msgstr ""
|
||||
|
||||
@@ -16452,7 +16456,7 @@ msgid "The structure is a JSON object that defines the result format to be retur
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/views/ticket_zoom/sidebar_ticket_summary.jst.eco:5
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarSummary/TicketSidebarSummaryContent.vue:98
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarSummary/TicketSidebarSummaryContent.vue:82
|
||||
msgid "The summary could not be generated. Please try again later or contact your administrator."
|
||||
msgstr ""
|
||||
|
||||
@@ -16707,11 +16711,6 @@ msgstr ""
|
||||
msgid "There is no valid PGP email signer."
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/views/ticket_zoom/sidebar_ticket_summary.jst.eco:18
|
||||
#: app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarSummary/TicketSidebarSummaryContent.vue:114
|
||||
msgid "There is not enough content yet to summarize this ticket."
|
||||
msgstr ""
|
||||
|
||||
#: lib/secure_mailing/pgp/tool/error.rb:160
|
||||
msgid "There was an unknown PGP error."
|
||||
msgstr ""
|
||||
@@ -18050,7 +18049,7 @@ msgstr ""
|
||||
msgid "Turn chat into ticket"
|
||||
msgstr ""
|
||||
|
||||
#: app/assets/javascripts/app/controllers/webhook.coffee:275
|
||||
#: app/assets/javascripts/app/controllers/webhook.coffee:280
|
||||
#: app/assets/javascripts/app/views/channel/email_account_wizard.jst.eco:89
|
||||
#: app/assets/javascripts/app/views/channel/email_notification_wizard.jst.eco:48
|
||||
#: app/assets/javascripts/app/views/getting_started/email.jst.eco:71
|
||||
|
||||
@@ -68,7 +68,11 @@ class AI::Service
|
||||
end
|
||||
|
||||
def request_fresh
|
||||
response = post_transform_result(ask_provider)
|
||||
result = ask_provider
|
||||
|
||||
validate_result!(result)
|
||||
|
||||
response = post_transform_result(result)
|
||||
|
||||
if response.nil?
|
||||
save_analytics_run if analytics?
|
||||
@@ -166,6 +170,8 @@ class AI::Service
|
||||
self.class.lookup_version(context_data, locale)
|
||||
end
|
||||
|
||||
def validate_result!(_result); end
|
||||
|
||||
def post_transform_result(result)
|
||||
result
|
||||
end
|
||||
|
||||
@@ -24,6 +24,17 @@ class AI::Service::TicketSummarize < AI::Service
|
||||
true
|
||||
end
|
||||
|
||||
def validate_result!(result)
|
||||
raise InvalidResultKeysError if !result.key?('language')
|
||||
|
||||
optional_keys = %w[
|
||||
customer_request
|
||||
conversation_summary
|
||||
]
|
||||
|
||||
raise InvalidResultKeysError if !optional_keys.intersect?(result.keys)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def options
|
||||
@@ -31,4 +42,10 @@ class AI::Service::TicketSummarize < AI::Service
|
||||
temperature: 0.1,
|
||||
}
|
||||
end
|
||||
|
||||
class InvalidResultKeysError < StandardError
|
||||
def initialize
|
||||
super(__('AI service result is missing expected keys'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
68
spec/lib/ai/service/ticket_summarize_spec.rb
Normal file
68
spec/lib/ai/service/ticket_summarize_spec.rb
Normal file
@@ -0,0 +1,68 @@
|
||||
# Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe AI::Service::TicketSummarize do
|
||||
subject(:ai_service) { described_class.new(current_user:, context_data:) }
|
||||
|
||||
let(:group) { create(:group) }
|
||||
let(:current_user) { create(:user, groups: [group]) }
|
||||
let(:ticket) { create(:ticket, group:) }
|
||||
let(:article) { create(:ticket_article, ticket:) }
|
||||
|
||||
let(:context_data) do
|
||||
{
|
||||
ticket:,
|
||||
}
|
||||
end
|
||||
|
||||
let(:llm_response) do
|
||||
{
|
||||
'customer_request' => 'Customer requested help with an issue.',
|
||||
'conversation_summary' => 'This is a summary of the conversation.',
|
||||
'language' => 'en-us',
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
setup_ai_provider('zammad_ai')
|
||||
|
||||
allow_any_instance_of(AI::Service::EmailRemoveQuote)
|
||||
.to receive(:ask_provider).and_return(article.body_as_text)
|
||||
|
||||
allow_any_instance_of(AI::Provider::ZammadAI)
|
||||
.to receive(:ask).and_return(llm_response)
|
||||
end
|
||||
|
||||
it 'passes through received ticket summary' do
|
||||
result = ai_service.execute
|
||||
expect(result.content).to include(llm_response)
|
||||
end
|
||||
|
||||
context 'when result is missing a required key' do
|
||||
let(:llm_response) do
|
||||
{
|
||||
'summary' => 'This is a summary of the conversation.',
|
||||
}
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { ai_service.execute }
|
||||
.to raise_error(AI::Service::TicketSummarize::InvalidResultKeysError)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when result is missing all of the optional keys' do
|
||||
let(:llm_response) do
|
||||
{
|
||||
'language' => 'en-us',
|
||||
'summary' => 'This is a summary of the conversation.',
|
||||
}
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { ai_service.execute }
|
||||
.to raise_error(AI::Service::TicketSummarize::InvalidResultKeysError)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -16,12 +16,13 @@ RSpec.describe 'Ticket Summary', authenticated_as: :authenticate, type: :system
|
||||
|
||||
let(:initial_content) do
|
||||
{
|
||||
'customer_request' => '',
|
||||
'customer_request' => article.body_as_text,
|
||||
'conversation_summary' => initial_summary,
|
||||
'open_questions' => [],
|
||||
'upcoming_events' => [],
|
||||
'customer_mood' => 'Neutral',
|
||||
'customer_emotion' => '😐',
|
||||
'language' => 'en-us',
|
||||
}
|
||||
end
|
||||
|
||||
@@ -33,6 +34,7 @@ RSpec.describe 'Ticket Summary', authenticated_as: :authenticate, type: :system
|
||||
'upcoming_events' => ['Next meeting on Friday', 'Follow-up call next week'],
|
||||
'customer_mood' => 'Happy',
|
||||
'customer_emotion' => '🙂',
|
||||
'language' => 'en-us',
|
||||
}.compact
|
||||
end
|
||||
|
||||
@@ -91,8 +93,8 @@ RSpec.describe 'Ticket Summary', authenticated_as: :authenticate, type: :system
|
||||
|
||||
it 'displays and updates summary in sidebar', performs_jobs: true do
|
||||
within '.sidebar[data-tab="summary"]' do
|
||||
expect(page).to have_text 'Conversation Summary'
|
||||
expect(page).to have_text initial_summary
|
||||
expect(page).to have_text('Conversation Summary')
|
||||
.and have_text(initial_summary)
|
||||
end
|
||||
|
||||
create(:ticket_article, ticket:)
|
||||
@@ -103,14 +105,15 @@ RSpec.describe 'Ticket Summary', authenticated_as: :authenticate, type: :system
|
||||
perform_enqueued_jobs(only: TicketAIAssistanceSummarizeJob)
|
||||
|
||||
within '.sidebar[data-tab="summary"]' do
|
||||
expect(page).to have_text updated_summary
|
||||
expect(page).to have_text('Conversation Summary')
|
||||
.and have_text(updated_summary)
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows customer intent in the summary sidebar', performs_jobs: true do
|
||||
within '.sidebar[data-tab="summary"]' do
|
||||
expect(page).to have_no_text 'Customer Intent'
|
||||
expect(page).to have_no_text 'Customer is facing an issue with the product.'
|
||||
expect(page).to have_text('Customer Intent')
|
||||
.and have_text(article.body_as_text)
|
||||
end
|
||||
|
||||
create(:ticket_article, ticket:)
|
||||
@@ -120,16 +123,16 @@ RSpec.describe 'Ticket Summary', authenticated_as: :authenticate, type: :system
|
||||
perform_enqueued_jobs(only: TicketAIAssistanceSummarizeJob)
|
||||
|
||||
within '.sidebar[data-tab="summary"]' do
|
||||
expect(page).to have_text 'Customer Intent'
|
||||
expect(page).to have_text 'Customer is facing an issue with the product.'
|
||||
expect(page).to have_text('Customer Intent')
|
||||
.and have_text('Customer is facing an issue with the product.')
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows open questions in the summary sidebar', performs_jobs: true do
|
||||
within '.sidebar[data-tab="summary"]' do
|
||||
expect(page).to have_no_text 'Open Questions'
|
||||
expect(page).to have_no_text 'What is the issue?'
|
||||
expect(page).to have_no_text 'How can we help?'
|
||||
expect(page).to have_no_text('Open Questions')
|
||||
.and have_no_text('What is the issue?')
|
||||
.and have_no_text('How can we help?')
|
||||
end
|
||||
|
||||
create(:ticket_article, ticket:)
|
||||
@@ -139,17 +142,17 @@ RSpec.describe 'Ticket Summary', authenticated_as: :authenticate, type: :system
|
||||
perform_enqueued_jobs(only: TicketAIAssistanceSummarizeJob)
|
||||
|
||||
within '.sidebar[data-tab="summary"]' do
|
||||
expect(page).to have_text 'Open Questions'
|
||||
expect(page).to have_text 'What is the issue?'
|
||||
expect(page).to have_text 'How can we help?'
|
||||
expect(page).to have_text('Open Questions')
|
||||
.and have_text('What is the issue?')
|
||||
.and have_text('How can we help?')
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows upcoming events in the summary sidebar', performs_jobs: true do
|
||||
within '.sidebar[data-tab="summary"]' do
|
||||
expect(page).to have_no_text 'Upcoming Events'
|
||||
expect(page).to have_no_text 'Next meeting on Friday'
|
||||
expect(page).to have_no_text 'Follow-up call next week'
|
||||
expect(page).to have_no_text('Upcoming Events')
|
||||
.and have_no_text('Next meeting on Friday')
|
||||
.and have_no_text('Follow-up call next week')
|
||||
end
|
||||
|
||||
create(:ticket_article, ticket:)
|
||||
@@ -159,17 +162,17 @@ RSpec.describe 'Ticket Summary', authenticated_as: :authenticate, type: :system
|
||||
perform_enqueued_jobs(only: TicketAIAssistanceSummarizeJob)
|
||||
|
||||
within '.sidebar[data-tab="summary"]' do
|
||||
expect(page).to have_text 'Upcoming Events'
|
||||
expect(page).to have_text 'Next meeting on Friday'
|
||||
expect(page).to have_text 'Follow-up call next week'
|
||||
expect(page).to have_text('Upcoming Events')
|
||||
.and have_text('Next meeting on Friday')
|
||||
.and have_text('Follow-up call next week')
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows customer sentiment and emotion in the summary sidebar', performs_jobs: true do
|
||||
within '.sidebar[data-tab="summary"]' do
|
||||
expect(page).to have_text 'Customer Sentiment'
|
||||
expect(page).to have_text 'Neutral'
|
||||
expect(page).to have_text '😐'
|
||||
expect(page).to have_text('Customer Sentiment')
|
||||
.and have_text('Neutral')
|
||||
.and have_text('😐')
|
||||
end
|
||||
|
||||
create(:ticket_article, ticket:)
|
||||
@@ -179,9 +182,9 @@ RSpec.describe 'Ticket Summary', authenticated_as: :authenticate, type: :system
|
||||
perform_enqueued_jobs(only: TicketAIAssistanceSummarizeJob)
|
||||
|
||||
within '.sidebar[data-tab="summary"]' do
|
||||
expect(page).to have_text 'Customer Sentiment'
|
||||
expect(page).to have_text 'Happy'
|
||||
expect(page).to have_text '🙂'
|
||||
expect(page).to have_text('Customer Sentiment')
|
||||
.and have_text('Happy')
|
||||
.and have_text('🙂')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -192,7 +195,7 @@ RSpec.describe 'Ticket Summary', authenticated_as: :authenticate, type: :system
|
||||
|
||||
it 'does not show sidebar' do
|
||||
expect(page).to have_text(ticket.title)
|
||||
expect(page).to have_no_css('.tabsSidebar-tab[data-tab=summary]')
|
||||
.and have_no_css('.tabsSidebar-tab[data-tab=summary]')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user