[Query Copilot] Copilot V2 Retrieving bubble (#1579)

* Changing order of the features

* test name changed

* updates snapshots

* Bubble implementation

* Bubble tests implemented

* Correction for CopilotSampleDB implementation

* rollback to previous query tab changes

---------

Co-authored-by: Predrag Klepic <v-prklepic@microsoft.com>
This commit is contained in:
Predrag Klepic 2023-08-21 11:19:04 +02:00 committed by GitHub
parent b646f9f4cb
commit 143f7d8f2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 375 additions and 5 deletions

View File

@ -0,0 +1,3 @@
<svg width="14" height="13" viewBox="0 0 14 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 0C1.61929 0 0.5 1.11929 0.5 2.5V9.5C0.5 10.8807 1.61929 12 3 12H4.75716C4.50353 11.6929 4.28261 11.3578 4.09971 11H3C2.17157 11 1.5 10.3284 1.5 9.5V2.5C1.5 1.67157 2.17157 1 3 1H10C10.8284 1 11.5 1.67157 11.5 2.5V3.59971C11.8578 3.78261 12.1929 4.00353 12.5 4.25716V2.5C12.5 1.11929 11.3807 0 10 0H3ZM9 13C11.4853 13 13.5 10.9853 13.5 8.5C13.5 6.01472 11.4853 4 9 4C6.51472 4 4.5 6.01472 4.5 8.5C4.5 10.9853 6.51472 13 9 13ZM10.8536 6.64645C11.0488 6.84171 11.0488 7.15829 10.8536 7.35355L9.70711 8.5L10.8536 9.64645C11.0488 9.84171 11.0488 10.1583 10.8536 10.3536C10.6583 10.5488 10.3417 10.5488 10.1464 10.3536L9 9.20711L7.85355 10.3536C7.65829 10.5488 7.34171 10.5488 7.14645 10.3536C6.95118 10.1583 6.95118 9.84171 7.14645 9.64645L8.29289 8.5L7.14645 7.35355C6.95118 7.15829 6.95118 6.84171 7.14645 6.64645C7.34171 6.45118 7.65829 6.45118 7.85355 6.64645L9 7.79289L10.1464 6.64645C10.3417 6.45118 10.6583 6.45118 10.8536 6.64645Z" fill="#0078D4"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,8 @@
@keyframes loadingAnimation {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}

View File

@ -0,0 +1,62 @@
import { DefaultButton } from "@fluentui/react";
import { shallow } from "enzyme";
import React from "react";
import { RetrievingBubble } from "./RetrievingBubble";
const mockUseQueryCopilot = {
isGeneratingQuery: false,
setIsGeneratingQuery: jest.fn(),
isGeneratingExplanation: false,
setIsGeneratingExplanation: jest.fn(),
shouldIncludeInMessages: true,
setShouldIncludeInMessages: jest.fn(),
};
jest.mock("hooks/useQueryCopilot", () => ({
useQueryCopilot: jest.fn(() => mockUseQueryCopilot),
}));
describe("RetrievingBubble", () => {
beforeEach(() => {
jest.clearAllMocks();
mockUseQueryCopilot.isGeneratingQuery = false;
mockUseQueryCopilot.isGeneratingExplanation = false;
mockUseQueryCopilot.setIsGeneratingQuery.mockClear();
mockUseQueryCopilot.setIsGeneratingExplanation.mockClear();
mockUseQueryCopilot.setShouldIncludeInMessages.mockClear();
});
it("should render properly when isGeneratingQuery is true", () => {
mockUseQueryCopilot.isGeneratingQuery = true;
const wrapper = shallow(<RetrievingBubble />);
expect(wrapper).toMatchSnapshot();
});
it("should render properly when isGeneratingExplanation is true", () => {
mockUseQueryCopilot.isGeneratingExplanation = true;
const wrapper = shallow(<RetrievingBubble />);
expect(wrapper).toMatchSnapshot();
});
it("when isGeneratingQuery is true clicking stop generating button invokes the correct callbacks", () => {
mockUseQueryCopilot.isGeneratingQuery = true;
const wrapper = shallow(<RetrievingBubble />);
wrapper.find(DefaultButton).at(0).simulate("click");
expect(mockUseQueryCopilot.setIsGeneratingQuery).toHaveBeenCalledWith(false);
expect(mockUseQueryCopilot.setIsGeneratingExplanation).toHaveBeenCalledTimes(0);
expect(mockUseQueryCopilot.setShouldIncludeInMessages).toHaveBeenCalledWith(false);
});
it("when isGeneratingExplanation is true clicking stop generating button invokes the correct callbacks", () => {
mockUseQueryCopilot.isGeneratingExplanation = true;
const wrapper = shallow(<RetrievingBubble />);
wrapper.find(DefaultButton).at(0).simulate("click");
expect(mockUseQueryCopilot.setIsGeneratingQuery).toHaveBeenCalledTimes(0);
expect(mockUseQueryCopilot.setIsGeneratingExplanation).toHaveBeenCalledWith(false);
expect(mockUseQueryCopilot.setShouldIncludeInMessages).toHaveBeenCalledWith(false);
});
});

View File

@ -0,0 +1,98 @@
import { DefaultButton, Image, Stack, Text } from "@fluentui/react";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import React from "react";
import StopGeneratingIcon from "../../../../../../images/StopGenerating.svg";
import "./RetrievingBubble.css";
export const RetrievingBubble = (): JSX.Element => {
const {
isGeneratingQuery,
setIsGeneratingQuery,
isGeneratingExplanation,
setIsGeneratingExplanation,
shouldIncludeInMessages,
setShouldIncludeInMessages,
} = useQueryCopilot();
const stopGenerating = () => {
if (isGeneratingQuery) {
setIsGeneratingQuery(false);
}
if (isGeneratingExplanation) {
setIsGeneratingExplanation(false);
}
if (shouldIncludeInMessages) {
setShouldIncludeInMessages(false);
}
};
const bubbleContent = (bubbleType: string) => {
return (
<Stack
horizontalAlign="end"
verticalAlign="end"
style={{
display: "flex",
alignItems: "center",
padding: "10px",
margin: "10px",
backgroundColor: "#FAFAFA",
borderRadius: "8px",
}}
>
<Text
style={{
width: "100%",
height: "46px",
backgroundColor: "white",
padding: "12px 16px 16px 16px",
gap: "12px",
borderRadius: "8px",
fontWeight: "bold",
}}
>
Retriveing {bubbleType}
</Text>
<div
style={{
width: "100%",
height: "4px",
backgroundColor: "#E6E6E6",
borderRadius: "4px",
overflow: "hidden",
}}
>
<div
style={{
width: "50%",
height: "100%",
backgroundColor: "#0078D4",
animation: "loadingAnimation 2s linear infinite",
}}
></div>
</div>
<Stack
horizontalAlign="center"
verticalAlign="center"
style={{ marginTop: "8px", gap: "8px", alignItems: "center" }}
>
<DefaultButton
onClick={stopGenerating}
styles={{ root: { border: "none", background: "none", padding: 0, color: "#424242" } }}
style={{ color: "#424242" }}
onRenderIcon={() => <Image src={StopGeneratingIcon} />}
>
Stop generating
</DefaultButton>
</Stack>
</Stack>
);
};
return (
<>
{isGeneratingQuery && bubbleContent("queries")}
{isGeneratingExplanation && bubbleContent("explanation")}
</>
);
};

View File

@ -0,0 +1,183 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`RetrievingBubble should render properly when isGeneratingExplanation is true 1`] = `
<Fragment>
<Stack
horizontalAlign="end"
style={
Object {
"alignItems": "center",
"backgroundColor": "#FAFAFA",
"borderRadius": "8px",
"display": "flex",
"margin": "10px",
"padding": "10px",
}
}
verticalAlign="end"
>
<Text
style={
Object {
"backgroundColor": "white",
"borderRadius": "8px",
"fontWeight": "bold",
"gap": "12px",
"height": "46px",
"padding": "12px 16px 16px 16px",
"width": "100%",
}
}
>
Retriveing
explanation
</Text>
<div
style={
Object {
"backgroundColor": "#E6E6E6",
"borderRadius": "4px",
"height": "4px",
"overflow": "hidden",
"width": "100%",
}
}
>
<div
style={
Object {
"animation": "loadingAnimation 2s linear infinite",
"backgroundColor": "#0078D4",
"height": "100%",
"width": "50%",
}
}
/>
</div>
<Stack
horizontalAlign="center"
style={
Object {
"alignItems": "center",
"gap": "8px",
"marginTop": "8px",
}
}
verticalAlign="center"
>
<CustomizedDefaultButton
onClick={[Function]}
onRenderIcon={[Function]}
style={
Object {
"color": "#424242",
}
}
styles={
Object {
"root": Object {
"background": "none",
"border": "none",
"color": "#424242",
"padding": 0,
},
}
}
>
Stop generating
</CustomizedDefaultButton>
</Stack>
</Stack>
</Fragment>
`;
exports[`RetrievingBubble should render properly when isGeneratingQuery is true 1`] = `
<Fragment>
<Stack
horizontalAlign="end"
style={
Object {
"alignItems": "center",
"backgroundColor": "#FAFAFA",
"borderRadius": "8px",
"display": "flex",
"margin": "10px",
"padding": "10px",
}
}
verticalAlign="end"
>
<Text
style={
Object {
"backgroundColor": "white",
"borderRadius": "8px",
"fontWeight": "bold",
"gap": "12px",
"height": "46px",
"padding": "12px 16px 16px 16px",
"width": "100%",
}
}
>
Retriveing
queries
</Text>
<div
style={
Object {
"backgroundColor": "#E6E6E6",
"borderRadius": "4px",
"height": "4px",
"overflow": "hidden",
"width": "100%",
}
}
>
<div
style={
Object {
"animation": "loadingAnimation 2s linear infinite",
"backgroundColor": "#0078D4",
"height": "100%",
"width": "50%",
}
}
/>
</div>
<Stack
horizontalAlign="center"
style={
Object {
"alignItems": "center",
"gap": "8px",
"marginTop": "8px",
}
}
verticalAlign="center"
>
<CustomizedDefaultButton
onClick={[Function]}
onRenderIcon={[Function]}
style={
Object {
"color": "#424242",
}
}
styles={
Object {
"root": Object {
"background": "none",
"border": "none",
"color": "#424242",
"padding": 0,
},
}
}
>
Stop generating
</CustomizedDefaultButton>
</Stack>
</Stack>
</Fragment>
`;

