diff --git a/app/javascript/dashboard/routes/dashboard/captain/responses/FaqSuggestions.spec.js b/app/javascript/dashboard/routes/dashboard/captain/responses/FaqSuggestions.spec.js index 561b4f668..e72cc5a08 100644 --- a/app/javascript/dashboard/routes/dashboard/captain/responses/FaqSuggestions.spec.js +++ b/app/javascript/dashboard/routes/dashboard/captain/responses/FaqSuggestions.spec.js @@ -60,11 +60,35 @@ const deferred = () => { return { promise, resolve }; }; +const PageLayoutStub = { + template: '
', +}; + +const FaqSuggestionCardStub = { + props: ['suggestion'], + template: '
{{ suggestion.question }}
', +}; + describe('FaqSuggestions', () => { beforeEach(() => { vi.clearAllMocks(); mocks.route.params.assistantId = 1; mocks.route.query = {}; + mocks.getterValues['captainFaqSuggestions/getRecords'].value = []; + mocks.getterValues['captainFaqSuggestions/getMeta'].value = { + totalCount: 0, + page: 1, + }; + mocks.dispatch.mockImplementation((action, payload) => { + if (action !== 'captainFaqSuggestions/setRecords') return; + + mocks.getterValues['captainFaqSuggestions/getRecords'].value = + payload.records; + mocks.getterValues['captainFaqSuggestions/getMeta'].value = { + totalCount: payload.meta.total_count, + page: payload.meta.page, + }; + }); }); it('keeps the latest assistant results when requests finish in the wrong order', async () => { @@ -77,6 +101,10 @@ describe('FaqSuggestions', () => { const wrapper = shallowMount(FaqSuggestions, { global: { mocks: { $t: key => key }, + stubs: { + PageLayout: PageLayoutStub, + FaqSuggestionCard: FaqSuggestionCardStub, + }, }, }); @@ -100,29 +128,8 @@ describe('FaqSuggestions', () => { }); await flushPromises(); - expect(mocks.apiGet).toHaveBeenNthCalledWith(1, { - page: 1, - search: '', - assistantId: 1, - }); - expect(mocks.apiGet).toHaveBeenNthCalledWith(2, { - page: 1, - search: '', - assistantId: 2, - }); - expect(mocks.dispatch).toHaveBeenCalledWith( - 'captainFaqSuggestions/setRecords', - { - records: [{ id: 2, question: 'Current assistant' }], - meta: { page: 1, total_count: 1 }, - } - ); - expect(mocks.dispatch).not.toHaveBeenCalledWith( - 'captainFaqSuggestions/setRecords', - expect.objectContaining({ - records: [{ id: 1, question: 'Previous assistant' }], - }) - ); + expect(wrapper.text()).toContain('Current assistant'); + expect(wrapper.text()).not.toContain('Previous assistant'); wrapper.unmount(); }); diff --git a/app/javascript/dashboard/store/captain/faqSuggestions.spec.js b/app/javascript/dashboard/store/captain/faqSuggestions.spec.js deleted file mode 100644 index afd15da1a..000000000 --- a/app/javascript/dashboard/store/captain/faqSuggestions.spec.js +++ /dev/null @@ -1,47 +0,0 @@ -import CaptainFaqSuggestionsAPI from 'dashboard/api/captain/faqSuggestions'; -import faqSuggestions from './faqSuggestions'; - -vi.mock('dashboard/api/captain/faqSuggestions', () => ({ - default: { - get: vi.fn(), - approve: vi.fn(), - dismiss: vi.fn(), - }, -})); - -const deferred = () => { - let resolve; - const promise = new Promise(resolvePromise => { - resolve = resolvePromise; - }); - return { promise, resolve }; -}; - -describe('captainFaqSuggestions', () => { - it('keeps the latest assistant count when requests finish in the wrong order', async () => { - const firstRequest = deferred(); - const secondRequest = deferred(); - CaptainFaqSuggestionsAPI.get - .mockReturnValueOnce(firstRequest.promise) - .mockReturnValueOnce(secondRequest.promise); - const commit = vi.fn(); - - const firstAction = faqSuggestions.actions.fetchOpenCount({ commit }, 1); - const secondAction = faqSuggestions.actions.fetchOpenCount({ commit }, 2); - - secondRequest.resolve({ data: { meta: { total_count: 4 } } }); - await secondAction; - firstRequest.resolve({ data: { meta: { total_count: 9 } } }); - await firstAction; - - expect(CaptainFaqSuggestionsAPI.get).toHaveBeenNthCalledWith(1, { - assistantId: 1, - page: 1, - }); - expect(CaptainFaqSuggestionsAPI.get).toHaveBeenNthCalledWith(2, { - assistantId: 2, - page: 1, - }); - expect(commit).toHaveBeenLastCalledWith('SET_OPEN_COUNT', 4); - }); -}); diff --git a/spec/enterprise/builders/captain/assistant_stats_builder_spec.rb b/spec/enterprise/builders/captain/assistant_stats_builder_spec.rb index 471f2ed28..785c80e8e 100644 --- a/spec/enterprise/builders/captain/assistant_stats_builder_spec.rb +++ b/spec/enterprise/builders/captain/assistant_stats_builder_spec.rb @@ -229,31 +229,6 @@ RSpec.describe Captain::AssistantStatsBuilder do end end - describe '#metrics knowledge' do - before do - create_list(:captain_assistant_response, 3, assistant: assistant, account: account, status: :approved) - assistant.faq_suggestions.create!( - question: 'How do I enable the feature?', - answer: 'Turn it on in settings.' - ) - create_list(:captain_document, 2, assistant: assistant, account: account) - end - - it 'returns approved FAQ, open suggestion, document counts and coverage' do - knowledge = described_class.new(assistant, '30').metrics[:knowledge] - - expect(knowledge).to eq(approved: 3, suggestions: 1, documents: 2, coverage: 75) - end - - it 'reports zero coverage when there are no responses' do - Captain::AssistantResponse.where(assistant: assistant).delete_all - - knowledge = described_class.new(assistant, '30').metrics[:knowledge] - - expect(knowledge[:coverage]).to eq(0) - end - end - describe '#period' do it 'labels a day range and exposes its bounds' do period = described_class.new(assistant, '30').period diff --git a/spec/enterprise/controllers/api/v1/accounts/captain/assistants_controller_spec.rb b/spec/enterprise/controllers/api/v1/accounts/captain/assistants_controller_spec.rb index c43924fc4..aa70c3a9b 100644 --- a/spec/enterprise/controllers/api/v1/accounts/captain/assistants_controller_spec.rb +++ b/spec/enterprise/controllers/api/v1/accounts/captain/assistants_controller_spec.rb @@ -252,6 +252,64 @@ RSpec.describe 'Api::V1::Accounts::Captain::Assistants', type: :request do end end + describe 'GET /api/v1/accounts/{account.id}/captain/assistants/{id}/stats' do + let(:assistant) { create(:captain_assistant, account: account) } + + it 'returns approved FAQ, open suggestion, document counts and coverage' do + create_list(:captain_assistant_response, 3, assistant: assistant, account: account, status: :approved) + assistant.faq_suggestions.create!(question: 'How do I enable the feature?', answer: 'Turn it on in settings.') + create_list(:captain_document, 2, assistant: assistant, account: account) + + get "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/stats", + params: { range: '30' }, + headers: admin.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + expect(json_response[:knowledge]).to eq(approved: 3, suggestions: 1, documents: 2, coverage: 75) + end + + it 'returns zero coverage when there are no FAQs or suggestions' do + get "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/stats", + params: { range: '30' }, + headers: admin.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + expect(json_response[:knowledge]).to include(approved: 0, suggestions: 0, coverage: 0) + end + + it 'counts only suggestions backed by conversations the agent can access' do + accessible_inbox = create(:inbox, account: account) + hidden_inbox = create(:inbox, account: account) + create(:inbox_member, user: agent, inbox: accessible_inbox) + create(:captain_assistant_response, assistant: assistant, account: account, status: :approved) + + accessible_suggestion = assistant.faq_suggestions.create!(question: 'Visible question', answer: 'Visible answer') + accessible_suggestion.observations.create!( + conversation: create(:conversation, account: account, inbox: accessible_inbox), + generated_question: accessible_suggestion.question, + generated_answer: accessible_suggestion.answer, + language: accessible_suggestion.language + ) + hidden_suggestion = assistant.faq_suggestions.create!(question: 'Hidden question', answer: 'Hidden answer') + hidden_suggestion.observations.create!( + conversation: create(:conversation, account: account, inbox: hidden_inbox), + generated_question: hidden_suggestion.question, + generated_answer: hidden_suggestion.answer, + language: hidden_suggestion.language + ) + + get "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/stats", + params: { range: '30' }, + headers: agent.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + expect(json_response[:knowledge]).to include(approved: 1, suggestions: 1, coverage: 50) + end + end + describe 'GET /api/v1/accounts/{account.id}/captain/assistants/{id}/summary' do let(:assistant) { create(:captain_assistant, account: account) } let(:alice) { create(:user, account: account, role: :administrator, name: 'Alice Adams') }