[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 <v-prklepic@microsoft.com>
This commit is contained in:
Predrag Klepic 2023-09-19 10:17:51 +02:00 committed by GitHub
parent c1c12019da
commit 260c99e15c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 189 additions and 99 deletions

View File

@ -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(<ExplanationButton />);
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(<ExplanationButton />);
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(<ExplanationButton />);
expect(wrapper.isEmptyRender()).toBe(true);
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -2,12 +2,13 @@ import { Stack, Text } from "@fluentui/react";
import { useQueryCopilot } from "hooks/useQueryCopilot"; import { useQueryCopilot } from "hooks/useQueryCopilot";
import React from "react"; import React from "react";
export const ExplanationBubble: React.FC = (): JSX.Element => { export const ExplanationButton: React.FC = (): JSX.Element => {
const { const {
showExplanationBubble, showExplanationBubble,
isGeneratingQuery, isGeneratingQuery,
chatMessages, chatMessages,
setChatMessages, setChatMessages,
generatedQuery,
generatedQueryComments, generatedQueryComments,
isGeneratingExplanation, isGeneratingExplanation,
setIsGeneratingExplanation, setIsGeneratingExplanation,
@ -24,7 +25,7 @@ export const ExplanationBubble: React.FC = (): JSX.Element => {
setTimeout(() => { setTimeout(() => {
if (useQueryCopilot.getState().shouldIncludeInMessages) { if (useQueryCopilot.getState().shouldIncludeInMessages) {
setIsGeneratingExplanation(false); setIsGeneratingExplanation(false);
setChatMessages([...chatMessages, { source: 2, message: generatedQueryComments }]); setChatMessages([...chatMessages, { source: 2, message: generatedQueryComments, sqlQuery: generatedQuery }]);
} }
}, 3000); }, 3000);
}; };

View File

@ -0,0 +1,32 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Explanation Button should render explanation bubble with generated comments 1`] = `
<Stack
style={
Object {
"alignItems": "center",
"display": "flex",
"margin": "5px",
"padding": "5px 5px 5px 50px",
}
}
>
<Text
onClick={[Function]}
style={
Object {
"border": "1.5px solid #B0BEFF",
"borderRadius": "4px",
"cursor": "pointer",
"marginBottom": "5px",
"padding": "2px",
"width": "100%",
}
}
>
Explain this query to me
</Text>
</Stack>
`;
exports[`Explanation Button should render nothing when conditions are not met 1`] = `""`;

View File

@ -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 (
<Stack
horizontalAlign="start"
verticalAlign="start"
tokens={{ padding: 8, childrenGap: 8 }}
style={{
backgroundColor: "white",
borderRadius: "8px",
margin: "5px 10px",
textAlign: "start",
}}
>
<Text>{copilotMessage.message}</Text>
<FeedbackButtons sqlQuery={copilotMessage.sqlQuery} />
<Text style={{ fontWeight: 400, fontSize: "10px", lineHeight: "14px" }}>
AI-generated content may be incorrect
</Text>
</Stack>
);
};

View File

@ -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 { shallow } from "enzyme";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import React from "react"; import React from "react";
import { ExplanationBubble } from "./ExplanationBubble";
describe("Explanation Bubble", () => { describe("Explanation Bubble snapshot tests", () => {
const initialStoreState = useQueryCopilot.getState(); it("should render", () => {
beforeEach(() => { const mockCopilotMessage: CopilotMessage = {
useQueryCopilot.setState(initialStoreState, true); source: 2,
useQueryCopilot.getState().showExplanationBubble = true; message: "Mock message",
useQueryCopilot.getState().shouldIncludeInMessages = false; };
jest.useFakeTimers();
});
afterEach(() => { const wrapper = shallow(<ExplanationBubble copilotMessage={mockCopilotMessage} />);
jest.clearAllMocks();
jest.useRealTimers();
});
it("should render explanation bubble with generated comments", () => {
useQueryCopilot.getState().shouldIncludeInMessages = true;
const wrapper = shallow(<ExplanationBubble />);
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(<ExplanationBubble />);
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(<ExplanationBubble />);
expect(wrapper.isEmptyRender()).toBe(true);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
}); });

View File

@ -1,32 +1,38 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // 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`] = `
<Stack <Stack
horizontalAlign="start"
style={ style={
Object { Object {
"alignItems": "center", "backgroundColor": "white",
"display": "flex", "borderRadius": "8px",
"margin": "5px", "margin": "5px 10px",
"padding": "5px 5px 5px 50px", "textAlign": "start",
} }
} }
tokens={
Object {
"childrenGap": 8,
"padding": 8,
}
}
verticalAlign="start"
> >
<Text>
Mock message
</Text>
<FeedbackButtons />
<Text <Text
onClick={[Function]}
style={ style={
Object { Object {
"border": "1.5px solid #B0BEFF", "fontSize": "10px",
"borderRadius": "4px", "fontWeight": 400,
"cursor": "pointer", "lineHeight": "14px",
"marginBottom": "5px",
"padding": "2px",
"width": "100%",
} }
} }
> >
Explain this query to me AI-generated content may be incorrect
</Text> </Text>
</Stack> </Stack>
`; `;
exports[`Explanation Bubble should render nothing when conditions are not met 1`] = `""`;

View File

@ -1,6 +1,7 @@
import { Stack } from "@fluentui/react"; import { Stack } from "@fluentui/react";
import { QueryCopilotProps } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces"; 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 { OutputBubble } from "Explorer/QueryCopilot/V2/Bubbles/Output/OutputBubble";
import { RetrievingBubble } from "Explorer/QueryCopilot/V2/Bubbles/Retriveing/RetrievingBubble"; import { RetrievingBubble } from "Explorer/QueryCopilot/V2/Bubbles/Retriveing/RetrievingBubble";
import { SampleBubble } from "Explorer/QueryCopilot/V2/Bubbles/Sample/SampleBubble"; import { SampleBubble } from "Explorer/QueryCopilot/V2/Bubbles/Sample/SampleBubble";
@ -42,27 +43,34 @@ export const QueryCopilotSidebar: React.FC<QueryCopilotProps> = ({ explorer }: Q
}} }}
> >
<WelcomeBubble /> <WelcomeBubble />
{chatMessages.map((message, index) => {chatMessages.map((message, index) => {
message.source === 0 || message.source === 2 ? ( switch (message.source) {
<Stack case 0:
key={index} return (
horizontalAlign="center" <Stack
tokens={{ padding: 8, childrenGap: 8 }} key={index}
style={{ horizontalAlign="center"
backgroundColor: message.source === 0 ? "#E0E7FF" : "white", tokens={{ padding: 8, childrenGap: 8 }}
borderRadius: "8px", style={{
margin: "5px 10px", backgroundColor: "#E0E7FF",
textAlign: "start", borderRadius: "8px",
}} margin: "5px 10px",
> textAlign: "start",
{message.message} }}
</Stack> >
) : ( {message.message}
<OutputBubble key={index} copilotMessage={message} /> </Stack>
) );
)} case 1:
return <OutputBubble key={index} copilotMessage={message} />;
case 2:
return <ExplanationBubble key={index} copilotMessage={message} />;
default:
return <></>;
}
})}
<RetrievingBubble /> <RetrievingBubble />
<ExplanationBubble /> <ExplanationButton />
{chatMessages.length === 0 && !isGeneratingQuery && <SampleBubble />} {chatMessages.length === 0 && !isGeneratingQuery && <SampleBubble />}
</Stack> </Stack>