View File

@ -12,6 +12,7 @@ export const Footer: React.FC = (): JSX.Element => {
setChatMessages, setChatMessages,
isSamplePromptsOpen, isSamplePromptsOpen,
setIsSamplePromptsOpen, setIsSamplePromptsOpen,
setIsGeneratingQuery,
} = useQueryCopilot(); } = useQueryCopilot();
const promptStyles: IButtonStyles = { const promptStyles: IButtonStyles = {
@ -36,6 +37,7 @@ export const Footer: React.FC = (): JSX.Element => {
if (userPrompt.trim() !== "") { if (userPrompt.trim() !== "") {
setChatMessages([...chatMessages, userPrompt]); setChatMessages([...chatMessages, userPrompt]);
setUserPrompt(""); setUserPrompt("");
setIsGeneratingQuery(true);
} }
}; };

View File

@ -2,7 +2,7 @@ import { shallow } from "enzyme";
import React from "react"; import React from "react";
import { WelcomeSidebarModal } from "./WelcomeSidebarModal"; import { WelcomeSidebarModal } from "./WelcomeSidebarModal";
describe("Footer snapshot test", () => { describe("WelcomeSidebarModal snapshot test", () => {
it("should render ", () => { it("should render ", () => {
const wrapper = shallow(<WelcomeSidebarModal />); const wrapper = shallow(<WelcomeSidebarModal />);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Footer snapshot test should render 1`] = ` exports[`WelcomeSidebarModal snapshot test should render 1`] = `
<Stack <Stack
style={ style={
Object { Object {

View File

@ -2,7 +2,7 @@ import { shallow } from "enzyme";
import React from "react"; import React from "react";
import { QueryCopilotSidebar } from "./QueryCopilotSidebar"; import { QueryCopilotSidebar } from "./QueryCopilotSidebar";
describe("Footer snapshot test", () => { describe("QueryCopilotSidebar snapshot test", () => {
it("should render ", () => { it("should render ", () => {
const wrapper = shallow(<QueryCopilotSidebar />); const wrapper = shallow(<QueryCopilotSidebar />);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();

View File

@ -1,4 +1,5 @@
import { Stack } from "@fluentui/react"; import { Stack } from "@fluentui/react";
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";
import { WelcomeBubble } from "Explorer/QueryCopilot/V2/Bubbles/Welcome/WelcomeBubble"; import { WelcomeBubble } from "Explorer/QueryCopilot/V2/Bubbles/Welcome/WelcomeBubble";
import { Footer } from "Explorer/QueryCopilot/V2/Footer/Footer"; import { Footer } from "Explorer/QueryCopilot/V2/Footer/Footer";
@ -47,6 +48,9 @@ export const QueryCopilotSidebar: React.FC = (): JSX.Element => {
{message} {message}
</Stack> </Stack>
))} ))}
<RetrievingBubble />
{chatMessages.length === 0 && <SampleBubble />} {chatMessages.length === 0 && <SampleBubble />}
</Stack> </Stack>
<Footer /> <Footer />

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Footer snapshot test should render 1`] = ` exports[`QueryCopilotSidebar snapshot test should render 1`] = `
<Stack <Stack
style={ style={
Object { Object {

View File

@ -106,9 +106,9 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
enableLegacyMongoShellV2: "true" === get("enablelegacymongoshellv2"), enableLegacyMongoShellV2: "true" === get("enablelegacymongoshellv2"),
enableLegacyMongoShellV2Debug: "true" === get("enablelegacymongoshellv2debug"), enableLegacyMongoShellV2Debug: "true" === get("enablelegacymongoshellv2debug"),
loadLegacyMongoShellFromBE: "true" === get("loadlegacymongoshellfrombe"), loadLegacyMongoShellFromBE: "true" === get("loadlegacymongoshellfrombe"),
enableCopilot: "true" === get("enablecopilot"),
enablePriorityBasedThrottling: "true" === get("enableprioritybasedthrottling"), enablePriorityBasedThrottling: "true" === get("enableprioritybasedthrottling"),
enableNPSSurvey: "true" === get("enablenpssurvey"), enableNPSSurvey: "true" === get("enablenpssurvey"),
enableCopilot: "true" === get("enablecopilot"),
copilotVersion: get("copilotVersion") ? get("copilotVersion") : "v1.0", copilotVersion: get("copilotVersion") ? get("copilotVersion") : "v1.0",
}; };
} }

View File

@ -13,6 +13,7 @@ export interface QueryCopilotState {
query: string; query: string;
selectedQuery: string; selectedQuery: string;
isGeneratingQuery: boolean; isGeneratingQuery: boolean;
isGeneratingExplanation: boolean;
isExecuting: boolean; isExecuting: boolean;
dislikeQuery: boolean | undefined; dislikeQuery: boolean | undefined;
showCallout: boolean; showCallout: boolean;
@ -31,6 +32,7 @@ export interface QueryCopilotState {
showCopilotSidebar: boolean; showCopilotSidebar: boolean;
chatMessages: string[]; chatMessages: string[];
shouldAllocateContainer: boolean; shouldAllocateContainer: boolean;
shouldIncludeInMessages: boolean;
openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) => void; openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) => void;
closeFeedbackModal: () => void; closeFeedbackModal: () => void;
@ -41,6 +43,7 @@ export interface QueryCopilotState {
setGeneratedQuery: (generatedQuery: string) => void; setGeneratedQuery: (generatedQuery: string) => void;
setSelectedQuery: (selectedQuery: string) => void; setSelectedQuery: (selectedQuery: string) => void;
setIsGeneratingQuery: (isGeneratingQuery: boolean) => void; setIsGeneratingQuery: (isGeneratingQuery: boolean) => void;
setIsGeneratingExplanation: (isGeneratingExplanation: boolean) => void;
setIsExecuting: (isExecuting: boolean) => void; setIsExecuting: (isExecuting: boolean) => void;
setLikeQuery: (likeQuery: boolean) => void; setLikeQuery: (likeQuery: boolean) => void;
setDislikeQuery: (dislikeQuery: boolean | undefined) => void; setDislikeQuery: (dislikeQuery: boolean | undefined) => void;
@ -61,6 +64,7 @@ export interface QueryCopilotState {
setChatMessages: (chatMessages: string[]) => void; setChatMessages: (chatMessages: string[]) => void;
setShouldAllocateContainer: (shouldAllocateContainer: boolean) => void; setShouldAllocateContainer: (shouldAllocateContainer: boolean) => void;
setShouldIncludeInMessages: (shouldIncludeInMessages: boolean) => void;
resetQueryCopilotStates: () => void; resetQueryCopilotStates: () => void;
} }
@ -77,6 +81,7 @@ export const useQueryCopilot: QueryCopilotStore = create((set) => ({
query: "", query: "",
selectedQuery: "", selectedQuery: "",
isGeneratingQuery: false, isGeneratingQuery: false,
isGeneratingExplanation: false,
isExecuting: false, isExecuting: false,
dislikeQuery: undefined, dislikeQuery: undefined,
showCallout: false, showCallout: false,
@ -95,6 +100,7 @@ export const useQueryCopilot: QueryCopilotStore = create((set) => ({
showCopilotSidebar: false, showCopilotSidebar: false,
chatMessages: [], chatMessages: [],
shouldAllocateContainer: true, shouldAllocateContainer: true,
shouldIncludeInMessages: true,
openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) => openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) =>
set({ generatedQuery, likeQuery, userPrompt, showFeedbackModal: true }), set({ generatedQuery, likeQuery, userPrompt, showFeedbackModal: true }),
@ -107,6 +113,7 @@ export const useQueryCopilot: QueryCopilotStore = create((set) => ({
setGeneratedQuery: (generatedQuery: string) => set({ generatedQuery }), setGeneratedQuery: (generatedQuery: string) => set({ generatedQuery }),
setSelectedQuery: (selectedQuery: string) => set({ selectedQuery }), setSelectedQuery: (selectedQuery: string) => set({ selectedQuery }),
setIsGeneratingQuery: (isGeneratingQuery: boolean) => set({ isGeneratingQuery }), setIsGeneratingQuery: (isGeneratingQuery: boolean) => set({ isGeneratingQuery }),
setIsGeneratingExplanation: (isGeneratingExplanation: boolean) => set({ isGeneratingExplanation }),
setIsExecuting: (isExecuting: boolean) => set({ isExecuting }), setIsExecuting: (isExecuting: boolean) => set({ isExecuting }),
setLikeQuery: (likeQuery: boolean) => set({ likeQuery }), setLikeQuery: (likeQuery: boolean) => set({ likeQuery }),
setDislikeQuery: (dislikeQuery: boolean | undefined) => set({ dislikeQuery }), setDislikeQuery: (dislikeQuery: boolean | undefined) => set({ dislikeQuery }),
@ -126,6 +133,7 @@ export const useQueryCopilot: QueryCopilotStore = create((set) => ({
setShowCopilotSidebar: (showCopilotSidebar: boolean) => set({ showCopilotSidebar }), setShowCopilotSidebar: (showCopilotSidebar: boolean) => set({ showCopilotSidebar }),
setChatMessages: (chatMessages: string[]) => set({ chatMessages }), setChatMessages: (chatMessages: string[]) => set({ chatMessages }),
setShouldAllocateContainer: (shouldAllocateContainer: boolean) => set({ shouldAllocateContainer }), setShouldAllocateContainer: (shouldAllocateContainer: boolean) => set({ shouldAllocateContainer }),
setShouldIncludeInMessages: (shouldIncludeInMessages: boolean) => set({ shouldIncludeInMessages }),
resetQueryCopilotStates: () => { resetQueryCopilotStates: () => {
set((state) => ({ set((state) => ({
@ -139,6 +147,7 @@ export const useQueryCopilot: QueryCopilotStore = create((set) => ({
query: "", query: "",
selectedQuery: "", selectedQuery: "",
isGeneratingQuery: false, isGeneratingQuery: false,
isGeneratingExplanation: false,
isExecuting: false, isExecuting: false,
dislikeQuery: undefined, dislikeQuery: undefined,
showCallout: false, showCallout: false,
@ -156,6 +165,7 @@ export const useQueryCopilot: QueryCopilotStore = create((set) => ({
showCopilotSidebar: false, showCopilotSidebar: false,
chatMessages: [], chatMessages: [],
shouldAllocateContainer: true, shouldAllocateContainer: true,
shouldIncludeInMessages: true,
})); }));
}, },
})); }));