mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-25 11:51:07 +00:00
Compare commits
1 Commits
update_cop
...
upload_doc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1d62d34d1 |
@@ -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 {
|
||||||
|
|||||||
@@ -28,4 +28,4 @@
|
|||||||
.clickDisabled {
|
.clickDisabled {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
20
package-lock.json
generated
20
package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 '\' '/' '#' '?'"
|
||||||
|
|||||||
@@ -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() && (
|
||||||
|
|||||||
@@ -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=""
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -140,7 +140,6 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
|||||||
setDatabaseInput(newInput);
|
setDatabaseInput(newInput);
|
||||||
}}
|
}}
|
||||||
ariaLabel={confirmDatabase}
|
ariaLabel={confirmDatabase}
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{isLastNonEmptyDatabase() && (
|
{isLastNonEmptyDatabase() && (
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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=""
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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 it’s accurate and appropriate before using it.
|
AI-generated content can have mistakes. Make sure it’s 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>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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 it’s 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>
|
|
||||||
`;
|
|
||||||
@@ -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)",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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" });
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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={
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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'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'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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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(),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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"}>
|
||||||
|
|||||||
@@ -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." }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -131,9 +131,6 @@ export enum Action {
|
|||||||
LaunchUITour,
|
LaunchUITour,
|
||||||
CancelUITour,
|
CancelUITour,
|
||||||
CompleteUITour,
|
CompleteUITour,
|
||||||
OpenQueryCopilotFromSplashScreen,
|
|
||||||
OpenQueryCopilotFromNewQuery,
|
|
||||||
ExecuteQueryGeneratedFromQueryCopilot,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ActionModifiers = {
|
export const ActionModifiers = {
|
||||||
|
|||||||
@@ -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() }),
|
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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"]`);
|
||||||
|
|||||||
Reference in New Issue
Block a user