From 759a9554744e42e98f8d3717dc702cdbcc54c331 Mon Sep 17 00:00:00 2001
From: Denys Mishunov <dmishunov@gitlab.com>
Date: Thu, 19 Oct 2023 17:35:20 +0200
Subject: [PATCH] Swapped AIGenieChat with DuoChat component

Changelog: changed
MR: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/134669
EE: true
---
 .../ai/tanuki_bot/components/app.vue          |  48 ++++--
 .../ai/tanuki_bot/components/app_spec.js      | 143 +++++++-----------
 2 files changed, 89 insertions(+), 102 deletions(-)

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 59ad49a90c113..d630a08874d0b 100644
--- a/ee/app/assets/javascripts/ai/tanuki_bot/components/app.vue
+++ b/ee/app/assets/javascripts/ai/tanuki_bot/components/app.vue
@@ -1,15 +1,18 @@
 <script>
 // eslint-disable-next-line no-restricted-imports
 import { mapActions, mapState } from 'vuex';
+import { GlDuoChat } from '@gitlab/ui';
 import { v4 as uuidv4 } from 'uuid';
 import { __, s__ } from '~/locale';
+import { renderMarkdown } from '~/notes/utils';
+import { renderGFM } from '~/behaviors/markdown/render_gfm';
+import { helpPagePath } from '~/helpers/help_page_helper';
 import { helpCenterState } from '~/super_sidebar/constants';
 import aiResponseSubscription from 'ee/graphql_shared/subscriptions/ai_completion_response.subscription.graphql';
 import getAiMessages from 'ee/ai/graphql/get_ai_messages.query.graphql';
 import chatMutation from 'ee/ai/graphql/chat.mutation.graphql';
 import Tracking from '~/tracking';
 import { i18n, GENIE_CHAT_RESET_MESSAGE } from 'ee/ai/constants';
-import AiGenieChat from 'ee/ai/components/ai_genie_chat.vue';
 import { TANUKI_BOT_TRACKING_EVENT_NAME } from '../constants';
 
 export default {
@@ -30,13 +33,15 @@ export default {
       __('How do I create a template?'),
     ],
   },
+  experimentHelpPagePath: helpPagePath('policy/experiment-beta-support', { anchor: 'experiment' }),
   components: {
-    AiGenieChat,
+    GlDuoChat,
   },
   mixins: [Tracking.mixin()],
   provide() {
     return {
-      trackingEventName: TANUKI_BOT_TRACKING_EVENT_NAME,
+      renderMarkdown,
+      renderGFM,
     };
   },
   props: {
@@ -108,6 +113,7 @@ export default {
     return {
       helpCenterState,
       clientSubscriptionId: uuidv4(),
+      toolName: i18n.GITLAB_DUO,
     };
   },
   computed: {
@@ -115,7 +121,7 @@ export default {
   },
   methods: {
     ...mapActions(['addDuoChatMessage', 'setMessages', 'setLoading']),
-    sendMessage(question) {
+    onSendChatPrompt(question) {
       if (question !== GENIE_CHAT_RESET_MESSAGE) {
         this.setLoading();
       }
@@ -144,28 +150,38 @@ export default {
           });
         });
     },
-    closeDrawer() {
+    onChatClose() {
       this.helpCenterState.showTanukiBotChatDrawer = false;
     },
+    onTrackFeedback({ feedbackOptions, extendedFeedback } = {}) {
+      this.track(TANUKI_BOT_TRACKING_EVENT_NAME, {
+        action: 'click_button',
+        label: 'response_feedback',
+        property: feedbackOptions,
+        extra: {
+          extendedFeedback,
+          prompt_location: 'after_content',
+        },
+      });
+    },
   },
 };
 </script>
 
 <template>
   <div>
-    <ai-genie-chat
+    <gl-duo-chat
       v-if="helpCenterState.showTanukiBotChatDrawer"
-      :is-loading="loading"
+      :title="$options.i18n.gitlabChat"
       :messages="messages"
-      :full-screen="true"
+      error=""
+      :is-loading="loading"
       :predefined-prompts="$options.i18n.predefinedPrompts"
-      is-chat-available
-      @send-chat-prompt="sendMessage"
-      @chat-hidden="closeDrawer"
-    >
-      <template #title>
-        {{ $options.i18n.gitlabChat }}
-      </template>
-    </ai-genie-chat>
+      :experiment-help-page-url="$options.experimentHelpPagePath"
+      :tool-name="toolName"
+      @send-chat-prompt="onSendChatPrompt"
+      @chat-hidden="onChatClose"
+      @track-feedback="onTrackFeedback"
+    />
   </div>
 </template>
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 318d6dc45a71b..2ca66def3a8aa 100644
--- a/ee/spec/frontend/ai/tanuki_bot/components/app_spec.js
+++ b/ee/spec/frontend/ai/tanuki_bot/components/app_spec.js
@@ -1,15 +1,11 @@
-import { GlSprintf } from '@gitlab/ui';
+import { GlDuoChat } from '@gitlab/ui';
 import Vue, { nextTick } from 'vue';
 import { v4 as uuidv4 } from 'uuid';
 // eslint-disable-next-line no-restricted-imports
 import Vuex from 'vuex';
 import VueApollo from 'vue-apollo';
 import TanukiBotChatApp from 'ee/ai/tanuki_bot/components/app.vue';
