Compare commits

..

1 Commits

Author SHA1 Message Date
Victor Meng
d1d62d34d1 Turn on carousel to insert sample data 2023-07-11 16:27:11 -07:00
39 changed files with 566 additions and 2848 deletions

20
package-lock.json generated
View File

@@ -164,7 +164,6 @@
"jest": "26.6.3", "jest": "26.6.3",
"jest-canvas-mock": "2.3.1", "jest-canvas-mock": "2.3.1",
"jest-playwright-preset": "1.5.1", "jest-playwright-preset": "1.5.1",
"jest-react-hooks-shallow": "1.5.1",
"jest-trx-results-processor": "0.0.7", "jest-trx-results-processor": "0.0.7",
"less": "3.8.1", "less": "3.8.1",
"less-loader": "4.1.0", "less-loader": "4.1.0",
@@ -19579,16 +19578,6 @@
"node": ">=8.9.0" "node": ">=8.9.0"
} }
}, },
"node_modules/jest-react-hooks-shallow": {
"version": "1.5.1",
"resolved": "https://msazure.pkgs.visualstudio.com/_packaging/AzurePortal/npm/registry/jest-react-hooks-shallow/-/jest-react-hooks-shallow-1.5.1.tgz",
"integrity": "sha1-1SI4EGG7nf9WcfhLBT0LPxy+N/k=",
"dev": true,
"license": "ISC",
"dependencies": {
"react": "^16.8.0"
}
},
"node_modules/jest-regex-util": { "node_modules/jest-regex-util": {
"version": "24.9.0", "version": "24.9.0",
"resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz",
@@ -48619,15 +48608,6 @@
} }
} }
}, },
"jest-react-hooks-shallow": {
"version": "1.5.1",
"resolved": "https://msazure.pkgs.visualstudio.com/_packaging/AzurePortal/npm/registry/jest-react-hooks-shallow/-/jest-react-hooks-shallow-1.5.1.tgz",
"integrity": "sha1-1SI4EGG7nf9WcfhLBT0LPxy+N/k=",
"dev": true,
"requires": {
"react": "^16.8.0"
}
},
"jest-regex-util": { "jest-regex-util": {
"version": "24.9.0", "version": "24.9.0",
"resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz",

View File

@@ -160,7 +160,6 @@
"jest": "26.6.3", "jest": "26.6.3",
"jest-canvas-mock": "2.3.1", "jest-canvas-mock": "2.3.1",
"jest-playwright-preset": "1.5.1", "jest-playwright-preset": "1.5.1",
"jest-react-hooks-shallow": "1.5.1",
"jest-trx-results-processor": "0.0.7", "jest-trx-results-processor": "0.0.7",
"less": "3.8.1", "less": "3.8.1",
"less-loader": "4.1.0", "less-loader": "4.1.0",

View File

@@ -435,89 +435,24 @@ export const QueryCopilotSampleContainerId = "SampleContainer";
export const QueryCopilotSampleContainerSchema = { export const QueryCopilotSampleContainerSchema = {
product: { product: {
sampleData: { sampleData: {
id: "c415e70f-9bf5-4cda-aebe-a290cb8b94c2", id: "de6fadec-0384-43c8-93ea-16c0170b5460",
name: "Amazing Phone 3000 (Black)", name: "Premium Phone Mini (Red)",
price: 223.33, price: 652.04,
category: "Electronics", category: "Electronics",
description: 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", "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: 84, stock: 74,
countryOfOrigin: "USA", countryOfOrigin: "Mexico",
firstAvailable: "2018-09-07 19:41:44", firstAvailable: "2018-07-07 17:42:28",
priceHistory: [238.68, 234.7, 221.49, 205.88, 220.15], priceHistory: [592.81],
customerRatings: [ customerRatings: [
{ { username: "dannyhowell", stars: 1, date: "2022-03-12 17:01:23", verifiedUser: true },
username: "steven66", { username: "lindsay26", stars: 1, date: "2022-12-29 07:18:20", verifiedUser: false },
firstName: "Carol", { username: "smithmiguel", stars: 3, date: "2022-09-08 11:43:27", verifiedUser: false },
gender: "female", { username: "julie07", stars: 3, date: "2021-03-14 23:54:10", verifiedUser: true },
lastName: "Shelton", { username: "kelly93", stars: 3, date: "2022-11-05 12:45:43", verifiedUser: false },
age: "25-35", { username: "katherinereynolds", stars: 2, date: "2022-09-14 11:49:36", verifiedUser: false },
area: "suburban", { username: "chandlerkenneth", stars: 1, date: "2022-01-14 12:14:43", verifiedUser: true },
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,
},
], ],
rareProperty: true, rareProperty: true,
}, },
@@ -559,24 +494,6 @@ export const QueryCopilotSampleContainerSchema = {
username: { username: {
type: "string", type: "string",
}, },
firstName: {
type: "string",
},
gender: {
type: "string",
},
lastName: {
type: "string",
},
age: {
type: "string",
},
area: {
type: "string",
},
address: {
type: "string",
},
stars: { stars: {
type: "number", type: "number",
}, },

View File

@@ -25,7 +25,6 @@ export class AccordionComponent extends React.Component<AccordionComponentProps>
export interface AccordionItemComponentProps { export interface AccordionItemComponentProps {
title: string; title: string;
isExpanded?: boolean; isExpanded?: boolean;
containerStyles?: React.CSSProperties;
styles?: React.CSSProperties; styles?: React.CSSProperties;
} }
@@ -55,9 +54,9 @@ export class AccordionItemComponent extends React.Component<AccordionItemCompone
} }
public render(): JSX.Element { public render(): JSX.Element {
const { containerStyles, styles } = this.props; const { styles } = this.props;
return ( return (
<div className="accordionItemContainer" style={{ ...containerStyles }}> <div className="accordionItemContainer">
<div className="accordionItemHeader" onClick={this.onHeaderClick} onKeyPress={this.onHeaderKeyPress}> <div className="accordionItemHeader" onClick={this.onHeaderClick} onKeyPress={this.onHeaderKeyPress}>
{this.renderCollapseExpandIcon()} {this.renderCollapseExpandIcon()}
{this.props.title} {this.props.title}

View File

@@ -31,13 +31,6 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
public componentDidMount(): void { public componentDidMount(): void {
this.createEditor(this.configureEditor.bind(this)); this.createEditor(this.configureEditor.bind(this));
setTimeout(() => {
const suggestionWidget = this.editor?.getDomNode()?.querySelector(".suggest-widget") as HTMLElement;
if (suggestionWidget) {
suggestionWidget.style.display = "none";
}
}, 100);
} }
public componentDidUpdate(previous: EditorReactProps) { public componentDidUpdate(previous: EditorReactProps) {

View File

@@ -1291,7 +1291,7 @@ export default class Explorer {
return; return;
} }
const sampleDataResourceTokenCollection = new ResourceTokenCollection(this, databaseId, collection, true); const sampleDataResourceTokenCollection = new ResourceTokenCollection(this, databaseId, collection);
useDatabases.setState({ sampleDataResourceTokenCollection }); useDatabases.setState({ sampleDataResourceTokenCollection });
} }
} }

View File

@@ -268,6 +268,7 @@ function createNewCollectionGroup(container: Explorer): CommandButtonComponentPr
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
id: "createNewContainerCommandButton", id: "createNewContainerCommandButton",
disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(),
}; };
} }
@@ -313,6 +314,7 @@ function createNewDatabase(container: Explorer): CommandButtonComponentProps {
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(),
}; };
} }
@@ -491,7 +493,7 @@ function createOpenTerminalButton(container: Explorer): CommandButtonComponentPr
onCommandClick: () => container.openNotebookTerminal(ViewModels.TerminalKind.Default), onCommandClick: () => container.openNotebookTerminal(ViewModels.TerminalKind.Default),
commandButtonLabel: label, commandButtonLabel: label,
hasPopup: false, hasPopup: false,
disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(), disabled: false,
ariaLabel: label, ariaLabel: label,
}; };
} }

View File

@@ -2,13 +2,13 @@ import { Checkbox, Dropdown, IDropdownOption, Link, Stack, Text, TextField } fro
import * as Constants from "Common/Constants"; import * as Constants from "Common/Constants";
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils"; import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
import { InfoTooltip } from "Common/Tooltip/InfoTooltip"; import { InfoTooltip } from "Common/Tooltip/InfoTooltip";
import { useSidePanel } from "hooks/useSidePanel";
import React, { FunctionComponent, useState } from "react";
import * as SharedConstants from "Shared/Constants"; import * as SharedConstants from "Shared/Constants";
import { Action } from "Shared/Telemetry/TelemetryConstants"; import { Action } from "Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
import { userContext } from "UserContext"; import { userContext } from "UserContext";
import { isServerlessAccount } from "Utils/CapabilityUtils"; import { isServerlessAccount } from "Utils/CapabilityUtils";
import { useSidePanel } from "hooks/useSidePanel";
import React, { FunctionComponent, useState } from "react";
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput"; import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { CassandraAPIDataClient } from "../../Tables/TableDataClient"; import { CassandraAPIDataClient } from "../../Tables/TableDataClient";
@@ -291,7 +291,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
styles={getTextFieldStyles({ fontSize: 12, width: 150 })} styles={getTextFieldStyles({ fontSize: 12, width: 150 })}
aria-required="true" aria-required="true"
required={true} required={true}
ariaLabel="addCollection-table Id Create table" ariaLabel="addCollection-tableId"
autoComplete="off" autoComplete="off"
pattern="[^/?#\\]*[^/?# \\]" pattern="[^/?#\\]*[^/?# \\]"
title="May not end with space nor contain characters '\' '/' '#' '?'" title="May not end with space nor contain characters '\' '/' '#' '?'"

View File

