Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
763f88a3c5 Bump word-wrap from 1.2.3 to 1.2.4
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-19 01:57:47 +00:00
34 changed files with 5110 additions and 4406 deletions

View File

@@ -230,7 +230,7 @@ input::-webkit-inner-spin-button {
.advanced-options-panel .advanced-options .select .select-options-link {
margin-left: 4px;
cursor: pointer;
padding: 2px;
outline: none;
}
.query-panel .row .column-headers .Field {

8375
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -435,89 +435,24 @@ export const QueryCopilotSampleContainerId = "SampleContainer";
export const QueryCopilotSampleContainerSchema = {
product: {
sampleData: {
id: "c415e70f-9bf5-4cda-aebe-a290cb8b94c2",
name: "Amazing Phone 3000 (Black)",
price: 223.33,
id: "de6fadec-0384-43c8-93ea-16c0170b5460",
name: "Premium Phone Mini (Red)",
price: 652.04,
category: "Electronics",
description:
"This Amazing Phone 3000 (Black) is made of black metal! It has a very well made aluminum body and it feels very comfortable. We loved the sound that comes out of it! Also, the design of the phone was a little loose at first because I was using the camera and felt uncomfortable wearing it. The phone is actually made slightly smaller than these photos! This is due to the addition of a 3.3mm filter",
stock: 84,
countryOfOrigin: "USA",
firstAvailable: "2018-09-07 19:41:44",
priceHistory: [238.68, 234.7, 221.49, 205.88, 220.15],
"This Premium Phone Mini (Red) is designed by the company under agreement with the FCC, so we'd give it a pass in either direction, but no one should be using this handset without a compatible handset. All in all, this phone is one of the most affordable Android handsets out there at $100. Check them out.\n\n9. HTC One M9 4",
stock: 74,
countryOfOrigin: "Mexico",
firstAvailable: "2018-07-07 17:42:28",
priceHistory: [592.81],
customerRatings: [
{
username: "steven66",
firstName: "Carol",
gender: "female",
lastName: "Shelton",
age: "25-35",
area: "suburban",
address: "261 Collins Burgs Apt. 332\nNorth Taylor, NM 32268",
stars: 5,
date: "2021-04-22 13:42:14",
verifiedUser: true,
},
{
username: "khudson",
firstName: "Ronald",
gender: "male",
lastName: "Webb",
age: "18-24",
area: "suburban",
address: "9912 Parker Court Apt. 068\nNorth Austin, HI 76225",
stars: 5,
date: "2021-02-07 07:00:22",
verifiedUser: false,
},
{
username: "lfrancis",
firstName: "Brady",
gender: "male",
lastName: "Wright",
age: "35-45",
area: "urban",
address: "PSC 5437, Box 3159\nAPO AA 26385",
stars: 2,
date: "2022-02-23 21:40:10",
verifiedUser: false,
},
{
username: "nicolemartinez",
firstName: "Megan",
gender: "female",
lastName: "Tran",
age: "18-24",
area: "rural",
address: "7445 Salazar Brooks\nNew Sarah, PW 18097",
stars: 4,
date: "2021-09-01 22:21:40",
verifiedUser: false,
},
{
username: "uguzman",
firstName: "Deanna",
gender: "female",
lastName: "Campbell",
age: "18-24",
area: "urban",
address: "41104 Moreno Fort Suite 872\nPort Michaelbury, AK 48712",
stars: 1,
date: "2022-03-07 02:23:14",
verifiedUser: false,
},
{
username: "rebeccahunt",
firstName: "Jared",
gender: "male",
lastName: "Lopez",
age: "18-24",
area: "rural",
address: "392 Morgan Village Apt. 785\nGreenshire, CT 05921",
stars: 5,
date: "2021-04-17 04:17:49",
verifiedUser: false,
},
{ username: "dannyhowell", stars: 1, date: "2022-03-12 17:01:23", verifiedUser: true },
{ username: "lindsay26", stars: 1, date: "2022-12-29 07:18:20", verifiedUser: false },
{ username: "smithmiguel", stars: 3, date: "2022-09-08 11:43:27", verifiedUser: false },
{ username: "julie07", stars: 3, date: "2021-03-14 23:54:10", verifiedUser: true },
{ username: "kelly93", stars: 3, date: "2022-11-05 12:45:43", verifiedUser: false },
{ username: "katherinereynolds", stars: 2, date: "2022-09-14 11:49:36", verifiedUser: false },
{ username: "chandlerkenneth", stars: 1, date: "2022-01-14 12:14:43", verifiedUser: true },
],
rareProperty: true,
},
@@ -559,24 +494,6 @@ export const QueryCopilotSampleContainerSchema = {
username: {
type: "string",
},
firstName: {
type: "string",
},
gender: {
type: "string",
},
lastName: {
type: "string",
},
age: {
type: "string",
},
area: {
type: "string",
},
address: {
type: "string",
},
stars: {
type: "number",
},

View File

@@ -137,7 +137,7 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
/>
{!isEntityValueDisable && (
<TooltipHost content="Edit property" id="editTooltip">
<div>
<div tabIndex={0}>
<Image
{...imageProps}
src={EditIcon}

View File

@@ -1,14 +1,14 @@
import {
allowedAadEndpoints,
allowedArcadiaEndpoints,
allowedArmEndpoints,
allowedBackendEndpoints,
allowedEmulatorEndpoints,
allowedGraphEndpoints,
allowedHostedExplorerEndpoints,
allowedJunoOrigins,
allowedMongoBackendEndpoints,
allowedMsalRedirectEndpoints,
defaultAllowedArmEndpoints,
defaultAllowedBackendEndpoints,
validateEndpoint,
} from "Utils/EndpointValidation";
@@ -20,8 +20,6 @@ export enum Platform {
export interface ConfigContext {
platform: Platform;
allowedArmEndpoints: ReadonlyArray<string>;
allowedBackendEndpoints: ReadonlyArray<string>;
allowedParentFrameOrigins: ReadonlyArray<string>;
gitSha?: string;
proxyPath?: string;
@@ -51,8 +49,6 @@ export interface ConfigContext {
// Default configuration
let configContext: Readonly<ConfigContext> = {
platform: Platform.Portal,
allowedArmEndpoints: defaultAllowedArmEndpoints,
allowedBackendEndpoints: defaultAllowedBackendEndpoints,
allowedParentFrameOrigins: [
`^https:\\/\\/cosmos\\.azure\\.(com|cn|us)$`,
`^https:\\/\\/[\\.\\w]*portal\\.azure\\.(com|cn|us)$`,
@@ -81,7 +77,7 @@ let configContext: Readonly<ConfigContext> = {
export function resetConfigContext(): void {
if (process.env.NODE_ENV !== "test") {
throw new Error("resetConfigContext can only be called in a test environment");
throw new Error("resetConfigContext can only becalled in a test environment");
}
configContext = {} as ConfigContext;
}
@@ -91,7 +87,7 @@ export function updateConfigContext(newContext: Partial<ConfigContext>): void {
return;
}
if (!validateEndpoint(newContext.ARM_ENDPOINT, configContext.allowedArmEndpoints || defaultAllowedArmEndpoints)) {
if (!validateEndpoint(newContext.ARM_ENDPOINT, allowedArmEndpoints)) {
delete newContext.ARM_ENDPOINT;
}
@@ -111,12 +107,7 @@ export function updateConfigContext(newContext: Partial<ConfigContext>): void {
delete newContext.ARCADIA_ENDPOINT;
}
if (
!validateEndpoint(
newContext.BACKEND_ENDPOINT,
configContext.allowedBackendEndpoints || defaultAllowedBackendEndpoints
)
) {
if (!validateEndpoint(newContext.BACKEND_ENDPOINT, allowedBackendEndpoints)) {
delete newContext.BACKEND_ENDPOINT;
}
@@ -139,7 +130,7 @@ export function updateConfigContext(newContext: Partial<ConfigContext>): void {
Object.assign(configContext, newContext);
}
// Injected for local development. These will be removed in the production bundle by webpack
// Injected for local develpment. These will be removed in the production bundle by webpack
if (process.env.NODE_ENV === "development") {
const port: string = process.env.PORT || "1234";
updateConfigContext({

View File

@@ -1,5 +1,3 @@
import { Action } from "Shared/Telemetry/TelemetryConstants";
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
import { ReactTabKind, useTabs } from "hooks/useTabs";
import React from "react";
import AddCollectionIcon from "../../images/AddCollection.svg";
@@ -148,10 +146,7 @@ export const createSampleCollectionContextMenuButton = (): TreeNodeMenuItem[] =>
if (userContext.apiType === "SQL") {
items.push({
iconSrc: AddSqlQueryIcon,
onClick: () => {
useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot);
traceOpen(Action.OpenQueryCopilotFromNewQuery, { apiType: userContext.apiType });
},
onClick: () => useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot),
label: "New SQL Query",
});
}

View File

@@ -1,8 +1,5 @@
import { Link } from "@fluentui/react/lib/Link";
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
import { sendMessage } from "Common/MessageHandler";
import { Platform } from "ConfigContext";
import { MessageTypes } from "Contracts/ExplorerContracts";
import { IGalleryItem } from "Juno/JunoClient";
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointValidation";
import * as ko from "knockout";
@@ -26,7 +23,7 @@ import { PhoenixClient } from "../Phoenix/PhoenixClient";
import * as ExplorerSettings from "../Shared/ExplorerSettings";
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
import { isAccountNewerThanThresholdInMs, userContext } from "../UserContext";
import { userContext } from "../UserContext";
import { getCollectionName, getUploadName } from "../Utils/APITypeUtils";
import { stringToBlob } from "../Utils/BlobUtils";
import { isCapabilityEnabled } from "../Utils/CapabilityUtils";
@@ -261,45 +258,6 @@ export default class Explorer {
// TODO: return result
}
private getRandomInt(max: number) {
return Math.floor(Math.random() * max);
}
public openNPSSurveyDialog(): void {
if (!Platform.Portal) {
return;
}
const NINETY_DAYS_IN_MS = 7776000000;
const ONE_DAY_IN_MS = 86400000;
const isAccountNewerThanNinetyDays = isAccountNewerThanThresholdInMs(
userContext.databaseAccount?.systemData?.createdAt || "",
NINETY_DAYS_IN_MS
);
// Try Cosmos DB subscription - survey shown to random 25% of users at day 1 in Data Explorer.
if (userContext.isTryCosmosDBSubscription) {
if (
isAccountNewerThanThresholdInMs(userContext.databaseAccount?.systemData?.createdAt || "", ONE_DAY_IN_MS) &&
this.getRandomInt(100) < 25
) {
sendMessage({ type: MessageTypes.DisplayNPSSurvey });
}
} else {
// An existing account is lesser than 90 days old. For existing account show to random 10 % of users in Data Explorer.
if (isAccountNewerThanNinetyDays) {
if (this.getRandomInt(100) < 10) {
sendMessage({ type: MessageTypes.DisplayNPSSurvey });
}
} else {
// An existing account is greater than 90 days. For existing account show to random 25 % of users in Data Explorer.
if (this.getRandomInt(100) < 25) {
sendMessage({ type: MessageTypes.DisplayNPSSurvey });
}
}
}
}
public async refreshDatabaseForResourceToken(): Promise<void> {
const databaseId = userContext.parsedResourceToken?.databaseId;
const collectionId = userContext.parsedResourceToken?.collectionId;
@@ -1333,7 +1291,7 @@ export default class Explorer {
return;
}
const sampleDataResourceTokenCollection = new ResourceTokenCollection(this, databaseId, collection, true);
const sampleDataResourceTokenCollection = new ResourceTokenCollection(this, databaseId, collection);
useDatabases.setState({ sampleDataResourceTokenCollection });
}
}

View File

@@ -1,5 +1,3 @@
import { Action } from "Shared/Telemetry/TelemetryConstants";
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
import { ReactTabKind, useTabs } from "hooks/useTabs";
import * as React from "react";
import AddCollectionIcon from "../../../../images/AddCollection.svg";
@@ -328,7 +326,6 @@ function createNewSQLQueryButton(selectedNodeState: SelectedNodeState): CommandB
onCommandClick: () => {
if (useSelectedNode.getState().isQueryCopilotCollectionSelected()) {
useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot);
traceOpen(Action.OpenQueryCopilotFromNewQuery, { apiType: userContext.apiType });
} else {
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection);

View File

@@ -1425,10 +1425,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
this.setState({ isExecuting: false });
TelemetryProcessor.traceSuccess(Action.CreateCollection, telemetryData, startKey);
useSidePanel.getState().closeSidePanel();
// open NPS Survey Dialog once the collection is created
if (userContext.features.enableNPSSurvey) {
this.props.explorer.openNPSSurveyDialog();
}
} catch (error) {
const errorMessage: string = getErrorMessage(error);
this.setState({ isExecuting: false, errorMessage, showErrorDetails: true });

View File

@@ -112,9 +112,6 @@
margin-top: 28px;
margin-left: 4px !important;
}
.addRemoveIcon [alt="editEntity"]:focus,.addRemoveIconLabel [alt="editEntity"]:focus{
border:1px dashed #605E5C
}
.addNewParamStyle {
margin-top: 5px;
margin-left: 5px !important;

View File

@@ -78,13 +78,11 @@ export const QueryCopilotFeedbackModal: React.FC = (): JSX.Element => {
}}
></ChoiceGroup>
<Text style={{ fontSize: 12, marginBottom: 14 }}>
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the{" "}
{
<Link href="https://privacy.microsoft.com/privacystatement" target="_blank">
Privacy statement
</Link>
}{" "}
for more information.
By pressing submit, your feedback will be used to improve Microsoft products and services. IT admins for your
organization will be able to view and manage your feedback data.{" "}
<Link href="" target="_blank">
Privacy statement
</Link>
</Text>
{likeQuery && (
<Checkbox

View File

@@ -42,8 +42,8 @@ export const WelcomeModal = ({ visible }: { visible: boolean }): JSX.Element =>
</Stack>
</Stack>
<Stack horizontalAlign="center">
<Stack.Item align="center" style={{ textAlign: "center" }}>
<Text className="title bold">Welcome to Query Copilot for Azure Cosmos DB (Private Preview)</Text>
<Stack.Item align="center">
<Text className="title bold">Welcome to Copilot in CosmosDB</Text>
</Stack.Item>
<Stack.Item align="center" className="text">
<Stack horizontal>
@@ -52,7 +52,7 @@ export const WelcomeModal = ({ visible }: { visible: boolean }): JSX.Element =>
</StackItem>
<StackItem align="start">
<Text className="bold">
Let Query Copilot do the work for you
Let copilot do the work for you
<br />
</Text>
</StackItem>
@@ -60,7 +60,7 @@ export const WelcomeModal = ({ visible }: { visible: boolean }): JSX.Element =>
<Text>
Ask Copilot to generate a query by describing the query in your words.
<br />
<Link href="http://aka.ms/cdb-copilot-learn-more">Learn more</Link>
<Link href="">Learn more</Link>
</Text>
</Stack.Item>
<Stack.Item align="center" className="text">
@@ -78,7 +78,7 @@ export const WelcomeModal = ({ visible }: { visible: boolean }): JSX.Element =>
<Text>
AI-generated content can have mistakes. Make sure its accurate and appropriate before using it.
<br />
<Link href="http://aka.ms/cdb-copilot-preview-terms">Read preview terms</Link>
<Link href="">Read preview terms</Link>
</Text>
</Stack.Item>
<Stack.Item align="center" className="text">
@@ -88,16 +88,15 @@ export const WelcomeModal = ({ visible }: { visible: boolean }): JSX.Element =>
</StackItem>
<StackItem align="start">
<Text className="bold">
Query Copilot works on a sample database.
Copilot currently works only a sample database
<br />
</Text>
</StackItem>
</Stack>
<Text>
While in Private Preview, Query Copilot is setup to work on sample database we have configured for you
at no cost.
Copilot is setup on a sample database we have configured for you at no cost
<br />
<Link href="http://aka.ms/cdb-copilot-learn-more">Learn more</Link>
<Link href="">Learn more</Link>
</Text>
</Stack.Item>
</Stack>

View File

@@ -118,16 +118,14 @@ 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
By pressing submit, your feedback will be used to improve Microsoft products and services. IT admins for your organization will be able to view and manage your feedback data.
<StyledLinkBase
href="https://privacy.microsoft.com/privacystatement"
href=""
target="_blank"
>
Privacy statement
</StyledLinkBase>
for more information.
</Text>
<Stack
horizontal={true}
@@ -273,16 +271,14 @@ exports[`Query Copilot Feedback Modal snapshot test should cancel submission 1`]
}
}
>
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the
By pressing submit, your feedback will be used to improve Microsoft products and services. IT admins for your organization will be able to view and manage your feedback data.
<StyledLinkBase
href="https://privacy.microsoft.com/privacystatement"
href=""
target="_blank"
>
Privacy statement
</StyledLinkBase>
for more information.
</Text>
<Stack
horizontal={true}
@@ -428,16 +424,14 @@ exports[`Query Copilot Feedback Modal snapshot test should close on cancel click
}
}
>
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the
By pressing submit, your feedback will be used to improve Microsoft products and services. IT admins for your organization will be able to view and manage your feedback data.
<StyledLinkBase
href="https://privacy.microsoft.com/privacystatement"
href=""
target="_blank"
>
Privacy statement
</StyledLinkBase>
for more information.
</Text>
<Stack
horizontal={true}
@@ -583,16 +577,14 @@ exports[`Query Copilot Feedback Modal snapshot test should get user unput 1`] =
}
}
>
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the
By pressing submit, your feedback will be used to improve Microsoft products and services. IT admins for your organization will be able to view and manage your feedback data.
<StyledLinkBase
href="https://privacy.microsoft.com/privacystatement"
href=""
target="_blank"
>
Privacy statement
</StyledLinkBase>
for more information.
</Text>
<Stack
horizontal={true}
@@ -738,16 +730,14 @@ exports[`Query Copilot Feedback Modal snapshot test should not render dont show
}
}
>
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the
By pressing submit, your feedback will be used to improve Microsoft products and services. IT admins for your organization will be able to view and manage your feedback data.
<StyledLinkBase
href="https://privacy.microsoft.com/privacystatement"
href=""
target="_blank"
>
Privacy statement
</StyledLinkBase>
for more information.
</Text>
<Stack
horizontal={true}
@@ -893,16 +883,14 @@ exports[`Query Copilot Feedback Modal snapshot test should record user contact c
}
}
>
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the
By pressing submit, your feedback will be used to improve Microsoft products and services. IT admins for your organization will be able to view and manage your feedback data.
<StyledLinkBase
href="https://privacy.microsoft.com/privacystatement"
href=""
target="_blank"
>
Privacy statement
</StyledLinkBase>
for more information.
</Text>
<Stack
horizontal={true}
@@ -1048,16 +1036,14 @@ exports[`Query Copilot Feedback Modal snapshot test should record user contact c
}
}
>
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the
By pressing submit, your feedback will be used to improve Microsoft products and services. IT admins for your organization will be able to view and manage your feedback data.
<StyledLinkBase
href="https://privacy.microsoft.com/privacystatement"
href=""
target="_blank"
>
Privacy statement
</StyledLinkBase>
for more information.
</Text>
<Stack
horizontal={true}
@@ -1203,16 +1189,14 @@ exports[`Query Copilot Feedback Modal snapshot test should render dont show agai
}
}
>
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the
By pressing submit, your feedback will be used to improve Microsoft products and services. IT admins for your organization will be able to view and manage your feedback data.
<StyledLinkBase
href="https://privacy.microsoft.com/privacystatement"
href=""
target="_blank"
>
Privacy statement
</StyledLinkBase>
for more information.
</Text>
<StyledCheckboxBase
checked={true}
@@ -1373,16 +1357,14 @@ exports[`Query Copilot Feedback Modal snapshot test should submit submission 1`]
}
}
>
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the
By pressing submit, your feedback will be used to improve Microsoft products and services. IT admins for your organization will be able to view and manage your feedback data.
<StyledLinkBase
href="https://privacy.microsoft.com/privacystatement"
href=""
target="_blank"
>
Privacy statement
</StyledLinkBase>
for more information.
</Text>
<Stack
horizontal={true}

View File

@@ -60,16 +60,11 @@ exports[`Query Copilot Welcome Modal snapshot test should render when isOpen is
>
<StackItem
align="center"
style={
Object {
"textAlign": "center",
}
}
>
<Text
className="title bold"
>
Welcome to Query Copilot for Azure Cosmos DB (Private Preview)
Welcome to Copilot in CosmosDB
</Text>
</StackItem>
<StackItem
@@ -93,7 +88,7 @@ exports[`Query Copilot Welcome Modal snapshot test should render when isOpen is
<Text
className="bold"
>
Let Query Copilot do the work for you
Let copilot do the work for you
<br />
</Text>
</StackItem>
@@ -102,7 +97,7 @@ exports[`Query Copilot Welcome Modal snapshot test should render when isOpen is
Ask Copilot to generate a query by describing the query in your words.
<br />
<StyledLinkBase
href="http://aka.ms/cdb-copilot-learn-more"
href=""
>
Learn more
</StyledLinkBase>
@@ -138,7 +133,7 @@ exports[`Query Copilot Welcome Modal snapshot test should render when isOpen is
AI-generated content can have mistakes. Make sure its accurate and appropriate before using it.
<br />
<StyledLinkBase
href="http://aka.ms/cdb-copilot-preview-terms"
href=""
>
Read preview terms
</StyledLinkBase>
@@ -165,16 +160,16 @@ exports[`Query Copilot Welcome Modal snapshot test should render when isOpen is
<Text
className="bold"
>
Query Copilot works on a sample database.
Copilot currently works only a sample database
<br />
</Text>
</StackItem>
</Stack>
<Text>
While in Private Preview, Query Copilot is setup to work on sample database we have configured for you at no cost.
Copilot is setup on a sample database we have configured for you at no cost
<br />
<StyledLinkBase
href="http://aka.ms/cdb-copilot-learn-more"
href=""
>
Learn more
</StyledLinkBase>

View File

@@ -1,48 +1,11 @@
import { IconButton } from "@fluentui/react";
import { shallow } from "enzyme";
import React from "react";
import { any } from "underscore";
import { CopyPopup } from "./CopyPopup";
describe("Copy Popup snapshot test", () => {
const setShowCopyPopupMock = jest.fn();
it("should render when showCopyPopup is true", () => {
const wrapper = shallow(<CopyPopup showCopyPopup={true} setShowCopyPopup={setShowCopyPopupMock} />);
expect(wrapper.exists()).toBe(true);
expect(wrapper.prop("setShowCopyPopup")).toBeUndefined();
const wrapper = shallow(<CopyPopup showCopyPopup={true} setShowCopyPopup={() => any} />);
expect(wrapper).toMatchSnapshot();
});
it("should render when showCopyPopup is false", () => {
const wrapper = shallow(<CopyPopup showCopyPopup={false} setShowCopyPopup={setShowCopyPopupMock} />);
expect(wrapper.prop("showCopyPopup")).toBeFalsy();
expect(wrapper.prop("setShowCopyPopup")).toBeUndefined();
expect(wrapper).toMatchSnapshot();
});
it("should call setShowCopyPopup(false) when close button is clicked", () => {
const wrapper = shallow(<CopyPopup showCopyPopup={true} setShowCopyPopup={setShowCopyPopupMock} />);
const closeButton = wrapper.find(IconButton);
closeButton.props().onClick?.({} as React.MouseEvent<HTMLButtonElement, MouseEvent>);
expect(setShowCopyPopupMock).toHaveBeenCalledWith(false);
});
it("should have the correct inline styles", () => {
const wrapper = shallow(<CopyPopup showCopyPopup={true} setShowCopyPopup={setShowCopyPopupMock} />);
const stackStyle = wrapper.find("Stack").first().props().style;
expect(stackStyle).toEqual({
position: "fixed",
width: 345,
height: 66,
padding: 10,
gap: 5,
top: 75,
right: 20,
background: "#FFFFFF",
boxShadow: "0 2px 6px rgba(0, 0, 0, 0.16)",
});
});
});

View File

@@ -1,113 +1,19 @@
import { mount, shallow } from "enzyme";
import { shallow } from "enzyme";
import React from "react";
import { any } from "underscore";
import { DeletePopup } from "./DeletePopup";
describe("Delete Popup snapshot test", () => {
const setShowDeletePopupMock = jest.fn();
const setQueryMock = jest.fn();
const clearFeedbackMock = jest.fn();
const showFeedbackBarMock = jest.fn();
it("should render when showDeletePopup is true", () => {
const wrapper = shallow(
<DeletePopup
showDeletePopup={true}
setShowDeletePopup={setShowDeletePopupMock}
setQuery={setQueryMock}
clearFeedback={clearFeedbackMock}
showFeedbackBar={showFeedbackBarMock}
setShowDeletePopup={() => any}
setQuery={() => any}
clearFeedback={() => any}
showFeedbackBar={() => any}
/>
);
expect(wrapper.find("Modal").prop("isOpen")).toBeTruthy();
expect(wrapper).toMatchSnapshot();
});
it("should not render when showDeletePopup is false", () => {
const wrapper = shallow(
<DeletePopup
showDeletePopup={false}
setShowDeletePopup={setShowDeletePopupMock}
setQuery={setQueryMock}
clearFeedback={clearFeedbackMock}
showFeedbackBar={showFeedbackBarMock}
/>
);
expect(wrapper.props().children.props.showDeletePopup).toBeFalsy();
expect(wrapper).toMatchSnapshot();
});
it("should call setQuery with an empty string and setShowDeletePopup(false) when delete button is clicked", () => {
const wrapper = mount(
<DeletePopup
showDeletePopup={true}
setShowDeletePopup={setShowDeletePopupMock}
setQuery={setQueryMock}
clearFeedback={clearFeedbackMock}
showFeedbackBar={showFeedbackBarMock}
/>
);
wrapper.find("PrimaryButton").simulate("click");
expect(setQueryMock).toHaveBeenCalledWith("");
expect(setShowDeletePopupMock).toHaveBeenCalledWith(false);
});
it("should call setShowDeletePopup(false) when close button is clicked", () => {
const setShowDeletePopupMock = jest.fn();
const wrapper = mount(
<DeletePopup
showDeletePopup={true}
setShowDeletePopup={setShowDeletePopupMock}
setQuery={setQueryMock}
clearFeedback={clearFeedbackMock}
showFeedbackBar={showFeedbackBarMock}
/>
);
wrapper.find("DefaultButton").at(1).simulate("click");
expect(setShowDeletePopupMock).toHaveBeenCalledWith(false);
});
it("should render the appropriate text content", () => {
const wrapper = shallow(
<DeletePopup
showDeletePopup={true}
setShowDeletePopup={setShowDeletePopupMock}
setQuery={setQueryMock}
clearFeedback={clearFeedbackMock}
showFeedbackBar={showFeedbackBarMock}
/>
);
const textContent = wrapper
.find("Text")
.map((text, index) => <React.Fragment key={index}>{text.props().children}</React.Fragment>);
expect(textContent).toEqual([
<React.Fragment key={0}>
<b>Delete code?</b>
</React.Fragment>,
<React.Fragment key={1}>
This will clear the query from the query builder pane along with all comments and also reset the prompt pane
</React.Fragment>,
]);
});
it("should have the correct inline style", () => {
const wrapper = shallow(
<DeletePopup
showDeletePopup={true}
setShowDeletePopup={setShowDeletePopupMock}
setQuery={setQueryMock}
clearFeedback={clearFeedbackMock}
showFeedbackBar={showFeedbackBarMock}
/>
);
const stackStyle = wrapper.find("Stack[style]").props().style;
expect(stackStyle).toEqual({ padding: "16px 24px", height: "auto" });
});
});

View File

@@ -1,7 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Copy Popup snapshot test should render when showCopyPopup is false 1`] = `<Fragment />`;
exports[`Copy Popup snapshot test should render when showCopyPopup is true 1`] = `
<Stack
style={

View File

@@ -1,83 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Delete Popup snapshot test should not render when showDeletePopup is false 1`] = `
<Modal
isOpen={false}
styles={
Object {
"main": Object {
"minHeight": "122px",
"minWidth": "880px",
},
}
}
>
<Stack
style={
Object {
"height": "auto",
"padding": "16px 24px",
}
}
>
<Text
style={
Object {
"fontSize": "18px",
"height": 24,
}
}
>
<b>
Delete code?
</b>
</Text>
<Text
style={
Object {
"marginBottom": 20,
"marginTop": 10,
}
}
>
This will clear the query from the query builder pane along with all comments and also reset the prompt pane
</Text>
<Stack
horizontal={true}
horizontalAlign="start"
tokens={
Object {
"childrenGap": 10,
}
}
>
<CustomizedPrimaryButton
onClick={[Function]}
style={
Object {
"height": 24,
"padding": "0px 20px",
}
}
>
Delete
</CustomizedPrimaryButton>
<CustomizedDefaultButton
onClick={[Function]}
style={
Object {
"height": 24,
"padding": "0px 20px",
}
}
>
Close
</CustomizedDefaultButton>
</Stack>
</Stack>
</Modal>
`;
exports[`Delete Popup snapshot test should render when showDeletePopup is true 1`] = `
<Modal
isOpen={true}

View File

@@ -5,7 +5,9 @@ import { QueryCopilotTab } from "./QueryCopilotTab";
describe("Query copilot tab snapshot test", () => {
it("should render with initial input", () => {
const wrapper = shallow(<QueryCopilotTab explorer={new Explorer()} />);
const wrapper = shallow(
<QueryCopilotTab initialInput="Write a query to return all records in this table" explorer={new Explorer()} />
);
expect(wrapper).toMatchSnapshot();
});
});

View File

@@ -34,8 +34,6 @@ import { DeletePopup } from "Explorer/QueryCopilot/Popup/DeletePopup";
import { querySampleDocuments, submitFeedback } from "Explorer/QueryCopilot/QueryCopilotUtilities";
import { SamplePrompts, SamplePromptsProps } from "Explorer/QueryCopilot/SamplePrompts/SamplePrompts";
import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection";
import { Action } from "Shared/Telemetry/TelemetryConstants";
import { traceFailure, traceStart, traceSuccess } from "Shared/Telemetry/TelemetryProcessor";
import { userContext } from "UserContext";
import { queryPagesUntilContentPresent } from "Utils/QueryUtils";
import { useQueryCopilot } from "hooks/useQueryCopilot";
@@ -56,6 +54,7 @@ interface SuggestedPrompt {
}
interface QueryCopilotTabProps {
initialInput: string;
explorer: Explorer;
}
@@ -72,50 +71,31 @@ const promptStyles: IButtonStyles = {
label: { fontWeight: 400, textAlign: "left", paddingLeft: 8 },
};
export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({ explorer }: QueryCopilotTabProps): JSX.Element => {
export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
initialInput,
explorer,
}: QueryCopilotTabProps): JSX.Element => {
const hideFeedbackModalForLikedQueries = useQueryCopilot((state) => state.hideFeedbackModalForLikedQueries);
const [userPrompt, setUserPrompt] = useState<string>(initialInput || "");
const [generatedQuery, setGeneratedQuery] = 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 inputEdited = useRef(false);
const {
hideFeedbackModalForLikedQueries,
userPrompt,
setUserPrompt,
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 [isSamplePromptsOpen, setIsSamplePromptsOpen] = useState<boolean>(false);
const [showDeletePopup, setShowDeletePopup] = useState<boolean>(false);
const [showFeedbackBar, setShowFeedbackBar] = useState<boolean>(false);
const [showCopyPopup, setshowCopyPopup] = useState<boolean>(false);
const [showErrorMessageBar, setShowErrorMessageBar] = useState<boolean>(false);
const sampleProps: SamplePromptsProps = {
isSamplePromptsOpen: isSamplePromptsOpen,
@@ -190,12 +170,10 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({ explorer }: Qu
userPrompt: userPrompt,
};
setShowDeletePopup(false);
useQueryCopilot.getState().refreshCorrelationId();
const response = await fetch("https://copilotorchestrater.azurewebsites.net/generateSQLQuery", {
method: "POST",
headers: {
"content-type": "application/json",
"x-ms-correlationid": useQueryCopilot.getState().correlationId,
},
body: JSON.stringify(payload),
});
@@ -210,8 +188,6 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({ explorer }: Qu
query += generateSQLQueryResponse.sql;
setQuery(query);
setGeneratedQuery(generateSQLQueryResponse.sql);
setGeneratedQueryComments(generateSQLQueryResponse.explanation);
setShowErrorMessageBar(false);
}
} else {
handleError(JSON.stringify(generateSQLQueryResponse), "copilotInternalServerError");
@@ -231,13 +207,6 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({ explorer }: Qu
};
const onExecuteQueryClick = async (): Promise<void> => {
traceStart(Action.ExecuteQueryGeneratedFromQueryCopilot, {
correlationId: useQueryCopilot.getState().correlationId,
userPrompt: userPrompt,
generatedQuery: generatedQuery,
generatedQueryComments: generatedQueryComments,
executedQuery: selectedQuery || query,
});
const queryToExecute = selectedQuery || query;
const queryIterator = querySampleDocuments(queryToExecute, {
enableCrossPartitionQuery: shouldEnableCrossPartitionKey(),
@@ -262,16 +231,8 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({ explorer }: Qu
setQueryResults(queryResults);
setErrorMessage("");
setShowErrorMessageBar(false);
traceSuccess(Action.ExecuteQueryGeneratedFromQueryCopilot, {
correlationId: useQueryCopilot.getState().correlationId,
});
} catch (error) {
const errorMessage = getErrorMessage(error);
traceFailure(Action.ExecuteQueryGeneratedFromQueryCopilot, {
correlationId: useQueryCopilot.getState().correlationId,
errorMessage: errorMessage,
});
setErrorMessage(errorMessage);
handleError(errorMessage, "executeQueryCopilotTab");
useTabs.getState().setIsQueryErrorThrown(true);
@@ -318,10 +279,9 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({ explorer }: Qu
return [executeQueryBtn, saveQueryBtn];
};
const showTeachingBubble = (): void => {
const shouldShowTeachingBubble = !inputEdited.current && userPrompt.trim() === "";
if (shouldShowTeachingBubble) {
if (!inputEdited.current) {
setTimeout(() => {
if (shouldShowTeachingBubble) {
if (!inputEdited.current) {
toggleCopilotTeachingBubbleVisible();
inputEdited.current = true;
}
@@ -335,24 +295,21 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({ explorer }: Qu
setShowCallout(false);
};
const startGenerateQueryProcess = () => {
updateHistories();
generateSQLQuery();
resetButtonState();
};
React.useEffect(() => {
useCommandBar.getState().setContextButtons(getCommandbarButtons());
}, [query, selectedQuery]);
React.useEffect(() => {
if (initialInput) {
generateSQLQuery();
}
showTeachingBubble();
useTabs.getState().setIsQueryErrorThrown(false);
}, []);
return (
<Stack className="tab-pane" style={{ padding: 24, width: "100%" }}>
<div style={isGeneratingQuery ? { height: "100%" } : { overflowY: "auto", height: "100%" }}>
<div style={{ overflowY: "auto", height: "100%" }}>
<Stack horizontal verticalAlign="center">
<Image src={CopilotIcon} />
<Text style={{ marginLeft: 8, fontWeight: 600, fontSize: 16 }}>Copilot</Text>
@@ -366,11 +323,6 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({ explorer }: Qu
inputEdited.current = true;
setShowSamplePrompts(true);
}}
onKeyDown={(e) => {
if (e.key === "Enter") {
startGenerateQueryProcess();
}
}}
style={{ lineHeight: 30 }}
styles={{ root: { width: "95%" } }}
disabled={isGeneratingQuery}
@@ -403,7 +355,11 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({ explorer }: Qu
iconProps={{ iconName: "Send" }}
disabled={isGeneratingQuery || !userPrompt.trim()}
style={{ marginLeft: 8 }}
onClick={() => startGenerateQueryProcess()}
onClick={() => {
updateHistories();
generateSQLQuery();
resetButtonState();
}}
/>
{isGeneratingQuery && <Spinner style={{ marginLeft: 8 }} />}
{showSamplePrompts && (
@@ -495,7 +451,7 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({ explorer }: Qu
}}
>
Learn about{" "}
<Link target="_blank" href="http://aka.ms/cdb-copilot-writing">
<Link target="_blank" href="">
writing effective prompts
</Link>
</Text>
@@ -509,7 +465,7 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({ explorer }: Qu
<Stack style={{ marginTop: 8, marginBottom: 24 }}>
<Text style={{ fontSize: 12 }}>
AI-generated content can have mistakes. Make sure it&apos;s accurate and appropriate before using it.{" "}
<Link href="http://aka.ms/cdb-copilot-preview-terms" target="_blank">
<Link href="" target="_blank">
Read preview terms
</Link>
{showErrorMessageBar && (
@@ -532,12 +488,7 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({ explorer }: Qu
target="#likeBtn"
onDismiss={() => {
setShowCallout(false);
submitFeedback({
generatedQuery: generatedQuery,
likeQuery: likeQuery,
description: "",
userPrompt: userPrompt,
});
submitFeedback({ generatedQuery, likeQuery, description: "", userPrompt: userPrompt });
}}
directionalHint={DirectionalHint.topCenter}
>

View File

@@ -1,225 +0,0 @@
import { FeedOptions } from "@azure/cosmos";
import { QueryCopilotSampleContainerSchema } from "Common/Constants";
import { handleError } from "Common/ErrorHandlingUtils";
import { sampleDataClient } from "Common/SampleDataClient";
import * as commonUtils from "Common/dataAccess/queryDocuments";
import DocumentId from "Explorer/Tree/DocumentId";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import { querySampleDocuments, readSampleDocument, submitFeedback } from "./QueryCopilotUtilities";
jest.mock("Explorer/Tree/DocumentId", () => {
return jest.fn().mockImplementation(() => {
return {
id: jest.fn(),
loadDocument: jest.fn(),
};
});
});
jest.mock("Utils/NotificationConsoleUtils", () => ({
logConsoleProgress: jest.fn(),
logConsoleError: jest.fn(),
}));
jest.mock("@azure/cosmos", () => ({
FeedOptions: jest.fn(),
QueryIterator: jest.fn(),
}));
jest.mock("Common/ErrorHandlingUtils", () => ({
handleError: jest.fn(),
}));
jest.mock("Utils/NotificationConsoleUtils", () => ({
logConsoleProgress: jest.fn().mockReturnValue((): void => undefined),
}));
jest.mock("Common/dataAccess/queryDocuments", () => ({
getCommonQueryOptions: jest.fn((options) => options),
}));
jest.mock("Common/SampleDataClient");
jest.mock("node-fetch");
describe("QueryCopilotUtilities", () => {
beforeEach(() => jest.clearAllMocks());
describe("submitFeedback", () => {
const payload = {
like: "like",
generatedSql: "GeneratedQuery",
userPrompt: "UserPrompt",
description: "Description",
contact: "Contact",
containerSchema: QueryCopilotSampleContainerSchema,
};
it("should call fetch with the payload with like", async () => {
const mockFetch = jest.fn().mockResolvedValueOnce({});
globalThis.fetch = mockFetch;
useQueryCopilot.getState().refreshCorrelationId();
await submitFeedback({
likeQuery: true,
generatedQuery: "GeneratedQuery",
userPrompt: "UserPrompt",
description: "Description",
contact: "Contact",
});
expect(mockFetch).toHaveBeenCalledWith(
"https://copilotorchestrater.azurewebsites.net/feedback",
expect.objectContaining({
method: "POST",
headers: {
"content-type": "application/json",
"x-ms-correlationid": useQueryCopilot.getState().correlationId,
},
})
);
const actualBody = JSON.parse(mockFetch.mock.calls[0][1].body);
expect(actualBody).toEqual(payload);
});
it("should call fetch with the payload with unlike and empty parameters", async () => {
payload.like = "dislike";
payload.description = "";
payload.contact = "";
const mockFetch = jest.fn().mockResolvedValueOnce({});
globalThis.fetch = mockFetch;
useQueryCopilot.getState().refreshCorrelationId();
await submitFeedback({
likeQuery: false,
generatedQuery: "GeneratedQuery",
userPrompt: "UserPrompt",
description: undefined,
contact: undefined,
});
expect(mockFetch).toHaveBeenCalledWith(
"https://copilotorchestrater.azurewebsites.net/feedback",
expect.objectContaining({
method: "POST",
headers: {
"content-type": "application/json",
"x-ms-correlationid": useQueryCopilot.getState().correlationId,
},
})
);
const actualBody = JSON.parse(mockFetch.mock.calls[0][1].body);
expect(actualBody).toEqual(payload);
});
it("should handle errors and call handleError", async () => {
globalThis.fetch = jest.fn().mockRejectedValueOnce(new Error("Mock error"));
await submitFeedback({
likeQuery: true,
generatedQuery: "GeneratedQuery",
userPrompt: "UserPrompt",
description: "Description",
contact: "Contact",
}).catch((error) => {
expect(error.message).toEqual("Mock error");
});
expect(handleError).toHaveBeenCalledWith(new Error("Mock error"), expect.any(String));
});
});
describe("querySampleDocuments", () => {
(sampleDataClient as jest.Mock).mockReturnValue({
database: jest.fn().mockReturnValue({
container: jest.fn().mockReturnValue({
items: {
query: jest.fn().mockReturnValue([]),
},
}),
}),
});
it("calls getCommonQueryOptions with the provided options", () => {
const query = "sample query";
const options: FeedOptions = { maxItemCount: 10 };
querySampleDocuments(query, options);
expect(commonUtils.getCommonQueryOptions).toHaveBeenCalledWith(options);
});
it("returns the result of items.query method", () => {
const query = "sample query";
const options: FeedOptions = { maxItemCount: 10 };
const mockResult = [
{ id: 1, name: "Document 1" },
{ id: 2, name: "Document 2" },
];
// Mock the items.query method to return the mockResult
(sampleDataClient().database("CopilotSampleDb").container("SampleContainer").items
.query as jest.Mock).mockReturnValue(mockResult);
const result = querySampleDocuments(query, options);
expect(result).toEqual(mockResult);
});
});
describe("readSampleDocument", () => {
it("should call the read method with the correct parameters", async () => {
(sampleDataClient as jest.Mock).mockReturnValue({
database: jest.fn().mockReturnValue({
container: jest.fn().mockReturnValue({
item: jest.fn().mockReturnValue({
read: jest.fn().mockResolvedValue({
resource: {},
}),
}),
}),
}),
});
const documentId = new DocumentId(null, "DocumentId", []);
const expectedResponse = {};
const result = await readSampleDocument(documentId);
expect(sampleDataClient).toHaveBeenCalled();
expect(sampleDataClient().database).toHaveBeenCalledWith("CopilotSampleDb");
expect(sampleDataClient().database("CopilotSampleDb").container).toHaveBeenCalledWith("SampleContainer");
expect(
sampleDataClient().database("CopilotSampleDb").container("SampleContainer").item("DocumentId", undefined).read
).toHaveBeenCalled();
expect(result).toEqual(expectedResponse);
});
it("should handle an error and re-throw it", async () => {
(sampleDataClient as jest.Mock).mockReturnValue({
database: jest.fn().mockReturnValue({
container: jest.fn().mockReturnValue({
item: jest.fn().mockReturnValue({
read: jest.fn().mockRejectedValue(new Error("Mock error")),
}),
}),
}),
});
const errorMock = new Error("Mock error");
const documentId = new DocumentId(null, "DocumentId", []);
await expect(readSampleDocument(documentId)).rejects.toStrictEqual(errorMock);
expect(sampleDataClient).toHaveBeenCalled();
expect(sampleDataClient().database).toHaveBeenCalledWith("CopilotSampleDb");
expect(sampleDataClient().database("CopilotSampleDb").container).toHaveBeenCalledWith("SampleContainer");
expect(
sampleDataClient().database("CopilotSampleDb").container("SampleContainer").item("DocumentId", undefined).read
).toHaveBeenCalled();
expect(handleError).toHaveBeenCalledWith(errorMock, "ReadDocument", expect.any(String));
});
});
});

View File

@@ -10,7 +10,6 @@ import { getPartitionKeyValue } from "Common/dataAccess/getPartitionKeyValue";
import { getCommonQueryOptions } from "Common/dataAccess/queryDocuments";
import DocumentId from "Explorer/Tree/DocumentId";
import { logConsoleProgress } from "Utils/NotificationConsoleUtils";
import { useQueryCopilot } from "hooks/useQueryCopilot";
interface FeedbackParams {
likeQuery: boolean;
@@ -36,7 +35,6 @@ export const submitFeedback = async (params: FeedbackParams): Promise<void> => {
method: "POST",
headers: {
"content-type": "application/json",
"x-ms-correlationid": useQueryCopilot.getState().correlationId,
},
body: JSON.stringify(payload),
});

View File

@@ -1,91 +1,26 @@
import { DefaultButton, IconButton } from "@fluentui/react";
import { shallow } from "enzyme";
import React from "react";
import { SamplePrompts, SamplePromptsProps } from "./SamplePrompts";
describe("Sample Prompts snapshot test", () => {
const setTextBoxMock = jest.fn();
const setIsSamplePromptsOpenMock = jest.fn();
const sampleProps: SamplePromptsProps = {
isSamplePromptsOpen: true,
setIsSamplePromptsOpen: setIsSamplePromptsOpenMock,
setTextBox: setTextBoxMock,
};
beforeEach(() => jest.clearAllMocks());
it("should render properly if isSamplePromptsOpen is true", () => {
const wrapper = shallow(<SamplePrompts sampleProps={sampleProps} />);
const sampleProps: SamplePromptsProps = {
isSamplePromptsOpen: true,
setIsSamplePromptsOpen: () => undefined,
setTextBox: () => undefined,
};
const wrapper = shallow(<SamplePrompts sampleProps={sampleProps} />);
expect(wrapper).toMatchSnapshot();
});
it("should render properly if isSamplePromptsOpen is false", () => {
sampleProps.isSamplePromptsOpen = false;
const sampleProps: SamplePromptsProps = {
isSamplePromptsOpen: false,
setIsSamplePromptsOpen: () => undefined,
setTextBox: () => undefined,
};
const wrapper = shallow(<SamplePrompts sampleProps={sampleProps} />);
expect(wrapper).toMatchSnapshot();
});
it("should call setTextBox and setIsSamplePromptsOpen(false) when a button is clicked", () => {
const wrapper = shallow(<SamplePrompts sampleProps={sampleProps} />);
wrapper.find(DefaultButton).at(0).simulate("click");
expect(setTextBoxMock).toHaveBeenCalledWith("Show me products less than 100 dolars");
expect(setIsSamplePromptsOpenMock).toHaveBeenCalledWith(false);
wrapper.find(DefaultButton).at(3).simulate("click");
expect(setTextBoxMock).toHaveBeenCalledWith(
"Write a query to return all records in this table created in the last thirty days"
);
expect(setIsSamplePromptsOpenMock).toHaveBeenCalledWith(false);
});
it("should call setIsSamplePromptsOpen(false) when the close button is clicked", () => {
const wrapper = shallow(<SamplePrompts sampleProps={sampleProps} />);
wrapper.find(IconButton).first().simulate("click");
expect(setIsSamplePromptsOpenMock).toHaveBeenCalledWith(false);
});
it("should call setTextBox and setIsSamplePromptsOpen(false) when a simple prompt button is clicked", () => {
const wrapper = shallow(<SamplePrompts sampleProps={sampleProps} />);
wrapper.find(DefaultButton).at(0).simulate("click");
expect(setTextBoxMock).toHaveBeenCalledWith("Show me products less than 100 dolars");
expect(setIsSamplePromptsOpenMock).toHaveBeenCalledWith(false);
wrapper.find(DefaultButton).at(1).simulate("click");
expect(setTextBoxMock).toHaveBeenCalledWith("Show schema");
expect(setIsSamplePromptsOpenMock).toHaveBeenCalledWith(false);
});
it("should call setTextBox and setIsSamplePromptsOpen(false) when an intermediate prompt button is clicked", () => {
const wrapper = shallow(<SamplePrompts sampleProps={sampleProps} />);
wrapper.find(DefaultButton).at(2).simulate("click");
expect(setTextBoxMock).toHaveBeenCalledWith(
"Show items with a description that contains a number between 0 and 99 inclusive."
);
expect(setIsSamplePromptsOpenMock).toHaveBeenCalledWith(false);
wrapper.find(DefaultButton).at(3).simulate("click");
expect(setTextBoxMock).toHaveBeenCalledWith(
"Write a query to return all records in this table created in the last thirty days"
);
expect(setIsSamplePromptsOpenMock).toHaveBeenCalledWith(false);
});
it("should call setTextBox and setIsSamplePromptsOpen(false) when a complex prompt button is clicked", () => {
const wrapper = shallow(<SamplePrompts sampleProps={sampleProps} />);
wrapper.find(DefaultButton).at(4).simulate("click");
expect(setTextBoxMock).toHaveBeenCalledWith("Show all the products that customer Bob has reviewed.");
expect(setIsSamplePromptsOpenMock).toHaveBeenCalledWith(false);
wrapper.find(DefaultButton).at(5).simulate("click");
expect(setTextBoxMock).toHaveBeenCalledWith("Which computers are more than 300 dollars and less than 400 dollars?");
expect(setIsSamplePromptsOpenMock).toHaveBeenCalledWith(false);
});
});

View File

@@ -54,7 +54,6 @@ exports[`Query copilot tab snapshot test should render with initial input 1`] =
id="naturalLanguageInput"
onChange={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
style={
Object {
"lineHeight": 30,
@@ -67,10 +66,10 @@ exports[`Query copilot tab snapshot test should render with initial input 1`] =
},
}
}
value=""
value="Write a query to return all records in this table"
/>
<CustomizedIconButton
disabled={true}
disabled={false}
iconProps={
Object {
"iconName": "Send",
@@ -102,7 +101,7 @@ exports[`Query copilot tab snapshot test should render with initial input 1`] =
AI-generated content can have mistakes. Make sure it's accurate and appropriate before using it.
<StyledLinkBase
href="http://aka.ms/cdb-copilot-preview-terms"
href=""
target="_blank"
>
Read preview terms

View File

@@ -97,12 +97,6 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
() => this.setState({}),
(state) => state.showResetPasswordBubble
),
},
{
dispose: useDatabases.subscribe(
() => this.setState({}),
(state) => state.sampleDataResourceTokenCollection
),
}
);
}
@@ -113,11 +107,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
};
private getSplashScreenButtons = (): JSX.Element => {
if (
useDatabases.getState().sampleDataResourceTokenCollection &&
userContext.features.enableCopilot &&
userContext.apiType === "SQL"
) {
if (userContext.features.enableCopilot && userContext.apiType === "SQL") {
return (
<Stack style={{ width: "66%", cursor: "pointer", margin: "40px auto" }} tokens={{ childrenGap: 16 }}>
<Stack horizontal tokens={{ childrenGap: 16 }}>
@@ -147,10 +137,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
description={
"Copilot is your AI buddy that helps you write Azure Cosmos DB queries like a pro. Try it using our sample data set now!"
}
onClick={() => {
useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot);
traceOpen(Action.OpenQueryCopilotFromSplashScreen, { apiType: userContext.apiType });
}}
onClick={() => useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot)}
/>
<SplashScreenButton
imgSrc={ConnectIcon}
@@ -259,9 +246,8 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
<form className="connectExplorerFormContainer">
<div className="splashScreenContainer">
<div className="splashScreen">
<h1
<div
className="title"
role="heading"
aria-label={
userContext.apiType === "Postgres"
? "Welcome to Azure Cosmos DB for PostgreSQL"
@@ -272,7 +258,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
? "Welcome to Azure Cosmos DB for PostgreSQL"
: "Welcome to Azure Cosmos DB"}
<FeaturePanelLauncher />
</h1>
</div>
<div className="subtitle">
{userContext.apiType === "Postgres"
? "Get started with our sample datasets, documentation, and additional tools."

View File

@@ -9,7 +9,6 @@ import { ConnectTab } from "Explorer/Tabs/ConnectTab";
import { PostgresConnectTab } from "Explorer/Tabs/PostgresConnectTab";
import { QuickstartTab } from "Explorer/Tabs/QuickstartTab";
import { userContext } from "UserContext";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import { useTeachingBubble } from "hooks/useTeachingBubble";
import ko from "knockout";
import React, { MutableRefObject, useEffect, useRef, useState } from "react";
@@ -159,7 +158,6 @@ const CloseButton = ({
onClick={(event: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
event.stopPropagation();
tab ? tab.onCloseTabButtonClick() : useTabs.getState().closeReactTab(tabKind);
tabKind === ReactTabKind.QueryCopilot && useQueryCopilot.getState().resetQueryCopilotStates();
}}
tabIndex={active ? 0 : undefined}
onKeyPress={({ nativeEvent: e }) => tab.onKeyPressClose(undefined, e)}
@@ -253,7 +251,7 @@ const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): J
case ReactTabKind.Quickstart:
return <QuickstartTab explorer={explorer} />;
case ReactTabKind.QueryCopilot:
return <QueryCopilotTab explorer={explorer} />;
return <QueryCopilotTab initialInput={useTabs.getState().queryCopilotTabInitialInput} explorer={explorer} />;
default:
throw Error(`Unsupported tab kind ${ReactTabKind[activeReactTab]}`);
}

View File

@@ -28,9 +28,8 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
public children: ko.ObservableArray<ViewModels.TreeNode>;
public selectedSubnodeKind: ko.Observable<ViewModels.CollectionTabKind>;
public isCollectionExpanded: ko.Observable<boolean>;
public isSampleCollection?: boolean;
constructor(container: Explorer, databaseId: string, data: DataModels.Collection, isSampleCollection?: boolean) {
constructor(container: Explorer, databaseId: string, data: DataModels.Collection) {
this.nodeKind = "Collection";
this.container = container;
this.databaseId = databaseId;
@@ -43,7 +42,6 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
this.children = ko.observableArray<ViewModels.TreeNode>([]);
this.selectedSubnodeKind = ko.observable<ViewModels.CollectionTabKind>();
this.isCollectionExpanded = ko.observable<boolean>(true);
this.isSampleCollection = isSampleCollection;
}
public expandCollection(): void {

View File

@@ -17,7 +17,7 @@ export const SampleDataTree = ({
const buildSampleDataTree = (): TreeNode => {
const updatedSampleTree: TreeNode = {
label: sampleDataResourceTokenCollection.databaseId,
isExpanded: false,
isExpanded: true,
iconSrc: CosmosDBIcon,
className: "databaseHeader",
children: [
@@ -47,7 +47,6 @@ export const SampleDataTree = ({
{
label: "Items",
onClick: () => sampleDataResourceTokenCollection.onDocumentDBDocumentsClick(),
contextMenu: ResourceTreeContextMenuButtonFactory.createSampleCollectionContextMenuButton(),
isSelected: () =>
useSelectedNode
.getState()

View File

@@ -66,12 +66,11 @@ export const useSelectedNode: UseStore<SelectedNodeState> = create((set, get) =>
return useNotebook.getState().connectionInfo?.status === ConnectionStatusType.Connected;
},
isQueryCopilotCollectionSelected: (): boolean => {
const selectedNode = get().selectedNode as ViewModels.CollectionBase;
const selectedNode = get().selectedNode;
if (
selectedNode &&
selectedNode.isSampleCollection &&
selectedNode.id() === QueryCopilotSampleContainerId &&
selectedNode.databaseId === QueryCopilotSampleDatabaseId
(selectedNode as ViewModels.Collection)?.databaseId === QueryCopilotSampleDatabaseId
) {
return true;
}

View File

@@ -36,7 +36,6 @@ export type Features = {
readonly enableLegacyMongoShellV2Debug: boolean;
readonly loadLegacyMongoShellFromBE: boolean;
readonly enableCopilot: boolean;
readonly enableNPSSurvey: boolean;
// can be set via both flight and feature flag
autoscaleDefault: boolean;
@@ -105,7 +104,6 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
enableLegacyMongoShellV2Debug: "true" === get("enablelegacymongoshellv2debug"),
loadLegacyMongoShellFromBE: "true" === get("loadlegacymongoshellfrombe"),
enableCopilot: "true" === get("enablecopilot"),
enableNPSSurvey: "true" === get("enablenpssurvey"),
};
}

View File

@@ -131,9 +131,6 @@ export enum Action {
LaunchUITour,
CancelUITour,
CompleteUITour,
OpenQueryCopilotFromSplashScreen,
OpenQueryCopilotFromNewQuery,
ExecuteQueryGeneratedFromQueryCopilot,
}
export const ActionModifiers = {

View File

@@ -91,7 +91,7 @@ const userContext: UserContext = {
collectionCreationDefaults: CollectionCreationDefaults,
};
export function isAccountNewerThanThresholdInMs(createdAt: string, threshold: number) {
function isAccountNewerThanThresholdInMs(createdAt: string, threshold: number) {
let createdAtMs: number = Date.parse(createdAt);
if (isNaN(createdAtMs)) {
createdAtMs = 0;

View File

@@ -38,7 +38,7 @@ function validateEndpointInternal(
return valid;
}
export const defaultAllowedArmEndpoints: ReadonlyArray<string> = [
export const allowedArmEndpoints: ReadonlyArray<string> = [
"https://management.azure.com",
"https://management.usgovcloudapi.net",
"https://management.chinacloudapi.cn",
@@ -46,7 +46,7 @@ export const defaultAllowedArmEndpoints: ReadonlyArray<string> = [
export const allowedAadEndpoints: ReadonlyArray<string> = ["https://login.microsoftonline.com/"];
export const defaultAllowedBackendEndpoints: ReadonlyArray<string> = [
export const allowedBackendEndpoints: ReadonlyArray<string> = [
"https://main.documentdb.ext.azure.com",
"https://main.documentdb.ext.azure.cn",
"https://main.documentdb.ext.azure.us",

View File

@@ -1,6 +1,3 @@
import { MinimalQueryIterator } from "Common/IteratorUtilities";
import { QueryResults } from "Contracts/ViewModels";
import { guid } from "Explorer/Tables/Utilities";
import create, { UseStore } from "zustand";
interface QueryCopilotState {
@@ -9,127 +6,20 @@ interface QueryCopilotState {
userPrompt: string;
showFeedbackModal: boolean;
hideFeedbackModalForLikedQueries: boolean;
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;
closeFeedbackModal: () => void;
setHideFeedbackModalForLikedQueries: (hideFeedbackModalForLikedQueries: boolean) => 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;
}
type QueryCopilotStore = UseStore<QueryCopilotState>;
export const useQueryCopilot: QueryCopilotStore = create((set) => ({
export const useQueryCopilot: UseStore<QueryCopilotState> = create((set) => ({
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: "",
openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) =>
set({ generatedQuery, likeQuery, userPrompt, showFeedbackModal: true }),
closeFeedbackModal: () => set({ generatedQuery: "", likeQuery: false, userPrompt: "", showFeedbackModal: false }),
setHideFeedbackModalForLikedQueries: (hideFeedbackModalForLikedQueries: boolean) =>
set({ hideFeedbackModalForLikedQueries }),
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: "",
}));
},
}));