[Query Copilot] V2 Backend integration (#1584)

* Initial implementetation of backend integration

* Added parameters and interfaces moved

* Initial client implementation

* Additional changes for React FC's

* Updated snapshot of Footer

* Additional Copilot implementation

* Test adjustments and client implementation

* Additional test implementations

* Naming convetion for functions

* Changing {} to any

* Additional changes to the type

* Additional test changes

* Removal of prevention

* adding comment

* Additional changes to tests

* Moving logic based on comments

* client implementation along with corrected tests

---------

Co-authored-by: Predrag Klepic <v-prklepic@microsoft.com>
This commit is contained in:
Predrag Klepic
2023-08-24 09:07:29 +02:00
committed by GitHub
parent 8405dbe8da
commit 449118a1bf
16 changed files with 452 additions and 344 deletions

View File

@@ -1,17 +1,41 @@
import { IconButton, Image, TextField } from "@fluentui/react";
import Explorer from "Explorer/Explorer";
import { SendQueryRequest } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
import { shallow } from "enzyme";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import React from "react";
import { act } from "react-dom/test-utils";
import { Footer } from "./Footer";
jest.mock("@azure/cosmos", () => ({
Constants: {
HttpHeaders: {},
},
}));
jest.mock("Common/SampleDataClient");
jest.mock("Explorer/Explorer");
jest.mock("node-fetch");
jest.mock("Common/ErrorHandlingUtils", () => ({
handleError: jest.fn(),
getErrorMessage: jest.fn(),
}));
jest.mock("Explorer/QueryCopilot/Shared/QueryCopilotClient");
describe("Footer snapshot test", () => {
const testMessage = "test message";
const initialStoreState = useQueryCopilot.getState();
beforeEach(() => {
useQueryCopilot.setState(initialStoreState, true);
});
it("should open sample prompts on button click", () => {
const wrapper = shallow(<Footer />);
const wrapper = shallow(<Footer explorer={new Explorer()} />);
const samplePromptsImage = wrapper.find(Image).first();
samplePromptsImage.simulate("click", {});
@@ -21,7 +45,7 @@ describe("Footer snapshot test", () => {
});
it("should update user input", () => {
const wrapper = shallow(<Footer />);
const wrapper = shallow(<Footer explorer={new Explorer()} />);
const newInput = "some new input";
const textInput = wrapper.find(TextField).first();
@@ -31,22 +55,24 @@ describe("Footer snapshot test", () => {
expect(wrapper).toMatchSnapshot();
});
it("should pass text with enter key", () => {
const testMessage = "test message";
it("should pass text with enter key", async () => {
useQueryCopilot.getState().setUserPrompt(testMessage);
const wrapper = shallow(<Footer />);
const wrapper = shallow(<Footer explorer={new Explorer()} />);
const textInput = wrapper.find(TextField).first();
// eslint-disable-next-line @typescript-eslint/no-empty-function
textInput.simulate("keydown", { key: "Enter", shiftKey: false, preventDefault: () => {} });
await act(async () => {
// eslint-disable-next-line @typescript-eslint/no-empty-function
textInput.simulate("keydown", { key: "Enter", shiftKey: false, preventDefault: () => {} });
});
expect(useQueryCopilot.getState().chatMessages).toEqual([testMessage]);
expect(useQueryCopilot.getState().userPrompt).toEqual("");
await Promise.resolve();
expect(SendQueryRequest).toHaveBeenCalledWith({ userPrompt: testMessage, explorer: expect.any(Explorer) });
expect(wrapper).toMatchSnapshot();
});
it("should not pass text with non enter key", () => {
const wrapper = shallow(<Footer />);
const wrapper = shallow(<Footer explorer={new Explorer()} />);
const textInput = wrapper.find(TextField).first();
// eslint-disable-next-line @typescript-eslint/no-empty-function
@@ -58,7 +84,7 @@ describe("Footer snapshot test", () => {
});
it("should not pass if no text", () => {
const wrapper = shallow(<Footer />);
const wrapper = shallow(<Footer explorer={new Explorer()} />);
const textInput = wrapper.find(TextField).first();
// eslint-disable-next-line @typescript-eslint/no-empty-function
@@ -69,16 +95,18 @@ describe("Footer snapshot test", () => {
expect(wrapper).toMatchSnapshot();
});
it("should pass text with icon button", () => {
const testMessage = "test message";
it("should pass text with icon button", async () => {
useQueryCopilot.getState().setUserPrompt(testMessage);
const wrapper = shallow(<Footer />);
const wrapper = shallow(<Footer explorer={new Explorer()} />);
const iconButton = wrapper.find(IconButton).first();
iconButton.simulate("click", {});
await act(async () => {
iconButton.simulate("click", {});
});
expect(useQueryCopilot.getState().chatMessages).toEqual([testMessage]);
expect(useQueryCopilot.getState().userPrompt).toEqual("");
await Promise.resolve();
expect(SendQueryRequest).toHaveBeenCalledWith({ userPrompt: testMessage, explorer: expect.any(Explorer) });
expect(wrapper).toMatchSnapshot();
});
});

View File

@@ -1,18 +1,18 @@
import { IButtonStyles, IconButton, Image, Stack, TextField } from "@fluentui/react";
import { SendQueryRequest } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
import { QueryCopilotProps } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import React from "react";
import HintIcon from "../../../../../images/Hint.svg";
import { SamplePrompts, SamplePromptsProps } from "../../Shared/SamplePrompts/SamplePrompts";
export const Footer: React.FC = (): JSX.Element => {
export const Footer: React.FC<QueryCopilotProps> = ({ explorer }: QueryCopilotProps): JSX.Element => {
const {
userPrompt,
setUserPrompt,
chatMessages,
setChatMessages,
isSamplePromptsOpen,
setIsSamplePromptsOpen,
setIsGeneratingQuery,
isGeneratingQuery,
} = useQueryCopilot();
const promptStyles: IButtonStyles = {
@@ -29,16 +29,12 @@ export const Footer: React.FC = (): JSX.Element => {
const handleEnterKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
handleSendMessage();
startSentMessageProcess();
}
};
const handleSendMessage = () => {
if (userPrompt.trim() !== "") {
setChatMessages([...chatMessages, userPrompt]);
setUserPrompt("");
setIsGeneratingQuery(true);
}
const startSentMessageProcess = async () => {
await SendQueryRequest({ userPrompt, explorer });
};
return (
@@ -66,6 +62,7 @@ export const Footer: React.FC = (): JSX.Element => {
onKeyDown={handleEnterKeyPress}
multiline
resizable={false}
disabled={isGeneratingQuery}
styles={{
root: {
width: "100%",
@@ -79,7 +76,7 @@ export const Footer: React.FC = (): JSX.Element => {
fieldGroup: { border: "none" },
}}
/>
<IconButton iconProps={{ iconName: "Send" }} onClick={handleSendMessage} />
<IconButton disabled={isGeneratingQuery} iconProps={{ iconName: "Send" }} onClick={startSentMessageProcess} />
</Stack>
);
};

View File

@@ -49,6 +49,7 @@ exports[`Footer snapshot test should not pass if no text 1`] = `
/>
</Stack>
<StyledTextFieldBase
disabled={false}
multiline={true}
onChange={[Function]}
onKeyDown={[Function]}
@@ -73,6 +74,7 @@ exports[`Footer snapshot test should not pass if no text 1`] = `
value=""
/>
<CustomizedIconButton
disabled={false}
iconProps={
Object {
"iconName": "Send",
@@ -132,6 +134,7 @@ exports[`Footer snapshot test should not pass text with non enter key 1`] = `
/>
</Stack>
<StyledTextFieldBase
disabled={false}
multiline={true}
onChange={[Function]}
onKeyDown={[Function]}
@@ -156,6 +159,7 @@ exports[`Footer snapshot test should not pass text with non enter key 1`] = `
value=""
/>
<CustomizedIconButton
disabled={false}
iconProps={
Object {
"iconName": "Send",
@@ -215,6 +219,7 @@ exports[`Footer snapshot test should open sample prompts on button click 1`] = `
/>
</Stack>
<StyledTextFieldBase
disabled={false}
multiline={true}
onChange={[Function]}
onKeyDown={[Function]}
@@ -239,6 +244,7 @@ exports[`Footer snapshot test should open sample prompts on button click 1`] = `
value=""
/>
<CustomizedIconButton
disabled={false}
iconProps={
Object {
"iconName": "Send",
@@ -298,6 +304,7 @@ exports[`Footer snapshot test should pass text with enter key 1`] = `
/>
</Stack>
<StyledTextFieldBase
disabled={false}
multiline={true}
onChange={[Function]}
onKeyDown={[Function]}
@@ -322,6 +329,7 @@ exports[`Footer snapshot test should pass text with enter key 1`] = `
value="test message"
/>
<CustomizedIconButton
disabled={false}
iconProps={
Object {
"iconName": "Send",
@@ -381,6 +389,7 @@ exports[`Footer snapshot test should pass text with icon button 1`] = `
/>
</Stack>
<StyledTextFieldBase
disabled={false}
multiline={true}
onChange={[Function]}
onKeyDown={[Function]}
@@ -405,6 +414,7 @@ exports[`Footer snapshot test should pass text with icon button 1`] = `
value="test message"
/>
<CustomizedIconButton
disabled={false}
iconProps={
Object {
"iconName": "Send",
@@ -464,6 +474,7 @@ exports[`Footer snapshot test should update user input 1`] = `
/>
</Stack>
<StyledTextFieldBase
disabled={false}
multiline={true}
onChange={[Function]}
onKeyDown={[Function]}
@@ -488,6 +499,7 @@ exports[`Footer snapshot test should update user input 1`] = `
value=""
/>
<CustomizedIconButton
disabled={false}
iconProps={
Object {
"iconName": "Send",

View File

@@ -1,4 +1,5 @@
import { Stack } from "@fluentui/react";
import Explorer from "Explorer/Explorer";
import { SampleBubble } from "Explorer/QueryCopilot/V2/Bubbles/Sample/SampleBubble";
import { shallow } from "enzyme";
import { useQueryCopilot } from "hooks/useQueryCopilot";
@@ -16,7 +17,7 @@ describe("Query Copilot Sidebar snapshot test", () => {
it("should render and set copilot used flag ", () => {
withHooks(() => {
useQueryCopilot.getState().setShowCopilotSidebar(true);
const wrapper = shallow(<QueryCopilotSidebar />);
const wrapper = shallow(<QueryCopilotSidebar explorer={new Explorer()} />);
expect(useQueryCopilot.getState().wasCopilotUsed).toBeTruthy();
expect(wrapper).toMatchSnapshot();
@@ -25,7 +26,7 @@ describe("Query Copilot Sidebar snapshot test", () => {
it("should render and not set copilot used flag ", () => {
withHooks(() => {
const wrapper = shallow(<QueryCopilotSidebar />);
const wrapper = shallow(<QueryCopilotSidebar explorer={new Explorer()} />);
expect(useQueryCopilot.getState().wasCopilotUsed).toBeFalsy();
expect(wrapper).toMatchSnapshot();
@@ -34,8 +35,8 @@ describe("Query Copilot Sidebar snapshot test", () => {
it("should render with chat messages", () => {
const message = "some test message";
useQueryCopilot.getState().setChatMessages([message]);
const wrapper = shallow(<QueryCopilotSidebar />);
useQueryCopilot.getState().setChatMessages([{ source: 0, message: message }]);
const wrapper = shallow(<QueryCopilotSidebar explorer={new Explorer()} />);
const messageContainer = wrapper.find(Stack).findWhere((x) => x.text() === message);
@@ -44,7 +45,7 @@ describe("Query Copilot Sidebar snapshot test", () => {
});
it("should render samples without messages", () => {
const wrapper = shallow(<QueryCopilotSidebar />);
const wrapper = shallow(<QueryCopilotSidebar explorer={new Explorer()} />);
const sampleBubble = wrapper.find(SampleBubble);

View File

@@ -1,4 +1,5 @@
import { Stack } from "@fluentui/react";
import { QueryCopilotProps } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
import { RetrievingBubble } from "Explorer/QueryCopilot/V2/Bubbles/Retriveing/RetrievingBubble";
import { SampleBubble } from "Explorer/QueryCopilot/V2/Bubbles/Sample/SampleBubble";
import { WelcomeBubble } from "Explorer/QueryCopilot/V2/Bubbles/Welcome/WelcomeBubble";
@@ -8,8 +9,14 @@ import { useQueryCopilot } from "hooks/useQueryCopilot";
import React from "react";
import { WelcomeSidebarModal } from "../Modal/WelcomeSidebarModal";
export const QueryCopilotSidebar: React.FC = (): JSX.Element => {
const { setWasCopilotUsed, showCopilotSidebar, chatMessages } = useQueryCopilot();
export const QueryCopilotSidebar: React.FC<QueryCopilotProps> = ({ explorer }: QueryCopilotProps): JSX.Element => {
const {
setWasCopilotUsed,
showCopilotSidebar,
chatMessages,
showWelcomeSidebar,
isGeneratingQuery,
} = useQueryCopilot();
React.useEffect(() => {
if (showCopilotSidebar) {
@@ -20,37 +27,58 @@ export const QueryCopilotSidebar: React.FC = (): JSX.Element => {
return (
<Stack style={{ width: "100%", height: "100%", backgroundColor: "#FAFAFA" }}>
<Header />
<WelcomeSidebarModal />
<Stack
style={{
flexGrow: 1,
display: "flex",
flexDirection: "column",
overflowY: "auto",
}}
>
<WelcomeBubble />
{chatMessages.map((message, index) => (
{showWelcomeSidebar ? (
<WelcomeSidebarModal />
) : (
<>
<Stack
key={index}
horizontalAlign="center"
tokens={{ padding: 8, childrenGap: 8 }}
style={{
backgroundColor: "#E0E7FF",
borderRadius: "8px",
margin: "5px 10px",
textAlign: "start",
flexGrow: 1,
display: "flex",
flexDirection: "column",
overflowY: "auto",
}}
>
{message}
<WelcomeBubble />
{chatMessages.map((message, index) =>
message.source === 0 ? (
<Stack
key={index}
horizontalAlign="center"
tokens={{ padding: 8, childrenGap: 8 }}
style={{
backgroundColor: "#E0E7FF",
borderRadius: "8px",
margin: "5px 10px",
textAlign: "start",
}}
>
{message.message}
</Stack>
) : (
<Stack
key={index}
horizontalAlign="center"
tokens={{ padding: 8, childrenGap: 8 }}
style={{
backgroundColor: "white",
borderRadius: "8px",
margin: "5px 10px",
textAlign: "start",
}}
>
{message.message}
</Stack>
)
)}
<RetrievingBubble />
{chatMessages.length === 0 && !isGeneratingQuery && <SampleBubble />}
</Stack>
))}
<RetrievingBubble />
{chatMessages.length === 0 && <SampleBubble />}
</Stack>
<Footer />
<Footer explorer={explorer} />
</>
)}
</Stack>
);
};

View File

@@ -12,21 +12,6 @@ exports[`Query Copilot Sidebar snapshot test should render and not set copilot u
>
<Header />
<WelcomeSidebarModal />
<Stack
style={
Object {
"display": "flex",
"flexDirection": "column",
"flexGrow": 1,
"overflowY": "auto",
}
}
>
<WelcomeBubble />
<RetrievingBubble />
<SampleBubble />
</Stack>
<Footer />
</Stack>
`;
@@ -42,21 +27,6 @@ exports[`Query Copilot Sidebar snapshot test should render and set copilot used
>
<Header />
<WelcomeSidebarModal />
<Stack
style={
Object {
"display": "flex",
"flexDirection": "column",
"flexGrow": 1,
"overflowY": "auto",
}
}
>
<WelcomeBubble />
<RetrievingBubble />
<SampleBubble />
</Stack>
<Footer />
</Stack>
`;
@@ -72,21 +42,6 @@ exports[`Query Copilot Sidebar snapshot test should render samples without messa
>
<Header />
<WelcomeSidebarModal />
<Stack
style={
Object {
"display": "flex",
"flexDirection": "column",
"flexGrow": 1,
"overflowY": "auto",
}
}
>
<WelcomeBubble />
<RetrievingBubble />
<SampleBubble />
</Stack>
<Footer />
</Stack>
`;
@@ -102,39 +57,5 @@ exports[`Query Copilot Sidebar snapshot test should render with chat messages 1`
>
<Header />
<WelcomeSidebarModal />
<Stack
style={
Object {
"display": "flex",
"flexDirection": "column",
"flexGrow": 1,
"overflowY": "auto",
}
}
>
<WelcomeBubble />
<Stack
horizontalAlign="center"
key="0"
style={
Object {
"backgroundColor": "#E0E7FF",
"borderRadius": "8px",
"margin": "5px 10px",
"textAlign": "start",
}
}
tokens={
Object {
"childrenGap": 8,
"padding": 8,
}
}
>
some test message
</Stack>
<RetrievingBubble />
</Stack>
<Footer />
</Stack>
`;