@@ -1,18 +1,18 @@
import { Text, TextField } from "@fluentui/react"; import { Text, TextField } from "@fluentui/react";
import { Areas } from "Common/Constants"; import { Areas } from "Common/Constants";
import { deleteCollection } from "Common/dataAccess/deleteCollection";
import DeleteFeedback from "Common/DeleteFeedback"; import DeleteFeedback from "Common/DeleteFeedback";
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils"; import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
import { deleteCollection } from "Common/dataAccess/deleteCollection";
import { Collection } from "Contracts/ViewModels"; import { Collection } from "Contracts/ViewModels";
import { useSidePanel } from "hooks/useSidePanel";
import { useTabs } from "hooks/useTabs";
import React, { FunctionComponent, useState } from "react";
import { DefaultExperienceUtility } from "Shared/DefaultExperienceUtility"; import { DefaultExperienceUtility } from "Shared/DefaultExperienceUtility";
import { Action, ActionModifiers } from "Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
import { userContext } from "UserContext"; import { userContext } from "UserContext";
import { getCollectionName } from "Utils/APITypeUtils"; import { getCollectionName } from "Utils/APITypeUtils";
import * as NotificationConsoleUtils from "Utils/NotificationConsoleUtils"; import * as NotificationConsoleUtils from "Utils/NotificationConsoleUtils";
import { useSidePanel } from "hooks/useSidePanel";
import { useTabs } from "hooks/useTabs";
import React, { FunctionComponent, useState } from "react";
import { useDatabases } from "../../useDatabases"; import { useDatabases } from "../../useDatabases";
import { useSelectedNode } from "../../useSelectedNode"; import { useSelectedNode } from "../../useSelectedNode";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
@@ -126,7 +126,6 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
setInputCollectionName(newInput); setInputCollectionName(newInput);
}} }}
ariaLabel={confirmContainer} ariaLabel={confirmContainer}
required
/> />
</div> </div>
{shouldRecordFeedback() && ( {shouldRecordFeedback() && (

View File

@@ -44,7 +44,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
autoFocus={true} autoFocus={true}
id="confirmCollectionId" id="confirmCollectionId"
onChange={[Function]} onChange={[Function]}
required={true}
styles={ styles={
Object { Object {
"fieldGroup": Object { "fieldGroup": Object {
@@ -60,7 +59,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
deferredValidationTime={200} deferredValidationTime={200}
id="confirmCollectionId" id="confirmCollectionId"
onChange={[Function]} onChange={[Function]}
required={true}
resizable={true} resizable={true}
styles={[Function]} styles={[Function]}
theme={ theme={
@@ -340,7 +338,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
value="" value=""
> >
<div <div
className="ms-TextField is-required root-55" className="ms-TextField root-55"
> >
<div <div
className="ms-TextField-wrapper" className="ms-TextField-wrapper"
@@ -358,7 +356,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
onChange={[Function]} onChange={[Function]}
onFocus={[Function]} onFocus={[Function]}
onInput={[Function]} onInput={[Function]}
required={true}
type="text" type="text"
value="" value=""
/> />

View File

@@ -140,7 +140,6 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
setDatabaseInput(newInput); setDatabaseInput(newInput);
}} }}
ariaLabel={confirmDatabase} ariaLabel={confirmDatabase}
required
/> />
</div> </div>
{isLastNonEmptyDatabase() && ( {isLastNonEmptyDatabase() && (

View File

@@ -1,5 +1,4 @@
import { IPanelProps, IRenderFunction, Panel, PanelType } from "@fluentui/react"; import { IPanelProps, IRenderFunction, Panel, PanelType } from "@fluentui/react";
import { useSelectedNode } from "Explorer/useSelectedNode";
import * as React from "react"; import * as React from "react";
import { useNotificationConsole } from "../../hooks/useNotificationConsole"; import { useNotificationConsole } from "../../hooks/useNotificationConsole";
import { useSidePanel } from "../../hooks/useSidePanel"; import { useSidePanel } from "../../hooks/useSidePanel";
@@ -78,20 +77,6 @@ export class PanelContainerComponent extends React.Component<PanelContainerProps
} }
private onDissmiss = (ev?: KeyboardEvent | React.SyntheticEvent<HTMLElement>): void => { private onDissmiss = (ev?: KeyboardEvent | React.SyntheticEvent<HTMLElement>): void => {
const collection = useSelectedNode.getState().findSelectedCollection();
if (collection) {
const targetElementDataTest: string | undefined = collection.id();
const targetElement: HTMLElement | null = document.querySelector(`[data-test="${targetElementDataTest}"]`);
if (targetElement) {
setTimeout(() => {
const moreButton: HTMLElement | null = targetElement.querySelector('[name="More"]');
if (moreButton) {
moreButton.focus();
}
}, 100);
clearTimeout;
}
}
if (ev && (ev.target as HTMLElement).id === "notificationConsoleHeader") { if (ev && (ev.target as HTMLElement).id === "notificationConsoleHeader") {
ev.preventDefault(); ev.preventDefault();
} else { } else {

View File

@@ -371,7 +371,6 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
autoFocus={true} autoFocus={true}
id="confirmDatabaseId" id="confirmDatabaseId"
onChange={[Function]} onChange={[Function]}
required={true}
styles={ styles={
Object { Object {
"fieldGroup": Object { "fieldGroup": Object {
@@ -386,7 +385,6 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
deferredValidationTime={200} deferredValidationTime={200}
id="confirmDatabaseId" id="confirmDatabaseId"
onChange={[Function]} onChange={[Function]}
required={true}
resizable={true} resizable={true}
styles={[Function]} styles={[Function]}
theme={ theme={
@@ -665,7 +663,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
validateOnLoad={true} validateOnLoad={true}
> >
<div <div
className="ms-TextField is-required root-58" className="ms-TextField root-58"
> >
<div <div
className="ms-TextField-wrapper" className="ms-TextField-wrapper"
@@ -683,7 +681,6 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
onChange={[Function]} onChange={[Function]}
onFocus={[Function]} onFocus={[Function]}
onInput={[Function]} onInput={[Function]}
required={true}
type="text" type="text"
value="" value=""
/> />

View File

@@ -13,11 +13,8 @@ import {
} from "@fluentui/react"; } from "@fluentui/react";
import { QueryCopilotSampleDatabaseId, StyleConstants } from "Common/Constants"; import { QueryCopilotSampleDatabaseId, StyleConstants } from "Common/Constants";
import { handleError } from "Common/ErrorHandlingUtils"; import { handleError } from "Common/ErrorHandlingUtils";
import { createCollection } from "Common/dataAccess/createCollection";
import * as DataModels from "Contracts/DataModels";
import { ContainerSampleGenerator } from "Explorer/DataSamples/ContainerSampleGenerator"; import { ContainerSampleGenerator } from "Explorer/DataSamples/ContainerSampleGenerator";
import Explorer from "Explorer/Explorer"; import Explorer from "Explorer/Explorer";
import { AllPropertiesIndexed } from "Explorer/Panes/AddCollectionPanel";
import { PromptCard } from "Explorer/QueryCopilot/PromptCard"; import { PromptCard } from "Explorer/QueryCopilot/PromptCard";
import { useDatabases } from "Explorer/useDatabases"; import { useDatabases } from "Explorer/useDatabases";
import { useCarousel } from "hooks/useCarousel"; import { useCarousel } from "hooks/useCarousel";
@@ -78,30 +75,27 @@ export const QueryCopilotCarousel: React.FC<QueryCopilotCarouselProps> = ({
}; };
const createSampleDatabase = async (): Promise<void> => { const createSampleDatabase = async (): Promise<void> => {
const database = useDatabases.getState().findDatabaseWithId(QueryCopilotSampleDatabaseId); // const database = useDatabases.getState().findDatabaseWithId(QueryCopilotSampleDatabaseId);
if (database) {
return;
}
try { try {
setIsCreatingDatabase(true); // setIsCreatingDatabase(true);
setSpinnerText("Setting up your database..."); // setSpinnerText("Setting up your database...");
const params: DataModels.CreateCollectionParams = { // const params: DataModels.CreateCollectionParams = {
createNewDatabase: true, // createNewDatabase: false,
collectionId: "SampleContainer", // collectionId: "SampleContainer",
databaseId: QueryCopilotSampleDatabaseId, // databaseId: QueryCopilotSampleDatabaseId,
databaseLevelThroughput: true, // databaseLevelThroughput: true,
autoPilotMaxThroughput: 1000, // autoPilotMaxThroughput: 1000,
offerThroughput: undefined, // offerThroughput: undefined,
indexingPolicy: AllPropertiesIndexed, // indexingPolicy: AllPropertiesIndexed,
partitionKey: { // partitionKey: {
paths: ["/categoryId"], // paths: ["/categoryId"],
kind: "Hash", // kind: "Hash",
version: 2, // version: 2,
}, // },
}; // };
await createCollection(params); // await createCollection(params);
await explorer.refreshAllDatabases(); // await explorer.refreshAllDatabases();
const database = useDatabases.getState().findDatabaseWithId(QueryCopilotSampleDatabaseId); const database = useDatabases.getState().findDatabaseWithId(QueryCopilotSampleDatabaseId);
await database.loadCollections(); await database.loadCollections();
const collection = database.findCollectionWithId("SampleContainer"); const collection = database.findCollectionWithId("SampleContainer");

View File

@@ -1,122 +0,0 @@
import { Checkbox, ChoiceGroup, DefaultButton, IconButton, PrimaryButton, TextField } from "@fluentui/react";
import { QueryCopilotFeedbackModal } from "Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal";
import { submitFeedback } from "Explorer/QueryCopilot/QueryCopilotUtilities";
import { getUserEmail } from "Utils/UserUtils";
import { shallow } from "enzyme";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import React from "react";
jest.mock("Utils/UserUtils");
(getUserEmail as jest.Mock).mockResolvedValue("test@email.com");
jest.mock("Explorer/QueryCopilot/QueryCopilotUtilities");
submitFeedback as jest.Mock;
describe("Query Copilot Feedback Modal snapshot test", () => {
beforeEach(() => {
jest.clearAllMocks();
});
it("shoud render and match snapshot", () => {
useQueryCopilot.getState().openFeedbackModal("test query", false, "test prompt");
const wrapper = shallow(<QueryCopilotFeedbackModal />);
expect(wrapper.props().isOpen).toBeTruthy();
expect(wrapper).toMatchSnapshot();
});
it("should close on cancel click", () => {
const wrapper = shallow(<QueryCopilotFeedbackModal />);
const cancelButton = wrapper.find(IconButton);
cancelButton.simulate("click");
wrapper.setProps({});
expect(wrapper.props().isOpen).toBeFalsy();
expect(wrapper).toMatchSnapshot();
});
it("should get user unput", () => {
const wrapper = shallow(<QueryCopilotFeedbackModal />);
const testUserInput = "test user input";
const userInput = wrapper.find(TextField).first();
userInput.simulate("change", {}, testUserInput);
expect(wrapper.find(TextField).first().props().value).toEqual(testUserInput);
expect(wrapper).toMatchSnapshot();
});
it("should record user contact choice no", () => {
const wrapper = shallow(<QueryCopilotFeedbackModal />);
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(<QueryCopilotFeedbackModal />);
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(<QueryCopilotFeedbackModal />);
const dontShowAgain = wrapper.find(Checkbox);
expect(dontShowAgain).toHaveLength(0);
expect(wrapper).toMatchSnapshot();
});
it("should render dont show again button and check it ", () => {
useQueryCopilot.getState().openFeedbackModal("test query", true, "test prompt");
const wrapper = shallow(<QueryCopilotFeedbackModal />);
const dontShowAgain = wrapper.find(Checkbox);
dontShowAgain.simulate("change", {}, true);
expect(wrapper.find(Checkbox)).toHaveLength(1);
expect(wrapper.find(Checkbox).first().props().checked).toBeTruthy();
expect(wrapper).toMatchSnapshot();
});
it("should cancel submission", () => {
const wrapper = shallow(<QueryCopilotFeedbackModal />);
const cancelButton = wrapper.find(DefaultButton);
cancelButton.simulate("click");
wrapper.setProps({});
expect(wrapper.props().isOpen).toBeFalsy();
expect(wrapper).toMatchSnapshot();
});
it("should submit submission", () => {
const wrapper = shallow(<QueryCopilotFeedbackModal />);
const submitButton = wrapper.find(PrimaryButton);
submitButton.simulate("click");
wrapper.setProps({});
expect(submitFeedback).toHaveBeenCalledTimes(1);
expect(submitFeedback).toHaveBeenCalledWith({
likeQuery: false,
generatedQuery: "",
userPrompt: "",
description: "",
contact: getUserEmail(),
});
expect(wrapper.props().isOpen).toBeFalsy();
expect(wrapper).toMatchSnapshot();
});
});

View File

@@ -1,32 +0,0 @@
import { shallow } from "enzyme";
import { withHooks } from "jest-react-hooks-shallow";
import React from "react";
import { WelcomeModal } from "./WelcomeModal";
describe("Query Copilot Welcome Modal snapshot test", () => {
it("should render when isOpen is true", () => {
withHooks(() => {
const spy = jest.spyOn(localStorage, "setItem");
spy.mockClear();
const wrapper = shallow(<WelcomeModal visible={true} />);
expect(wrapper.props().children.props.isOpen).toBeTruthy();
expect(wrapper).toMatchSnapshot();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenLastCalledWith("hideWelcomeModal", "true");
});
});
it("should not render when isOpen is false", () => {
withHooks(() => {
const spy = jest.spyOn(localStorage, "setItem");
spy.mockClear();
const wrapper = shallow(<WelcomeModal visible={false} />);
expect(wrapper.props().children.props.isOpen).toBeFalsy();
expect(spy).not.toHaveBeenCalled();
expect(spy.mock.instances.length).toBe(0);
});
});
});

View File

@@ -1,196 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Query Copilot Welcome Modal snapshot test should render when isOpen is true 1`] = `
<Fragment>
<Modal
isBlocking={false}
isOpen={true}
onDismiss={[Function]}
>
<Stack
className="modalContentPadding"
>
<Stack
horizontal={true}
>
<Stack
grow={4}
horizontal={true}
horizontalAlign="end"
>
<StackItem>
<Image
src=""
/>
</StackItem>
</Stack>
<Stack
className="exitPadding"
grow={1}
horizontal={true}
horizontalAlign="end"
verticalAlign="start"
>
<StackItem
className="previewMargin"
>
<Text
className="preview"
>
Preview
</Text>
</StackItem>
<StackItem>
<CustomizedIconButton
ariaLabel="Exit"
className="exitIcon"
iconProps={
Object {
"iconName": "Cancel",
}
}
onClick={[Function]}
title="Exit"
/>
</StackItem>
</Stack>
</Stack>
<Stack
horizontalAlign="center"
>
<StackItem
align="center"
>
<Text
className="title bold"
>
Welcome to Copilot in CosmosDB
</Text>
</StackItem>
<StackItem
align="center"
className="text"
>
<Stack
horizontal={true}
>
<StackItem
align="start"
className="imageTextPadding"
>
<Image
src=""
/>
</StackItem>
<StackItem
align="start"
>
<Text
className="bold"
>
Let copilot do the work for you
<br />
</Text>
</StackItem>
</Stack>
<Text>
Ask Copilot to generate a query by describing the query in your words.
<br />
<StyledLinkBase
href=""
>
Learn more
</StyledLinkBase>
</Text>
</StackItem>
<StackItem
align="center"
className="text"
>
<Stack
horizontal={true}
>
<StackItem
align="start"
className="imageTextPadding"
>
<Image
src=""
/>
</StackItem>
<StackItem
align="start"
>
<Text
className="bold"
>
Use your judgement
<br />
</Text>
</StackItem>
</Stack>
<Text>
AI-generated content can have mistakes. Make sure its accurate and appropriate before using it.
<br />
<StyledLinkBase
href=""
>
Read preview terms
</StyledLinkBase>
</Text>
</StackItem>
<StackItem
align="center"
className="text"
>
<Stack
horizontal={true}
>
<StackItem
align="start"
className="imageTextPadding"
>
<Image
src=""
/>
</StackItem>
<StackItem
align="start"
>
<Text
className="bold"
>
Copilot currently works only a sample database
<br />
</Text>
</StackItem>
</Stack>
<Text>
Copilot is setup on a sample database we have configured for you at no cost
<br />
<StyledLinkBase
href=""
>
Learn more
</StyledLinkBase>
</Text>
</StackItem>
</Stack>
<Stack
className="buttonPadding"
>
<StackItem
align="center"
>
<CustomizedPrimaryButton
className="tryButton"
onClick={[Function]}
>
Try Copilot
</CustomizedPrimaryButton>
</StackItem>
</Stack>
</Stack>
</Modal>
</Fragment>
`;

View File

@@ -1,48 +1,11 @@
import { IconButton } from "@fluentui/react";
import { shallow } from "enzyme"; import { shallow } from "enzyme";
import React from "react"; import React from "react";
import { any } from "underscore";
import { CopyPopup } from "./CopyPopup"; import { CopyPopup } from "./CopyPopup";
describe("Copy Popup snapshot test", () => { describe("Copy Popup snapshot test", () => {
const setShowCopyPopupMock = jest.fn();
it("should render when showCopyPopup is true", () => { it("should render when showCopyPopup is true", () => {
const wrapper = shallow(<CopyPopup showCopyPopup={true} setShowCopyPopup={setShowCopyPopupMock} />); const wrapper = shallow(<CopyPopup showCopyPopup={true} setShowCopyPopup={() => any} />);
expect(wrapper.exists()).toBe(true);
expect(wrapper.prop("setShowCopyPopup")).toBeUndefined();
expect(wrapper).toMatchSnapshot(); 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 React from "react";
import { any } from "underscore";
import { DeletePopup } from "./DeletePopup"; import { DeletePopup } from "./DeletePopup";
describe("Delete Popup snapshot test", () => { 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", () => { it("should render when showDeletePopup is true", () => {
const wrapper = shallow( const wrapper = shallow(
<DeletePopup <DeletePopup
showDeletePopup={true} showDeletePopup={true}
setShowDeletePopup={setShowDeletePopupMock} setShowDeletePopup={() => any}
setQuery={setQueryMock} setQuery={() => any}
clearFeedback={clearFeedbackMock} clearFeedback={() => any}
showFeedbackBar={showFeedbackBarMock} showFeedbackBar={() => any}
/> />
); );
expect(wrapper.find("Modal").prop("isOpen")).toBeTruthy();
expect(wrapper).toMatchSnapshot(); 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 // 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`] = ` exports[`Copy Popup snapshot test should render when showCopyPopup is true 1`] = `
<Stack <Stack
style={ style={

View File

@@ -1,83 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // 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`] = ` exports[`Delete Popup snapshot test should render when showDeletePopup is true 1`] = `
<Modal <Modal
isOpen={true} isOpen={true}

View File

@@ -13,7 +13,7 @@ import {
import { submitFeedback } from "Explorer/QueryCopilot/QueryCopilotUtilities"; import { submitFeedback } from "Explorer/QueryCopilot/QueryCopilotUtilities";
import { useQueryCopilot } from "hooks/useQueryCopilot"; import { useQueryCopilot } from "hooks/useQueryCopilot";
import React from "react"; import React from "react";
import { getUserEmail } from "../../../Utils/UserUtils"; import { getUserEmail } from "../../Utils/UserUtils";
export const QueryCopilotFeedbackModal: React.FC = (): JSX.Element => { export const QueryCopilotFeedbackModal: React.FC = (): JSX.Element => {
const { const {
@@ -28,7 +28,6 @@ export const QueryCopilotFeedbackModal: React.FC = (): JSX.Element => {
const [description, setDescription] = React.useState<string>(""); const [description, setDescription] = React.useState<string>("");
const [doNotShowAgainChecked, setDoNotShowAgainChecked] = React.useState<boolean>(false); const [doNotShowAgainChecked, setDoNotShowAgainChecked] = React.useState<boolean>(false);
const [contact, setContact] = React.useState<string>(getUserEmail()); const [contact, setContact] = React.useState<string>(getUserEmail());
return ( return (
<Modal isOpen={showFeedbackModal}> <Modal isOpen={showFeedbackModal}>
<Stack style={{ padding: 24 }}> <Stack style={{ padding: 24 }}>

View File

@@ -1,5 +1,5 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
import { FeedOptions } from "@azure/cosmos"; import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import { import {
Callout, Callout,
CommandBarButton, CommandBarButton,
@@ -17,10 +17,16 @@ import {
TextField, TextField,
} from "@fluentui/react"; } from "@fluentui/react";
import { useBoolean } from "@fluentui/react-hooks"; import { useBoolean } from "@fluentui/react-hooks";
import { QueryCopilotSampleContainerId, QueryCopilotSampleContainerSchema } from "Common/Constants"; import {
QueryCopilotSampleContainerId,
QueryCopilotSampleContainerSchema,
QueryCopilotSampleDatabaseId,
} from "Common/Constants";
import { getErrorMessage, handleError } from "Common/ErrorHandlingUtils"; import { getErrorMessage, handleError } from "Common/ErrorHandlingUtils";
import { shouldEnableCrossPartitionKey } from "Common/HeadersUtility"; import { shouldEnableCrossPartitionKey } from "Common/HeadersUtility";
import { MinimalQueryIterator } from "Common/IteratorUtilities"; import { MinimalQueryIterator } from "Common/IteratorUtilities";
import { sampleDataClient } from "Common/SampleDataClient";
import { getCommonQueryOptions } from "Common/dataAccess/queryDocuments";
import { queryDocumentsPage } from "Common/dataAccess/queryDocumentsPage"; import { queryDocumentsPage } from "Common/dataAccess/queryDocumentsPage";
import { QueryResults } from "Contracts/ViewModels"; import { QueryResults } from "Contracts/ViewModels";
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
@@ -31,7 +37,7 @@ import { SaveQueryPane } from "Explorer/Panes/SaveQueryPane/SaveQueryPane";
import { WelcomeModal } from "Explorer/QueryCopilot/Modal/WelcomeModal"; import { WelcomeModal } from "Explorer/QueryCopilot/Modal/WelcomeModal";
import { CopyPopup } from "Explorer/QueryCopilot/Popup/CopyPopup"; import { CopyPopup } from "Explorer/QueryCopilot/Popup/CopyPopup";
import { DeletePopup } from "Explorer/QueryCopilot/Popup/DeletePopup"; import { DeletePopup } from "Explorer/QueryCopilot/Popup/DeletePopup";
import { querySampleDocuments, submitFeedback } from "Explorer/QueryCopilot/QueryCopilotUtilities"; import { submitFeedback } from "Explorer/QueryCopilot/QueryCopilotUtilities";
import { SamplePrompts, SamplePromptsProps } from "Explorer/QueryCopilot/SamplePrompts/SamplePrompts"; import { SamplePrompts, SamplePromptsProps } from "Explorer/QueryCopilot/SamplePrompts/SamplePrompts";
import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection"; import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection";
import { userContext } from "UserContext"; import { userContext } from "UserContext";
@@ -44,6 +50,7 @@ import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg";
import HintIcon from "../../../images/Hint.svg"; import HintIcon from "../../../images/Hint.svg";
import CopilotIcon from "../../../images/QueryCopilotNewLogo.svg"; import CopilotIcon from "../../../images/QueryCopilotNewLogo.svg";
import RecentIcon from "../../../images/Recent.svg"; import RecentIcon from "../../../images/Recent.svg";
import SamplePromptsIcon from "../../../images/SamplePromptsIcon.svg";
import XErrorMessage from "../../../images/X-errorMessage.svg"; import XErrorMessage from "../../../images/X-errorMessage.svg";
import SaveQueryIcon from "../../../images/save-cosmos.svg"; import SaveQueryIcon from "../../../images/save-cosmos.svg";
import { useTabs } from "../../hooks/useTabs"; import { useTabs } from "../../hooks/useTabs";
@@ -121,12 +128,12 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
}; };
const cachedHistoriesString = localStorage.getItem(`${userContext.databaseAccount?.id}-queryCopilotHistories`); const cachedHistoriesString = localStorage.getItem(`${userContext.databaseAccount?.id}-queryCopilotHistories`);
const cachedHistories = cachedHistoriesString?.split("|"); const cachedHistories = cachedHistoriesString?.split(",");
const [histories, setHistories] = useState<string[]>(cachedHistories || []); const [histories, setHistories] = useState<string[]>(cachedHistories || []);
const suggestedPrompts: SuggestedPrompt[] = [ const suggestedPrompts: SuggestedPrompt[] = [
{ id: 1, text: 'Show all products that have the word "ultra" in the name or description' }, { id: 1, text: "Give me all customers whose names start with C" },
{ id: 2, text: "What are all of the possible categories for the products, and their counts?" }, { id: 2, text: "Show me all customers" },
{ id: 3, text: 'Show me all products that have been reviewed by someone with a username that contains "bob"' }, { id: 3, text: "Show me all customers who bought a bike in 2019" },
]; ];
const [filteredHistories, setFilteredHistories] = useState<string[]>(histories); const [filteredHistories, setFilteredHistories] = useState<string[]>(histories);
const [filteredSuggestedPrompts, setFilteredSuggestedPrompts] = useState<SuggestedPrompt[]>(suggestedPrompts); const [filteredSuggestedPrompts, setFilteredSuggestedPrompts] = useState<SuggestedPrompt[]>(suggestedPrompts);
@@ -148,16 +155,9 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
}; };
const updateHistories = (): void => { const updateHistories = (): void => {
const formattedUserPrompt = userPrompt.replace(/\s+/g, " ").trim(); const newHistories = histories.length < 3 ? [userPrompt, ...histories] : [userPrompt, histories[1], histories[2]];
const existingHistories = histories.map((history) => history.replace(/\s+/g, " ").trim());
const updatedHistories = existingHistories.filter(
(history) => history.toLowerCase() !== formattedUserPrompt.toLowerCase()
);
const newHistories = [formattedUserPrompt, ...updatedHistories.slice(0, 2)];
setHistories(newHistories); setHistories(newHistories);
localStorage.setItem(`${userContext.databaseAccount.id}-queryCopilotHistories`, newHistories.join("|")); localStorage.setItem(`${userContext.databaseAccount.id}-queryCopilotHistories`, newHistories.join(","));
}; };
const generateSQLQuery = async (): Promise<void> => { const generateSQLQuery = async (): Promise<void> => {
@@ -179,21 +179,14 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
}); });
const generateSQLQueryResponse: GenerateSQLQueryResponse = await response?.json(); const generateSQLQueryResponse: GenerateSQLQueryResponse = await response?.json();
if (response.ok) { if (generateSQLQueryResponse?.sql) {
if (generateSQLQueryResponse?.sql) { let query = `-- **Prompt:** ${userPrompt}\r\n`;
let query = `-- **Prompt:** ${userPrompt}\r\n`; if (generateSQLQueryResponse.explanation) {
if (generateSQLQueryResponse.explanation) { query += `-- **Explanation of query:** ${generateSQLQueryResponse.explanation}\r\n`;
query += `-- **Explanation of query:** ${generateSQLQueryResponse.explanation}\r\n`;
}
query += generateSQLQueryResponse.sql;
setQuery(query);
setGeneratedQuery(generateSQLQueryResponse.sql);
setShowErrorMessageBar(false);
} }
} else { query += generateSQLQueryResponse.sql;
handleError(JSON.stringify(generateSQLQueryResponse), "copilotInternalServerError"); setQuery(query);
useTabs.getState().setIsQueryErrorThrown(true); setGeneratedQuery(generateSQLQueryResponse.sql);
setShowErrorMessageBar(true);
} }
} catch (error) { } catch (error) {
handleError(error, "executeNaturalLanguageQuery"); handleError(error, "executeNaturalLanguageQuery");
@@ -207,6 +200,14 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
} }
}; };
const querySampleDocuments = (query: string, options: FeedOptions): QueryIterator<ItemDefinition & Resource> => {
options = getCommonQueryOptions(options);
return sampleDataClient()
.database(QueryCopilotSampleDatabaseId)
.container(QueryCopilotSampleContainerId)
.items.query(query, options);
};
const onExecuteQueryClick = async (): Promise<void> => { const onExecuteQueryClick = async (): Promise<void> => {
const queryToExecute = selectedQuery || query; const queryToExecute = selectedQuery || query;
const queryIterator = querySampleDocuments(queryToExecute, { const queryIterator = querySampleDocuments(queryToExecute, {
@@ -232,7 +233,6 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
setQueryResults(queryResults); setQueryResults(queryResults);
setErrorMessage(""); setErrorMessage("");
setShowErrorMessageBar(false);
} catch (error) { } catch (error) {
const errorMessage = getErrorMessage(error); const errorMessage = getErrorMessage(error);
setErrorMessage(errorMessage); setErrorMessage(errorMessage);
@@ -254,7 +254,6 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
commandButtonLabel: executeQueryBtnLabel, commandButtonLabel: executeQueryBtnLabel,
ariaLabel: executeQueryBtnLabel, ariaLabel: executeQueryBtnLabel,
hasPopup: false, hasPopup: false,
disabled: query?.trim() === "",
}; };
const saveQueryBtn = { const saveQueryBtn = {
@@ -265,20 +264,18 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
commandButtonLabel: "Save Query", commandButtonLabel: "Save Query",
ariaLabel: "Save Query", ariaLabel: "Save Query",
hasPopup: false, hasPopup: false,
disabled: query?.trim() === "",
}; };
// Sample Prompts temporary disabled due current design const samplePromptsBtn = {
// const samplePromptsBtn = { iconSrc: SamplePromptsIcon,
// iconSrc: SamplePromptsIcon, iconAlt: "Sample Prompts",
// iconAlt: "Sample Prompts", onCommandClick: () => setIsSamplePromptsOpen(true),
// onCommandClick: () => setIsSamplePromptsOpen(true), commandButtonLabel: "Sample Prompts",
// commandButtonLabel: "Sample Prompts", ariaLabel: "Sample Prompts",
// ariaLabel: "Sample Prompts", hasPopup: false,
// hasPopup: false, };
// };
return [executeQueryBtn, saveQueryBtn]; return [executeQueryBtn, saveQueryBtn, samplePromptsBtn];
}; };
const showTeachingBubble = (): void => { const showTeachingBubble = (): void => {
if (!inputEdited.current) { if (!inputEdited.current) {
@@ -310,283 +307,269 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
}, []); }, []);
return ( return (
<Stack className="tab-pane" style={{ padding: 24, width: "100%" }}> <Stack className="tab-pane" style={{ padding: 24, width: "100%", height: "100%" }}>
<div style={{ overflowY: "auto", height: "100%" }}> <Stack horizontal verticalAlign="center">
<Stack horizontal verticalAlign="center"> <Image src={CopilotIcon} />
<Image src={CopilotIcon} /> <Text style={{ marginLeft: 8, fontWeight: 600, fontSize: 16 }}>Copilot</Text>
<Text style={{ marginLeft: 8, fontWeight: 600, fontSize: 16 }}>Copilot</Text> </Stack>
</Stack> <Stack horizontal verticalAlign="center" style={{ marginTop: 16, width: "100%", position: "relative" }}>
<Stack horizontal verticalAlign="center" style={{ marginTop: 16, width: "100%", position: "relative" }}> <TextField
<TextField id="naturalLanguageInput"
id="naturalLanguageInput" value={userPrompt}
value={userPrompt} onChange={handleUserPromptChange}
onChange={handleUserPromptChange} onClick={() => {
onClick={() => { inputEdited.current = true;
inputEdited.current = true; setShowSamplePrompts(true);
setShowSamplePrompts(true); }}
}} style={{ lineHeight: 30 }}
style={{ lineHeight: 30 }} styles={{ root: { width: "95%" } }}
styles={{ root: { width: "95%" } }} disabled={isGeneratingQuery}
disabled={isGeneratingQuery} autoComplete="off"
autoComplete="off" />
/> {copilotTeachingBubbleVisible && (
{copilotTeachingBubbleVisible && ( <TeachingBubble
<TeachingBubble calloutProps={{ directionalHint: DirectionalHint.bottomCenter }}
calloutProps={{ directionalHint: DirectionalHint.bottomCenter }} target="#naturalLanguageInput"
target="#naturalLanguageInput" hasCloseButton={true}
hasCloseButton={true} closeButtonAriaLabel="Close"
closeButtonAriaLabel="Close" onDismiss={toggleCopilotTeachingBubbleVisible}
onDismiss={toggleCopilotTeachingBubbleVisible} hasSmallHeadline={true}
hasSmallHeadline={true} headline="Write a prompt"
headline="Write a prompt" >
Write a prompt here and Copilot will generate the query for you. You can also choose from our{" "}
<Link
onClick={() => {
setShowSamplePrompts(true);
toggleCopilotTeachingBubbleVisible();
}}
style={{ color: "white", fontWeight: 600 }}
> >
Write a prompt here and Copilot will generate the query for you. You can also choose from our{" "} sample prompts
<Link </Link>{" "}
onClick={() => { or write your own query
setShowSamplePrompts(true); </TeachingBubble>
toggleCopilotTeachingBubbleVisible(); )}
}} <IconButton
style={{ color: "white", fontWeight: 600 }} iconProps={{ iconName: "Send" }}
> disabled={isGeneratingQuery || !userPrompt.trim()}
sample prompts style={{ marginLeft: 8 }}
</Link>{" "} onClick={() => {
or write your own query updateHistories();
</TeachingBubble> generateSQLQuery();
)} resetButtonState();
<IconButton }}
iconProps={{ iconName: "Send" }} />
disabled={isGeneratingQuery || !userPrompt.trim()} {isGeneratingQuery && <Spinner style={{ marginLeft: 8 }} />}
style={{ marginLeft: 8 }} {showSamplePrompts && (
onClick={() => { <Callout
updateHistories(); styles={{ root: { minWidth: 400 } }}
generateSQLQuery(); target="#naturalLanguageInput"
resetButtonState(); isBeakVisible={false}
}} onDismiss={() => setShowSamplePrompts(false)}
/> directionalHintFixed={true}
{isGeneratingQuery && <Spinner style={{ marginLeft: 8 }} />} directionalHint={DirectionalHint.bottomLeftEdge}
{showSamplePrompts && ( alignTargetEdge={true}
<Callout gapSpace={4}
styles={{ root: { minWidth: 400 } }} >
target="#naturalLanguageInput" <Stack>
isBeakVisible={false} {filteredHistories?.length > 0 && (
onDismiss={() => setShowSamplePrompts(false)} <Stack>
directionalHintFixed={true} <Text
directionalHint={DirectionalHint.bottomLeftEdge} style={{
alignTargetEdge={true} width: "100%",
gapSpace={4} fontSize: 14,
> fontWeight: 600,
<Stack> color: "#0078D4",
{filteredHistories?.length > 0 && ( marginLeft: 16,
<Stack> padding: "4px 0",
<Text
style={{
width: "100%",
fontSize: 14,
fontWeight: 600,
color: "#0078D4",
marginLeft: 16,
padding: "4px 0",
}}
>
Recent
</Text>
{filteredHistories.map((history, i) => (
<DefaultButton
key={i}
onClick={() => {
setUserPrompt(history);
setShowSamplePrompts(false);
}}
onRenderIcon={() => <Image src={RecentIcon} />}
styles={promptStyles}
>
{history}
</DefaultButton>
))}
</Stack>
)}
{filteredSuggestedPrompts?.length > 0 && (
<Stack>
<Text
style={{
width: "100%",
fontSize: 14,
fontWeight: 600,
color: "#0078D4",
marginLeft: 16,
padding: "4px 0",
}}
>
Suggested Prompts
</Text>
{filteredSuggestedPrompts.map((prompt) => (
<DefaultButton
key={prompt.id}
onClick={() => {
setUserPrompt(prompt.text);
setShowSamplePrompts(false);
}}
onRenderIcon={() => <Image src={HintIcon} />}
styles={promptStyles}
>
{prompt.text}
</DefaultButton>
))}
</Stack>
)}
{(filteredHistories?.length > 0 || filteredSuggestedPrompts?.length > 0) && (
<Stack>
<Separator
styles={{
root: {
selectors: { "::before": { background: "#E1DFDD" } },
padding: 0,
},
}}
/>
<Text
style={{
width: "100%",
fontSize: 14,
marginLeft: 16,
padding: "4px 0",
}}
>
Learn about{" "}
<Link target="_blank" href="">
writing effective prompts
</Link>
</Text>
</Stack>
)}
</Stack>
</Callout>
)}
</Stack>
<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="" target="_blank">
Read preview terms
</Link>
{showErrorMessageBar && (
<Stack style={{ backgroundColor: "#FEF0F1", padding: "4px 8px" }} horizontal verticalAlign="center">
<Image src={XErrorMessage} style={{ marginRight: "8px" }} />
<Text style={{ fontSize: 12 }}>
We ran into an error and were not able to execute query. Please try again after sometime
</Text>
</Stack>
)}
</Text>
</Stack>
{showFeedbackBar && (
<Stack style={{ backgroundColor: "#FFF8F0", padding: "2px 8px" }} horizontal verticalAlign="center">
<Text style={{ fontWeight: 600, fontSize: 12 }}>Provide feedback on the query generated</Text>
{showCallout && !hideFeedbackModalForLikedQueries && (
<Callout
style={{ padding: 8 }}
target="#likeBtn"
onDismiss={() => {
setShowCallout(false);
submitFeedback({ generatedQuery, likeQuery, description: "", userPrompt: userPrompt });
}}
directionalHint={DirectionalHint.topCenter}
>
<Text>
Thank you. Need to give{" "}
<Link
onClick={() => {
setShowCallout(false);
useQueryCopilot.getState().openFeedbackModal(generatedQuery, true, userPrompt);
}} }}
> >
more feedback? Recent
</Link> </Text>
</Text> {filteredHistories.map((history, i) => (
</Callout> <DefaultButton
)} key={i}
<IconButton onClick={() => {
id="likeBtn" setUserPrompt(history);
style={{ marginLeft: 20 }} setShowSamplePrompts(false);
iconProps={{ iconName: likeQuery === true ? "LikeSolid" : "Like" }} }}
onClick={() => { onRenderIcon={() => <Image src={RecentIcon} />}
setShowCallout(!likeQuery); styles={promptStyles}
setLikeQuery(!likeQuery); >
if (dislikeQuery) { {history}
setDislikeQuery(!dislikeQuery); </DefaultButton>
} ))}
}} </Stack>
/> )}
<IconButton <Text
style={{ margin: "0 10px" }} style={{
iconProps={{ iconName: dislikeQuery === true ? "DislikeSolid" : "Dislike" }} width: "100%",
onClick={() => { fontSize: 14,
if (!dislikeQuery) { fontWeight: 600,
useQueryCopilot.getState().openFeedbackModal(generatedQuery, false, userPrompt); color: "#0078D4",
setLikeQuery(false); marginLeft: 16,
} padding: "4px 0",
setDislikeQuery(!dislikeQuery); }}
setShowCallout(false); >
}} Suggested Prompts
/> </Text>
<Separator vertical style={{ color: "#EDEBE9" }} /> {filteredSuggestedPrompts.map((prompt) => (
<CommandBarButton <DefaultButton
onClick={copyGeneratedCode} key={prompt.id}
iconProps={{ iconName: "Copy" }} onClick={() => {
style={{ margin: "0 10px", backgroundColor: "#FFF8F0", transition: "background-color 0.3s ease" }} setUserPrompt(prompt.text);
> setShowSamplePrompts(false);
Copy code }}
</CommandBarButton> onRenderIcon={() => <Image src={HintIcon} />}
<CommandBarButton styles={promptStyles}
onClick={() => { >
setShowDeletePopup(true); {prompt.text}
}} </DefaultButton>
iconProps={{ iconName: "Delete" }} ))}
style={{ margin: "0 10px", backgroundColor: "#FFF8F0", transition: "background-color 0.3s ease" }} <Separator
> styles={{
Delete code root: {
</CommandBarButton> selectors: { "::before": { background: "#E1DFDD" } },
padding: 0,
},
}}
/>
<Text
style={{
width: "100%",
fontSize: 14,
marginLeft: 16,
padding: "4px 0",
}}
>
Learn about{" "}
<Link target="_blank" href="">
writing effective prompts
</Link>
</Text>
</Stack>
</Callout>
)}
</Stack>
<Text style={{ marginTop: 8, marginBottom: 24, fontSize: 12 }}>
AI-generated content can have mistakes. Make sure it&apos;s accurate and appropriate before using it.{" "}
<Link href="" target="_blank">
Read preview terms
</Link>
{showErrorMessageBar && (
<Stack style={{ backgroundColor: "#FEF0F1", padding: "4px 8px" }} horizontal verticalAlign="center">
<Image src={XErrorMessage} style={{ marginRight: "8px" }} />
<Text style={{ fontSize: 12 }}>
We ran into an error and were not able to execute query. Please try again after sometime
</Text>
</Stack> </Stack>
)} )}
</Text>
<Stack className="tabPaneContentContainer"> {showFeedbackBar && (
<SplitterLayout vertical={true} primaryIndex={0} primaryMinSize={100} secondaryMinSize={200}> <Stack style={{ backgroundColor: "#FFF8F0", padding: "2px 8px" }} horizontal verticalAlign="center">
<EditorReact <Text style={{ fontWeight: 600, fontSize: 12 }}>Provide feedback on the query generated</Text>
language={"sql"} {showCallout && !hideFeedbackModalForLikedQueries && (
content={query} <Callout
isReadOnly={false} style={{ padding: 8 }}
ariaLabel={"Editing Query"} target="#likeBtn"
lineNumbers={"on"} onDismiss={() => {
onContentChanged={(newQuery: string) => setQuery(newQuery)} setShowCallout(false);
onContentSelected={(selectedQuery: string) => setSelectedQuery(selectedQuery)} submitFeedback({ generatedQuery, likeQuery, description: "", userPrompt: userPrompt });
/> }}
<QueryResultSection directionalHint={DirectionalHint.topCenter}
isMongoDB={false} >
queryEditorContent={selectedQuery || query} <Text>
error={errorMessage} Thank you. Need to give{" "}
queryResults={queryResults} <Link
isExecuting={isExecuting} onClick={() => {
executeQueryDocumentsPage={(firstItemIndex: number) => setShowCallout(false);
queryDocumentsPerPage(firstItemIndex, queryIterator) useQueryCopilot.getState().openFeedbackModal(generatedQuery, true, userPrompt);
}}
>
more feedback?
</Link>
</Text>
</Callout>
)}
<IconButton
id="likeBtn"
style={{ marginLeft: 20 }}
iconProps={{ iconName: likeQuery === true ? "LikeSolid" : "Like" }}
onClick={() => {
setShowCallout(!likeQuery);
setLikeQuery(!likeQuery);
if (dislikeQuery) {
setDislikeQuery(!dislikeQuery);
} }
/> }}
</SplitterLayout>
</Stack>
<WelcomeModal visible={localStorage.getItem("hideWelcomeModal") !== "true"} />
{isSamplePromptsOpen && <SamplePrompts sampleProps={sampleProps} />}
{query !== "" && query.trim().length !== 0 && (
<DeletePopup
showDeletePopup={showDeletePopup}
setShowDeletePopup={setShowDeletePopup}
setQuery={setQuery}
clearFeedback={resetButtonState}
showFeedbackBar={setShowFeedbackBar}
/> />
)} <IconButton
<CopyPopup showCopyPopup={showCopyPopup} setShowCopyPopup={setshowCopyPopup} /> style={{ margin: "0 10px" }}
</div> iconProps={{ iconName: dislikeQuery === true ? "DislikeSolid" : "Dislike" }}
onClick={() => {
if (!dislikeQuery) {
useQueryCopilot.getState().openFeedbackModal(generatedQuery, false, userPrompt);
setLikeQuery(false);
}
setDislikeQuery(!dislikeQuery);
setShowCallout(false);
}}
/>
<Separator vertical style={{ color: "#EDEBE9" }} />
<CommandBarButton
onClick={copyGeneratedCode}
iconProps={{ iconName: "Copy" }}
style={{ margin: "0 10px", backgroundColor: "#FFF8F0", transition: "background-color 0.3s ease" }}
>
Copy code
</CommandBarButton>
<CommandBarButton
onClick={() => {
setShowDeletePopup(true);
}}
iconProps={{ iconName: "Delete" }}
style={{ margin: "0 10px", backgroundColor: "#FFF8F0", transition: "background-color 0.3s ease" }}
>
Delete code
</CommandBarButton>
</Stack>
)}
<Stack className="tabPaneContentContainer">
<SplitterLayout vertical={true} primaryIndex={0} primaryMinSize={100} secondaryMinSize={200}>
<EditorReact
language={"sql"}
content={query}
isReadOnly={false}
ariaLabel={"Editing Query"}
lineNumbers={"on"}
onContentChanged={(newQuery: string) => setQuery(newQuery)}
onContentSelected={(selectedQuery: string) => setSelectedQuery(selectedQuery)}
/>
<QueryResultSection
isMongoDB={false}
queryEditorContent={selectedQuery || query}
error={errorMessage}
queryResults={queryResults}
isExecuting={isExecuting}
executeQueryDocumentsPage={(firstItemIndex: number) => queryDocumentsPerPage(firstItemIndex, queryIterator)}
/>
</SplitterLayout>
</Stack>
<WelcomeModal visible={localStorage.getItem("hideWelcomeModal") !== "true"} />
{isSamplePromptsOpen && <SamplePrompts sampleProps={sampleProps} />}
{query !== "" && query.trim().length !== 0 && (
<DeletePopup
showDeletePopup={showDeletePopup}
setShowDeletePopup={setShowDeletePopup}
setQuery={setQuery}
clearFeedback={resetButtonState}
showFeedbackBar={setShowFeedbackBar}
/>
)}
<CopyPopup showCopyPopup={showCopyPopup} setShowCopyPopup={setshowCopyPopup} />
</Stack> </Stack>
); );
}; };

View File

@@ -1,15 +1,5 @@
import { FeedOptions, Item, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos"; import { QueryCopilotSampleContainerSchema } from "Common/Constants";
import {
QueryCopilotSampleContainerId,
QueryCopilotSampleContainerSchema,
QueryCopilotSampleDatabaseId,
} from "Common/Constants";
import { handleError } from "Common/ErrorHandlingUtils"; import { handleError } from "Common/ErrorHandlingUtils";
import { sampleDataClient } from "Common/SampleDataClient";
import { getPartitionKeyValue } from "Common/dataAccess/getPartitionKeyValue";
import { getCommonQueryOptions } from "Common/dataAccess/queryDocuments";
import DocumentId from "Explorer/Tree/DocumentId";
import { logConsoleProgress } from "Utils/NotificationConsoleUtils";
interface FeedbackParams { interface FeedbackParams {
likeQuery: boolean; likeQuery: boolean;
@@ -31,41 +21,16 @@ export const submitFeedback = async (params: FeedbackParams): Promise<void> => {
contact: contact || "", contact: contact || "",
}; };
await fetch("https://copilotorchestrater.azurewebsites.net/feedback", { const response = await fetch("https://copilotorchestrater.azurewebsites.net/feedback", {
method: "POST", method: "POST",
headers: { headers: {
"content-type": "application/json", "content-type": "application/json",
}, },
body: JSON.stringify(payload), body: JSON.stringify(payload),
}); });
// eslint-disable-next-line no-console
console.log(response);
} catch (error) { } catch (error) {
handleError(error, "copilotSubmitFeedback"); handleError(error, "copilotSubmitFeedback");
} }
}; };
export const querySampleDocuments = (query: string, options: FeedOptions): QueryIterator<ItemDefinition & Resource> => {
options = getCommonQueryOptions(options);
return sampleDataClient()
.database(QueryCopilotSampleDatabaseId)
.container(QueryCopilotSampleContainerId)
.items.query(query, options);
};
export const readSampleDocument = async (documentId: DocumentId): Promise<Item> => {
const clearMessage = logConsoleProgress(`Reading item ${documentId.id()}`);
try {
const response = await sampleDataClient()
.database(QueryCopilotSampleDatabaseId)
.container(QueryCopilotSampleContainerId)
.item(documentId.id(), getPartitionKeyValue(documentId))
.read();
return response?.resource;
} catch (error) {
handleError(error, "ReadDocument", `Failed to read item ${documentId.id()}`);
throw error;
} finally {
clearMessage();
}
};

View File

@@ -1,91 +1,26 @@
import { DefaultButton, IconButton } from "@fluentui/react";
import { shallow } from "enzyme"; import { shallow } from "enzyme";
import React from "react"; import React from "react";
import { SamplePrompts, SamplePromptsProps } from "./SamplePrompts"; import { SamplePrompts, SamplePromptsProps } from "./SamplePrompts";
describe("Sample Prompts snapshot test", () => { 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", () => { 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(); expect(wrapper).toMatchSnapshot();
}); });
it("should render properly if isSamplePromptsOpen is false", () => { 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} />); const wrapper = shallow(<SamplePrompts sampleProps={sampleProps} />);
expect(wrapper).toMatchSnapshot(); 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

@@ -5,148 +5,133 @@ exports[`Query copilot tab snapshot test should render with initial input 1`] =
className="tab-pane" className="tab-pane"
style={ style={
Object { Object {
"height": "100%",
"padding": 24, "padding": 24,
"width": "100%", "width": "100%",
} }
} }
> >
<div <Stack
horizontal={true}
verticalAlign="center"
>
<Image
src=""
/>
<Text
style={
Object {
"fontSize": 16,
"fontWeight": 600,
"marginLeft": 8,
}
}
>
Copilot
</Text>
</Stack>
<Stack
horizontal={true}
style={ style={
Object { Object {
"height": "100%", "marginTop": 16,
"overflowY": "auto", "position": "relative",
"width": "100%",
}
}
verticalAlign="center"
>
<StyledTextFieldBase
autoComplete="off"
disabled={false}
id="naturalLanguageInput"
onChange={[Function]}
onClick={[Function]}
style={
Object {
"lineHeight": 30,
}
}
styles={
Object {
"root": Object {
"width": "95%",
},
}
}
value="Write a query to return all records in this table"
/>
<CustomizedIconButton
disabled={false}
iconProps={
Object {
"iconName": "Send",
}
}
onClick={[Function]}
style={
Object {
"marginLeft": 8,
}
}
/>
</Stack>
<Text
style={
Object {
"fontSize": 12,
"marginBottom": 24,
"marginTop": 8,
} }
} }
> >
<Stack AI-generated content can have mistakes. Make sure it's accurate and appropriate before using it.
horizontal={true}
verticalAlign="center"
>
<Image
src=""
/>
<Text
style={
Object {
"fontSize": 16,
"fontWeight": 600,
"marginLeft": 8,
}
}
>
Copilot
</Text>
</Stack>
<Stack
horizontal={true}
style={
Object {
"marginTop": 16,
"position": "relative",
"width": "100%",
}
}
verticalAlign="center"
>
<StyledTextFieldBase
autoComplete="off"
disabled={false}
id="naturalLanguageInput"
onChange={[Function]}
onClick={[Function]}
style={
Object {
"lineHeight": 30,
}
}
styles={
Object {
"root": Object {
"width": "95%",
},
}
}
value="Write a query to return all records in this table"
/>
<CustomizedIconButton
disabled={false}
iconProps={
Object {
"iconName": "Send",
}
}
onClick={[Function]}
style={
Object {
"marginLeft": 8,
}
}
/>
</Stack>
<Stack
style={
Object {
"marginBottom": 24,
"marginTop": 8,
}
}
>
<Text
style={
Object {
"fontSize": 12,
}
}
>
AI-generated content can have mistakes. Make sure it's accurate and appropriate before using it.
<StyledLinkBase <StyledLinkBase
href="" href=""
target="_blank" target="_blank"
>
Read preview terms
</StyledLinkBase>
</Text>
</Stack>
<Stack
className="tabPaneContentContainer"
> >
<t Read preview terms
customClassName="" </StyledLinkBase>
onDragEnd={null} </Text>
onDragStart={null} <Stack
onSecondaryPaneSizeChange={null} className="tabPaneContentContainer"
percentage={false} >
primaryIndex={0} <t
primaryMinSize={100} customClassName=""
secondaryMinSize={200} onDragEnd={null}
vertical={true} onDragStart={null}
> onSecondaryPaneSizeChange={null}
<EditorReact percentage={false}
ariaLabel="Editing Query" primaryIndex={0}
content="" primaryMinSize={100}
isReadOnly={false} secondaryMinSize={200}
language="sql" vertical={true}
lineNumbers="on" >
onContentChanged={[Function]} <EditorReact
onContentSelected={[Function]} ariaLabel="Editing Query"
/> content=""
<QueryResultSection isReadOnly={false}
error="" language="sql"
executeQueryDocumentsPage={[Function]} lineNumbers="on"
isExecuting={false} onContentChanged={[Function]}
isMongoDB={false} onContentSelected={[Function]}
queryEditorContent="" />
/> <QueryResultSection
</t> error=""
</Stack> executeQueryDocumentsPage={[Function]}
<WelcomeModal isExecuting={false}
visible={true} isMongoDB={false}
/> queryEditorContent=""
<CopyPopup />
setShowCopyPopup={[Function]} </t>
showCopyPopup={false} </Stack>
/> <WelcomeModal
</div> visible={true}
/>
<CopyPopup
setShowCopyPopup={[Function]}
showCopyPopup={false}
/>
</Stack> </Stack>
`; `;

View File

@@ -581,7 +581,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
> >
{item.title} {item.title}
</Link> </Link>
<Image src={LinkIcon} alt={item.title} /> <Image src={LinkIcon} alt=" " />
</Stack> </Stack>
<Text>{item.description}</Text> <Text>{item.description}</Text>
</Stack> </Stack>
@@ -600,7 +600,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
<li key={`${item.title}${item.description}${index}`}> <li key={`${item.title}${item.description}${index}`}>
<Stack style={{ marginBottom: 26 }}> <Stack style={{ marginBottom: 26 }}>
<Stack horizontal> <Stack horizontal>
<Image style={{ marginRight: 8 }} src={item.iconSrc} alt={item.title} /> <Image style={{ marginRight: 8 }} src={item.iconSrc} />
<Link style={{ fontSize: 14 }} onClick={item.onClick} title={item.info}> <Link style={{ fontSize: 14 }} onClick={item.onClick} title={item.info}>
{item.title} {item.title}
</Link> </Link>
@@ -720,7 +720,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
> >
{item.title} {item.title}
</Link> </Link>
<Image src={LinkIcon} alt={item.title} /> <Image src={LinkIcon} />
</Stack> </Stack>
<Text>{item.description}</Text> <Text>{item.description}</Text>
</Stack> </Stack>

View File

@@ -1,5 +1,4 @@
import { extractPartitionKey, ItemDefinition, PartitionKeyDefinition, QueryIterator, Resource } from "@azure/cosmos"; import { extractPartitionKey, ItemDefinition, PartitionKeyDefinition, QueryIterator, Resource } from "@azure/cosmos";
import { querySampleDocuments, readSampleDocument } from "Explorer/QueryCopilot/QueryCopilotUtilities";
import * as ko from "knockout"; import * as ko from "knockout";
import Q from "q"; import Q from "q";
import DeleteDocumentIcon from "../../../images/DeleteDocument.svg"; import DeleteDocumentIcon from "../../../images/DeleteDocument.svg";
@@ -8,12 +7,7 @@ import NewDocumentIcon from "../../../images/NewDocument.svg";
import SaveIcon from "../../../images/save-cosmos.svg"; import SaveIcon from "../../../images/save-cosmos.svg";
import UploadIcon from "../../../images/Upload_16x16.svg"; import UploadIcon from "../../../images/Upload_16x16.svg";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import { import { DocumentsGridMetrics, KeyCodes } from "../../Common/Constants";
DocumentsGridMetrics,
KeyCodes,
QueryCopilotSampleContainerId,
QueryCopilotSampleDatabaseId,
} from "../../Common/Constants";
import { createDocument } from "../../Common/dataAccess/createDocument"; import { createDocument } from "../../Common/dataAccess/createDocument";
import { deleteDocument } from "../../Common/dataAccess/deleteDocument"; import { deleteDocument } from "../../Common/dataAccess/deleteDocument";
import { queryDocuments } from "../../Common/dataAccess/queryDocuments"; import { queryDocuments } from "../../Common/dataAccess/queryDocuments";
@@ -77,7 +71,6 @@ export default class DocumentsTab extends TabsBase {
private _documentsIterator: QueryIterator<ItemDefinition & Resource>; private _documentsIterator: QueryIterator<ItemDefinition & Resource>;
private _resourceTokenPartitionKey: string; private _resourceTokenPartitionKey: string;
private _isQueryCopilotSampleContainer: boolean;
constructor(options: ViewModels.DocumentsTabOptions) { constructor(options: ViewModels.DocumentsTabOptions) {
super(options); super(options);
@@ -324,9 +317,6 @@ export default class DocumentsTab extends TabsBase {
this.selectedDocumentContent.subscribe((newContent: string) => this._onEditorContentChange(newContent)); this.selectedDocumentContent.subscribe((newContent: string) => this._onEditorContentChange(newContent));
this.showPartitionKey = this._shouldShowPartitionKey(); this.showPartitionKey = this._shouldShowPartitionKey();
this._isQueryCopilotSampleContainer =
this.collection?.databaseId === QueryCopilotSampleDatabaseId &&
this.collection?.id() === QueryCopilotSampleContainerId;
} }
private _shouldShowPartitionKey(): boolean { private _shouldShowPartitionKey(): boolean {
@@ -688,6 +678,7 @@ export default class DocumentsTab extends TabsBase {
} }
public createIterator(): QueryIterator<ItemDefinition & Resource> { public createIterator(): QueryIterator<ItemDefinition & Resource> {
let filters = this.lastFilterContents();
const filter: string = this.filterContent().trim(); const filter: string = this.filterContent().trim();
const query: string = this.buildQuery(filter); const query: string = this.buildQuery(filter);
let options: any = {}; let options: any = {};
@@ -697,16 +688,12 @@ export default class DocumentsTab extends TabsBase {
options.partitionKey = this._resourceTokenPartitionKey; options.partitionKey = this._resourceTokenPartitionKey;
} }
return this._isQueryCopilotSampleContainer return queryDocuments(this.collection.databaseId, this.collection.id(), query, options);
? querySampleDocuments(query, options)
: queryDocuments(this.collection.databaseId, this.collection.id(), query, options);
} }
public async selectDocument(documentId: DocumentId): Promise<void> { public async selectDocument(documentId: DocumentId): Promise<void> {
this.selectedDocumentId(documentId); this.selectedDocumentId(documentId);
const content = await (this._isQueryCopilotSampleContainer const content = await readDocument(this.collection, documentId);
? readSampleDocument(documentId)
: readDocument(this.collection, documentId));
this.initDocumentEditor(documentId, content); this.initDocumentEditor(documentId, content);
} }
@@ -823,7 +810,7 @@ export default class DocumentsTab extends TabsBase {
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: false, hasPopup: false,
disabled: !this.newDocumentButton.enabled() || useSelectedNode.getState().isQueryCopilotCollectionSelected(), disabled: !this.newDocumentButton.enabled(),
id: "mongoNewDocumentBtn", id: "mongoNewDocumentBtn",
}); });
} }
@@ -837,8 +824,7 @@ export default class DocumentsTab extends TabsBase {
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: false, hasPopup: false,
disabled: disabled: !this.saveNewDocumentButton.enabled(),
!this.saveNewDocumentButton.enabled() || useSelectedNode.getState().isQueryCopilotCollectionSelected(),
}); });
} }
@@ -851,9 +837,7 @@ export default class DocumentsTab extends TabsBase {
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: false, hasPopup: false,
disabled: disabled: !this.discardNewDocumentChangesButton.enabled(),
!this.discardNewDocumentChangesButton.enabled() ||
useSelectedNode.getState().isQueryCopilotCollectionSelected(),
}); });
} }
@@ -866,8 +850,7 @@ export default class DocumentsTab extends TabsBase {
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: false, hasPopup: false,
disabled: disabled: !this.saveExistingDocumentButton.enabled(),
!this.saveExistingDocumentButton.enabled() || useSelectedNode.getState().isQueryCopilotCollectionSelected(),
}); });
} }
@@ -880,9 +863,7 @@ export default class DocumentsTab extends TabsBase {
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: false, hasPopup: false,
disabled: disabled: !this.discardExisitingDocumentChangesButton.enabled(),
!this.discardExisitingDocumentChangesButton.enabled() ||
useSelectedNode.getState().isQueryCopilotCollectionSelected(),
}); });
} }
@@ -895,9 +876,7 @@ export default class DocumentsTab extends TabsBase {
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: false, hasPopup: false,
disabled: disabled: !this.deleteExisitingDocumentButton.enabled(),
!this.deleteExisitingDocumentButton.enabled() ||
useSelectedNode.getState().isQueryCopilotCollectionSelected(),
}); });
} }
@@ -941,9 +920,7 @@ export default class DocumentsTab extends TabsBase {
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
disabled: disabled: useSelectedNode.getState().isDatabaseNodeOrNoneSelected(),
useSelectedNode.getState().isDatabaseNodeOrNoneSelected() ||
useSelectedNode.getState().isQueryCopilotCollectionSelected(),
}; };
} }
} }