-import AiGenieChat from 'ee/ai/components/ai_genie_chat.vue';
-import AiGenieChatConversation from 'ee/ai/components/ai_genie_chat_conversation.vue';
-import AiGenieChatMessage from 'ee/ai/components/ai_genie_chat_message.vue';
-import UserFeedback from 'ee/ai/components/user_feedback.vue';
-import { i18n, GENIE_CHAT_RESET_MESSAGE } from 'ee/ai/constants';
+import { GENIE_CHAT_RESET_MESSAGE } from 'ee/ai/constants';
 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';
@@ -22,7 +18,6 @@ import waitForPromises from 'helpers/wait_for_promises';
 import { helpCenterState } from '~/super_sidebar/constants';
 import {
   MOCK_USER_MESSAGE,
-  MOCK_TANUKI_MESSAGE,
   MOCK_USER_ID,
   MOCK_RESOURCE_ID,
   MOCK_TANUKI_SUCCESS_RES,
@@ -70,19 +65,10 @@ describe('GitLab Duo Chat', () => {
       store,
       apolloProvider,
       propsData,
-      stubs: {
-        AiGenieChat,
-        GlSprintf,
-        AiGenieChatConversation,
-        AiGenieChatMessage,
-      },
     });
   };
 
-  const findWarning = () => wrapper.findByTestId('chat-legal-warning');
-  const findGenieChat = () => wrapper.findComponent(AiGenieChat);
-  const findGeneratedByAI = () => wrapper.findByText(i18n.GENIE_CHAT_LEGAL_GENERATED_BY_AI);
-  const findAllUserFeedback = () => wrapper.findAllComponents(UserFeedback);
+  const findGlDuoChat = () => wrapper.findComponent(GlDuoChat);
 
   beforeEach(() => {
     uuidv4.mockImplementation(() => '123');
@@ -95,83 +81,57 @@ describe('GitLab Duo Chat', () => {
     expect(wrapper.vm.clientSubscriptionId).toBe('123');
   });
 
+  it('fetches the cached messages on mount', () => {
+    createComponent();
+    expect(queryHandlerMock).toHaveBeenCalled();
+  });
+
   describe('rendering', () => {
     beforeEach(() => {
       createComponent();
       helpCenterState.showTanukiBotChatDrawer = true;
     });
 
-    it('renders a legal info when rendered', () => {
-      expect(findWarning().exists()).toBe(true);
-    });
-
-    it('renders a generated by AI note', () => {
-      expect(findGeneratedByAI().exists()).toBe(true);
-    });
-
-    it('passes down the example prompts', () => {
-      expect(findGenieChat().props().predefinedPrompts).toEqual(
-        wrapper.vm.$options.i18n.predefinedPrompts,
-      );
+    it('renders the DuoChat component', () => {
+      expect(findGlDuoChat().exists()).toBe(true);
     });
   });
 
-  describe('AiGenieChat interactions', () => {
+  describe('chat props', () => {
     beforeEach(() => {
       createComponent();
       helpCenterState.showTanukiBotChatDrawer = true;
     });
-
-    it('closes the chat on @chat-hidden', async () => {
-      findGenieChat().vm.$emit('chat-hidden');
-      await nextTick();
-      expect(helpCenterState.showTanukiBotChatDrawer).toBe(false);
-      expect(findGenieChat().exists()).toBe(false);
-    });
   });
 
-  describe('Chat', () => {
+  describe('events handling', () => {
     beforeEach(() => {
-      createComponent({ loading: true });
+      createComponent();
       helpCenterState.showTanukiBotChatDrawer = true;
     });
 
-    it('renders AiGenieChat component', () => {
-      expect(findGenieChat().exists()).toBe(true);
-    });
-
-    it('fetches the cached messages on mount', () => {
-      expect(queryHandlerMock).toHaveBeenCalled();
-    });
-
-    it('renders the User Feedback component for every assistent mesage', () => {
-      createComponent({
-        messages: [MOCK_USER_MESSAGE, MOCK_TANUKI_MESSAGE, MOCK_USER_MESSAGE, MOCK_TANUKI_MESSAGE],
+    describe('@chat-hidden', () => {
+      beforeEach(async () => {
+        findGlDuoChat().vm.$emit('chat-hidden');
+        await nextTick();
       });
 
-      expect(findAllUserFeedback().length).toBe(2);
-
-      findAllUserFeedback().wrappers.forEach((component) => {
-        expect(component.props('eventName')).toBe(TANUKI_BOT_TRACKING_EVENT_NAME);
-        expect(component.props('promptLocation')).toBe('after_content');
+      it('closes the chat on @chat-hidden', () => {
+        expect(helpCenterState.showTanukiBotChatDrawer).toBe(false);
+        expect(findGlDuoChat().exists()).toBe(false);
       });
     });
 
-    describe('when input is submitted', () => {
-      beforeEach(() => {
-        findGenieChat().vm.$emit('send-chat-prompt', MOCK_USER_MESSAGE.msg);
+    describe('@send-chat-prompt', () => {
+      it('does set loading to `true` for a message other than the reset one', () => {
+        findGlDuoChat().vm.$emit('send-chat-prompt', MOCK_USER_MESSAGE.msg);
+        expect(actionSpies.setLoading).toHaveBeenCalled();
       });
-
-      describe('loading state', () => {
-        it('does set loading to `true` for a message other than the reset one', () => {
-          expect(actionSpies.setLoading).toHaveBeenCalled();
-        });
-        it('does not set loading to `true` for a reset message', async () => {
-          actionSpies.setLoading.mockReset();
-          findGenieChat().vm.$emit('send-chat-prompt', GENIE_CHAT_RESET_MESSAGE);
-          await nextTick();
-          expect(actionSpies.setLoading).not.toHaveBeenCalled();
-        });
+      it('does not set loading to `true` for a reset message', async () => {
+        actionSpies.setLoading.mockReset();
+        findGlDuoChat().vm.$emit('send-chat-prompt', GENIE_CHAT_RESET_MESSAGE);
+        await nextTick();
+        expect(actionSpies.setLoading).not.toHaveBeenCalled();
       });
 
       describe.each`
@@ -186,7 +146,7 @@ describe('GitLab Duo Chat', () => {
           'calls correct GraphQL mutation with fallback to userId when input is submitted and feature flag is $isFlagEnabled',
           async ({ expectedMutation } = {}) => {
             createComponent({}, { userId: MOCK_USER_ID, resourceId });
-            findGenieChat().vm.$emit('send-chat-prompt', MOCK_USER_MESSAGE.msg);
+            findGlDuoChat().vm.$emit('send-chat-prompt', MOCK_USER_MESSAGE.msg);
 
             await nextTick();
 
@@ -219,25 +179,36 @@ describe('GitLab Duo Chat', () => {
             MOCK_TANUKI_SUCCESS_RES.data.aiCompletionResponse,
           );
         });
+      });
+    });
 
-        describe('snowplow tracking', () => {
-          let trackingSpy;
+    describe('@track-feedback', () => {
+      let trackingSpy;
 
-          beforeEach(() => {
-            trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
-          });
+      beforeEach(() => {
+        trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+      });
 
-          afterEach(() => {
-            unmockTracking();
-          });
+      afterEach(() => {
+        unmockTracking();
+      });
 
-          it('tracks the snowplow event on successful mutation for chat', async () => {
-            createComponent();
-            findGenieChat().vm.$emit('send-chat-prompt', MOCK_USER_MESSAGE.msg);
+      it('tracks the snowplow event on successful mutation for chat', async () => {
+        createComponent();
+        findGlDuoChat().vm.$emit('track-feedback', {
+          feedbackOptions: ['foo', 'bar'],
+          extendedFeedback: 'baz',
+        });
 
-            await waitForPromises();
-            expect(trackingSpy).toHaveBeenCalled();
-          });
+        await waitForPromises();
+        expect(trackingSpy).toHaveBeenCalledWith(undefined, TANUKI_BOT_TRACKING_EVENT_NAME, {
+          action: 'click_button',
+          label: 'response_feedback',
+          property: ['foo', 'bar'],
+          extra: {
+            extendedFeedback: 'baz',
+            prompt_location: 'after_content',
+          },
         });
       });
     });
@@ -258,7 +229,7 @@ describe('GitLab Duo Chat', () => {
           helpCenterState.showTanukiBotChatDrawer = true;
           await nextTick();
 
-          findGenieChat().vm.$emit('send-chat-prompt', MOCK_USER_MESSAGE.msg);
+          findGlDuoChat().vm.$emit('send-chat-prompt', MOCK_USER_MESSAGE.msg);
         });
 
         it('once error arrives via GraphQL subscription calls addDuoChatMessage', () => {
@@ -288,7 +259,7 @@ describe('GitLab Duo Chat', () => {
 
         helpCenterState.showTanukiBotChatDrawer = true;
         await nextTick();
-        findGenieChat().vm.$emit('send-chat-prompt', MOCK_USER_MESSAGE.msg);
+        findGlDuoChat().vm.$emit('send-chat-prompt', MOCK_USER_MESSAGE.msg);
       });
 
       it('calls addDuoChatMessage', () => {
-- 
GitLab