diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index 991829441..245a96dc8 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -1385,11 +1385,15 @@ export default class Explorer { } public async configureCopilot(): Promise { - if (userContext.apiType !== "SQL") { + if (userContext.apiType !== "SQL" || !userContext.subscriptionId) { return; } - const copilotEnabled = await getCopilotEnabled(); - const copilotUserDBEnabled = await isCopilotFeatureRegistered(userContext.subscriptionId); + const copilotEnabledPromise = getCopilotEnabled(); + const copilotUserDBEnabledPromise = isCopilotFeatureRegistered(userContext.subscriptionId); + const [copilotEnabled, copilotUserDBEnabled] = await Promise.all([ + copilotEnabledPromise, + copilotUserDBEnabledPromise, + ]); useQueryCopilot.getState().setCopilotEnabled(copilotEnabled); useQueryCopilot.getState().setCopilotUserDBEnabled(copilotUserDBEnabled); } diff --git a/src/Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal.test.tsx b/src/Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal.test.tsx index 4c805ad36..e4f54a107 100644 --- a/src/Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal.test.tsx +++ b/src/Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal.test.tsx @@ -1,4 +1,4 @@ -import { Checkbox, ChoiceGroup, DefaultButton, IconButton, PrimaryButton, TextField } from "@fluentui/react"; +import { Checkbox, DefaultButton, IconButton, PrimaryButton, TextField } from "@fluentui/react"; import Explorer from "Explorer/Explorer"; import { QueryCopilotFeedbackModal } from "Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal"; import { useCopilotStore } from "Explorer/QueryCopilot/QueryCopilotContext"; @@ -83,42 +83,6 @@ describe("Query Copilot Feedback Modal snapshot test", () => { expect(wrapper).toMatchSnapshot(); }); - it("should record user contact choice no", () => { - const wrapper = shallow( - , - ); - const contactAllowed = wrapper.find(ChoiceGroup); - - contactAllowed.simulate("change", {}, { key: "no" }); - - expect(getUserEmail).toHaveBeenCalledTimes(3); - expect(wrapper.find(ChoiceGroup).props().selectedKey).toEqual("no"); - expect(wrapper).toMatchSnapshot(); - }); - - it("should record user contact choice yes", () => { - const wrapper = shallow( - , - ); - const contactAllowed = wrapper.find(ChoiceGroup); - - contactAllowed.simulate("change", {}, { key: "yes" }); - - expect(getUserEmail).toHaveBeenCalledTimes(4); - expect(wrapper.find(ChoiceGroup).props().selectedKey).toEqual("yes"); - expect(wrapper).toMatchSnapshot(); - }); - it("should not render dont show again button", () => { const wrapper = shallow( { generatedQuery: "test query", userPrompt: "test prompt", description: "", - contact: getUserEmail(), }, explorer: explorer, }); diff --git a/src/Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal.tsx b/src/Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal.tsx index 6cea9ff68..d8d68f4cc 100644 --- a/src/Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal.tsx +++ b/src/Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal.tsx @@ -1,6 +1,5 @@ import { Checkbox, - ChoiceGroup, DefaultButton, IconButton, Link, @@ -14,7 +13,6 @@ import Explorer from "Explorer/Explorer"; import { useCopilotStore } from "Explorer/QueryCopilot/QueryCopilotContext"; import { SubmitFeedback } from "Explorer/QueryCopilot/Shared/QueryCopilotClient"; import React from "react"; -import { getUserEmail } from "../../../Utils/UserUtils"; export const QueryCopilotFeedbackModal = ({ explorer, @@ -35,16 +33,14 @@ export const QueryCopilotFeedbackModal = ({ closeFeedbackModal, setHideFeedbackModalForLikedQueries, } = useCopilotStore(); - const [isContactAllowed, setIsContactAllowed] = React.useState(false); const [description, setDescription] = React.useState(""); const [doNotShowAgainChecked, setDoNotShowAgainChecked] = React.useState(false); - const [contact, setContact] = React.useState(getUserEmail()); const handleSubmit = () => { closeFeedbackModal(); setHideFeedbackModalForLikedQueries(doNotShowAgainChecked); SubmitFeedback({ - params: { generatedQuery, likeQuery, description, userPrompt, contact }, + params: { generatedQuery, likeQuery, description, userPrompt }, explorer, databaseId, containerId, @@ -77,30 +73,6 @@ export const QueryCopilotFeedbackModal = ({ defaultValue={generatedQuery} readOnly /> - { - setIsContactAllowed(option.key === "yes"); - setContact(option.key === "yes" ? getUserEmail() : ""); - }} - > By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the{" "} { diff --git a/src/Explorer/QueryCopilot/Modal/WelcomeModal.css b/src/Explorer/QueryCopilot/Modal/WelcomeModal.css index c332a40d0..0e5245c11 100644 --- a/src/Explorer/QueryCopilot/Modal/WelcomeModal.css +++ b/src/Explorer/QueryCopilot/Modal/WelcomeModal.css @@ -1,7 +1,6 @@ .modalContentPadding { padding-top: 15px; width: 513px; - height: 638px; } .exitPadding { @@ -39,7 +38,7 @@ } .buttonPadding { - padding: 15px 0px 0px 0px; + padding: 15px 0px 15px 0px; } .tryButton { diff --git a/src/Explorer/QueryCopilot/Modal/WelcomeModal.tsx b/src/Explorer/QueryCopilot/Modal/WelcomeModal.tsx index bec25b488..37ac22b41 100644 --- a/src/Explorer/QueryCopilot/Modal/WelcomeModal.tsx +++ b/src/Explorer/QueryCopilot/Modal/WelcomeModal.tsx @@ -23,7 +23,7 @@ export const WelcomeModal = ({ visible }: { visible: boolean }): JSX.Element => isBlocking={false} styles={{ main: { - maxHeight: 530, + maxHeight: 600, borderRadius: 10, overflow: "hidden", }, @@ -53,7 +53,7 @@ export const WelcomeModal = ({ visible }: { visible: boolean }): JSX.Element => - Welcome to Copilot in Azure Cosmos DB + Welcome to Microsoft Copilot for Azure in Cosmos DB (preview) diff --git a/src/Explorer/QueryCopilot/Modal/__snapshots__/QueryCopilotFeedbackModal.test.tsx.snap b/src/Explorer/QueryCopilot/Modal/__snapshots__/QueryCopilotFeedbackModal.test.tsx.snap index d0b2a3481..f56c85581 100644 --- a/src/Explorer/QueryCopilot/Modal/__snapshots__/QueryCopilotFeedbackModal.test.tsx.snap +++ b/src/Explorer/QueryCopilot/Modal/__snapshots__/QueryCopilotFeedbackModal.test.tsx.snap @@ -76,43 +76,6 @@ exports[`Query Copilot Feedback Modal snapshot test shoud render and match snaps } } /> - - - - - - - By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the - - - Privacy statement - - - for more information. - - - - Submit - - - Cancel - - - - - -`; - -exports[`Query Copilot Feedback Modal snapshot test should record user contact choice no 1`] = ` - -
- - - - Send feedback to Microsoft - - - - - Your feedback will help improve the experience. - - - - - - By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the - - - Privacy statement - - - for more information. - - - - Submit - - - Cancel - - - -
-
-`; - -exports[`Query Copilot Feedback Modal snapshot test should record user contact choice yes 1`] = ` - -
- - - - Send feedback to Microsoft - - - - - Your feedback will help improve the experience. - - - - - - - Welcome to Copilot in Azure Cosmos DB + Welcome to Microsoft Copilot for Azure in Cosmos DB (preview) = ({ @@ -98,6 +107,7 @@ export const QueryCopilotPromptbar: React.FC = ({ setGeneratedQueryComments, setQueryResults, setErrorMessage, + errorMessage, } = useCopilotStore(); const sampleProps: SamplePromptsProps = { @@ -230,6 +240,16 @@ export const QueryCopilotPromptbar: React.FC = ({ responseCode: response.status, }); } + } else if (response?.status === HttpStatusCodes.TooManyRequests) { + handleError(JSON.stringify(generateSQLQueryResponse), "copilotTooManyRequestError"); + useTabs.getState().setIsQueryErrorThrown(true); + setShowErrorMessageBar(true); + setErrorMessage("Ratelimit exceeded 5 per 1 minute. Please try again after sometime"); + TelemetryProcessor.traceFailure(Action.QueryGenerationFromCopilotPrompt, { + databaseName: databaseId, + collectionId: containerId, + responseCode: response.status, + }); } else { handleError(JSON.stringify(generateSQLQueryResponse), "copilotInternalServerError"); useTabs.getState().setIsQueryErrorThrown(true); @@ -361,7 +381,7 @@ export const QueryCopilotPromptbar: React.FC = ({ {isGeneratingQuery && } {showSamplePrompts && ( setShowSamplePrompts(false)} @@ -393,7 +413,7 @@ export const QueryCopilotPromptbar: React.FC = ({ setShowSamplePrompts(false); inputEdited.current = true; }} - onRenderIcon={() => } + onRenderIcon={() => } styles={promptStyles} > {history} @@ -469,7 +489,7 @@ export const QueryCopilotPromptbar: React.FC = ({ {showErrorMessageBar && ( - We ran into an error and were not able to execute query. + {errorMessage ? errorMessage : "We ran into an error and were not able to execute query."} )} {showInvalidQueryMessageBar && ( diff --git a/src/Explorer/QueryCopilot/Shared/QueryCopilotClient.ts b/src/Explorer/QueryCopilot/Shared/QueryCopilotClient.ts index b46cd9afc..5d98a5717 100644 --- a/src/Explorer/QueryCopilot/Shared/QueryCopilotClient.ts +++ b/src/Explorer/QueryCopilot/Shared/QueryCopilotClient.ts @@ -35,18 +35,38 @@ import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot"; import { useTabs } from "hooks/useTabs"; import * as StringUtility from "../../../Shared/StringUtility"; +async function fetchWithTimeout( + url: string, + headers: { + [x: string]: string; + }, +) { + const timeout = 10000; + const options = { timeout }; + + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), timeout); + + const response = await window.fetch(url, { + headers, + ...options, + signal: controller.signal, + }); + clearTimeout(id); + + return response; +} + export const isCopilotFeatureRegistered = async (subscriptionId: string): Promise => { const api_version = "2021-07-01"; - const url = `${configContext.ARM_ENDPOINT}/subscriptions/${subscriptionId}/providers/Microsoft.Features/featureProviders/Microsoft.DocumentDB/subscriptionFeatureRegistrations/CopilotInAzureCDB?api-version=${api_version}`; + const url = `${configContext.ARM_ENDPOINT}/subscriptions/${subscriptionId}/providers/Microsoft.Features/featureProviders/Microsoft.DocumentDB/subscriptionFeatureRegistrations/MicrosoftCopilotForAzureInCDB?api-version=${api_version}`; const authorizationHeader: AuthorizationTokenHeaderMetadata = getAuthorizationHeader(); const headers = { [authorizationHeader.header]: authorizationHeader.token }; let response; try { - response = await window.fetch(url, { - headers, - }); + response = await fetchWithTimeout(url, headers); } catch (error) { return false; } @@ -67,9 +87,7 @@ export const getCopilotEnabled = async (): Promise => { let response; try { - response = await window.fetch(url, { - headers, - }); + response = await fetchWithTimeout(url, headers); } catch (error) { return false; } diff --git a/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx b/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx index 4793f3791..990bac5c9 100644 --- a/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx +++ b/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx @@ -443,7 +443,7 @@ export default class QueryTabComponent extends React.Component { this._toggleCopilot(!this.state.copilotActive); }, - commandButtonLabel: "Copilot", + commandButtonLabel: this.state.copilotActive ? "Disable Copilot" : "Enable Copilot", ariaLabel: "Copilot", hasPopup: false, }; diff --git a/src/hooks/useKnockoutExplorer.ts b/src/hooks/useKnockoutExplorer.ts index 24aca7295..30689da49 100644 --- a/src/hooks/useKnockoutExplorer.ts +++ b/src/hooks/useKnockoutExplorer.ts @@ -58,19 +58,21 @@ export function useKnockoutExplorer(platform: Platform): Explorer { userContext.features.phoenixNotebooks = true; userContext.features.phoenixFeatures = true; } + let explorer: Explorer; if (platform === Platform.Hosted) { - const explorer = await configureHosted(); - setExplorer(explorer); + explorer = await configureHosted(); } else if (platform === Platform.Emulator) { - const explorer = configureEmulator(); - setExplorer(explorer); + explorer = configureEmulator(); } else if (platform === Platform.Portal) { - const explorer = await configurePortal(); - setExplorer(explorer); + explorer = await configurePortal(); } else if (platform === Platform.Fabric) { - const explorer = await configureFabric(); - setExplorer(explorer); + explorer = await configureFabric(); } + if (explorer && userContext.features.enableCopilot) { + await updateContextForCopilot(explorer); + await updateContextForSampleData(explorer); + } + setExplorer(explorer); } }; effect(); @@ -79,9 +81,6 @@ export function useKnockoutExplorer(platform: Platform): Explorer { useEffect(() => { if (explorer) { applyExplorerBindings(explorer); - if (userContext.features.enableCopilot) { - updateContextForCopilot(explorer).then(() => updateContextForSampleData(explorer)); - } } }, [explorer]);