From 260c99e15cff1a691ba0e92d9a69ba0352b239a9 Mon Sep 17 00:00:00 2001 From: Predrag Klepic <60631598+Klepic95@users.noreply.github.com> Date: Tue, 19 Sep 2023 10:17:51 +0200 Subject: [PATCH] [Query Copilot V2] Explanation bubble added buttons (#1609) * Fixing naming convention * Additional implementation for Explanation * Added snaps * Removing snapshots * re-updated snapshots --------- Co-authored-by: Predrag Klepic --- .../Button/ExplanationButton.test.tsx | 69 ++++++++++++++++++ .../ExplanationButton.tsx} | 5 +- .../ExplanationButton.test.tsx.snap | 32 +++++++++ .../Explanation/ExplainationBubble.tsx | 26 +++++++ .../Explanation/ExplanationBubble.test.tsx | 70 +++---------------- .../ExplanationBubble.test.tsx.snap | 36 ++++++---- .../V2/Sidebar/QueryCopilotSidebar.tsx | 50 +++++++------ 7 files changed, 189 insertions(+), 99 deletions(-) create mode 100644 src/Explorer/QueryCopilot/V2/Bubbles/Explanation/Button/ExplanationButton.test.tsx rename src/Explorer/QueryCopilot/V2/Bubbles/Explanation/{ExplanationBubble.tsx => Button/ExplanationButton.tsx} (91%) create mode 100644 src/Explorer/QueryCopilot/V2/Bubbles/Explanation/Button/__snapshots__/ExplanationButton.test.tsx.snap create mode 100644 src/Explorer/QueryCopilot/V2/Bubbles/Explanation/ExplainationBubble.tsx diff --git a/src/Explorer/QueryCopilot/V2/Bubbles/Explanation/Button/ExplanationButton.test.tsx b/src/Explorer/QueryCopilot/V2/Bubbles/Explanation/Button/ExplanationButton.test.tsx new file mode 100644 index 000000000..d5b984526 --- /dev/null +++ b/src/Explorer/QueryCopilot/V2/Bubbles/Explanation/Button/ExplanationButton.test.tsx @@ -0,0 +1,69 @@ +import { Text } from "@fluentui/react"; +import { shallow } from "enzyme"; +import { useQueryCopilot } from "hooks/useQueryCopilot"; +import React from "react"; +import { ExplanationButton } from "./ExplanationButton"; + +describe("Explanation Button", () => { + const initialStoreState = useQueryCopilot.getState(); + beforeEach(() => { + useQueryCopilot.setState(initialStoreState, true); + useQueryCopilot.getState().showExplanationBubble = true; + useQueryCopilot.getState().shouldIncludeInMessages = false; + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.clearAllMocks(); + jest.useRealTimers(); + }); + + it("should render explanation bubble with generated comments", () => { + useQueryCopilot.getState().shouldIncludeInMessages = true; + + const wrapper = shallow(); + + expect(wrapper.find("Stack")).toHaveLength(1); + expect(wrapper.find("Text")).toHaveLength(1); + expect(wrapper).toMatchSnapshot(); + }); + + it("should render 'Explain this query' link", () => { + useQueryCopilot.getState().shouldIncludeInMessages = true; + const mockSetChatMessages = jest.fn(); + const mockSetIsGeneratingExplanation = jest.fn(); + const mockSetShouldIncludeInMessages = jest.fn(); + const mockSetShowExplanationBubble = jest.fn(); + useQueryCopilot.getState().setChatMessages = mockSetChatMessages; + useQueryCopilot.getState().setIsGeneratingExplanation = mockSetIsGeneratingExplanation; + useQueryCopilot.getState().setShouldIncludeInMessages = mockSetShouldIncludeInMessages; + useQueryCopilot.getState().setShowExplanationBubble = mockSetShowExplanationBubble; + + const wrapper = shallow(); + + const textElement = wrapper.find(Text); + textElement.simulate("click"); + + expect(mockSetChatMessages).toHaveBeenCalledWith([ + ...initialStoreState.chatMessages, + { source: 0, message: "Explain this query to me" }, + ]); + expect(mockSetIsGeneratingExplanation).toHaveBeenCalledWith(true); + expect(mockSetShouldIncludeInMessages).toHaveBeenCalledWith(true); + expect(mockSetShowExplanationBubble).toHaveBeenCalledWith(false); + + jest.advanceTimersByTime(3000); + + expect(mockSetIsGeneratingExplanation).toHaveBeenCalledWith(false); + expect(mockSetChatMessages).toHaveBeenCalled(); + }); + + it("should render nothing when conditions are not met", () => { + useQueryCopilot.getState().showExplanationBubble = false; + + const wrapper = shallow(); + + expect(wrapper.isEmptyRender()).toBe(true); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/src/Explorer/QueryCopilot/V2/Bubbles/Explanation/ExplanationBubble.tsx b/src/Explorer/QueryCopilot/V2/Bubbles/Explanation/Button/ExplanationButton.tsx similarity index 91% rename from src/Explorer/QueryCopilot/V2/Bubbles/Explanation/ExplanationBubble.tsx rename to src/Explorer/QueryCopilot/V2/Bubbles/Explanation/Button/ExplanationButton.tsx index 1227556fa..b8003ce14 100644 --- a/src/Explorer/QueryCopilot/V2/Bubbles/Explanation/ExplanationBubble.tsx +++ b/src/Explorer/QueryCopilot/V2/Bubbles/Explanation/Button/ExplanationButton.tsx @@ -2,12 +2,13 @@ import { Stack, Text } from "@fluentui/react"; import { useQueryCopilot } from "hooks/useQueryCopilot"; import React from "react"; -export const ExplanationBubble: React.FC = (): JSX.Element => { +export const ExplanationButton: React.FC = (): JSX.Element => { const { showExplanationBubble, isGeneratingQuery, chatMessages, setChatMessages, + generatedQuery, generatedQueryComments, isGeneratingExplanation, setIsGeneratingExplanation, @@ -24,7 +25,7 @@ export const ExplanationBubble: React.FC = (): JSX.Element => { setTimeout(() => { if (useQueryCopilot.getState().shouldIncludeInMessages) { setIsGeneratingExplanation(false); - setChatMessages([...chatMessages, { source: 2, message: generatedQueryComments }]); + setChatMessages([...chatMessages, { source: 2, message: generatedQueryComments, sqlQuery: generatedQuery }]); } }, 3000); }; diff --git a/src/Explorer/QueryCopilot/V2/Bubbles/Explanation/Button/__snapshots__/ExplanationButton.test.tsx.snap b/src/Explorer/QueryCopilot/V2/Bubbles/Explanation/Button/__snapshots__/ExplanationButton.test.tsx.snap new file mode 100644 index 000000000..8e81e6695 --- /dev/null +++ b/src/Explorer/QueryCopilot/V2/Bubbles/Explanation/Button/__snapshots__/ExplanationButton.test.tsx.snap @@ -0,0 +1,32 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Explanation Button should render explanation bubble with generated comments 1`] = ` + + + Explain this query to me + + +`; + +exports[`Explanation Button should render nothing when conditions are not met 1`] = `""`; diff --git a/src/Explorer/QueryCopilot/V2/Bubbles/Explanation/ExplainationBubble.tsx b/src/Explorer/QueryCopilot/V2/Bubbles/Explanation/ExplainationBubble.tsx new file mode 100644 index 000000000..967355d9a --- /dev/null +++ b/src/Explorer/QueryCopilot/V2/Bubbles/Explanation/ExplainationBubble.tsx @@ -0,0 +1,26 @@ +import { Stack, Text } from "@fluentui/react"; +import { CopilotMessage } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces"; +import { FeedbackButtons } from "Explorer/QueryCopilot/V2/Bubbles/Output/Buttons/Feedback/FeedbackButtons"; +import React from "react"; + +export const ExplanationBubble = ({ copilotMessage }: { copilotMessage: CopilotMessage }): JSX.Element => { + return ( + + {copilotMessage.message} + + + AI-generated content may be incorrect + + + ); +}; diff --git a/src/Explorer/QueryCopilot/V2/Bubbles/Explanation/ExplanationBubble.test.tsx b/src/Explorer/QueryCopilot/V2/Bubbles/Explanation/ExplanationBubble.test.tsx index 77b457425..27d73e197 100644 --- a/src/Explorer/QueryCopilot/V2/Bubbles/Explanation/ExplanationBubble.test.tsx +++ b/src/Explorer/QueryCopilot/V2/Bubbles/Explanation/ExplanationBubble.test.tsx @@ -1,69 +1,17 @@ -import { Text } from "@fluentui/react"; +import { CopilotMessage } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces"; +import { ExplanationBubble } from "Explorer/QueryCopilot/V2/Bubbles/Explanation/ExplainationBubble"; import { shallow } from "enzyme"; -import { useQueryCopilot } from "hooks/useQueryCopilot"; import React from "react"; -import { ExplanationBubble } from "./ExplanationBubble"; -describe("Explanation Bubble", () => { - const initialStoreState = useQueryCopilot.getState(); - beforeEach(() => { - useQueryCopilot.setState(initialStoreState, true); - useQueryCopilot.getState().showExplanationBubble = true; - useQueryCopilot.getState().shouldIncludeInMessages = false; - jest.useFakeTimers(); - }); +describe("Explanation Bubble snapshot tests", () => { + it("should render", () => { + const mockCopilotMessage: CopilotMessage = { + source: 2, + message: "Mock message", + }; - afterEach(() => { - jest.clearAllMocks(); - jest.useRealTimers(); - }); + const wrapper = shallow(); - it("should render explanation bubble with generated comments", () => { - useQueryCopilot.getState().shouldIncludeInMessages = true; - - const wrapper = shallow(); - - expect(wrapper.find("Stack")).toHaveLength(1); - expect(wrapper.find("Text")).toHaveLength(1); - expect(wrapper).toMatchSnapshot(); - }); - - it("should render 'Explain this query' link", () => { - useQueryCopilot.getState().shouldIncludeInMessages = true; - const mockSetChatMessages = jest.fn(); - const mockSetIsGeneratingExplanation = jest.fn(); - const mockSetShouldIncludeInMessages = jest.fn(); - const mockSetShowExplanationBubble = jest.fn(); - useQueryCopilot.getState().setChatMessages = mockSetChatMessages; - useQueryCopilot.getState().setIsGeneratingExplanation = mockSetIsGeneratingExplanation; - useQueryCopilot.getState().setShouldIncludeInMessages = mockSetShouldIncludeInMessages; - useQueryCopilot.getState().setShowExplanationBubble = mockSetShowExplanationBubble; - - const wrapper = shallow(); - - const textElement = wrapper.find(Text); - textElement.simulate("click"); - - expect(mockSetChatMessages).toHaveBeenCalledWith([ - ...initialStoreState.chatMessages, - { source: 0, message: "Explain this query to me" }, - ]); - expect(mockSetIsGeneratingExplanation).toHaveBeenCalledWith(true); - expect(mockSetShouldIncludeInMessages).toHaveBeenCalledWith(true); - expect(mockSetShowExplanationBubble).toHaveBeenCalledWith(false); - - jest.advanceTimersByTime(3000); - - expect(mockSetIsGeneratingExplanation).toHaveBeenCalledWith(false); - expect(mockSetChatMessages).toHaveBeenCalled(); - }); - - it("should render nothing when conditions are not met", () => { - useQueryCopilot.getState().showExplanationBubble = false; - - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); expect(wrapper).toMatchSnapshot(); }); }); diff --git a/src/Explorer/QueryCopilot/V2/Bubbles/Explanation/__snapshots__/ExplanationBubble.test.tsx.snap b/src/Explorer/QueryCopilot/V2/Bubbles/Explanation/__snapshots__/ExplanationBubble.test.tsx.snap index ff0f3e711..a5097dc10 100644 --- a/src/Explorer/QueryCopilot/V2/Bubbles/Explanation/__snapshots__/ExplanationBubble.test.tsx.snap +++ b/src/Explorer/QueryCopilot/V2/Bubbles/Explanation/__snapshots__/ExplanationBubble.test.tsx.snap @@ -1,32 +1,38 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Explanation Bubble should render explanation bubble with generated comments 1`] = ` +exports[`Explanation Bubble snapshot tests should render 1`] = ` + + Mock message + + - Explain this query to me + AI-generated content may be incorrect `; - -exports[`Explanation Bubble should render nothing when conditions are not met 1`] = `""`; diff --git a/src/Explorer/QueryCopilot/V2/Sidebar/QueryCopilotSidebar.tsx b/src/Explorer/QueryCopilot/V2/Sidebar/QueryCopilotSidebar.tsx index c26fa302e..68137548b 100644 --- a/src/Explorer/QueryCopilot/V2/Sidebar/QueryCopilotSidebar.tsx +++ b/src/Explorer/QueryCopilot/V2/Sidebar/QueryCopilotSidebar.tsx @@ -1,6 +1,7 @@ import { Stack } from "@fluentui/react"; import { QueryCopilotProps } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces"; -import { ExplanationBubble } from "Explorer/QueryCopilot/V2/Bubbles/Explanation/ExplanationBubble"; +import { ExplanationButton } from "Explorer/QueryCopilot/V2/Bubbles/Explanation/Button/ExplanationButton"; +import { ExplanationBubble } from "Explorer/QueryCopilot/V2/Bubbles/Explanation/ExplainationBubble"; import { OutputBubble } from "Explorer/QueryCopilot/V2/Bubbles/Output/OutputBubble"; import { RetrievingBubble } from "Explorer/QueryCopilot/V2/Bubbles/Retriveing/RetrievingBubble"; import { SampleBubble } from "Explorer/QueryCopilot/V2/Bubbles/Sample/SampleBubble"; @@ -42,27 +43,34 @@ export const QueryCopilotSidebar: React.FC = ({ explorer }: Q }} > - {chatMessages.map((message, index) => - message.source === 0 || message.source === 2 ? ( - - {message.message} - - ) : ( - - ) - )} + {chatMessages.map((message, index) => { + switch (message.source) { + case 0: + return ( + + {message.message} + + ); + case 1: + return ; + case 2: + return ; + default: + return <>; + } + })} - + {chatMessages.length === 0 && !isGeneratingQuery && }