mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2024-11-29 00:47:01 +00:00
[Query Copilot] Maintain Query Copilot state when switching tabs (#1559)
* Sample implementation for saving states * Maintaining Query Copilot state * Fixing failed PR checks * Additional changes based on checks * snapshots updated * Changes based on merging previous PR * test mock changed * Fixing minor bug for close button * Destruct of queryCopilotState * passing only function in Tabs component * Maintaining copilot state with zustand store * additional test changes * test snapshot updated --------- Co-authored-by: Predrag Klepic <v-prklepic@microsoft.com>
This commit is contained in:
parent
c873fed7aa
commit
fa55d528ad
@ -5,9 +5,7 @@ import { QueryCopilotTab } from "./QueryCopilotTab";
|
|||||||
|
|
||||||
describe("Query copilot tab snapshot test", () => {
|
describe("Query copilot tab snapshot test", () => {
|
||||||
it("should render with initial input", () => {
|
it("should render with initial input", () => {
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(<QueryCopilotTab explorer={new Explorer()} />);
|
||||||
<QueryCopilotTab initialInput="Write a query to return all records in this table" explorer={new Explorer()} />
|
|
||||||
);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -56,7 +56,6 @@ interface SuggestedPrompt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface QueryCopilotTabProps {
|
interface QueryCopilotTabProps {
|
||||||
initialInput: string;
|
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,32 +72,50 @@ const promptStyles: IButtonStyles = {
|
|||||||
label: { fontWeight: 400, textAlign: "left", paddingLeft: 8 },
|
label: { fontWeight: 400, textAlign: "left", paddingLeft: 8 },
|
||||||
};
|
};
|
||||||
|
|
||||||
export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
|
export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({ explorer }: QueryCopilotTabProps): JSX.Element => {
|
||||||
initialInput,
|
|
||||||
explorer,
|
|
||||||
}: QueryCopilotTabProps): JSX.Element => {
|
|
||||||
const hideFeedbackModalForLikedQueries = useQueryCopilot((state) => state.hideFeedbackModalForLikedQueries);
|
|
||||||
const [userPrompt, setUserPrompt] = useState<string>(initialInput || "");
|
|
||||||
const [generatedQuery, setGeneratedQuery] = useState<string>("");
|
|
||||||
const [generatedQueryComments, setGeneratedQueryComments] = useState<string>("");
|
|
||||||
const [query, setQuery] = useState<string>("");
|
|
||||||
const [selectedQuery, setSelectedQuery] = useState<string>("");
|
|
||||||
const [isGeneratingQuery, setIsGeneratingQuery] = useState<boolean>(false);
|
|
||||||
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
|
||||||
const [likeQuery, setLikeQuery] = useState<boolean>();
|
|
||||||
const [dislikeQuery, setDislikeQuery] = useState<boolean>();
|
|
||||||
const [showCallout, setShowCallout] = useState<boolean>(false);
|
|
||||||
const [showSamplePrompts, setShowSamplePrompts] = useState<boolean>(false);
|
|
||||||
const [queryIterator, setQueryIterator] = useState<MinimalQueryIterator>();
|
|
||||||
const [queryResults, setQueryResults] = useState<QueryResults>();
|
|
||||||
const [errorMessage, setErrorMessage] = useState<string>("");
|
|
||||||
const [copilotTeachingBubbleVisible, { toggle: toggleCopilotTeachingBubbleVisible }] = useBoolean(false);
|
const [copilotTeachingBubbleVisible, { toggle: toggleCopilotTeachingBubbleVisible }] = useBoolean(false);
|
||||||
const inputEdited = useRef(false);
|
const inputEdited = useRef(false);
|
||||||
const [isSamplePromptsOpen, setIsSamplePromptsOpen] = useState<boolean>(false);
|
const {
|
||||||
const [showDeletePopup, setShowDeletePopup] = useState<boolean>(false);
|
hideFeedbackModalForLikedQueries,
|
||||||
const [showFeedbackBar, setShowFeedbackBar] = useState<boolean>(false);
|
userPrompt,
|
||||||
const [showCopyPopup, setshowCopyPopup] = useState<boolean>(false);
|
setUserPrompt,
|
||||||
const [showErrorMessageBar, setShowErrorMessageBar] = useState<boolean>(false);
|
generatedQuery,
|
||||||
|
setGeneratedQuery,
|
||||||
|
query,
|
||||||
|
setQuery,
|
||||||
|
selectedQuery,
|
||||||
|
setSelectedQuery,
|
||||||
|
isGeneratingQuery,
|
||||||
|
setIsGeneratingQuery,
|
||||||
|
isExecuting,
|
||||||
|
setIsExecuting,
|
||||||
|
likeQuery,
|
||||||
|
setLikeQuery,
|
||||||
|
dislikeQuery,
|
||||||
|
setDislikeQuery,
|
||||||
|
showCallout,
|
||||||
|
setShowCallout,
|
||||||
|
showSamplePrompts,
|
||||||
|
setShowSamplePrompts,
|
||||||
|
queryIterator,
|
||||||
|
setQueryIterator,
|
||||||
|
queryResults,
|
||||||
|
setQueryResults,
|
||||||
|
errorMessage,
|
||||||
|
setErrorMessage,
|
||||||
|
isSamplePromptsOpen,
|
||||||
|
setIsSamplePromptsOpen,
|
||||||
|
showDeletePopup,
|
||||||
|
setShowDeletePopup,
|
||||||
|
showFeedbackBar,
|
||||||
|
setShowFeedbackBar,
|
||||||
|
showCopyPopup,
|
||||||
|
setshowCopyPopup,
|
||||||
|
showErrorMessageBar,
|
||||||
|
setShowErrorMessageBar,
|
||||||
|
generatedQueryComments,
|
||||||
|
setGeneratedQueryComments,
|
||||||
|
} = useQueryCopilot();
|
||||||
|
|
||||||
const sampleProps: SamplePromptsProps = {
|
const sampleProps: SamplePromptsProps = {
|
||||||
isSamplePromptsOpen: isSamplePromptsOpen,
|
isSamplePromptsOpen: isSamplePromptsOpen,
|
||||||
@ -301,9 +318,10 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
|
|||||||
return [executeQueryBtn, saveQueryBtn];
|
return [executeQueryBtn, saveQueryBtn];
|
||||||
};
|
};
|
||||||
const showTeachingBubble = (): void => {
|
const showTeachingBubble = (): void => {
|
||||||
if (!inputEdited.current) {
|
const shouldShowTeachingBubble = !inputEdited.current && userPrompt.trim() === "";
|
||||||
|
if (shouldShowTeachingBubble) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!inputEdited.current) {
|
if (shouldShowTeachingBubble) {
|
||||||
toggleCopilotTeachingBubbleVisible();
|
toggleCopilotTeachingBubbleVisible();
|
||||||
inputEdited.current = true;
|
inputEdited.current = true;
|
||||||
}
|
}
|
||||||
@ -328,9 +346,6 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
|
|||||||
}, [query, selectedQuery]);
|
}, [query, selectedQuery]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (initialInput) {
|
|
||||||
generateSQLQuery();
|
|
||||||
}
|
|
||||||
showTeachingBubble();
|
showTeachingBubble();
|
||||||
useTabs.getState().setIsQueryErrorThrown(false);
|
useTabs.getState().setIsQueryErrorThrown(false);
|
||||||
}, []);
|
}, []);
|
||||||
@ -517,7 +532,12 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
|
|||||||
target="#likeBtn"
|
target="#likeBtn"
|
||||||
onDismiss={() => {
|
onDismiss={() => {
|
||||||
setShowCallout(false);
|
setShowCallout(false);
|
||||||
submitFeedback({ generatedQuery, likeQuery, description: "", userPrompt: userPrompt });
|
submitFeedback({
|
||||||
|
generatedQuery: generatedQuery,
|
||||||
|
likeQuery: likeQuery,
|
||||||
|
description: "",
|
||||||
|
userPrompt: userPrompt,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
directionalHint={DirectionalHint.topCenter}
|
directionalHint={DirectionalHint.topCenter}
|
||||||
>
|
>
|
||||||
|
@ -67,10 +67,10 @@ exports[`Query copilot tab snapshot test should render with initial input 1`] =
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
value="Write a query to return all records in this table"
|
value=""
|
||||||
/>
|
/>
|
||||||
<CustomizedIconButton
|
<CustomizedIconButton
|
||||||
disabled={false}
|
disabled={true}
|
||||||
iconProps={
|
iconProps={
|
||||||
Object {
|
Object {
|
||||||
"iconName": "Send",
|
"iconName": "Send",
|
||||||
|
@ -9,6 +9,7 @@ import { ConnectTab } from "Explorer/Tabs/ConnectTab";
|
|||||||
import { PostgresConnectTab } from "Explorer/Tabs/PostgresConnectTab";
|
import { PostgresConnectTab } from "Explorer/Tabs/PostgresConnectTab";
|
||||||
import { QuickstartTab } from "Explorer/Tabs/QuickstartTab";
|
import { QuickstartTab } from "Explorer/Tabs/QuickstartTab";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
|
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import React, { MutableRefObject, useEffect, useRef, useState } from "react";
|
import React, { MutableRefObject, useEffect, useRef, useState } from "react";
|
||||||
@ -158,6 +159,7 @@ const CloseButton = ({
|
|||||||
onClick={(event: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
|
onClick={(event: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
tab ? tab.onCloseTabButtonClick() : useTabs.getState().closeReactTab(tabKind);
|
tab ? tab.onCloseTabButtonClick() : useTabs.getState().closeReactTab(tabKind);
|
||||||
|
tabKind === ReactTabKind.QueryCopilot && useQueryCopilot.getState().resetQueryCopilotStates();
|
||||||
}}
|
}}
|
||||||
tabIndex={active ? 0 : undefined}
|
tabIndex={active ? 0 : undefined}
|
||||||
onKeyPress={({ nativeEvent: e }) => tab.onKeyPressClose(undefined, e)}
|
onKeyPress={({ nativeEvent: e }) => tab.onKeyPressClose(undefined, e)}
|
||||||
@ -251,7 +253,7 @@ const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): J
|
|||||||
case ReactTabKind.Quickstart:
|
case ReactTabKind.Quickstart:
|
||||||
return <QuickstartTab explorer={explorer} />;
|
return <QuickstartTab explorer={explorer} />;
|
||||||
case ReactTabKind.QueryCopilot:
|
case ReactTabKind.QueryCopilot:
|
||||||
return <QueryCopilotTab initialInput={useTabs.getState().queryCopilotTabInitialInput} explorer={explorer} />;
|
return <QueryCopilotTab explorer={explorer} />;
|
||||||
default:
|
default:
|
||||||
throw Error(`Unsupported tab kind ${ReactTabKind[activeReactTab]}`);
|
throw Error(`Unsupported tab kind ${ReactTabKind[activeReactTab]}`);
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { MinimalQueryIterator } from "Common/IteratorUtilities";
|
||||||
|
import { QueryResults } from "Contracts/ViewModels";
|
||||||
import { guid } from "Explorer/Tables/Utilities";
|
import { guid } from "Explorer/Tables/Utilities";
|
||||||
import create, { UseStore } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
|
|
||||||
@ -8,23 +10,126 @@ interface QueryCopilotState {
|
|||||||
showFeedbackModal: boolean;
|
showFeedbackModal: boolean;
|
||||||
hideFeedbackModalForLikedQueries: boolean;
|
hideFeedbackModalForLikedQueries: boolean;
|
||||||
correlationId: string;
|
correlationId: string;
|
||||||
|
query: string;
|
||||||
|
selectedQuery: string;
|
||||||
|
isGeneratingQuery: boolean;
|
||||||
|
isExecuting: boolean;
|
||||||
|
dislikeQuery: boolean | undefined;
|
||||||
|
showCallout: boolean;
|
||||||
|
showSamplePrompts: boolean;
|
||||||
|
queryIterator: MinimalQueryIterator | undefined;
|
||||||
|
queryResults: QueryResults | undefined;
|
||||||
|
errorMessage: string;
|
||||||
|
isSamplePromptsOpen: boolean;
|
||||||
|
showDeletePopup: boolean;
|
||||||
|
showFeedbackBar: boolean;
|
||||||
|
showCopyPopup: boolean;
|
||||||
|
showErrorMessageBar: boolean;
|
||||||
|
generatedQueryComments: string;
|
||||||
|
|
||||||
openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) => void;
|
openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) => void;
|
||||||
closeFeedbackModal: () => void;
|
closeFeedbackModal: () => void;
|
||||||
setHideFeedbackModalForLikedQueries: (hideFeedbackModalForLikedQueries: boolean) => void;
|
setHideFeedbackModalForLikedQueries: (hideFeedbackModalForLikedQueries: boolean) => void;
|
||||||
refreshCorrelationId: () => void;
|
refreshCorrelationId: () => void;
|
||||||
|
setUserPrompt: (userPrompt: string) => void;
|
||||||
|
setQuery: (query: string) => void;
|
||||||
|
setGeneratedQuery: (generatedQuery: string) => void;
|
||||||
|
setSelectedQuery: (selectedQuery: string) => void;
|
||||||
|
setIsGeneratingQuery: (isGeneratingQuery: boolean) => void;
|
||||||
|
setIsExecuting: (isExecuting: boolean) => void;
|
||||||
|
setLikeQuery: (likeQuery: boolean) => void;
|
||||||
|
setDislikeQuery: (dislikeQuery: boolean | undefined) => void;
|
||||||
|
setShowCallout: (showCallout: boolean) => void;
|
||||||
|
setShowSamplePrompts: (showSamplePrompts: boolean) => void;
|
||||||
|
setQueryIterator: (queryIterator: MinimalQueryIterator | undefined) => void;
|
||||||
|
setQueryResults: (queryResults: QueryResults | undefined) => void;
|
||||||
|
setErrorMessage: (errorMessage: string) => void;
|
||||||
|
setIsSamplePromptsOpen: (isSamplePromptsOpen: boolean) => void;
|
||||||
|
setShowDeletePopup: (showDeletePopup: boolean) => void;
|
||||||
|
setShowFeedbackBar: (showFeedbackBar: boolean) => void;
|
||||||
|
setshowCopyPopup: (showCopyPopup: boolean) => void;
|
||||||
|
setShowErrorMessageBar: (showErrorMessageBar: boolean) => void;
|
||||||
|
setGeneratedQueryComments: (generatedQueryComments: string) => void;
|
||||||
|
resetQueryCopilotStates: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useQueryCopilot: UseStore<QueryCopilotState> = create((set) => ({
|
type QueryCopilotStore = UseStore<QueryCopilotState>;
|
||||||
|
|
||||||
|
export const useQueryCopilot: QueryCopilotStore = create((set) => ({
|
||||||
generatedQuery: "",
|
generatedQuery: "",
|
||||||
likeQuery: false,
|
likeQuery: false,
|
||||||
userPrompt: "",
|
userPrompt: "",
|
||||||
showFeedbackModal: false,
|
showFeedbackModal: false,
|
||||||
hideFeedbackModalForLikedQueries: false,
|
hideFeedbackModalForLikedQueries: false,
|
||||||
correlationId: "",
|
correlationId: "",
|
||||||
|
query: "",
|
||||||
|
selectedQuery: "",
|
||||||
|
isGeneratingQuery: false,
|
||||||
|
isExecuting: false,
|
||||||
|
dislikeQuery: undefined,
|
||||||
|
showCallout: false,
|
||||||
|
showSamplePrompts: false,
|
||||||
|
queryIterator: undefined,
|
||||||
|
queryResults: undefined,
|
||||||
|
errorMessage: "",
|
||||||
|
isSamplePromptsOpen: false,
|
||||||
|
showDeletePopup: false,
|
||||||
|
showFeedbackBar: false,
|
||||||
|
showCopyPopup: false,
|
||||||
|
showErrorMessageBar: false,
|
||||||
|
generatedQueryComments: "",
|
||||||
|
|
||||||
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 }),
|
||||||
closeFeedbackModal: () => set({ generatedQuery: "", likeQuery: false, userPrompt: "", showFeedbackModal: false }),
|
closeFeedbackModal: () => set({ generatedQuery: "", likeQuery: false, userPrompt: "", showFeedbackModal: false }),
|
||||||
setHideFeedbackModalForLikedQueries: (hideFeedbackModalForLikedQueries: boolean) =>
|
setHideFeedbackModalForLikedQueries: (hideFeedbackModalForLikedQueries: boolean) =>
|
||||||
set({ hideFeedbackModalForLikedQueries }),
|
set({ hideFeedbackModalForLikedQueries }),
|
||||||
refreshCorrelationId: () => set({ correlationId: guid() }),
|
refreshCorrelationId: () => set({ correlationId: guid() }),
|
||||||
|
setUserPrompt: (userPrompt: string) => set({ userPrompt }),
|
||||||
|
setQuery: (query: string) => set({ query }),
|
||||||
|
setGeneratedQuery: (generatedQuery: string) => set({ generatedQuery }),
|
||||||
|
setSelectedQuery: (selectedQuery: string) => set({ selectedQuery }),
|
||||||
|
setIsGeneratingQuery: (isGeneratingQuery: boolean) => set({ isGeneratingQuery }),
|
||||||
|
setIsExecuting: (isExecuting: boolean) => set({ isExecuting }),
|
||||||
|
setLikeQuery: (likeQuery: boolean) => set({ likeQuery }),
|
||||||
|
setDislikeQuery: (dislikeQuery: boolean | undefined) => set({ dislikeQuery }),
|
||||||
|
setShowCallout: (showCallout: boolean) => set({ showCallout }),
|
||||||
|
setShowSamplePrompts: (showSamplePrompts: boolean) => set({ showSamplePrompts }),
|
||||||
|
setQueryIterator: (queryIterator: MinimalQueryIterator | undefined) => set({ queryIterator }),
|
||||||
|
setQueryResults: (queryResults: QueryResults | undefined) => set({ queryResults }),
|
||||||
|
setErrorMessage: (errorMessage: string) => set({ errorMessage }),
|
||||||
|
setIsSamplePromptsOpen: (isSamplePromptsOpen: boolean) => set({ isSamplePromptsOpen }),
|
||||||
|
setShowDeletePopup: (showDeletePopup: boolean) => set({ showDeletePopup }),
|
||||||
|
setShowFeedbackBar: (showFeedbackBar: boolean) => set({ showFeedbackBar }),
|
||||||
|
setshowCopyPopup: (showCopyPopup: boolean) => set({ showCopyPopup }),
|
||||||
|
setShowErrorMessageBar: (showErrorMessageBar: boolean) => set({ showErrorMessageBar }),
|
||||||
|
setGeneratedQueryComments: (generatedQueryComments: string) => set({ generatedQueryComments }),
|
||||||
|
|
||||||
|
resetQueryCopilotStates: () => {
|
||||||
|
set((state) => ({
|
||||||
|
...state,
|
||||||
|
generatedQuery: "",
|
||||||
|
likeQuery: false,
|
||||||
|
userPrompt: "",
|
||||||
|
showFeedbackModal: false,
|
||||||
|
hideFeedbackModalForLikedQueries: false,
|
||||||
|
correlationId: "",
|
||||||
|
query: "",
|
||||||
|
selectedQuery: "",
|
||||||
|
isGeneratingQuery: false,
|
||||||
|
isExecuting: false,
|
||||||
|
dislikeQuery: undefined,
|
||||||
|
showCallout: false,
|
||||||
|
showSamplePrompts: false,
|
||||||
|
queryIterator: undefined,
|
||||||
|
queryResults: undefined,
|
||||||
|
errorMessage: "",
|
||||||
|
isSamplePromptsOpen: false,
|
||||||
|
showDeletePopup: false,
|
||||||
|
showFeedbackBar: false,
|
||||||
|
showCopyPopup: false,
|
||||||
|
showErrorMessageBar: false,
|
||||||
|
generatedQueryComments: "",
|
||||||
|
}));
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
Loading…
Reference in New Issue
Block a user