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
45 changed files with 577 additions and 3134 deletions

View File

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

View File

@@ -28,4 +28,4 @@
.clickDisabled { .clickDisabled {
pointer-events: none; pointer-events: none;
} }
} }

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",
@@ -233,4 +232,4 @@
"prettier": { "prettier": {
"printWidth": 120 "printWidth": 120
} }
} }

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

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

View File

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

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

@@ -1,5 +1,3 @@
import { Action } from "Shared/Telemetry/TelemetryConstants";
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
import { ReactTabKind, useTabs } from "hooks/useTabs"; import { ReactTabKind, useTabs } from "hooks/useTabs";
import * as React from "react"; import * as React from "react";
import AddCollectionIcon from "../../../../images/AddCollection.svg"; import AddCollectionIcon from "../../../../images/AddCollection.svg";
@@ -270,6 +268,7 @@ function createNewCollectionGroup(container: Explorer): CommandButtonComponentPr
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
id: "createNewContainerCommandButton", id: "createNewContainerCommandButton",
disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(),
}; };
} }
@@ -315,6 +314,7 @@ function createNewDatabase(container: Explorer): CommandButtonComponentProps {
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(),
}; };
} }
@@ -328,7 +328,6 @@ function createNewSQLQueryButton(selectedNodeState: SelectedNodeState): CommandB
onCommandClick: () => { onCommandClick: () => {
if (useSelectedNode.getState().isQueryCopilotCollectionSelected()) { if (useSelectedNode.getState().isQueryCopilotCollectionSelected()) {
useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot); useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot);
traceOpen(Action.OpenQueryCopilotFromNewQuery, { apiType: userContext.apiType });
} else { } else {
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection(); const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection); selectedCollection && selectedCollection.onNewQueryClick(selectedCollection);
@@ -494,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

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

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

@@ -60,7 +60,7 @@ export const WelcomeModal = ({ visible }: { visible: boolean }): JSX.Element =>
<Text> <Text>
Ask Copilot to generate a query by describing the query in your words. Ask Copilot to generate a query by describing the query in your words.
<br /> <br />
<Link href="http://aka.ms/cdb-copilot-learn-more">Learn more</Link> <Link href="">Learn more</Link>
</Text> </Text>
</Stack.Item> </Stack.Item>
<Stack.Item align="center" className="text"> <Stack.Item align="center" className="text">
@@ -78,7 +78,7 @@ export const WelcomeModal = ({ visible }: { visible: boolean }): JSX.Element =>
<Text> <Text>
AI-generated content can have mistakes. Make sure its accurate and appropriate before using it. AI-generated content can have mistakes. Make sure its accurate and appropriate before using it.
<br /> <br />
<Link href="http://aka.ms/cdb-copilot-preview-terms">Read preview terms</Link> <Link href="">Read preview terms</Link>
</Text> </Text>
</Stack.Item> </Stack.Item>
<Stack.Item align="center" className="text"> <Stack.Item align="center" className="text">
@@ -96,7 +96,7 @@ export const WelcomeModal = ({ visible }: { visible: boolean }): JSX.Element =>
<Text> <Text>
Copilot is setup on a sample database we have configured for you at no cost Copilot is setup on a sample database we have configured for you at no cost
<br /> <br />
<Link href="http://aka.ms/cdb-copilot-learn-more">Learn more</Link> <Link href="">Learn more</Link>
</Text> </Text>
</Stack.Item> </Stack.Item>
</Stack> </Stack>

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="http://aka.ms/cdb-copilot-learn-more"
>
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="http://aka.ms/cdb-copilot-preview-terms"
>
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="http://aka.ms/cdb-copilot-learn-more"
>
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 }}>
@@ -80,7 +79,7 @@ export const QueryCopilotFeedbackModal: React.FC = (): JSX.Element => {
<Text style={{ fontSize: 12, marginBottom: 14 }}> <Text style={{ fontSize: 12, marginBottom: 14 }}>
By pressing submit, your feedback will be used to improve Microsoft products and services. IT admins for your By pressing submit, your feedback will be used to improve Microsoft products and services. IT admins for your
organization will be able to view and manage your feedback data.{" "} organization will be able to view and manage your feedback data.{" "}
<Link href="https://privacy.microsoft.com/privacystatement" target="_blank"> <Link href="" target="_blank">
Privacy statement Privacy statement
</Link> </Link>
</Text> </Text>

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 { JunoEndpoints, 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,11 +37,9 @@ 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 { Action } from "Shared/Telemetry/TelemetryConstants";
import { traceFailure, traceStart, traceSuccess } from "Shared/Telemetry/TelemetryProcessor";
import { userContext } from "UserContext"; import { userContext } from "UserContext";
import { queryPagesUntilContentPresent } from "Utils/QueryUtils"; import { queryPagesUntilContentPresent } from "Utils/QueryUtils";
import { useQueryCopilot } from "hooks/useQueryCopilot"; import { useQueryCopilot } from "hooks/useQueryCopilot";
@@ -46,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";
@@ -80,7 +85,6 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
const hideFeedbackModalForLikedQueries = useQueryCopilot((state) => state.hideFeedbackModalForLikedQueries); const hideFeedbackModalForLikedQueries = useQueryCopilot((state) => state.hideFeedbackModalForLikedQueries);
const [userPrompt, setUserPrompt] = useState<string>(initialInput || ""); const [userPrompt, setUserPrompt] = useState<string>(initialInput || "");
const [generatedQuery, setGeneratedQuery] = useState<string>(""); const [generatedQuery, setGeneratedQuery] = useState<string>("");
const [generatedQueryComments, setGeneratedQueryComments] = useState<string>("");
const [query, setQuery] = useState<string>(""); const [query, setQuery] = useState<string>("");
const [selectedQuery, setSelectedQuery] = useState<string>(""); const [selectedQuery, setSelectedQuery] = useState<string>("");
const [isGeneratingQuery, setIsGeneratingQuery] = useState<boolean>(false); const [isGeneratingQuery, setIsGeneratingQuery] = useState<boolean>(false);
@@ -124,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);
@@ -151,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> => {
@@ -173,33 +170,23 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
userPrompt: userPrompt, userPrompt: userPrompt,
}; };
setShowDeletePopup(false); setShowDeletePopup(false);
useQueryCopilot.getState().refreshCorrelationId(); const response = await fetch("https://copilotorchestrater.azurewebsites.net/generateSQLQuery", {
const response = await fetch(`${JunoEndpoints.Prod}/generateSQLQuery`, {
method: "POST", method: "POST",
headers: { headers: {
"content-type": "application/json", "content-type": "application/json",
"x-ms-correlationid": useQueryCopilot.getState().correlationId,
}, },
body: JSON.stringify(payload), body: JSON.stringify(payload),
}); });
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);
setGeneratedQueryComments(generateSQLQueryResponse.explanation);
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");
@@ -213,14 +200,15 @@ 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> => {
traceStart(Action.ExecuteQueryGeneratedFromQueryCopilot, {
correlationId: useQueryCopilot.getState().correlationId,
userPrompt: userPrompt,
generatedQuery: generatedQuery,
generatedQueryComments: generatedQueryComments,
executedQuery: selectedQuery || query,
});
const queryToExecute = selectedQuery || query; const queryToExecute = selectedQuery || query;
const queryIterator = querySampleDocuments(queryToExecute, { const queryIterator = querySampleDocuments(queryToExecute, {
enableCrossPartitionQuery: shouldEnableCrossPartitionKey(), enableCrossPartitionQuery: shouldEnableCrossPartitionKey(),
@@ -245,16 +233,8 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
setQueryResults(queryResults); setQueryResults(queryResults);
setErrorMessage(""); setErrorMessage("");
setShowErrorMessageBar(false);
traceSuccess(Action.ExecuteQueryGeneratedFromQueryCopilot, {
correlationId: useQueryCopilot.getState().correlationId,
});
} catch (error) { } catch (error) {
const errorMessage = getErrorMessage(error); const errorMessage = getErrorMessage(error);
traceFailure(Action.ExecuteQueryGeneratedFromQueryCopilot, {
correlationId: useQueryCopilot.getState().correlationId,
errorMessage: errorMessage,
});
setErrorMessage(errorMessage); setErrorMessage(errorMessage);
handleError(errorMessage, "executeQueryCopilotTab"); handleError(errorMessage, "executeQueryCopilotTab");
useTabs.getState().setIsQueryErrorThrown(true); useTabs.getState().setIsQueryErrorThrown(true);
@@ -274,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 = {
@@ -285,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) {
@@ -317,12 +294,6 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
setShowCallout(false); setShowCallout(false);
}; };
const startGenerateQueryProcess = () => {
updateHistories();
generateSQLQuery();
resetButtonState();
};
React.useEffect(() => { React.useEffect(() => {
useCommandBar.getState().setContextButtons(getCommandbarButtons()); useCommandBar.getState().setContextButtons(getCommandbarButtons());
}, [query, selectedQuery]); }, [query, selectedQuery]);
@@ -336,284 +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 }}
onKeyDown={(e) => { styles={{ root: { width: "95%" } }}
if (e.key === "Enter") { disabled={isGeneratingQuery}
startGenerateQueryProcess(); autoComplete="off"
} />
}} {copilotTeachingBubbleVisible && (
style={{ lineHeight: 30 }} <TeachingBubble
styles={{ root: { width: "95%" } }} calloutProps={{ directionalHint: DirectionalHint.bottomCenter }}
disabled={isGeneratingQuery} target="#naturalLanguageInput"
autoComplete="off" hasCloseButton={true}
/> closeButtonAriaLabel="Close"
{copilotTeachingBubbleVisible && ( onDismiss={toggleCopilotTeachingBubbleVisible}
<TeachingBubble hasSmallHeadline={true}
calloutProps={{ directionalHint: DirectionalHint.bottomCenter }} headline="Write a prompt"
target="#naturalLanguageInput" >
hasCloseButton={true} Write a prompt here and Copilot will generate the query for you. You can also choose from our{" "}
closeButtonAriaLabel="Close" <Link
onDismiss={toggleCopilotTeachingBubbleVisible} onClick={() => {
hasSmallHeadline={true} setShowSamplePrompts(true);
headline="Write a prompt" 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={() => startGenerateQueryProcess()} <Callout
/> styles={{ root: { minWidth: 400 } }}
{isGeneratingQuery && <Spinner style={{ marginLeft: 8 }} />} target="#naturalLanguageInput"
{showSamplePrompts && ( isBeakVisible={false}
<Callout onDismiss={() => setShowSamplePrompts(false)}
styles={{ root: { minWidth: 400 } }} directionalHintFixed={true}
target="#naturalLanguageInput" directionalHint={DirectionalHint.bottomLeftEdge}
isBeakVisible={false} alignTargetEdge={true}
onDismiss={() => setShowSamplePrompts(false)} gapSpace={4}
directionalHintFixed={true} >
directionalHint={DirectionalHint.bottomLeftEdge} <Stack>
alignTargetEdge={true} {filteredHistories?.length > 0 && (
gapSpace={4} <Stack>
> <Text
<Stack> style={{
{filteredHistories?.length > 0 && ( width: "100%",
<Stack> fontSize: 14,
<Text fontWeight: 600,
style={{ color: "#0078D4",
width: "100%", marginLeft: 16,
fontSize: 14, padding: "4px 0",
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="http://aka.ms/cdb-copilot-writing">
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="http://aka.ms/cdb-copilot-preview-terms" 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,225 +0,0 @@
import { FeedOptions } from "@azure/cosmos";
import { QueryCopilotSampleContainerSchema } from "Common/Constants";
import { handleError } from "Common/ErrorHandlingUtils";
import { sampleDataClient } from "Common/SampleDataClient";
import * as commonUtils from "Common/dataAccess/queryDocuments";
import DocumentId from "Explorer/Tree/DocumentId";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import { querySampleDocuments, readSampleDocument, submitFeedback } from "./QueryCopilotUtilities";
jest.mock("Explorer/Tree/DocumentId", () => {
return jest.fn().mockImplementation(() => {
return {
id: jest.fn(),
loadDocument: jest.fn(),
};
});
});
jest.mock("Utils/NotificationConsoleUtils", () => ({
logConsoleProgress: jest.fn(),
logConsoleError: jest.fn(),
}));
jest.mock("@azure/cosmos", () => ({
FeedOptions: jest.fn(),
QueryIterator: jest.fn(),
}));
jest.mock("Common/ErrorHandlingUtils", () => ({
handleError: jest.fn(),
}));
jest.mock("Utils/NotificationConsoleUtils", () => ({
logConsoleProgress: jest.fn().mockReturnValue((): void => undefined),
}));
jest.mock("Common/dataAccess/queryDocuments", () => ({
getCommonQueryOptions: jest.fn((options) => options),
}));
jest.mock("Common/SampleDataClient");
jest.mock("node-fetch");
describe("QueryCopilotUtilities", () => {
beforeEach(() => jest.clearAllMocks());
describe("submitFeedback", () => {
const payload = {
like: "like",
generatedSql: "GeneratedQuery",
userPrompt: "UserPrompt",
description: "Description",
contact: "Contact",
containerSchema: QueryCopilotSampleContainerSchema,
};
it("should call fetch with the payload with like", async () => {
const mockFetch = jest.fn().mockResolvedValueOnce({});
globalThis.fetch = mockFetch;
useQueryCopilot.getState().refreshCorrelationId();
await submitFeedback({
likeQuery: true,
generatedQuery: "GeneratedQuery",
userPrompt: "UserPrompt",
description: "Description",
contact: "Contact",
});
expect(mockFetch).toHaveBeenCalledWith(
"https://copilotorchestrater.azurewebsites.net/feedback",
expect.objectContaining({
method: "POST",
headers: {
"content-type": "application/json",
"x-ms-correlationid": useQueryCopilot.getState().correlationId,
},
})
);
const actualBody = JSON.parse(mockFetch.mock.calls[0][1].body);
expect(actualBody).toEqual(payload);
});
it("should call fetch with the payload with unlike and empty parameters", async () => {
payload.like = "dislike";
payload.description = "";
payload.contact = "";
const mockFetch = jest.fn().mockResolvedValueOnce({});
globalThis.fetch = mockFetch;
useQueryCopilot.getState().refreshCorrelationId();
await submitFeedback({
likeQuery: false,
generatedQuery: "GeneratedQuery",
userPrompt: "UserPrompt",
description: undefined,
contact: undefined,
});
expect(mockFetch).toHaveBeenCalledWith(
"https://copilotorchestrater.azurewebsites.net/feedback",
expect.objectContaining({
method: "POST",
headers: {
"content-type": "application/json",
"x-ms-correlationid": useQueryCopilot.getState().correlationId,
},
})
);
const actualBody = JSON.parse(mockFetch.mock.calls[0][1].body);
expect(actualBody).toEqual(payload);
});
it("should handle errors and call handleError", async () => {
globalThis.fetch = jest.fn().mockRejectedValueOnce(new Error("Mock error"));
await submitFeedback({
likeQuery: true,
generatedQuery: "GeneratedQuery",
userPrompt: "UserPrompt",
description: "Description",
contact: "Contact",
}).catch((error) => {
expect(error.message).toEqual("Mock error");
});
expect(handleError).toHaveBeenCalledWith(new Error("Mock error"), expect.any(String));
});
});
describe("querySampleDocuments", () => {
(sampleDataClient as jest.Mock).mockReturnValue({
database: jest.fn().mockReturnValue({
container: jest.fn().mockReturnValue({
items: {
query: jest.fn().mockReturnValue([]),
},
}),
}),
});
it("calls getCommonQueryOptions with the provided options", () => {
const query = "sample query";
const options: FeedOptions = { maxItemCount: 10 };
querySampleDocuments(query, options);
expect(commonUtils.getCommonQueryOptions).toHaveBeenCalledWith(options);
});
it("returns the result of items.query method", () => {
const query = "sample query";
const options: FeedOptions = { maxItemCount: 10 };
const mockResult = [
{ id: 1, name: "Document 1" },
{ id: 2, name: "Document 2" },
];
// Mock the items.query method to return the mockResult
(sampleDataClient().database("CopilotSampleDb").container("SampleContainer").items
.query as jest.Mock).mockReturnValue(mockResult);
const result = querySampleDocuments(query, options);
expect(result).toEqual(mockResult);
});
});
describe("readSampleDocument", () => {
it("should call the read method with the correct parameters", async () => {
(sampleDataClient as jest.Mock).mockReturnValue({
database: jest.fn().mockReturnValue({
container: jest.fn().mockReturnValue({
item: jest.fn().mockReturnValue({
read: jest.fn().mockResolvedValue({
resource: {},
}),
}),
}),
}),
});
const documentId = new DocumentId(null, "DocumentId", []);
const expectedResponse = {};
const result = await readSampleDocument(documentId);
expect(sampleDataClient).toHaveBeenCalled();
expect(sampleDataClient().database).toHaveBeenCalledWith("CopilotSampleDb");
expect(sampleDataClient().database("CopilotSampleDb").container).toHaveBeenCalledWith("SampleContainer");
expect(
sampleDataClient().database("CopilotSampleDb").container("SampleContainer").item("DocumentId", undefined).read
).toHaveBeenCalled();
expect(result).toEqual(expectedResponse);
});
it("should handle an error and re-throw it", async () => {
(sampleDataClient as jest.Mock).mockReturnValue({
database: jest.fn().mockReturnValue({
container: jest.fn().mockReturnValue({
item: jest.fn().mockReturnValue({
read: jest.fn().mockRejectedValue(new Error("Mock error")),
}),
}),
}),
});
const errorMock = new Error("Mock error");
const documentId = new DocumentId(null, "DocumentId", []);
await expect(readSampleDocument(documentId)).rejects.toStrictEqual(errorMock);
expect(sampleDataClient).toHaveBeenCalled();
expect(sampleDataClient().database).toHaveBeenCalledWith("CopilotSampleDb");
expect(sampleDataClient().database("CopilotSampleDb").container).toHaveBeenCalledWith("SampleContainer");
expect(
sampleDataClient().database("CopilotSampleDb").container("SampleContainer").item("DocumentId", undefined).read
).toHaveBeenCalled();
expect(handleError).toHaveBeenCalledWith(errorMock, "ReadDocument", expect.any(String));
});
});
});

View File

@@ -1,17 +1,5 @@
import { FeedOptions, Item, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos"; import { QueryCopilotSampleContainerSchema } from "Common/Constants";
import {
JunoEndpoints,
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";
import { useQueryCopilot } from "hooks/useQueryCopilot";
interface FeedbackParams { interface FeedbackParams {
likeQuery: boolean; likeQuery: boolean;
@@ -33,42 +21,16 @@ export const submitFeedback = async (params: FeedbackParams): Promise<void> => {
contact: contact || "", contact: contact || "",
}; };
await fetch(`${JunoEndpoints.Prod}/feedback`, { const response = await fetch("https://copilotorchestrater.azurewebsites.net/feedback", {
method: "POST", method: "POST",
headers: { headers: {
"content-type": "application/json", "content-type": "application/json",
"x-ms-correlationid": useQueryCopilot.getState().correlationId,
}, },
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,149 +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" <StyledLinkBase
href=""
target="_blank"
> >
<Image Read preview terms
src="" </StyledLinkBase>
</Text>
<Stack
className="tabPaneContentContainer"
>
<t
customClassName=""
onDragEnd={null}
onDragStart={null}
onSecondaryPaneSizeChange={null}
percentage={false}
primaryIndex={0}
primaryMinSize={100}
secondaryMinSize={200}
vertical={true}
>
<EditorReact
ariaLabel="Editing Query"
content=""
isReadOnly={false}
language="sql"
lineNumbers="on"
onContentChanged={[Function]}
onContentSelected={[Function]}
/> />
<Text <QueryResultSection
style={ error=""
Object { executeQueryDocumentsPage={[Function]}
"fontSize": 16, isExecuting={false}
"fontWeight": 600, isMongoDB={false}
"marginLeft": 8, queryEditorContent=""
}
}
>
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]}
onKeyDown={[Function]}
style={
Object {
"lineHeight": 30,
}
}
styles={
Object {
"root": Object {
"width": "95%",
},
}
}
value="Write a query to return all records in this table"
/> />
<CustomizedIconButton </t>
disabled={false} </Stack>
iconProps={ <WelcomeModal
Object { visible={true}
"iconName": "Send", />
} <CopyPopup
} setShowCopyPopup={[Function]}
onClick={[Function]} showCopyPopup={false}
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
href="http://aka.ms/cdb-copilot-preview-terms"
target="_blank"
>
Read preview terms
</StyledLinkBase>
</Text>
</Stack>
<Stack
className="tabPaneContentContainer"
>
<t
customClassName=""
onDragEnd={null}
onDragStart={null}
onSecondaryPaneSizeChange={null}
percentage={false}
primaryIndex={0}
primaryMinSize={100}
secondaryMinSize={200}
vertical={true}
>
<EditorReact
ariaLabel="Editing Query"
content=""
isReadOnly={false}
language="sql"
lineNumbers="on"
onContentChanged={[Function]}
onContentSelected={[Function]}
/>
<QueryResultSection
error=""
executeQueryDocumentsPage={[Function]}
isExecuting={false}
isMongoDB={false}
queryEditorContent=""
/>
</t>
</Stack>
<WelcomeModal
visible={true}
/>
<CopyPopup
setShowCopyPopup={[Function]}
showCopyPopup={false}
/>
</div>
</Stack> </Stack>
`; `;

View File

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

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

View File

@@ -1,4 +1,3 @@
import { guid } from "Explorer/Tables/Utilities";
import create, { UseStore } from "zustand"; import create, { UseStore } from "zustand";
interface QueryCopilotState { interface QueryCopilotState {
@@ -7,11 +6,9 @@ interface QueryCopilotState {
userPrompt: string; userPrompt: string;
showFeedbackModal: boolean; showFeedbackModal: boolean;
hideFeedbackModalForLikedQueries: boolean; hideFeedbackModalForLikedQueries: boolean;
correlationId: string;
openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) => void; openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) => void;
closeFeedbackModal: () => void; closeFeedbackModal: () => void;
setHideFeedbackModalForLikedQueries: (hideFeedbackModalForLikedQueries: boolean) => void; setHideFeedbackModalForLikedQueries: (hideFeedbackModalForLikedQueries: boolean) => void;
refreshCorrelationId: () => void;
} }
export const useQueryCopilot: UseStore<QueryCopilotState> = create((set) => ({ export const useQueryCopilot: UseStore<QueryCopilotState> = create((set) => ({
@@ -20,11 +17,9 @@ export const useQueryCopilot: UseStore<QueryCopilotState> = create((set) => ({
userPrompt: "", userPrompt: "",
showFeedbackModal: false, showFeedbackModal: false,
hideFeedbackModalForLikedQueries: false, hideFeedbackModalForLikedQueries: false,
correlationId: "",
openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) => openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) =>
set({ generatedQuery, likeQuery, userPrompt, showFeedbackModal: true }), set({ generatedQuery, likeQuery, userPrompt, showFeedbackModal: true }),
closeFeedbackModal: () => set({ generatedQuery: "", likeQuery: false, userPrompt: "", showFeedbackModal: false }), closeFeedbackModal: () => set({ generatedQuery: "", likeQuery: false, userPrompt: "", showFeedbackModal: false }),
setHideFeedbackModalForLikedQueries: (hideFeedbackModalForLikedQueries: boolean) => setHideFeedbackModalForLikedQueries: (hideFeedbackModalForLikedQueries: boolean) =>
set({ hideFeedbackModalForLikedQueries }), set({ hideFeedbackModalForLikedQueries }),
refreshCorrelationId: () => set({ correlationId: guid() }),
})); }));

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"]`);