diff --git a/src/Explorer/QueryCopilot/Shared/QueryCopilotClient.ts b/src/Explorer/QueryCopilot/Shared/QueryCopilotClient.ts index 149e02df0..1989eca2c 100644 --- a/src/Explorer/QueryCopilot/Shared/QueryCopilotClient.ts +++ b/src/Explorer/QueryCopilot/Shared/QueryCopilotClient.ts @@ -21,6 +21,9 @@ export const SendQueryRequest = async ({ }): Promise => { if (userPrompt.trim() !== "") { useQueryCopilot.getState().setIsGeneratingQuery(true); + useQueryCopilot.getState().setShouldIncludeInMessages(true); + useQueryCopilot.getState().setShowQueryExplanation(false); + useQueryCopilot.getState().setShowExplanationBubble(false); useTabs.getState().setIsTabExecuting(true); useTabs.getState().setIsQueryErrorThrown(false); useQueryCopilot @@ -62,14 +65,17 @@ export const SendQueryRequest = async ({ if (generateSQLQueryResponse?.sql) { let query = `Here is a query which will help you with provided prompt.\r\n **Prompt:** ${userPrompt}`; query += `\r\n${generateSQLQueryResponse.sql}`; - useQueryCopilot - .getState() - .setChatMessages([ - ...useQueryCopilot.getState().chatMessages, - { source: 1, message: query, explanation: generateSQLQueryResponse.explanation }, - ]); - useQueryCopilot.getState().setGeneratedQuery(generateSQLQueryResponse.sql); - useQueryCopilot.getState().setGeneratedQueryComments(generateSQLQueryResponse.explanation); + if (useQueryCopilot.getState().shouldIncludeInMessages) { + useQueryCopilot + .getState() + .setChatMessages([ + ...useQueryCopilot.getState().chatMessages, + { source: 1, message: query, explanation: generateSQLQueryResponse.explanation }, + ]); + useQueryCopilot.getState().setShowExplanationBubble(true); + useQueryCopilot.getState().setGeneratedQuery(generateSQLQueryResponse.sql); + useQueryCopilot.getState().setGeneratedQueryComments(generateSQLQueryResponse.explanation); + } } } else { handleError(JSON.stringify(generateSQLQueryResponse), "copilotInternalServerError"); diff --git a/src/Explorer/QueryCopilot/V2/Bubbles/Explanation/ExplanationBubble.test.tsx b/src/Explorer/QueryCopilot/V2/Bubbles/Explanation/ExplanationBubble.test.tsx new file mode 100644 index 000000000..1a0620001 --- /dev/null +++ b/src/Explorer/QueryCopilot/V2/Bubbles/Explanation/ExplanationBubble.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 { 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(); + }); + + afterEach(() => { + jest.clearAllMocks(); + jest.useRealTimers(); + }); + + it("should render explanation bubble with generated comments", () => { + useQueryCopilot.getState().showQueryExplanation = true; + useQueryCopilot.getState().shouldIncludeInMessages = true; + + const wrapper = shallow(); + + expect(wrapper.find("Stack")).toHaveLength(1); + expect(wrapper.find("Text")).toHaveLength(0); + expect(wrapper).toMatchSnapshot(); + }); + + it("should render 'Explain this query' link", () => { + const mockSetChatMessages = jest.fn(); + const mockSetIsGeneratingExplanation = jest.fn(); + const mockSetShouldIncludeInMessages = jest.fn(); + const mockSetShowQueryExplanation = jest.fn(); + useQueryCopilot.getState().setChatMessages = mockSetChatMessages; + useQueryCopilot.getState().setIsGeneratingExplanation = mockSetIsGeneratingExplanation; + useQueryCopilot.getState().setShouldIncludeInMessages = mockSetShouldIncludeInMessages; + useQueryCopilot.getState().setShowQueryExplanation = mockSetShowQueryExplanation; + + 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(mockSetShowQueryExplanation).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(3000); + + expect(mockSetIsGeneratingExplanation).toHaveBeenCalledWith(false); + expect(mockSetShowQueryExplanation).toHaveBeenCalledWith(true); + }); + + 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/ExplanationBubble.tsx new file mode 100644 index 000000000..301249637 --- /dev/null +++ b/src/Explorer/QueryCopilot/V2/Bubbles/Explanation/ExplanationBubble.tsx @@ -0,0 +1,74 @@ +import { Stack, Text } from "@fluentui/react"; +import { useQueryCopilot } from "hooks/useQueryCopilot"; +import React from "react"; + +export const ExplanationBubble: React.FC = (): JSX.Element => { + const { + showExplanationBubble, + isGeneratingQuery, + showQueryExplanation, + setShowQueryExplanation, + chatMessages, + setChatMessages, + generatedQueryComments, + isGeneratingExplanation, + setIsGeneratingExplanation, + shouldIncludeInMessages, + setShouldIncludeInMessages, + } = useQueryCopilot(); + + const showExplanation = () => { + setChatMessages([...chatMessages, { source: 0, message: "Explain this query to me" }]); + setIsGeneratingExplanation(true); + setShouldIncludeInMessages(true); + + setTimeout(() => { + setIsGeneratingExplanation(false); + setShowQueryExplanation(true); + }, 3000); + }; + + return ( + showExplanationBubble && + !isGeneratingQuery && + !isGeneratingExplanation && + (showQueryExplanation && shouldIncludeInMessages ? ( + + {generatedQueryComments} + + ) : ( + + + Explain this query to me + + + )) + ); +}; 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 new file mode 100644 index 000000000..59da7aa64 --- /dev/null +++ b/src/Explorer/QueryCopilot/V2/Bubbles/Explanation/__snapshots__/ExplanationBubble.test.tsx.snap @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Explanation Bubble should render explanation bubble with generated comments 1`] = ` + +`; + +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 27b8969c6..688b30768 100644 --- a/src/Explorer/QueryCopilot/V2/Sidebar/QueryCopilotSidebar.tsx +++ b/src/Explorer/QueryCopilot/V2/Sidebar/QueryCopilotSidebar.tsx @@ -1,5 +1,6 @@ import { Stack } from "@fluentui/react"; import { QueryCopilotProps } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces"; +import { ExplanationBubble } from "Explorer/QueryCopilot/V2/Bubbles/Explanation/ExplanationBubble"; 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"; @@ -11,7 +12,13 @@ import React from "react"; import { WelcomeSidebarModal } from "../Modal/WelcomeSidebarModal"; export const QueryCopilotSidebar: React.FC = ({ explorer }: QueryCopilotProps): JSX.Element => { - const { setWasCopilotUsed, showCopilotSidebar, chatMessages, isGeneratingQuery } = useQueryCopilot(); + const { + setWasCopilotUsed, + showCopilotSidebar, + chatMessages, + isGeneratingQuery, + showWelcomeSidebar, + } = useQueryCopilot(); React.useEffect(() => { if (showCopilotSidebar) { @@ -22,36 +29,60 @@ export const QueryCopilotSidebar: React.FC = ({ explorer }: Q return (
- - - - {chatMessages.map((message, index) => ( + {showWelcomeSidebar ? ( + + ) : ( + <> - {message} + + {chatMessages.map((message, index) => + message.source === 0 ? ( + + {message.message} + + ) : ( + // This part should be wired with OutputBubble + + {message.message} + + ) + )} + + + + + {chatMessages.length === 0 && !isGeneratingQuery && } - ))} - - - {chatMessages.length === 0 && !isGeneratingQuery && } - -