diff --git a/ee/app/assets/javascripts/ai/tanuki_bot/components/app.vue b/ee/app/assets/javascripts/ai/tanuki_bot/components/app.vue index cc4701e2297a1946baa99ca74798ec8482f93b31..04299155786a264f6dee91ffd4442d57da9bf9c7 100644 --- a/ee/app/assets/javascripts/ai/tanuki_bot/components/app.vue +++ b/ee/app/assets/javascripts/ai/tanuki_bot/components/app.vue @@ -11,6 +11,7 @@ import aiResponseSubscription from 'ee/graphql_shared/subscriptions/ai_completio import DuoChatCallout from 'ee/ai/components/global_callout/duo_chat_callout.vue'; import getAiMessages from 'ee/ai/graphql/get_ai_messages.query.graphql'; import chatMutation from 'ee/ai/graphql/chat.mutation.graphql'; +import duoUserFeedbackMutation from 'ee/ai/graphql/duo_user_feedback.mutation.graphql'; import Tracking from '~/tracking'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { i18n, GENIE_CHAT_RESET_MESSAGE, GENIE_CHAT_CLEAN_MESSAGE } from 'ee/ai/constants'; @@ -162,7 +163,7 @@ export default { onCalloutDismissed() { this.helpCenterState.showTanukiBotChatDrawer = true; }, - onTrackFeedback({ feedbackChoices, didWhat, improveWhat } = {}) { + onTrackFeedback({ feedbackChoices, didWhat, improveWhat, message } = {}) { this.track(TANUKI_BOT_TRACKING_EVENT_NAME, { action: 'click_button', label: 'response_feedback', @@ -173,6 +174,29 @@ export default { prompt_location: 'after_content', }, }); + + if (message) { + const { id, requestId, extras, role, content } = message; + this.$apollo + .mutate({ + mutation: duoUserFeedbackMutation, + variables: { + input: { + aiMessageId: id, + }, + }, + }) + .catch(() => { + // silent failure because of fire and forget + }); + + this.addDuoChatMessage({ + requestId, + role, + content, + extras: { ...extras, hasFeedback: true }, + }); + } }, }, }; diff --git a/ee/spec/frontend/ai/tanuki_bot/components/app_spec.js b/ee/spec/frontend/ai/tanuki_bot/components/app_spec.js index 70befa0de631db0cbcdf4450329a7b9043bc8879..d65100af89c8dd42611e0c9c00c882cbe4c7bc48 100644 --- a/ee/spec/frontend/ai/tanuki_bot/components/app_spec.js +++ b/ee/spec/frontend/ai/tanuki_bot/components/app_spec.js @@ -10,6 +10,7 @@ import { GENIE_CHAT_RESET_MESSAGE, GENIE_CHAT_CLEAN_MESSAGE } from 'ee/ai/consta import { TANUKI_BOT_TRACKING_EVENT_NAME } from 'ee/ai/tanuki_bot/constants'; import aiResponseSubscription from 'ee/graphql_shared/subscriptions/ai_completion_response.subscription.graphql'; import chatMutation from 'ee/ai/graphql/chat.mutation.graphql'; +import duoUserFeedbackMutation from 'ee/ai/graphql/duo_user_feedback.mutation.graphql'; import getAiMessages from 'ee/ai/graphql/get_ai_messages.query.graphql'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import createMockApollo from 'helpers/mock_apollo_helper'; @@ -43,8 +44,24 @@ describe('GitLab Duo Chat', () => { const subscriptionHandlerMock = jest.fn().mockResolvedValue(MOCK_TANUKI_SUCCESS_RES); const chatMutationHandlerMock = jest.fn().mockResolvedValue(MOCK_TANUKI_BOT_MUTATATION_RES); + const duoUserFeedbackMutationHandlerMock = jest.fn().mockResolvedValue({}); const queryHandlerMock = jest.fn().mockResolvedValue(MOCK_CHAT_CACHED_MESSAGES_RES); + const feedbackData = { + feedbackChoices: ['useful', 'not_relevant'], + didWhat: 'provided clarity', + improveWhat: 'more examples', + message: { + requestId: '1234567890', + id: 'abcdefgh', + role: 'user', + content: 'test', + extras: { + exampleExtraContent: 1, + }, + }, + }; + const findCallout = () => wrapper.findComponent(DuoChatCallout); const createComponent = ({ @@ -62,6 +79,7 @@ describe('GitLab Duo Chat', () => { const apolloProvider = createMockApollo([ [aiResponseSubscription, subscriptionHandlerMock], [chatMutation, chatMutationHandlerMock], + [duoUserFeedbackMutation, duoUserFeedbackMutationHandlerMock], [getAiMessages, queryHandlerMock], ]); @@ -295,6 +313,34 @@ describe('GitLab Duo Chat', () => { }, }); }); + + it('calls the feedback GraphQL mutation when message is passed', async () => { + createComponent(); + findGlDuoChat().vm.$emit('track-feedback', feedbackData); + + await waitForPromises(); + expect(duoUserFeedbackMutationHandlerMock).toHaveBeenCalledWith({ + input: { + aiMessageId: feedbackData.message.id, + }, + }); + }); + + it('updates Vuex store correctly when message is passed', async () => { + createComponent(); + findGlDuoChat().vm.$emit('track-feedback', feedbackData); + + await waitForPromises(); + expect(actionSpies.addDuoChatMessage).toHaveBeenCalledWith( + expect.any(Object), + expect.objectContaining({ + requestId: feedbackData.message.requestId, + role: feedbackData.message.role, + content: feedbackData.message.content, + extras: { ...feedbackData.message.extras, hasFeedback: true }, + }), + ); + }); }); });