[Query Copilot V2] Wire and adjust Output bubble with backend communication (#1599)
* Initial wiring of copilot backend and bubble * Additional changes in explanation bubbles * Changes based on checks * test snapshots updated --------- Co-authored-by: Predrag Klepic <v-prklepic@microsoft.com>
This commit is contained in:
parent
8c2ca4ab8e
commit
76408e2f98
|
@ -20,15 +20,13 @@ export const SendQueryRequest = async ({
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
}): Promise<void> => {
|
}): Promise<void> => {
|
||||||
if (userPrompt.trim() !== "") {
|
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
|
useQueryCopilot
|
||||||
.getState()
|
.getState()
|
||||||
.setChatMessages([...useQueryCopilot.getState().chatMessages, { source: 0, message: userPrompt }]);
|
.setChatMessages([...useQueryCopilot.getState().chatMessages, { source: 0, message: userPrompt }]);
|
||||||
|
useQueryCopilot.getState().setIsGeneratingQuery(true);
|
||||||
|
useQueryCopilot.getState().setShouldIncludeInMessages(true);
|
||||||
|
useTabs.getState().setIsTabExecuting(true);
|
||||||
|
useTabs.getState().setIsQueryErrorThrown(false);
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
useQueryCopilot.getState().containerStatus.status !== ContainerStatusType.Active &&
|
useQueryCopilot.getState().containerStatus.status !== ContainerStatusType.Active &&
|
||||||
|
@ -62,14 +60,16 @@ export const SendQueryRequest = async ({
|
||||||
const generateSQLQueryResponse: GenerateSQLQueryResponse = await response?.json();
|
const generateSQLQueryResponse: GenerateSQLQueryResponse = await response?.json();
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
if (generateSQLQueryResponse?.sql) {
|
if (generateSQLQueryResponse?.sql) {
|
||||||
let query = `Here is a query which will help you with provided prompt.\r\n **Prompt:** ${userPrompt}`;
|
const bubbleMessage = `Here is a query which will help you with provided prompt.\r\n **Prompt:** "${userPrompt}"`;
|
||||||
query += `\r\n${generateSQLQueryResponse.sql}`;
|
|
||||||
if (useQueryCopilot.getState().shouldIncludeInMessages) {
|
if (useQueryCopilot.getState().shouldIncludeInMessages) {
|
||||||
useQueryCopilot
|
useQueryCopilot.getState().setChatMessages([
|
||||||
.getState()
|
|
||||||
.setChatMessages([
|
|
||||||
...useQueryCopilot.getState().chatMessages,
|
...useQueryCopilot.getState().chatMessages,
|
||||||
{ source: 1, message: query, explanation: generateSQLQueryResponse.explanation },
|
{
|
||||||
|
source: 1,
|
||||||
|
message: bubbleMessage,
|
||||||
|
sqlQuery: generateSQLQueryResponse.sql,
|
||||||
|
explanation: generateSQLQueryResponse.explanation,
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
useQueryCopilot.getState().setShowExplanationBubble(true);
|
useQueryCopilot.getState().setShowExplanationBubble(true);
|
||||||
useQueryCopilot.getState().setGeneratedQuery(generateSQLQueryResponse.sql);
|
useQueryCopilot.getState().setGeneratedQuery(generateSQLQueryResponse.sql);
|
||||||
|
|
|
@ -11,11 +11,13 @@ export interface GenerateSQLQueryResponse {
|
||||||
enum MessageSource {
|
enum MessageSource {
|
||||||
User,
|
User,
|
||||||
AI,
|
AI,
|
||||||
|
AIExplanation,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CopilotMessage {
|
export interface CopilotMessage {
|
||||||
source: MessageSource;
|
source: MessageSource;
|
||||||
message: string;
|
message: string;
|
||||||
|
sqlQuery?: string;
|
||||||
explanation?: string;
|
explanation?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,25 +19,25 @@ describe("Explanation Bubble", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render explanation bubble with generated comments", () => {
|
it("should render explanation bubble with generated comments", () => {
|
||||||
useQueryCopilot.getState().showQueryExplanation = true;
|
|
||||||
useQueryCopilot.getState().shouldIncludeInMessages = true;
|
useQueryCopilot.getState().shouldIncludeInMessages = true;
|
||||||
|
|
||||||
const wrapper = shallow(<ExplanationBubble />);
|
const wrapper = shallow(<ExplanationBubble />);
|
||||||
|
|
||||||
expect(wrapper.find("Stack")).toHaveLength(1);
|
expect(wrapper.find("Stack")).toHaveLength(1);
|
||||||
expect(wrapper.find("Text")).toHaveLength(0);
|
expect(wrapper.find("Text")).toHaveLength(1);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render 'Explain this query' link", () => {
|
it("should render 'Explain this query' link", () => {
|
||||||
|
useQueryCopilot.getState().shouldIncludeInMessages = true;
|
||||||
const mockSetChatMessages = jest.fn();
|
const mockSetChatMessages = jest.fn();
|
||||||
const mockSetIsGeneratingExplanation = jest.fn();
|
const mockSetIsGeneratingExplanation = jest.fn();
|
||||||
const mockSetShouldIncludeInMessages = jest.fn();
|
const mockSetShouldIncludeInMessages = jest.fn();
|
||||||
const mockSetShowQueryExplanation = jest.fn();
|
const mockSetShowExplanationBubble = jest.fn();
|
||||||
useQueryCopilot.getState().setChatMessages = mockSetChatMessages;
|
useQueryCopilot.getState().setChatMessages = mockSetChatMessages;
|
||||||
useQueryCopilot.getState().setIsGeneratingExplanation = mockSetIsGeneratingExplanation;
|
useQueryCopilot.getState().setIsGeneratingExplanation = mockSetIsGeneratingExplanation;
|
||||||
useQueryCopilot.getState().setShouldIncludeInMessages = mockSetShouldIncludeInMessages;
|
useQueryCopilot.getState().setShouldIncludeInMessages = mockSetShouldIncludeInMessages;
|
||||||
useQueryCopilot.getState().setShowQueryExplanation = mockSetShowQueryExplanation;
|
useQueryCopilot.getState().setShowExplanationBubble = mockSetShowExplanationBubble;
|
||||||
|
|
||||||
const wrapper = shallow(<ExplanationBubble />);
|
const wrapper = shallow(<ExplanationBubble />);
|
||||||
|
|
||||||
|
@ -50,12 +50,12 @@ describe("Explanation Bubble", () => {
|
||||||
]);
|
]);
|
||||||
expect(mockSetIsGeneratingExplanation).toHaveBeenCalledWith(true);
|
expect(mockSetIsGeneratingExplanation).toHaveBeenCalledWith(true);
|
||||||
expect(mockSetShouldIncludeInMessages).toHaveBeenCalledWith(true);
|
expect(mockSetShouldIncludeInMessages).toHaveBeenCalledWith(true);
|
||||||
expect(mockSetShowQueryExplanation).not.toHaveBeenCalled();
|
expect(mockSetShowExplanationBubble).toHaveBeenCalledWith(false);
|
||||||
|
|
||||||
jest.advanceTimersByTime(3000);
|
jest.advanceTimersByTime(3000);
|
||||||
|
|
||||||
expect(mockSetIsGeneratingExplanation).toHaveBeenCalledWith(false);
|
expect(mockSetIsGeneratingExplanation).toHaveBeenCalledWith(false);
|
||||||
expect(mockSetShowQueryExplanation).toHaveBeenCalledWith(true);
|
expect(mockSetChatMessages).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render nothing when conditions are not met", () => {
|
it("should render nothing when conditions are not met", () => {
|
||||||
|
|
|
@ -6,53 +6,39 @@ export const ExplanationBubble: React.FC = (): JSX.Element => {
|
||||||
const {
|
const {
|
||||||
showExplanationBubble,
|
showExplanationBubble,
|
||||||
isGeneratingQuery,
|
isGeneratingQuery,
|
||||||
showQueryExplanation,
|
|
||||||
setShowQueryExplanation,
|
|
||||||
chatMessages,
|
chatMessages,
|
||||||
setChatMessages,
|
setChatMessages,
|
||||||
generatedQueryComments,
|
generatedQueryComments,
|
||||||
isGeneratingExplanation,
|
isGeneratingExplanation,
|
||||||
setIsGeneratingExplanation,
|
setIsGeneratingExplanation,
|
||||||
shouldIncludeInMessages,
|
|
||||||
setShouldIncludeInMessages,
|
setShouldIncludeInMessages,
|
||||||
|
setShowExplanationBubble,
|
||||||
} = useQueryCopilot();
|
} = useQueryCopilot();
|
||||||
|
|
||||||
const showExplanation = () => {
|
const showExplanation = () => {
|
||||||
setChatMessages([...chatMessages, { source: 0, message: "Explain this query to me" }]);
|
setChatMessages([...chatMessages, { source: 0, message: "Explain this query to me" }]);
|
||||||
setIsGeneratingExplanation(true);
|
setIsGeneratingExplanation(true);
|
||||||
setShouldIncludeInMessages(true);
|
setShouldIncludeInMessages(true);
|
||||||
|
setShowExplanationBubble(false);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
if (useQueryCopilot.getState().shouldIncludeInMessages) {
|
||||||
setIsGeneratingExplanation(false);
|
setIsGeneratingExplanation(false);
|
||||||
setShowQueryExplanation(true);
|
setChatMessages([...chatMessages, { source: 2, message: generatedQueryComments }]);
|
||||||
|
}
|
||||||
}, 3000);
|
}, 3000);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
showExplanationBubble &&
|
showExplanationBubble &&
|
||||||
!isGeneratingQuery &&
|
!isGeneratingQuery &&
|
||||||
!isGeneratingExplanation &&
|
!isGeneratingExplanation && (
|
||||||
(showQueryExplanation && shouldIncludeInMessages ? (
|
|
||||||
<Stack
|
|
||||||
horizontalAlign="center"
|
|
||||||
tokens={{ padding: 8, childrenGap: 8 }}
|
|
||||||
style={{
|
|
||||||
backgroundColor: "white",
|
|
||||||
borderRadius: "8px",
|
|
||||||
margin: "5px 10px",
|
|
||||||
textAlign: "start",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{generatedQueryComments}
|
|
||||||
</Stack>
|
|
||||||
) : (
|
|
||||||
<Stack
|
<Stack
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
padding: "5px 5px 5px 40px",
|
padding: "5px 5px 5px 50px",
|
||||||
margin: "5px",
|
margin: "5px",
|
||||||
width: "100%",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
|
@ -69,6 +55,6 @@ export const ExplanationBubble: React.FC = (): JSX.Element => {
|
||||||
Explain this query to me
|
Explain this query to me
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
))
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,22 +2,31 @@
|
||||||
|
|
||||||
exports[`Explanation Bubble should render explanation bubble with generated comments 1`] = `
|
exports[`Explanation Bubble should render explanation bubble with generated comments 1`] = `
|
||||||
<Stack
|
<Stack
|
||||||
horizontalAlign="center"
|
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "white",
|
"alignItems": "center",
|
||||||
"borderRadius": "8px",
|
"display": "flex",
|
||||||
"margin": "5px 10px",
|
"margin": "5px",
|
||||||
"textAlign": "start",
|
"padding": "5px 5px 5px 50px",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tokens={
|
>
|
||||||
|
<Text
|
||||||
|
onClick={[Function]}
|
||||||
|
style={
|
||||||
Object {
|
Object {
|
||||||
"childrenGap": 8,
|
"border": "1.5px solid #B0BEFF",
|
||||||
"padding": 8,
|
"borderRadius": "4px",
|
||||||
|
"cursor": "pointer",
|
||||||
|
"marginBottom": "5px",
|
||||||
|
"padding": "2px",
|
||||||
|
"width": "100%",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
>
|
||||||
|
Explain this query to me
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Explanation Bubble should render nothing when conditions are not met 1`] = `""`;
|
exports[`Explanation Bubble should render nothing when conditions are not met 1`] = `""`;
|
||||||
|
|
|
@ -10,7 +10,7 @@ describe("Copy button snapshot tests", () => {
|
||||||
it("should render and click copy", async () => {
|
it("should render and click copy", async () => {
|
||||||
const testInput = "test input query";
|
const testInput = "test input query";
|
||||||
useQueryCopilot.getState().setGeneratedQuery(testInput);
|
useQueryCopilot.getState().setGeneratedQuery(testInput);
|
||||||
const wrapper = shallow(<CopyButton />);
|
const wrapper = shallow(<CopyButton sqlQuery={""} />);
|
||||||
|
|
||||||
const button = wrapper.find(IconButton).first();
|
const button = wrapper.find(IconButton).first();
|
||||||
button.simulate("click", {});
|
button.simulate("click", {});
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import { IconButton } from "@fluentui/react";
|
import { IconButton } from "@fluentui/react";
|
||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import CopilotCopy from "../../../../../../../../images/CopilotCopy.svg";
|
import CopilotCopy from "../../../../../../../../images/CopilotCopy.svg";
|
||||||
|
|
||||||
export const CopyButton: React.FC = (): JSX.Element => {
|
export const CopyButton = ({ sqlQuery }: { sqlQuery: string }): JSX.Element => {
|
||||||
const copyGeneratedCode = (): void => {
|
const copyGeneratedCode = (): void => {
|
||||||
const queryElement = document.createElement("textarea");
|
const queryElement = document.createElement("textarea");
|
||||||
queryElement.value = useQueryCopilot.getState().generatedQuery;
|
queryElement.value = sqlQuery;
|
||||||
document.body.appendChild(queryElement);
|
document.body.appendChild(queryElement);
|
||||||
queryElement.select();
|
queryElement.select();
|
||||||
document.execCommand("copy");
|
document.execCommand("copy");
|
||||||
|
|
|
@ -20,7 +20,7 @@ beforeEach(() => {
|
||||||
|
|
||||||
describe("Feedback buttons snapshot tests", () => {
|
describe("Feedback buttons snapshot tests", () => {
|
||||||
it("should click like and show callout", () => {
|
it("should click like and show callout", () => {
|
||||||
const wrapper = shallow(<FeedbackButtons />);
|
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||||
|
|
||||||
let likeButton = wrapper.find(IconButton).first();
|
let likeButton = wrapper.find(IconButton).first();
|
||||||
const dislikeButton = wrapper.find(IconButton).last();
|
const dislikeButton = wrapper.find(IconButton).last();
|
||||||
|
@ -35,7 +35,7 @@ describe("Feedback buttons snapshot tests", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should click like and dismiss callout", () => {
|
it("should click like and dismiss callout", () => {
|
||||||
const wrapper = shallow(<FeedbackButtons />);
|
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||||
|
|
||||||
const likeButton = wrapper.find(IconButton).first();
|
const likeButton = wrapper.find(IconButton).first();
|
||||||
likeButton.simulate("click");
|
likeButton.simulate("click");
|
||||||
|
@ -49,7 +49,7 @@ describe("Feedback buttons snapshot tests", () => {
|
||||||
|
|
||||||
it("should click like and submit feedback", () => {
|
it("should click like and submit feedback", () => {
|
||||||
const spy = jest.spyOn(useQueryCopilot.getState(), "openFeedbackModal");
|
const spy = jest.spyOn(useQueryCopilot.getState(), "openFeedbackModal");
|
||||||
const wrapper = shallow(<FeedbackButtons />);
|
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||||
|
|
||||||
const likeButton = wrapper.find(IconButton).first();
|
const likeButton = wrapper.find(IconButton).first();
|
||||||
likeButton.simulate("click");
|
likeButton.simulate("click");
|
||||||
|
@ -61,7 +61,7 @@ describe("Feedback buttons snapshot tests", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should hover over like", () => {
|
it("should hover over like", () => {
|
||||||
const wrapper = shallow(<FeedbackButtons />);
|
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||||
|
|
||||||
let likeButton = wrapper.find(IconButton).first();
|
let likeButton = wrapper.find(IconButton).first();
|
||||||
likeButton.simulate("mouseover");
|
likeButton.simulate("mouseover");
|
||||||
|
@ -72,7 +72,7 @@ describe("Feedback buttons snapshot tests", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should hover over rest like and leave", () => {
|
it("should hover over rest like and leave", () => {
|
||||||
const wrapper = shallow(<FeedbackButtons />);
|
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||||
|
|
||||||
let likeButton = wrapper.find(IconButton).first();
|
let likeButton = wrapper.find(IconButton).first();
|
||||||
likeButton.simulate("mouseover");
|
likeButton.simulate("mouseover");
|
||||||
|
@ -84,7 +84,7 @@ describe("Feedback buttons snapshot tests", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should hover over pressed like and leave", () => {
|
it("should hover over pressed like and leave", () => {
|
||||||
const wrapper = shallow(<FeedbackButtons />);
|
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||||
|
|
||||||
let likeButton = wrapper.find(IconButton).first();
|
let likeButton = wrapper.find(IconButton).first();
|
||||||
likeButton.simulate("click");
|
likeButton.simulate("click");
|
||||||
|
@ -97,7 +97,7 @@ describe("Feedback buttons snapshot tests", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should hover over like and click", () => {
|
it("should hover over like and click", () => {
|
||||||
const wrapper = shallow(<FeedbackButtons />);
|
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||||
|
|
||||||
let likeButton = wrapper.find(IconButton).first();
|
let likeButton = wrapper.find(IconButton).first();
|
||||||
likeButton.simulate("mouseover");
|
likeButton.simulate("mouseover");
|
||||||
|
@ -109,7 +109,7 @@ describe("Feedback buttons snapshot tests", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should dobule click on like", () => {
|
it("should dobule click on like", () => {
|
||||||
const wrapper = shallow(<FeedbackButtons />);
|
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||||
|
|
||||||
let likeButton = wrapper.find(IconButton).first();
|
let likeButton = wrapper.find(IconButton).first();
|
||||||
likeButton.simulate("click");
|
likeButton.simulate("click");
|
||||||
|
@ -124,7 +124,7 @@ describe("Feedback buttons snapshot tests", () => {
|
||||||
|
|
||||||
it("should click dislike and show popup", () => {
|
it("should click dislike and show popup", () => {
|
||||||
const spy = jest.spyOn(useQueryCopilot.getState(), "openFeedbackModal");
|
const spy = jest.spyOn(useQueryCopilot.getState(), "openFeedbackModal");
|
||||||
const wrapper = shallow(<FeedbackButtons />);
|
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||||
|
|
||||||
const likeButton = wrapper.find(IconButton).first();
|
const likeButton = wrapper.find(IconButton).first();
|
||||||
let dislikeButton = wrapper.find(IconButton).last();
|
let dislikeButton = wrapper.find(IconButton).last();
|
||||||
|
@ -140,7 +140,7 @@ describe("Feedback buttons snapshot tests", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should hover over dislike", () => {
|
it("should hover over dislike", () => {
|
||||||
const wrapper = shallow(<FeedbackButtons />);
|
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||||
|
|
||||||
let dislikeButton = wrapper.find(IconButton).last();
|
let dislikeButton = wrapper.find(IconButton).last();
|
||||||
dislikeButton.simulate("mouseover");
|
dislikeButton.simulate("mouseover");
|
||||||
|
@ -151,7 +151,7 @@ describe("Feedback buttons snapshot tests", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should hover over rest dislike and leave", () => {
|
it("should hover over rest dislike and leave", () => {
|
||||||
const wrapper = shallow(<FeedbackButtons />);
|
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||||
|
|
||||||
let dislikeButton = wrapper.find(IconButton).last();
|
let dislikeButton = wrapper.find(IconButton).last();
|
||||||
dislikeButton.simulate("mouseover");
|
dislikeButton.simulate("mouseover");
|
||||||
|
@ -163,7 +163,7 @@ describe("Feedback buttons snapshot tests", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should hover over pressed dislike and leave", () => {
|
it("should hover over pressed dislike and leave", () => {
|
||||||
const wrapper = shallow(<FeedbackButtons />);
|
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||||
|
|
||||||
let dislikeButton = wrapper.find(IconButton).last();
|
let dislikeButton = wrapper.find(IconButton).last();
|
||||||
dislikeButton.simulate("click");
|
dislikeButton.simulate("click");
|
||||||
|
@ -178,7 +178,7 @@ describe("Feedback buttons snapshot tests", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should hover over dislike and click", () => {
|
it("should hover over dislike and click", () => {
|
||||||
const wrapper = shallow(<FeedbackButtons />);
|
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||||
|
|
||||||
let dislikeButton = wrapper.find(IconButton).last();
|
let dislikeButton = wrapper.find(IconButton).last();
|
||||||
dislikeButton.simulate("mouseover");
|
dislikeButton.simulate("mouseover");
|
||||||
|
@ -190,7 +190,7 @@ describe("Feedback buttons snapshot tests", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should dobule click on dislike", () => {
|
it("should dobule click on dislike", () => {
|
||||||
const wrapper = shallow(<FeedbackButtons />);
|
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||||
|
|
||||||
let dislikeButton = wrapper.find(IconButton).last();
|
let dislikeButton = wrapper.find(IconButton).last();
|
||||||
dislikeButton.simulate("click");
|
dislikeButton.simulate("click");
|
||||||
|
|
|
@ -6,8 +6,8 @@ import LikeHover from "../../../../../../../../images/CopilotLikeHover.svg";
|
||||||
import LikePressed from "../../../../../../../../images/CopilotLikePressed.svg";
|
import LikePressed from "../../../../../../../../images/CopilotLikePressed.svg";
|
||||||
import LikeRest from "../../../../../../../../images/CopilotLikeRest.svg";
|
import LikeRest from "../../../../../../../../images/CopilotLikeRest.svg";
|
||||||
|
|
||||||
export const FeedbackButtons: React.FC = (): JSX.Element => {
|
export const FeedbackButtons = ({ sqlQuery }: { sqlQuery: string }): JSX.Element => {
|
||||||
const { generatedQuery, userPrompt } = useQueryCopilot();
|
const { userPrompt } = useQueryCopilot();
|
||||||
|
|
||||||
const [likeQuery, setLikeQuery] = useState<boolean>(false);
|
const [likeQuery, setLikeQuery] = useState<boolean>(false);
|
||||||
const [dislikeQuery, setDislikeQuery] = useState<boolean>(false);
|
const [dislikeQuery, setDislikeQuery] = useState<boolean>(false);
|
||||||
|
@ -35,7 +35,7 @@ export const FeedbackButtons: React.FC = (): JSX.Element => {
|
||||||
<Link
|
<Link
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCalloutVisible(false);
|
setCalloutVisible(false);
|
||||||
useQueryCopilot.getState().openFeedbackModal(generatedQuery, true, userPrompt);
|
useQueryCopilot.getState().openFeedbackModal(sqlQuery, true, userPrompt);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
more feedback?
|
more feedback?
|
||||||
|
@ -82,7 +82,7 @@ export const FeedbackButtons: React.FC = (): JSX.Element => {
|
||||||
setLikeQuery(false);
|
setLikeQuery(false);
|
||||||
setDislikeImageLink(LikePressed);
|
setDislikeImageLink(LikePressed);
|
||||||
setLikeImageLink(LikeRest);
|
setLikeImageLink(LikeRest);
|
||||||
useQueryCopilot.getState().openFeedbackModal(generatedQuery, false, userPrompt);
|
useQueryCopilot.getState().openFeedbackModal(sqlQuery, false, userPrompt);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onMouseOver={() => setDislikeImageLink(LikeHover)}
|
onMouseOver={() => setDislikeImageLink(LikeHover)}
|
||||||
|
|
|
@ -1,19 +1,10 @@
|
||||||
import { ActionButton } from "@fluentui/react";
|
|
||||||
import { InsertButton } from "Explorer/QueryCopilot/V2/Bubbles/Output/Buttons/Insert/InsertButton";
|
import { InsertButton } from "Explorer/QueryCopilot/V2/Bubbles/Output/Buttons/Insert/InsertButton";
|
||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
describe("Insert button snapshot tests", () => {
|
describe("Insert button snapshot tests", () => {
|
||||||
it("should click and update state", () => {
|
it("should click and update state", () => {
|
||||||
const testQuery = "test query";
|
const wrapper = shallow(<InsertButton sqlQuery={""} />);
|
||||||
useQueryCopilot.getState().setGeneratedQuery(testQuery);
|
|
||||||
const wrapper = shallow(<InsertButton />);
|
|
||||||
|
|
||||||
const button = wrapper.find(ActionButton).first();
|
|
||||||
button.simulate("click");
|
|
||||||
|
|
||||||
expect(useQueryCopilot.getState().query).toEqual(testQuery);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,12 +3,12 @@ import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import CopilotInsert from "../../../../../../../../images/CopilotInsert.svg";
|
import CopilotInsert from "../../../../../../../../images/CopilotInsert.svg";
|
||||||
|
|
||||||
export const InsertButton: React.FC = (): JSX.Element => {
|
export const InsertButton = ({ sqlQuery }: { sqlQuery: string }): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
iconProps={{ imageProps: { src: CopilotInsert } }}
|
iconProps={{ imageProps: { src: CopilotInsert } }}
|
||||||
style={{ borderRadius: "4px", borderWidth: "1px", borderColor: "#D1D1D1", height: "24px", paddingBottom: "2px" }}
|
style={{ borderRadius: "4px", borderWidth: "1px", borderColor: "#D1D1D1", height: "24px", paddingBottom: "2px" }}
|
||||||
onClick={() => useQueryCopilot.getState().setQuery(useQueryCopilot.getState().generatedQuery)}
|
onClick={() => useQueryCopilot.getState().setQuery(sqlQuery)}
|
||||||
>
|
>
|
||||||
Insert
|
Insert
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import React from "react";
|
||||||
|
|
||||||
describe("Output Bubble Buttons snapshot tests", () => {
|
describe("Output Bubble Buttons snapshot tests", () => {
|
||||||
it("should render", () => {
|
it("should render", () => {
|
||||||
const wrapper = shallow(<OutputBubbleButtons />);
|
const wrapper = shallow(<OutputBubbleButtons sqlQuery={""} />);
|
||||||
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,17 +5,17 @@ import { InsertButton } from "Explorer/QueryCopilot/V2/Bubbles/Output/Buttons/In
|
||||||
import { MoreButton } from "Explorer/QueryCopilot/V2/Bubbles/Output/Buttons/More/MoreButton";
|
import { MoreButton } from "Explorer/QueryCopilot/V2/Bubbles/Output/Buttons/More/MoreButton";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
export const OutputBubbleButtons: React.FC = (): JSX.Element => {
|
export const OutputBubbleButtons = ({ sqlQuery }: { sqlQuery: string }): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<Stack.Item style={{ paddingTop: "5px" }}>
|
<Stack.Item style={{ paddingTop: "5px" }}>
|
||||||
<InsertButton />
|
<InsertButton sqlQuery={sqlQuery} />
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
<CopyButton />
|
<CopyButton sqlQuery={sqlQuery} />
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
<FeedbackButtons />
|
<FeedbackButtons sqlQuery={sqlQuery} />
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
<MoreButton />
|
<MoreButton />
|
||||||
|
|
|
@ -11,13 +11,19 @@ exports[`Output Bubble Buttons snapshot tests should render 1`] = `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<InsertButton />
|
<InsertButton
|
||||||
|
sqlQuery=""
|
||||||
|
/>
|
||||||
</StackItem>
|
</StackItem>
|
||||||
<StackItem>
|
<StackItem>
|
||||||
<CopyButton />
|
<CopyButton
|
||||||
|
sqlQuery=""
|
||||||
|
/>
|
||||||
</StackItem>
|
</StackItem>
|
||||||
<StackItem>
|
<StackItem>
|
||||||
<FeedbackButtons />
|
<FeedbackButtons
|
||||||
|
sqlQuery=""
|
||||||
|
/>
|
||||||
</StackItem>
|
</StackItem>
|
||||||
<StackItem>
|
<StackItem>
|
||||||
<MoreButton />
|
<MoreButton />
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
|
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
|
||||||
import { OutputBubble } from "Explorer/QueryCopilot/V2/Bubbles/Output/OutputBubble";
|
import { OutputBubble } from "Explorer/QueryCopilot/V2/Bubbles/Output/OutputBubble";
|
||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
|
||||||
import { withHooks } from "jest-react-hooks-shallow";
|
import { withHooks } from "jest-react-hooks-shallow";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
describe("Output Bubble snapshot tests", () => {
|
describe("Output Bubble snapshot tests", () => {
|
||||||
it("should render and update height", () => {
|
it("should render and update height", () => {
|
||||||
withHooks(() => {
|
withHooks(() => {
|
||||||
useQueryCopilot.getState().setGeneratedQuery("test query");
|
const wrapper = shallow(
|
||||||
useQueryCopilot.getState().setGeneratedQueryComments("test comments");
|
<OutputBubble
|
||||||
const wrapper = shallow(<OutputBubble />);
|
copilotMessage={{ message: "testMessage", source: 1, explanation: "testExplanation", sqlQuery: "testSQL" }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
const editor = wrapper.find(EditorReact).first();
|
const editor = wrapper.find(EditorReact).first();
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { Stack, Text } from "@fluentui/react";
|
import { Stack, Text } from "@fluentui/react";
|
||||||
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
|
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
|
||||||
|
import { CopilotMessage } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
||||||
import { OutputBubbleButtons } from "Explorer/QueryCopilot/V2/Bubbles/Output/Buttons/OutputBubbleButtons";
|
import { OutputBubbleButtons } from "Explorer/QueryCopilot/V2/Bubbles/Output/Buttons/OutputBubbleButtons";
|
||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
export const OutputBubble: React.FC = (): JSX.Element => {
|
export const OutputBubble = ({ copilotMessage }: { copilotMessage: CopilotMessage }): JSX.Element => {
|
||||||
const [windowHeight, setWindowHeight] = useState<string>();
|
const [windowHeight, setWindowHeight] = useState<string>();
|
||||||
|
|
||||||
const calculateQueryWindowHeight = (): string => {
|
const calculateQueryWindowHeight = (): string => {
|
||||||
|
@ -29,13 +29,11 @@ export const OutputBubble: React.FC = (): JSX.Element => {
|
||||||
}}
|
}}
|
||||||
tokens={{ padding: 8, childrenGap: 8 }}
|
tokens={{ padding: 8, childrenGap: 8 }}
|
||||||
>
|
>
|
||||||
<Stack.Item style={{ alignSelf: "flex-start", paddingLeft: "2px" }}>
|
<Stack.Item style={{ alignSelf: "flex-start", paddingLeft: "2px" }}>{copilotMessage.message}</Stack.Item>
|
||||||
{useQueryCopilot.getState()?.generatedQueryComments}
|
|
||||||
</Stack.Item>
|
|
||||||
<Stack.Item style={{ alignSelf: "stretch", flexGrow: 4 }}>
|
<Stack.Item style={{ alignSelf: "stretch", flexGrow: 4 }}>
|
||||||
<EditorReact
|
<EditorReact
|
||||||
language={"sql"}
|
language={"sql"}
|
||||||
content={useQueryCopilot.getState()?.generatedQuery}
|
content={copilotMessage.sqlQuery}
|
||||||
isReadOnly={true}
|
isReadOnly={true}
|
||||||
ariaLabel={"AI Response"}
|
ariaLabel={"AI Response"}
|
||||||
wordWrap="on"
|
wordWrap="on"
|
||||||
|
@ -48,7 +46,7 @@ export const OutputBubble: React.FC = (): JSX.Element => {
|
||||||
/>
|
/>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
<Stack.Item style={{ alignSelf: "flex-start" }}>
|
<Stack.Item style={{ alignSelf: "flex-start" }}>
|
||||||
<OutputBubbleButtons />
|
<OutputBubbleButtons sqlQuery={copilotMessage.sqlQuery} />
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
<Text style={{ fontWeight: 400, fontSize: "10px", lineHeight: "14px" }}>
|
<Text style={{ fontWeight: 400, fontSize: "10px", lineHeight: "14px" }}>
|
||||||
|
|
|
@ -28,7 +28,7 @@ exports[`Output Bubble snapshot tests should render and update height 1`] = `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
test comments
|
testMessage
|
||||||
</StackItem>
|
</StackItem>
|
||||||
<StackItem
|
<StackItem
|
||||||
style={
|
style={
|
||||||
|
@ -40,7 +40,7 @@ exports[`Output Bubble snapshot tests should render and update height 1`] = `
|
||||||
>
|
>
|
||||||
<EditorReact
|
<EditorReact
|
||||||
ariaLabel="AI Response"
|
ariaLabel="AI Response"
|
||||||
content="test query"
|
content="testSQL"
|
||||||
isReadOnly={true}
|
isReadOnly={true}
|
||||||
language="sql"
|
language="sql"
|
||||||
lineDecorationsWidth={0}
|
lineDecorationsWidth={0}
|
||||||
|
@ -68,7 +68,9 @@ exports[`Output Bubble snapshot tests should render and update height 1`] = `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<OutputBubbleButtons />
|
<OutputBubbleButtons
|
||||||
|
sqlQuery="testSQL"
|
||||||
|
/>
|
||||||
</StackItem>
|
</StackItem>
|
||||||
<StackItem>
|
<StackItem>
|
||||||
<Text
|
<Text
|
||||||
|
|
|
@ -43,13 +43,13 @@ export const QueryCopilotSidebar: React.FC<QueryCopilotProps> = ({ explorer }: Q
|
||||||
>
|
>
|
||||||
<WelcomeBubble />
|
<WelcomeBubble />
|
||||||
{chatMessages.map((message, index) =>
|
{chatMessages.map((message, index) =>
|
||||||
message.source === 0 ? (
|
message.source === 0 || message.source === 2 ? (
|
||||||
<Stack
|
<Stack
|
||||||
key={index}
|
key={index}
|
||||||
horizontalAlign="center"
|
horizontalAlign="center"
|
||||||
tokens={{ padding: 8, childrenGap: 8 }}
|
tokens={{ padding: 8, childrenGap: 8 }}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: "#E0E7FF",
|
backgroundColor: message.source === 0 ? "#E0E7FF" : "white",
|
||||||
borderRadius: "8px",
|
borderRadius: "8px",
|
||||||
margin: "5px 10px",
|
margin: "5px 10px",
|
||||||
textAlign: "start",
|
textAlign: "start",
|
||||||
|
@ -58,23 +58,9 @@ export const QueryCopilotSidebar: React.FC<QueryCopilotProps> = ({ explorer }: Q
|
||||||
{message.message}
|
{message.message}
|
||||||
</Stack>
|
</Stack>
|
||||||
) : (
|
) : (
|
||||||
// This part should be wired with OutputBubble
|
<OutputBubble key={index} copilotMessage={message} />
|
||||||
<Stack
|
|
||||||
key={index}
|
|
||||||
horizontalAlign="center"
|
|
||||||
tokens={{ padding: 8, childrenGap: 8 }}
|
|
||||||
style={{
|
|
||||||
backgroundColor: "white",
|
|
||||||
borderRadius: "8px",
|
|
||||||
margin: "5px 10px",
|
|
||||||
textAlign: "start",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{message.message}
|
|
||||||
</Stack>
|
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
<OutputBubble />
|
|
||||||
<RetrievingBubble />
|
<RetrievingBubble />
|
||||||
<ExplanationBubble />
|
<ExplanationBubble />
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,6 @@ export interface QueryCopilotState {
|
||||||
chatMessages: CopilotMessage[];
|
chatMessages: CopilotMessage[];
|
||||||
shouldIncludeInMessages: boolean;
|
shouldIncludeInMessages: boolean;
|
||||||
showExplanationBubble: boolean;
|
showExplanationBubble: boolean;
|
||||||
showQueryExplanation: boolean;
|
|
||||||
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
||||||
containerStatus: ContainerInfo;
|
containerStatus: ContainerInfo;
|
||||||
isAllocatingContainer: boolean;
|
isAllocatingContainer: boolean;
|
||||||
|
@ -72,7 +71,6 @@ export interface QueryCopilotState {
|
||||||
setChatMessages: (chatMessages: CopilotMessage[]) => void;
|
setChatMessages: (chatMessages: CopilotMessage[]) => void;
|
||||||
setShouldIncludeInMessages: (shouldIncludeInMessages: boolean) => void;
|
setShouldIncludeInMessages: (shouldIncludeInMessages: boolean) => void;
|
||||||
setShowExplanationBubble: (showExplanationBubble: boolean) => void;
|
setShowExplanationBubble: (showExplanationBubble: boolean) => void;
|
||||||
setShowQueryExplanation: (showQueryExplanation: boolean) => void;
|
|
||||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void;
|
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void;
|
||||||
setContainerStatus: (containerStatus: ContainerInfo) => void;
|
setContainerStatus: (containerStatus: ContainerInfo) => void;
|
||||||
setIsAllocatingContainer: (isAllocatingContainer: boolean) => void;
|
setIsAllocatingContainer: (isAllocatingContainer: boolean) => void;
|
||||||
|
@ -113,7 +111,6 @@ export const useQueryCopilot: QueryCopilotStore = create((set) => ({
|
||||||
chatMessages: [],
|
chatMessages: [],
|
||||||
shouldIncludeInMessages: true,
|
shouldIncludeInMessages: true,
|
||||||
showExplanationBubble: false,
|
showExplanationBubble: false,
|
||||||
showQueryExplanation: false,
|
|
||||||
notebookServerInfo: {
|
notebookServerInfo: {
|
||||||
notebookServerEndpoint: undefined,
|
notebookServerEndpoint: undefined,
|
||||||
authToken: undefined,
|
authToken: undefined,
|
||||||
|
@ -158,7 +155,6 @@ export const useQueryCopilot: QueryCopilotStore = create((set) => ({
|
||||||
setChatMessages: (chatMessages: CopilotMessage[]) => set({ chatMessages }),
|
setChatMessages: (chatMessages: CopilotMessage[]) => set({ chatMessages }),
|
||||||
setShouldIncludeInMessages: (shouldIncludeInMessages: boolean) => set({ shouldIncludeInMessages }),
|
setShouldIncludeInMessages: (shouldIncludeInMessages: boolean) => set({ shouldIncludeInMessages }),
|
||||||
setShowExplanationBubble: (showExplanationBubble: boolean) => set({ showExplanationBubble }),
|
setShowExplanationBubble: (showExplanationBubble: boolean) => set({ showExplanationBubble }),
|
||||||
setShowQueryExplanation: (showQueryExplanation: boolean) => set({ showQueryExplanation }),
|
|
||||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
||||||
set({ notebookServerInfo }),
|
set({ notebookServerInfo }),
|
||||||
setContainerStatus: (containerStatus: ContainerInfo) => set({ containerStatus }),
|
setContainerStatus: (containerStatus: ContainerInfo) => set({ containerStatus }),
|
||||||
|
@ -206,7 +202,6 @@ export const useQueryCopilot: QueryCopilotStore = create((set) => ({
|
||||||
chatMessages: [],
|
chatMessages: [],
|
||||||
shouldIncludeInMessages: true,
|
shouldIncludeInMessages: true,
|
||||||
showExplanationBubble: false,
|
showExplanationBubble: false,
|
||||||
showQueryExplanation: false,
|
|
||||||
notebookServerInfo: {
|
notebookServerInfo: {
|
||||||
notebookServerEndpoint: undefined,
|
notebookServerEndpoint: undefined,
|
||||||
authToken: undefined,
|
authToken: undefined,
|
||||||
|
|
Loading…
Reference in New Issue