View File

@@ -68,7 +68,7 @@ export const Tabs = ({ explorer }: TabsProps): JSX.Element => {
function TabNav({ tab, active, tabKind }: { tab?: Tab; active: boolean; tabKind?: ReactTabKind }) { function TabNav({ tab, active, tabKind }: { tab?: Tab; active: boolean; tabKind?: ReactTabKind }) {
const [hovering, setHovering] = useState(false); const [hovering, setHovering] = useState(false);
const focusTab = useRef<HTMLLIElement>() as MutableRefObject<HTMLLIElement>; const focusTab = useRef<HTMLLIElement>() as MutableRefObject<HTMLLIElement>;
const tabId = tab ? tab.tabId : ""; const tabId = tab ? tab.tabId : "connect";
const getReactTabTitle = (): ko.Observable<string> => { const getReactTabTitle = (): ko.Observable<string> => {
if (tabKind === ReactTabKind.QueryCopilot) { if (tabKind === ReactTabKind.QueryCopilot) {

View File

@@ -2,10 +2,10 @@ import * as ko from "knockout";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { useTabs } from "../../hooks/useTabs";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { useTabs } from "../../hooks/useTabs";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import DocumentsTab from "../Tabs/DocumentsTab"; import DocumentsTab from "../Tabs/DocumentsTab";
import { NewQueryTab } from "../Tabs/QueryTab/QueryTab"; import { NewQueryTab } from "../Tabs/QueryTab/QueryTab";
@@ -28,9 +28,8 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
public children: ko.ObservableArray<ViewModels.TreeNode>; public children: ko.ObservableArray<ViewModels.TreeNode>;
public selectedSubnodeKind: ko.Observable<ViewModels.CollectionTabKind>; public selectedSubnodeKind: ko.Observable<ViewModels.CollectionTabKind>;
public isCollectionExpanded: ko.Observable<boolean>; 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.nodeKind = "Collection";
this.container = container; this.container = container;
this.databaseId = databaseId; this.databaseId = databaseId;
@@ -43,7 +42,6 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
this.children = ko.observableArray<ViewModels.TreeNode>([]); this.children = ko.observableArray<ViewModels.TreeNode>([]);
this.selectedSubnodeKind = ko.observable<ViewModels.CollectionTabKind>(); this.selectedSubnodeKind = ko.observable<ViewModels.CollectionTabKind>();
this.isCollectionExpanded = ko.observable<boolean>(true); this.isCollectionExpanded = ko.observable<boolean>(true);
this.isSampleCollection = isSampleCollection;
} }
public expandCollection(): void { public expandCollection(): void {
@@ -141,7 +139,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
documentsTab = new DocumentsTab({ documentsTab = new DocumentsTab({
partitionKey: this.partitionKey, partitionKey: this.partitionKey,
resourceTokenPartitionKey: userContext.parsedResourceToken?.partitionKey, resourceTokenPartitionKey: userContext.parsedResourceToken.partitionKey,
documentIds: ko.observableArray<DocumentId>([]), documentIds: ko.observableArray<DocumentId>([]),
tabKind: ViewModels.CollectionTabKind.Documents, tabKind: ViewModels.CollectionTabKind.Documents,
title: "Items", title: "Items",

View File

@@ -790,10 +790,14 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
{!isNotebookEnabled && isSampleDataEnabled && ( {!isNotebookEnabled && isSampleDataEnabled && (
<> <>
<AccordionComponent> <AccordionComponent>
<AccordionItemComponent title={"MY DATA"} isExpanded={!gitHubNotebooksContentRoot}> <AccordionItemComponent
title={"MY DATA"}
isExpanded={!gitHubNotebooksContentRoot}
styles={{ maxHeight: 230 }}
>
<TreeComponent className="dataResourceTree" rootNode={dataRootNode} /> <TreeComponent className="dataResourceTree" rootNode={dataRootNode} />
</AccordionItemComponent> </AccordionItemComponent>
<AccordionItemComponent title={"SAMPLE DATA"} containerStyles={{ display: "table" }}> <AccordionItemComponent title={"SAMPLE DATA"}>
<SampleDataTree sampleDataResourceTokenCollection={sampleDataResourceTokenCollection} /> <SampleDataTree sampleDataResourceTokenCollection={sampleDataResourceTokenCollection} />
</AccordionItemComponent> </AccordionItemComponent>
</AccordionComponent> </AccordionComponent>
@@ -804,10 +808,14 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
{isNotebookEnabled && isSampleDataEnabled && ( {isNotebookEnabled && isSampleDataEnabled && (
<> <>
<AccordionComponent> <AccordionComponent>
<AccordionItemComponent title={"MY DATA"} isExpanded={!gitHubNotebooksContentRoot}> <AccordionItemComponent
title={"MY DATA"}
isExpanded={!gitHubNotebooksContentRoot}
styles={{ maxHeight: 130 }}
>
<TreeComponent className="dataResourceTree" rootNode={dataRootNode} /> <TreeComponent className="dataResourceTree" rootNode={dataRootNode} />
</AccordionItemComponent> </AccordionItemComponent>
<AccordionItemComponent title={"SAMPLE DATA"} containerStyles={{ display: "table" }}> <AccordionItemComponent title={"SAMPLE DATA"}>
<SampleDataTree sampleDataResourceTokenCollection={sampleDataResourceTokenCollection} /> <SampleDataTree sampleDataResourceTokenCollection={sampleDataResourceTokenCollection} />
</AccordionItemComponent> </AccordionItemComponent>
<AccordionItemComponent title={"NOTEBOOKS"}> <AccordionItemComponent title={"NOTEBOOKS"}>

View File

@@ -2,7 +2,7 @@ import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdap
import TabsBase from "Explorer/Tabs/TabsBase"; import TabsBase from "Explorer/Tabs/TabsBase";
import { useSelectedNode } from "Explorer/useSelectedNode"; import { useSelectedNode } from "Explorer/useSelectedNode";
import { useTabs } from "hooks/useTabs"; import { useTabs } from "hooks/useTabs";
import React from "react"; import React, { useEffect, useState } from "react";
import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg"; import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg";
import CollectionIcon from "../../../images/tree-collection.svg"; import CollectionIcon from "../../../images/tree-collection.svg";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
@@ -14,64 +14,51 @@ export const SampleDataTree = ({
}: { }: {
sampleDataResourceTokenCollection: ViewModels.CollectionBase; sampleDataResourceTokenCollection: ViewModels.CollectionBase;
}): JSX.Element => { }): JSX.Element => {
const buildSampleDataTree = (): TreeNode => { const [root, setRoot] = useState<TreeNode | undefined>(undefined);
const updatedSampleTree: TreeNode = {
label: sampleDataResourceTokenCollection.databaseId, useEffect(() => {
isExpanded: true, if (sampleDataResourceTokenCollection) {
iconSrc: CosmosDBIcon, const updatedSampleTree: TreeNode = {
className: "databaseHeader", label: sampleDataResourceTokenCollection.databaseId,
children: [ isExpanded: false,
{ iconSrc: CosmosDBIcon,
label: sampleDataResourceTokenCollection.id(), className: "databaseHeader",
iconSrc: CollectionIcon, children: [
isExpanded: false, {
className: "collectionHeader", label: sampleDataResourceTokenCollection.id(),
contextMenu: ResourceTreeContextMenuButtonFactory.createSampleCollectionContextMenuButton(), iconSrc: CollectionIcon,
onClick: () => { isExpanded: false,
useSelectedNode.getState().setSelectedNode(sampleDataResourceTokenCollection); className: "dataResourceTree",
useCommandBar.getState().setContextButtons([]); contextMenu: ResourceTreeContextMenuButtonFactory.createSampleCollectionContextMenuButton(),
useTabs onClick: () => {
.getState() // Rewritten version of expandCollapseCollection
.refreshActiveTab( useSelectedNode.getState().setSelectedNode(sampleDataResourceTokenCollection);
useCommandBar.getState().setContextButtons([]);
useTabs().refreshActiveTab(
(tab: TabsBase) => (tab: TabsBase) =>
tab.collection?.id() === sampleDataResourceTokenCollection.id() && tab.collection?.id() === sampleDataResourceTokenCollection.id() &&
tab.collection.databaseId === sampleDataResourceTokenCollection.databaseId tab.collection.databaseId === sampleDataResourceTokenCollection.databaseId
); );
},
isSelected: () =>
useSelectedNode
.getState()
.isDataNodeSelected(sampleDataResourceTokenCollection.databaseId, sampleDataResourceTokenCollection.id()),
onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(sampleDataResourceTokenCollection),
children: [
{
label: "Items",
onClick: () => sampleDataResourceTokenCollection.onDocumentDBDocumentsClick(),
isSelected: () =>
useSelectedNode
.getState()
.isDataNodeSelected(
sampleDataResourceTokenCollection.databaseId,
sampleDataResourceTokenCollection.id(),
[ViewModels.CollectionTabKind.Documents]
),
}, },
], isSelected: () =>
}, useSelectedNode
], .getState()
}; .isDataNodeSelected(
sampleDataResourceTokenCollection.databaseId,
sampleDataResourceTokenCollection.id()
),
onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(sampleDataResourceTokenCollection),
children: [
{
label: "Items",
},
],
},
],
};
setRoot(updatedSampleTree);
}
}, [sampleDataResourceTokenCollection]);
return { return <TreeComponent className="dataResourceTree" rootNode={root || { label: "Sample data not initialized." }} />;
label: undefined,
isExpanded: true,
children: [updatedSampleTree],
};
};
return (
<TreeComponent
className="dataResourceTree"
rootNode={sampleDataResourceTokenCollection ? buildSampleDataTree() : { label: "Sample data not initialized." }}
/>
);
}; };

View File

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

View File

@@ -17,7 +17,7 @@ import "../externals/jquery.typeahead.min.css";
import "../externals/jquery.typeahead.min.js"; import "../externals/jquery.typeahead.min.js";
// Image Dependencies // Image Dependencies
import { QueryCopilotCarousel } from "Explorer/QueryCopilot/CopilotCarousel"; import { QueryCopilotCarousel } from "Explorer/QueryCopilot/CopilotCarousel";
import { QueryCopilotFeedbackModal } from "Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal"; import { QueryCopilotFeedbackModal } from "Explorer/QueryCopilot/QueryCopilotFeedbackModal";
import { useQueryCopilot } from "hooks/useQueryCopilot"; import { useQueryCopilot } from "hooks/useQueryCopilot";
import "../images/CosmosDB_rgb_ui_lighttheme.ico"; import "../images/CosmosDB_rgb_ui_lighttheme.ico";
import hdeConnectImage from "../images/HdeConnectCosmosDB.svg"; import hdeConnectImage from "../images/HdeConnectCosmosDB.svg";
@@ -63,7 +63,7 @@ initializeIcons();
const App: React.FunctionComponent = () => { const App: React.FunctionComponent = () => {
const [isLeftPaneExpanded, setIsLeftPaneExpanded] = useState<boolean>(true); const [isLeftPaneExpanded, setIsLeftPaneExpanded] = useState<boolean>(true);
const isCarouselOpen = useCarousel((state) => state.shouldOpen); const isCarouselOpen = useCarousel((state) => state.shouldOpen);
const isCopilotCarouselOpen = useCarousel((state) => state.showCopilotCarousel); // const isCopilotCarouselOpen = useCarousel((state) => state.showCopilotCarousel);
const shouldShowModal = useQueryCopilot((state) => state.showFeedbackModal); const shouldShowModal = useQueryCopilot((state) => state.showFeedbackModal);
const config = useConfig(); const config = useConfig();
@@ -127,7 +127,7 @@ const App: React.FunctionComponent = () => {
{<QuickstartCarousel isOpen={isCarouselOpen} />} {<QuickstartCarousel isOpen={isCarouselOpen} />}
{<SQLQuickstartTutorial />} {<SQLQuickstartTutorial />}
{<MongoQuickstartTutorial />} {<MongoQuickstartTutorial />}
{<QueryCopilotCarousel isOpen={isCopilotCarouselOpen} explorer={explorer} />} {<QueryCopilotCarousel isOpen={true} explorer={explorer} />}
{shouldShowModal && <QueryCopilotFeedbackModal />} {shouldShowModal && <QueryCopilotFeedbackModal />}
</div> </div>
); );

View File

@@ -1,8 +1,7 @@
import { initializeIcons } from "@fluentui/react";
import { configure } from "enzyme"; import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16"; import Adapter from "enzyme-adapter-react-16";
import "jest-canvas-mock"; import "jest-canvas-mock";
import enableHooks from "jest-react-hooks-shallow"; import { initializeIcons } from "@fluentui/react";
import { TextDecoder, TextEncoder } from "util"; import { TextDecoder, TextEncoder } from "util";
configure({ adapter: new Adapter() }); configure({ adapter: new Adapter() });
initializeIcons(); initializeIcons();
@@ -11,30 +10,6 @@ if (typeof window.URL.createObjectURL === "undefined") {
Object.defineProperty(window.URL, "createObjectURL", { value: () => {} }); Object.defineProperty(window.URL, "createObjectURL", { value: () => {} });
} }
enableHooks(jest, { dontMockByDefault: true });
const localStorageMock = (function () {
let store: { [key: string]: string } = {};
return {
getItem: function (key: string) {
return store[key] || null;
},
setItem: function (key: string, value: string) {
store[key] = value.toString();
},
removeItem: function (key: string) {
delete store[key];
},
clear: function () {
store = {};
},
};
})();
Object.defineProperty(window, "localStorage", {
value: localStorageMock,
});
// TODO Remove when jquery and documentdbclient SDK are removed // TODO Remove when jquery and documentdbclient SDK are removed
(<any>window).$ = (<any>window).jQuery = require("jquery"); (<any>window).$ = (<any>window).jQuery = require("jquery");
(<any>global).$ = (<any>global).$.jQuery = require("jquery"); (<any>global).$ = (<any>global).$.jQuery = require("jquery");

View File

@@ -16,8 +16,8 @@ test("Cassandra keyspace and table CRUD", async () => {
await explorer.click('[data-test="New Table"]'); await explorer.click('[data-test="New Table"]');
await explorer.click('[aria-label="Keyspace id"]'); await explorer.click('[aria-label="Keyspace id"]');
await explorer.fill('[aria-label="Keyspace id"]', keyspaceId); await explorer.fill('[aria-label="Keyspace id"]', keyspaceId);
await explorer.click('[aria-label="addCollection-table Id Create table"]'); await explorer.click('[aria-label="addCollection-tableId"]');
await explorer.fill('[aria-label="addCollection-table Id Create table"]', tableId); await explorer.fill('[aria-label="addCollection-tableId"]', tableId);
await explorer.click("#sidePanelOkButton"); await explorer.click("#sidePanelOkButton");
await explorer.click(`.nodeItem >> text=${keyspaceId}`); await explorer.click(`.nodeItem >> text=${keyspaceId}`);
await explorer.click(`[data-test="${tableId}"] [aria-label="More"]`); await explorer.click(`[data-test="${tableId}"] [aria-label="More"]`);