mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-04-16 19:39:19 +01:00
Perf/copilot cleanup and optimizations (#2447)
* perf: remove deprecated copilot feature, add ARM timeouts, fix race conditions - Remove entire QueryCopilot feature (~50 files deleted, ~30 files cleaned) - Remove CopilotConfigured and SampleDataLoaded metric phases - Fix DatabaseTreeRendered 76% stuck rate (remove one-shot guard in useMetricPhases) - Add 8s default timeout to ARM requests (AbortController-based) - Fix MSAL token forceRefresh (true -> false, use cache) - Add concurrency limit of 5 to collection loading in Explorer - Remove orphaned SampleDataClient.ts and queryCopilotSampleData.json - Clean up dead sampleDataConnectionInfo field from UserContext * Clean up copilot and optimize initialization * Clean up copilot and optimize initialization
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -259,7 +259,6 @@ export class Areas {
|
||||
public static Tab: string = "Tab";
|
||||
public static ShareDialog: string = "Share Access Dialog";
|
||||
public static Notebook: string = "Notebook";
|
||||
public static Copilot: string = "Copilot";
|
||||
public static CloudShell: string = "Cloud Shell";
|
||||
}
|
||||
|
||||
@@ -456,7 +455,6 @@ export enum ContainerStatusType {
|
||||
|
||||
export enum PoolIdType {
|
||||
DefaultPoolId = "default",
|
||||
QueryCopilot = "query-copilot",
|
||||
}
|
||||
|
||||
export const EmulatorMasterKey =
|
||||
@@ -543,243 +541,6 @@ export class FeedbackLabels {
|
||||
public static readonly provideFeedback: string = "Provide feedback";
|
||||
}
|
||||
|
||||
export const QueryCopilotSampleDatabaseId = "CopilotSampleDB";
|
||||
export const QueryCopilotSampleContainerId = "SampleContainer";
|
||||
|
||||
export const QueryCopilotSampleContainerSchema = {
|
||||
product: {
|
||||
sampleData: {
|
||||
id: "c415e70f-9bf5-4cda-aebe-a290cb8b94c2",
|
||||
name: "Amazing Phone 3000 (Black)",
|
||||
price: 223.33,
|
||||
category: "Electronics",
|
||||
description:
|
||||
"This Amazing Phone 3000 (Black) is made of black metal! It has a very well made aluminum body and it feels very comfortable. We loved the sound that comes out of it! Also, the design of the phone was a little loose at first because I was using the camera and felt uncomfortable wearing it. The phone is actually made slightly smaller than these photos! This is due to the addition of a 3.3mm filter",
|
||||
stock: 84,
|
||||
countryOfOrigin: "USA",
|
||||
firstAvailable: "2018-09-07 19:41:44",
|
||||
priceHistory: [238.68, 234.7, 221.49, 205.88, 220.15],
|
||||
customerRatings: [
|
||||
{
|
||||
username: "steven66",
|
||||
firstName: "Carol",
|
||||
gender: "female",
|
||||
lastName: "Shelton",
|
||||
age: "25-35",
|
||||
area: "suburban",
|
||||
address: "261 Collins Burgs Apt. 332\nNorth Taylor, NM 32268",
|
||||
stars: 5,
|
||||
date: "2021-04-22 13:42:14",
|
||||
verifiedUser: true,
|
||||
},
|
||||
{
|
||||
username: "khudson",
|
||||
firstName: "Ronald",
|
||||
gender: "male",
|
||||
lastName: "Webb",
|
||||
age: "18-24",
|
||||
area: "suburban",
|
||||
address: "9912 Parker Court Apt. 068\nNorth Austin, HI 76225",
|
||||
stars: 5,
|
||||
date: "2021-02-07 07:00:22",
|
||||
verifiedUser: false,
|
||||
},
|
||||
{
|
||||
username: "lfrancis",
|
||||
firstName: "Brady",
|
||||
gender: "male",
|
||||
lastName: "Wright",
|
||||
age: "35-45",
|
||||
area: "urban",
|
||||
address: "PSC 5437, Box 3159\nAPO AA 26385",
|
||||
stars: 2,
|
||||
date: "2022-02-23 21:40:10",
|
||||
verifiedUser: false,
|
||||
},
|
||||
{
|
||||
username: "nicolemartinez",
|
||||
firstName: "Megan",
|
||||
gender: "female",
|
||||
lastName: "Tran",
|
||||
age: "18-24",
|
||||
area: "rural",
|
||||
address: "7445 Salazar Brooks\nNew Sarah, PW 18097",
|
||||
stars: 4,
|
||||
date: "2021-09-01 22:21:40",
|
||||
verifiedUser: false,
|
||||
},
|
||||
{
|
||||
username: "uguzman",
|
||||
firstName: "Deanna",
|
||||
gender: "female",
|
||||
lastName: "Campbell",
|
||||
age: "18-24",
|
||||
area: "urban",
|
||||
address: "41104 Moreno Fort Suite 872\nPort Michaelbury, AK 48712",
|
||||
stars: 1,
|
||||
date: "2022-03-07 02:23:14",
|
||||
verifiedUser: false,
|
||||
},
|
||||
{
|
||||
username: "rebeccahunt",
|
||||
firstName: "Jared",
|
||||
gender: "male",
|
||||
lastName: "Lopez",
|
||||
age: "18-24",
|
||||
area: "rural",
|
||||
address: "392 Morgan Village Apt. 785\nGreenshire, CT 05921",
|
||||
stars: 5,
|
||||
date: "2021-04-17 04:17:49",
|
||||
verifiedUser: false,
|
||||
},
|
||||
],
|
||||
rareProperty: true,
|
||||
},
|
||||
schema: {
|
||||
properties: {
|
||||
id: {
|
||||
type: "string",
|
||||
},
|
||||
name: {
|
||||
type: "string",
|
||||
},
|
||||
price: {
|
||||
type: "number",
|
||||
},
|
||||
category: {
|
||||
type: "string",
|
||||
},
|
||||
description: {
|
||||
type: "string",
|
||||
},
|
||||
stock: {
|
||||
type: "number",
|
||||
},
|
||||
countryOfOrigin: {
|
||||
type: "string",
|
||||
},
|
||||
firstAvailable: {
|
||||
type: "string",
|
||||
},
|
||||
priceHistory: {
|
||||
items: {
|
||||
type: "number",
|
||||
},
|
||||
type: "array",
|
||||
},
|
||||
customerRatings: {
|
||||
items: {
|
||||
properties: {
|
||||
username: {
|
||||
type: "string",
|
||||
},
|
||||
firstName: {
|
||||
type: "string",
|
||||
},
|
||||
gender: {
|
||||
type: "string",
|
||||
},
|
||||
lastName: {
|
||||
type: "string",
|
||||
},
|
||||
age: {
|
||||
type: "string",
|
||||
},
|
||||
area: {
|
||||
type: "string",
|
||||
},
|
||||
address: {
|
||||
type: "string",
|
||||
},
|
||||
stars: {
|
||||
type: "number",
|
||||
},
|
||||
date: {
|
||||
type: "string",
|
||||
},
|
||||
verifiedUser: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
type: "object",
|
||||
},
|
||||
type: "array",
|
||||
},
|
||||
rareProperty: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
type: "object",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const ShortenedQueryCopilotSampleContainerSchema = {
|
||||
containerSchema: {
|
||||
product: {
|
||||
sampleData: {
|
||||
categoryName: "Components, Saddles",
|
||||
|
||||
name: "LL Road Seat/Saddle",
|
||||
|
||||
price: 27.12,
|
||||
|
||||
tags: [
|
||||
{
|
||||
id: "0573D684-9140-4DEE-89AF-4E4A90E65666",
|
||||
|
||||
name: "Tag-113",
|
||||
},
|
||||
|
||||
{
|
||||
id: "6C2F05C8-1E61-4912-BE1A-C67A378429BB",
|
||||
|
||||
name: "Tag-5",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
schema: {
|
||||
properties: {
|
||||
categoryName: {
|
||||
type: "string",
|
||||
},
|
||||
|
||||
name: {
|
||||
type: "string",
|
||||
},
|
||||
|
||||
price: {
|
||||
type: "number",
|
||||
},
|
||||
|
||||
tags: {
|
||||
items: {
|
||||
properties: {
|
||||
id: {
|
||||
type: "string",
|
||||
},
|
||||
|
||||
name: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
|
||||
type: "object",
|
||||
},
|
||||
|
||||
type: "array",
|
||||
},
|
||||
},
|
||||
|
||||
type: "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
userPrompt: "find all products",
|
||||
};
|
||||
|
||||
export enum MongoGuidRepresentation {
|
||||
Standard = "Standard",
|
||||
CSharpLegacy = "CSharpLegacy",
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import * as Cosmos from "@azure/cosmos";
|
||||
import { userContext } from "UserContext";
|
||||
|
||||
let _sampleDataclient: Cosmos.CosmosClient;
|
||||
|
||||
export function sampleDataClient(): Cosmos.CosmosClient {
|
||||
if (_sampleDataclient) {
|
||||
return _sampleDataclient;
|
||||
}
|
||||
|
||||
const sampleDataConnectionInfo = userContext.sampleDataConnectionInfo;
|
||||
const options: Cosmos.CosmosClientOptions = {
|
||||
endpoint: sampleDataConnectionInfo.accountEndpoint,
|
||||
tokenProvider: async () => {
|
||||
const sampleDataConnectionInfo = userContext.sampleDataConnectionInfo;
|
||||
return Promise.resolve(sampleDataConnectionInfo.resourceToken);
|
||||
},
|
||||
connectionPolicy: {
|
||||
enableEndpointDiscovery: false,
|
||||
},
|
||||
userAgentSuffix: "Azure Portal",
|
||||
};
|
||||
|
||||
_sampleDataclient = new Cosmos.CosmosClient(options);
|
||||
return _sampleDataclient;
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
import { CosmosClient } from "@azure/cosmos";
|
||||
import { sampleDataClient } from "Common/SampleDataClient";
|
||||
import { userContext } from "UserContext";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import { client } from "../CosmosClient";
|
||||
import { handleError } from "../ErrorHandlingUtils";
|
||||
@@ -10,20 +8,6 @@ export async function readCollection(databaseId: string, collectionId: string):
|
||||
return await readCollectionInternal(cosmosClient, databaseId, collectionId);
|
||||
}
|
||||
|
||||
export async function readSampleCollection(): Promise<DataModels.Collection> {
|
||||
const cosmosClient = sampleDataClient();
|
||||
|
||||
const sampleDataConnectionInfo = userContext.sampleDataConnectionInfo;
|
||||
const databaseId = sampleDataConnectionInfo?.databaseId;
|
||||
const collectionId = sampleDataConnectionInfo?.collectionId;
|
||||
|
||||
if (!databaseId || !collectionId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return await readCollectionInternal(cosmosClient, databaseId, collectionId);
|
||||
}
|
||||
|
||||
export async function readCollectionInternal(
|
||||
cosmosClient: CosmosClient,
|
||||
databaseId: string,
|
||||
|
||||
@@ -735,10 +735,6 @@ export enum PhoenixErrorType {
|
||||
UserMissingPermissionsError = "UserMissingPermissionsError",
|
||||
}
|
||||
|
||||
export interface CopilotEnabledConfiguration {
|
||||
isEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface FeatureRegistration {
|
||||
name: string;
|
||||
properties: {
|
||||
|
||||
@@ -9,9 +9,6 @@ import {
|
||||
import { useDatabases } from "Explorer/useDatabases";
|
||||
import { Keys, t } from "Localization";
|
||||
import { isFabric, isFabricNative, openRestoreContainerDialog } from "Platform/Fabric/FabricUtil";
|
||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
|
||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||
import React from "react";
|
||||
import AddCollectionIcon from "../../images/AddCollection.svg";
|
||||
import AddSqlQueryIcon from "../../images/AddSqlQuery_16x16.svg";
|
||||
@@ -218,29 +215,7 @@ export const createCollectionContextMenuButton = (
|
||||
};
|
||||
|
||||
export const createSampleCollectionContextMenuButton = (): TreeNodeMenuItem[] => {
|
||||
const items: TreeNodeMenuItem[] = [];
|
||||
if (userContext.apiType === "SQL") {
|
||||
const copilotVersion = userContext.features.copilotVersion;
|
||||
if (copilotVersion === "v1.0") {
|
||||
items.push({
|
||||
iconSrc: AddSqlQueryIcon,
|
||||
onClick: () => {
|
||||
useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot);
|
||||
traceOpen(Action.OpenQueryCopilotFromNewQuery, { apiType: userContext.apiType });
|
||||
},
|
||||
label: t(Keys.contextMenu.newSqlQuery),
|
||||
});
|
||||
} else if (copilotVersion === "v2.0") {
|
||||
const sampleCollection = useDatabases.getState().sampleDataResourceTokenCollection;
|
||||
items.push({
|
||||
iconSrc: AddSqlQueryIcon,
|
||||
onClick: () => sampleCollection && sampleCollection.onNewQueryClick(sampleCollection, undefined),
|
||||
label: t(Keys.contextMenu.newSqlQuery),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
return [];
|
||||
};
|
||||
|
||||
export const createStoreProcedureContextMenuItems = (
|
||||
|
||||
@@ -23,17 +23,10 @@ export class ContainerSampleGenerator {
|
||||
/**
|
||||
* Factory function to load the json data file
|
||||
*/
|
||||
public static async createSampleGeneratorAsync(
|
||||
container: Explorer,
|
||||
isCopilot?: boolean,
|
||||
): Promise<ContainerSampleGenerator> {
|
||||
public static async createSampleGeneratorAsync(container: Explorer): Promise<ContainerSampleGenerator> {
|
||||
const generator = new ContainerSampleGenerator(container);
|
||||
let dataFileContent: any;
|
||||
if (isCopilot) {
|
||||
dataFileContent = await import(
|
||||
/* webpackChunkName: "queryCopilotSampleData" */ "../../../sampleData/queryCopilotSampleData.json"
|
||||
);
|
||||
} else if (userContext.apiType === "Gremlin") {
|
||||
if (userContext.apiType === "Gremlin") {
|
||||
dataFileContent = await import(
|
||||
/* webpackChunkName: "gremlinSampleJsonData" */ "../../../sampleData/gremlinSampleData.json"
|
||||
);
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import * as msal from "@azure/msal-browser";
|
||||
import { Link } from "@fluentui/react/lib/Link";
|
||||
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
|
||||
import { Environment, getEnvironment } from "Common/EnvironmentUtility";
|
||||
import { sendMessage } from "Common/MessageHandler";
|
||||
import { Platform, configContext } from "ConfigContext";
|
||||
import { MessageTypes } from "Contracts/ExplorerContracts";
|
||||
import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane";
|
||||
import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||
import { IGalleryItem } from "Juno/JunoClient";
|
||||
import {
|
||||
isFabricMirrored,
|
||||
@@ -14,12 +12,10 @@ import {
|
||||
isFabricNative,
|
||||
scheduleRefreshFabricToken,
|
||||
} from "Platform/Fabric/FabricUtil";
|
||||
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||
import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils";
|
||||
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils";
|
||||
import { featureRegistered } from "Utils/FeatureRegistrationUtils";
|
||||
import { update } from "Utils/arm/generatedClients/cosmos/databaseAccounts";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import * as ko from "knockout";
|
||||
import React from "react";
|
||||
import _ from "underscore";
|
||||
@@ -27,11 +23,11 @@ import shallow from "zustand/shallow";
|
||||
import { AuthType } from "../AuthType";
|
||||
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
|
||||
import * as Constants from "../Common/Constants";
|
||||
import { Areas, ConnectionStatusType, HttpStatusCodes, Notebook, PoolIdType } from "../Common/Constants";
|
||||
import { Areas, ConnectionStatusType, HttpStatusCodes, Notebook } from "../Common/Constants";
|
||||
import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHandlingUtils";
|
||||
import * as Logger from "../Common/Logger";
|
||||
import { QueriesClient } from "../Common/QueriesClient";
|
||||
import { readCollection, readSampleCollection } from "../Common/dataAccess/readCollection";
|
||||
import { readCollection } from "../Common/dataAccess/readCollection";
|
||||
import { readDatabases } from "../Common/dataAccess/readDatabases";
|
||||
import * as DataModels from "../Contracts/DataModels";
|
||||
import { ContainerConnectionInfo, IPhoenixServiceInfo, IProvisionData, IResponse } from "../Contracts/DataModels";
|
||||
@@ -471,15 +467,9 @@ export default class Explorer {
|
||||
this._isInitializingNotebooks = false;
|
||||
}
|
||||
|
||||
public async allocateContainer(poolId: PoolIdType, mode?: string): Promise<void> {
|
||||
const shouldUseNotebookStates = poolId === PoolIdType.DefaultPoolId ? true : false;
|
||||
const notebookServerInfo = shouldUseNotebookStates
|
||||
? useNotebook.getState().notebookServerInfo
|
||||
: useQueryCopilot.getState().notebookServerInfo;
|
||||
|
||||
const isAllocating = shouldUseNotebookStates
|
||||
? useNotebook.getState().isAllocating
|
||||
: useQueryCopilot.getState().isAllocatingContainer;
|
||||
public async allocateContainer(): Promise<void> {
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
const isAllocating = useNotebook.getState().isAllocating;
|
||||
if (
|
||||
isAllocating === false &&
|
||||
(notebookServerInfo === undefined ||
|
||||
@@ -489,34 +479,23 @@ export default class Explorer {
|
||||
status: ConnectionStatusType.Connecting,
|
||||
};
|
||||
|
||||
shouldUseNotebookStates && useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||
|
||||
let connectionInfo;
|
||||
let provisionData: IProvisionData;
|
||||
try {
|
||||
TelemetryProcessor.traceStart(Action.PhoenixConnection, {
|
||||
dataExplorerArea: Areas.Notebook,
|
||||
});
|
||||
if (shouldUseNotebookStates) {
|
||||
useNotebook.getState().setIsAllocating(true);
|
||||
provisionData = {
|
||||
cosmosEndpoint: userContext?.databaseAccount?.properties?.documentEndpoint,
|
||||
poolId: undefined,
|
||||
};
|
||||
} else {
|
||||
useQueryCopilot.getState().setIsAllocatingContainer(true);
|
||||
provisionData = {
|
||||
poolId: poolId,
|
||||
databaseId: useTabs.getState().activeTab.collection.databaseId,
|
||||
containerId: useTabs.getState().activeTab.collection.id(),
|
||||
mode: mode,
|
||||
};
|
||||
}
|
||||
useNotebook.getState().setIsAllocating(true);
|
||||
const provisionData: IProvisionData = {
|
||||
cosmosEndpoint: userContext?.databaseAccount?.properties?.documentEndpoint,
|
||||
poolId: undefined,
|
||||
};
|
||||
connectionInfo = await this.phoenixClient.allocateContainer(provisionData);
|
||||
if (!connectionInfo?.data?.phoenixServiceUrl) {
|
||||
throw new Error(`PhoenixServiceUrl is invalid!`);
|
||||
}
|
||||
await this.setNotebookInfo(shouldUseNotebookStates, connectionInfo, connectionStatus);
|
||||
await this.setNotebookInfo(connectionInfo, connectionStatus);
|
||||
TelemetryProcessor.traceSuccess(Action.PhoenixConnection, {
|
||||
dataExplorerArea: Areas.Notebook,
|
||||
});
|
||||
@@ -527,27 +506,21 @@ export default class Explorer {
|
||||
error: getErrorMessage(error),
|
||||
errorStack: getErrorStack(error),
|
||||
});
|
||||
if (shouldUseNotebookStates) {
|
||||
connectionStatus.status = ConnectionStatusType.Failed;
|
||||
shouldUseNotebookStates
|
||||
? useNotebook.getState().resetContainerConnection(connectionStatus)
|
||||
: useQueryCopilot.getState().resetContainerConnection();
|
||||
if (error?.status === HttpStatusCodes.Forbidden && error.message) {
|
||||
useDialog.getState().showOkModalDialog("Connection Failed", `${error.message}`);
|
||||
} else {
|
||||
useDialog
|
||||
.getState()
|
||||
.showOkModalDialog(
|
||||
"Connection Failed",
|
||||
"We are unable to connect to the temporary workspace. Please try again in a few minutes. If the error persists, file a support ticket.",
|
||||
);
|
||||
}
|
||||
connectionStatus.status = ConnectionStatusType.Failed;
|
||||
useNotebook.getState().resetContainerConnection(connectionStatus);
|
||||
if (error?.status === HttpStatusCodes.Forbidden && error.message) {
|
||||
useDialog.getState().showOkModalDialog("Connection Failed", `${error.message}`);
|
||||
} else {
|
||||
useDialog
|
||||
.getState()
|
||||
.showOkModalDialog(
|
||||
"Connection Failed",
|
||||
"We are unable to connect to the temporary workspace. Please try again in a few minutes. If the error persists, file a support ticket.",
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
shouldUseNotebookStates
|
||||
? useNotebook.getState().setIsAllocating(false)
|
||||
: useQueryCopilot.getState().setIsAllocatingContainer(false);
|
||||
useNotebook.getState().setIsAllocating(false);
|
||||
this.refreshCommandBarButtons();
|
||||
this.refreshNotebookList();
|
||||
this._isInitializingNotebooks = false;
|
||||
@@ -556,7 +529,6 @@ export default class Explorer {
|
||||
}
|
||||
|
||||
public async setNotebookInfo(
|
||||
shouldUseNotebookStates: boolean,
|
||||
connectionInfo: IResponse<IPhoenixServiceInfo>,
|
||||
connectionStatus: DataModels.ContainerConnectionInfo,
|
||||
): Promise<void> {
|
||||
@@ -564,10 +536,10 @@ export default class Explorer {
|
||||
forwardingId: connectionInfo.data.forwardingId,
|
||||
dbAccountName: userContext.databaseAccount.name,
|
||||
};
|
||||
await this.phoenixClient.initiateContainerHeartBeat(shouldUseNotebookStates, containerData);
|
||||
await this.phoenixClient.initiateContainerHeartBeat(true, containerData);
|
||||
|
||||
connectionStatus.status = ConnectionStatusType.Connected;
|
||||
shouldUseNotebookStates && useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||
|
||||
const noteBookServerInfo = {
|
||||
notebookServerEndpoint:
|
||||
@@ -577,14 +549,11 @@ export default class Explorer {
|
||||
authToken: userContext.features.notebookServerToken || connectionInfo.data.authToken,
|
||||
forwardingId: connectionInfo.data.forwardingId,
|
||||
};
|
||||
shouldUseNotebookStates
|
||||
? useNotebook.getState().setNotebookServerInfo(noteBookServerInfo)
|
||||
: useQueryCopilot.getState().setNotebookServerInfo(noteBookServerInfo);
|
||||
useNotebook.getState().setNotebookServerInfo(noteBookServerInfo);
|
||||
|
||||
shouldUseNotebookStates &&
|
||||
this.notebookManager?.notebookClient
|
||||
.getMemoryUsage()
|
||||
.then((memoryUsageInfo) => useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo));
|
||||
this.notebookManager?.notebookClient
|
||||
.getMemoryUsage()
|
||||
.then((memoryUsageInfo) => useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo));
|
||||
}
|
||||
|
||||
private getDeltaDatabases(
|
||||
@@ -780,7 +749,7 @@ export default class Explorer {
|
||||
throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`);
|
||||
}
|
||||
if (notebookContentItem.type === NotebookContentItemType.Notebook && useNotebook.getState().isPhoenixNotebooks) {
|
||||
await this.allocateContainer(PoolIdType.DefaultPoolId);
|
||||
await this.allocateContainer();
|
||||
}
|
||||
|
||||
const notebookTabs = useTabs
|
||||
@@ -1009,7 +978,7 @@ export default class Explorer {
|
||||
if (userContext.features.enableCloudShell) {
|
||||
this.connectToNotebookTerminal(kind);
|
||||
} else if (useNotebook.getState().isPhoenixFeatures) {
|
||||
await this.allocateContainer(PoolIdType.DefaultPoolId);
|
||||
await this.allocateContainer();
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
if (notebookServerInfo && notebookServerInfo.notebookServerEndpoint !== undefined) {
|
||||
this.connectToNotebookTerminal(kind);
|
||||
@@ -1153,7 +1122,7 @@ export default class Explorer {
|
||||
await useNotebook.getState().getPhoenixStatus();
|
||||
}
|
||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
||||
await this.allocateContainer(PoolIdType.DefaultPoolId);
|
||||
await this.allocateContainer();
|
||||
}
|
||||
|
||||
// We still use github urls like https://github.com/Azure-Samples/cosmos-notebooks/blob/master/CSharp_quickstarts/GettingStarted_CSharp.ipynb
|
||||
@@ -1207,23 +1176,38 @@ export default class Explorer {
|
||||
scenarioMonitor.start(MetricScenario.DatabaseLoad);
|
||||
}
|
||||
|
||||
if (userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo") {
|
||||
if (userContext.authType === AuthType.ResourceToken) {
|
||||
scenarioMonitor.skipPhase(MetricScenario.DatabaseLoad, ApplicationMetricPhase.CollectionsLoaded);
|
||||
scenarioMonitor.skipPhase(MetricScenario.DatabaseLoad, ApplicationMetricPhase.DatabaseTreeRendered);
|
||||
this.databasesRefreshed = this.refreshDatabaseForResourceToken().then(() => {
|
||||
scenarioMonitor.completePhase(MetricScenario.DatabaseLoad, ApplicationMetricPhase.DatabasesFetched);
|
||||
});
|
||||
} else {
|
||||
this.databasesRefreshed = this.refreshAllDatabases();
|
||||
}
|
||||
await this.databasesRefreshed; // await: we rely on the databases to be loaded before restoring the tabs further in the flow
|
||||
}
|
||||
// Run independent initialization tasks in parallel:
|
||||
// - Database loading (ARM/SDK calls for databases + collections)
|
||||
// - Notebook enabled check (Phoenix + Portal backend — no dependency on databases)
|
||||
// - Feature registration check (ARM call — no dependency on databases or notebooks)
|
||||
const databasesTask =
|
||||
userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo"
|
||||
? (async () => {
|
||||
if (userContext.authType === AuthType.ResourceToken) {
|
||||
scenarioMonitor.skipPhase(MetricScenario.DatabaseLoad, ApplicationMetricPhase.CollectionsLoaded);
|
||||
scenarioMonitor.skipPhase(MetricScenario.DatabaseLoad, ApplicationMetricPhase.DatabaseTreeRendered);
|
||||
this.databasesRefreshed = this.refreshDatabaseForResourceToken().then(() => {
|
||||
scenarioMonitor.completePhase(MetricScenario.DatabaseLoad, ApplicationMetricPhase.DatabasesFetched);
|
||||
});
|
||||
} else {
|
||||
this.databasesRefreshed = this.refreshAllDatabases();
|
||||
}
|
||||
await this.databasesRefreshed;
|
||||
})()
|
||||
: Promise.resolve();
|
||||
|
||||
if (!isFabricNative()) {
|
||||
await useNotebook.getState().refreshNotebooksEnabledStateForAccount();
|
||||
}
|
||||
const notebooksTask = !isFabricNative()
|
||||
? useNotebook.getState().refreshNotebooksEnabledStateForAccount()
|
||||
: Promise.resolve();
|
||||
|
||||
const featureRegistrationTask =
|
||||
userContext.authType === AuthType.AAD && userContext.apiType === "SQL" && !isFabricNative()
|
||||
? featureRegistered(userContext.subscriptionId, "ThroughputBucketing")
|
||||
: Promise.resolve(false);
|
||||
|
||||
const [, , throughputBucketsEnabled] = await Promise.all([databasesTask, notebooksTask, featureRegistrationTask]);
|
||||
|
||||
// Notebook initialization depends on refreshNotebooksEnabledStateForAccount completing above
|
||||
// TODO: remove reference to isNotebookEnabled and isNotebooksEnabledForAccount
|
||||
const isNotebookEnabled =
|
||||
configContext.platform !== Platform.Fabric &&
|
||||
@@ -1244,64 +1228,8 @@ export default class Explorer {
|
||||
await this.initNotebooks(userContext.databaseAccount);
|
||||
}
|
||||
|
||||
if (userContext.authType === AuthType.AAD && userContext.apiType === "SQL" && !isFabricNative()) {
|
||||
const throughputBucketsEnabled = await featureRegistered(userContext.subscriptionId, "ThroughputBucketing");
|
||||
if (throughputBucketsEnabled) {
|
||||
updateUserContext({ throughputBucketsEnabled });
|
||||
}
|
||||
|
||||
this.refreshSampleData();
|
||||
}
|
||||
|
||||
public async configureCopilot(): Promise<void> {
|
||||
if (
|
||||
userContext.apiType !== "SQL" ||
|
||||
!userContext.subscriptionId ||
|
||||
![Environment.Development, Environment.Mpac, Environment.Prod].includes(getEnvironment())
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const copilotEnabledPromise = getCopilotEnabled();
|
||||
const copilotUserDBEnabledPromise = isCopilotFeatureRegistered(userContext.subscriptionId);
|
||||
const [copilotEnabled, copilotUserDBEnabled] = await Promise.all([
|
||||
copilotEnabledPromise,
|
||||
copilotUserDBEnabledPromise,
|
||||
]);
|
||||
const copilotSampleDBEnabled = LocalStorageUtility.getEntryString(StorageKey.CopilotSampleDBEnabled) === "true";
|
||||
useQueryCopilot.getState().setCopilotEnabled(copilotEnabled && copilotUserDBEnabled);
|
||||
useQueryCopilot.getState().setCopilotUserDBEnabled(copilotUserDBEnabled);
|
||||
useQueryCopilot
|
||||
.getState()
|
||||
.setCopilotSampleDBEnabled(copilotEnabled && copilotUserDBEnabled && copilotSampleDBEnabled);
|
||||
}
|
||||
|
||||
public refreshSampleData(): void {
|
||||
if (!userContext.sampleDataConnectionInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const databaseId = userContext.sampleDataConnectionInfo?.databaseId;
|
||||
if (!databaseId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const startKey = TelemetryProcessor.traceStart(Action.RefreshSampleData, {
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
databaseId,
|
||||
});
|
||||
readSampleCollection()
|
||||
.then((collection: DataModels.Collection) => {
|
||||
if (!collection) {
|
||||
TelemetryProcessor.traceSuccess(Action.RefreshSampleData, { sampleCollectionFound: false }, startKey);
|
||||
return;
|
||||
}
|
||||
|
||||
const sampleDataResourceTokenCollection = new ResourceTokenCollection(this, databaseId, collection, true);
|
||||
useDatabases.setState({ sampleDataResourceTokenCollection });
|
||||
TelemetryProcessor.traceSuccess(Action.RefreshSampleData, { sampleCollectionFound: true }, startKey);
|
||||
})
|
||||
.catch((error) => {
|
||||
TelemetryProcessor.traceFailure(Action.RefreshSampleData, { error: getErrorMessage(error) }, startKey);
|
||||
Logger.logError(getErrorMessage(error), "Explorer/refreshSampleData");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { isFabric } from "Platform/Fabric/FabricUtil";
|
||||
import { userContext } from "UserContext";
|
||||
import * as React from "react";
|
||||
import create, { UseStore } from "zustand";
|
||||
import { ConnectionStatusType, PoolIdType } from "../../../Common/Constants";
|
||||
import { ConnectionStatusType } from "../../../Common/Constants";
|
||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
import Explorer from "../../Explorer";
|
||||
import { useSelectedNode } from "../../useSelectedNode";
|
||||
@@ -136,9 +136,7 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
||||
|
||||
// Add connection status if needed (using the hook values we got at the top level)
|
||||
if ((isPhoenixNotebooks || isPhoenixFeatures) && connectionInfo?.status !== ConnectionStatusType.Connect) {
|
||||
uiFabricControlButtons.unshift(
|
||||
CommandBarUtil.createConnectionStatus(container, PoolIdType.DefaultPoolId, "connectionStatus"),
|
||||
);
|
||||
uiFabricControlButtons.unshift(CommandBarUtil.createConnectionStatus(container, "connectionStatus"));
|
||||
}
|
||||
|
||||
const rootStyle = {
|
||||
|
||||
@@ -29,7 +29,7 @@ import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPa
|
||||
import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane";
|
||||
import { SettingsPane, useDataPlaneRbac } from "../../Panes/SettingsPane/SettingsPane";
|
||||
import { useDatabases } from "../../useDatabases";
|
||||
import { SelectedNodeState, useSelectedNode } from "../../useSelectedNode";
|
||||
import { SelectedNodeState } from "../../useSelectedNode";
|
||||
import { ThemeToggleButton } from "./ThemeToggleButton";
|
||||
|
||||
let counter = 0;
|
||||
@@ -103,9 +103,7 @@ export function createStaticCommandBarButtons(
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: true,
|
||||
disabled:
|
||||
useSelectedNode.getState().isQueryCopilotCollectionSelected() ||
|
||||
selectedNodeState.isDatabaseNodeOrNoneSelected(),
|
||||
disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
|
||||
};
|
||||
|
||||
newStoredProcedureBtn.children = createScriptCommandButtons(selectedNodeState);
|
||||
@@ -260,8 +258,7 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
|
||||
onCommandClick: () => container.openEnableSynapseLinkDialog(),
|
||||
commandButtonLabel: label,
|
||||
hasPopup: false,
|
||||
disabled:
|
||||
useSelectedNode.getState().isQueryCopilotCollectionSelected() || useNotebook.getState().isSynapseLinkUpdating,
|
||||
disabled: useNotebook.getState().isSynapseLinkUpdating,
|
||||
ariaLabel: label,
|
||||
};
|
||||
}
|
||||
@@ -360,9 +357,7 @@ export function createScriptCommandButtons(selectedNodeState: SelectedNodeState)
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: true,
|
||||
disabled:
|
||||
useSelectedNode.getState().isQueryCopilotCollectionSelected() ||
|
||||
selectedNodeState.isDatabaseNodeOrNoneSelected(),
|
||||
disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
|
||||
styles: {
|
||||
root: {
|
||||
backgroundColor: "var(--colorNeutralBackground1)",
|
||||
@@ -396,9 +391,7 @@ export function createScriptCommandButtons(selectedNodeState: SelectedNodeState)
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: true,
|
||||
disabled:
|
||||
useSelectedNode.getState().isQueryCopilotCollectionSelected() ||
|
||||
selectedNodeState.isDatabaseNodeOrNoneSelected(),
|
||||
disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
|
||||
styles: {
|
||||
root: {
|
||||
backgroundColor: "var(--colorNeutralBackground1)",
|
||||
@@ -432,9 +425,7 @@ export function createScriptCommandButtons(selectedNodeState: SelectedNodeState)
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: true,
|
||||
disabled:
|
||||
useSelectedNode.getState().isQueryCopilotCollectionSelected() ||
|
||||
selectedNodeState.isDatabaseNodeOrNoneSelected(),
|
||||
disabled: selectedNodeState.isDatabaseNodeOrNoneSelected(),
|
||||
styles: {
|
||||
root: {
|
||||
backgroundColor: "var(--colorNeutralBackground1)",
|
||||
@@ -469,7 +460,7 @@ function createOpenQueryButton(container: Explorer): CommandButtonComponentProps
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: true,
|
||||
disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(),
|
||||
disabled: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -483,7 +474,7 @@ function createOpenQueryFromDiskButton(): CommandButtonComponentProps {
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: true,
|
||||
disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(),
|
||||
disabled: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,11 @@ import {
|
||||
IDropdownStyles,
|
||||
TooltipHost,
|
||||
} from "@fluentui/react";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import { KeyboardHandlerMap } from "KeyboardShortcuts";
|
||||
import * as React from "react";
|
||||
import _ from "underscore";
|
||||
import ChevronDownIcon from "../../../../images/Chevron_down.svg";
|
||||
import { PoolIdType } from "../../../Common/Constants";
|
||||
|
||||
import { StyleConstants } from "../../../Common/StyleConstants";
|
||||
import { configContext, Platform } from "../../../ConfigContext";
|
||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
@@ -64,11 +63,7 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
|
||||
onClick: btn.onCommandClick
|
||||
? (ev?: React.MouseEvent<HTMLElement, MouseEvent> | React.KeyboardEvent<HTMLElement>) => {
|
||||
btn.onCommandClick(ev);
|
||||
let copilotEnabled = false;
|
||||
if (useQueryCopilot.getState().copilotEnabled && useQueryCopilot.getState().copilotUserDBEnabled) {
|
||||
copilotEnabled = useQueryCopilot.getState().copilotEnabledforExecution;
|
||||
}
|
||||
TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label, copilotEnabled });
|
||||
TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label });
|
||||
}
|
||||
: undefined,
|
||||
key: `${btn.commandButtonLabel}${index}`,
|
||||
@@ -270,10 +265,10 @@ export const createMemoryTracker = (key: string): ICommandBarItemProps => {
|
||||
};
|
||||
};
|
||||
|
||||
export const createConnectionStatus = (container: Explorer, poolId: PoolIdType, key: string): ICommandBarItemProps => {
|
||||
export const createConnectionStatus = (container: Explorer, key: string): ICommandBarItemProps => {
|
||||
return {
|
||||
key,
|
||||
onRender: () => <ConnectionStatus container={container} poolId={poolId} />,
|
||||
onRender: () => <ConnectionStatus container={container} />,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -14,15 +14,14 @@ import { useId } from "@fluentui/react-hooks";
|
||||
import { ActionButton, DefaultButton } from "@fluentui/react/lib/Button";
|
||||
import * as React from "react";
|
||||
import "../../../../less/hostedexplorer.less";
|
||||
import { ConnectionStatusType, ContainerStatusType, Notebook, PoolIdType } from "../../../Common/Constants";
|
||||
import { ConnectionStatusType, ContainerStatusType, Notebook } from "../../../Common/Constants";
|
||||
import Explorer from "../../Explorer";
|
||||
import { useNotebook } from "../../Notebook/useNotebook";
|
||||
import "../CommandBar/ConnectionStatusComponent.less";
|
||||
interface Props {
|
||||
container: Explorer;
|
||||
poolId: PoolIdType;
|
||||
}
|
||||
export const ConnectionStatus: React.FC<Props> = ({ container, poolId }: Props): JSX.Element => {
|
||||
export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Element => {
|
||||
const connectionInfo = useNotebook((state) => state.connectionInfo);
|
||||
const [second, setSecond] = React.useState("00");
|
||||
const [minute, setMinute] = React.useState("00");
|
||||
@@ -94,7 +93,7 @@ export const ConnectionStatus: React.FC<Props> = ({ container, poolId }: Props):
|
||||
(connectionInfo.status === ConnectionStatusType.Connect || connectionInfo.status === ConnectionStatusType.Reconnect)
|
||||
) {
|
||||
return (
|
||||
<ActionButton className="commandReactBtn" onClick={() => container.allocateContainer(poolId)}>
|
||||
<ActionButton className="commandReactBtn" onClick={() => container.allocateContainer()}>
|
||||
<TooltipHost content={toolTipContent}>
|
||||
<Stack className="connectionStatusContainer" horizontal>
|
||||
<Icon iconName="ConnectVirtualMachine" className="connectIcon" />
|
||||
@@ -134,9 +133,7 @@ export const ConnectionStatus: React.FC<Props> = ({ container, poolId }: Props):
|
||||
id={buttonId}
|
||||
className={connectionInfo.status === ConnectionStatusType.Failed ? "commandReactBtn" : "connectedReactBtn"}
|
||||
onClick={(e: React.MouseEvent<HTMLSpanElement>) =>
|
||||
connectionInfo.status === ConnectionStatusType.Failed
|
||||
? container.allocateContainer(poolId)
|
||||
: e.preventDefault()
|
||||
connectionInfo.status === ConnectionStatusType.Failed ? container.allocateContainer() : e.preventDefault()
|
||||
}
|
||||
>
|
||||
<Stack className="connectionStatusContainer" horizontal>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { IDropdownOption } from "@fluentui/react";
|
||||
import { Keys, t } from "Localization";
|
||||
import React, { FormEvent, FunctionComponent, useEffect, useState } from "react";
|
||||
import { HttpStatusCodes, PoolIdType } from "../../../Common/Constants";
|
||||
import { HttpStatusCodes } from "../../../Common/Constants";
|
||||
import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils";
|
||||
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
|
||||
import { IPinnedRepo, JunoClient } from "../../../Juno/JunoClient";
|
||||
import { Keys, t } from "Localization";
|
||||
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
@@ -110,7 +110,7 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
||||
};
|
||||
isGithubTree = false;
|
||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
||||
await container.allocateContainer(PoolIdType.DefaultPoolId);
|
||||
await container.allocateContainer();
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import { SplitterDirection } from "Common/Splitter";
|
||||
import { InfoTooltip } from "Common/Tooltip/InfoTooltip";
|
||||
import { Platform, configContext } from "ConfigContext";
|
||||
import { useDialog } from "Explorer/Controls/Dialog";
|
||||
import { useDatabases } from "Explorer/useDatabases";
|
||||
import { Keys, t } from "Localization";
|
||||
import { isFabric, isFabricNative } from "Platform/Fabric/FabricUtil";
|
||||
import {
|
||||
@@ -50,7 +49,6 @@ import { logConsoleError, logConsoleInfo } from "Utils/NotificationConsoleUtils"
|
||||
import * as PriorityBasedExecutionUtils from "Utils/PriorityBasedExecutionUtils";
|
||||
import { getReadOnlyKeys, listKeys } from "Utils/arm/generatedClients/cosmos/databaseAccounts";
|
||||
import { useClientWriteEnabled } from "hooks/useClientWriteEnabled";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import { useSidePanel } from "hooks/useSidePanel";
|
||||
import React, { FunctionComponent, useState } from "react";
|
||||
import create, { UseStore } from "zustand";
|
||||
@@ -115,7 +113,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
}): JSX.Element => {
|
||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
||||
const [refreshExplorer, setRefreshExplorer] = useState<boolean>(false);
|
||||
const [refreshExplorer] = useState<boolean>(false);
|
||||
const [pageOption, setPageOption] = useState<string>(
|
||||
LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage) === Constants.Queries.unlimitedItemsPerPage
|
||||
? Constants.Queries.UnlimitedPageOption
|
||||
@@ -199,9 +197,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
? LocalStorageUtility.getEntryString(StorageKey.PriorityLevel)
|
||||
: Constants.PriorityLevel.Default,
|
||||
);
|
||||
const [copilotSampleDBEnabled, setCopilotSampleDBEnabled] = useState<boolean>(
|
||||
LocalStorageUtility.getEntryString(StorageKey.CopilotSampleDBEnabled) === "true",
|
||||
);
|
||||
|
||||
const [mongoGuidRepresentation, setMongoGuidRepresentation] = useState<Constants.MongoGuidRepresentation>(
|
||||
LocalStorageUtility.hasItem(StorageKey.MongoGuidRepresentation)
|
||||
@@ -269,12 +264,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
}
|
||||
});
|
||||
|
||||
const shouldShowCopilotSampleDBOption =
|
||||
userContext.apiType === "SQL" &&
|
||||
useQueryCopilot.getState().copilotEnabled &&
|
||||
useDatabases.getState().sampleDataResourceTokenCollection &&
|
||||
!isEmulator;
|
||||
|
||||
const shouldShowMongoGuidRepresentationOption = userContext.apiType === "Mongo";
|
||||
|
||||
const handlerOnSubmit = async () => {
|
||||
@@ -402,7 +391,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
LocalStorageUtility.setEntryString(StorageKey.QueryControlEnabled, queryControlEnabled.toString());
|
||||
LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, maxDegreeOfParallelism);
|
||||
LocalStorageUtility.setEntryString(StorageKey.PriorityLevel, priorityLevel.toString());
|
||||
LocalStorageUtility.setEntryString(StorageKey.CopilotSampleDBEnabled, copilotSampleDBEnabled.toString());
|
||||
LocalStorageUtility.setEntryString(StorageKey.DefaultQueryResultsView, defaultQueryResultsView);
|
||||
|
||||
if (shouldShowGraphAutoVizOption) {
|
||||
@@ -594,12 +582,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleSampleDatabaseChange = async (ev: React.MouseEvent<HTMLElement>, checked?: boolean): Promise<void> => {
|
||||
setCopilotSampleDBEnabled(checked);
|
||||
useQueryCopilot.getState().setCopilotSampleDBEnabled(checked);
|
||||
setRefreshExplorer(false);
|
||||
};
|
||||
|
||||
const handleOnMongoGuidRepresentationOptionChange = (
|
||||
ev: React.FormEvent<HTMLInputElement>,
|
||||
option: IDropdownOption,
|
||||
@@ -1194,34 +1176,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
)}
|
||||
{shouldShowCopilotSampleDBOption && (
|
||||
<AccordionItem value="13">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>{t(Keys.panes.settings.enableSampleDatabase)}</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
{t(Keys.panes.settings.enableSampleDatabaseDescription)}
|
||||
</div>
|
||||
<Checkbox
|
||||
styles={{
|
||||
label: { padding: 0 },
|
||||
}}
|
||||
className="padding"
|
||||
ariaLabel={t(Keys.panes.settings.enableSampleDbAriaLabel)}
|
||||
checked={copilotSampleDBEnabled}
|
||||
onChange={handleSampleDatabaseChange}
|
||||
onRenderLabel={() => (
|
||||
<span style={{ color: "var(--colorNeutralForeground1)" }}>
|
||||
{t(Keys.panes.settings.enableSampleDatabase)}
|
||||
</span>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
)}
|
||||
{shouldShowMongoGuidRepresentationOption && (
|
||||
<AccordionItem value="14">
|
||||
<AccordionHeader>
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import Explorer from "../Explorer";
|
||||
import { QueryCopilotCarousel } from "./CopilotCarousel";
|
||||
|
||||
describe("Query Copilot Carousel snapshot test", () => {
|
||||
it("should render when isOpen is true", () => {
|
||||
const wrapper = shallow(<QueryCopilotCarousel isOpen={true} explorer={new Explorer()} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,278 +0,0 @@
|
||||
import {
|
||||
DefaultButton,
|
||||
ISeparatorStyles,
|
||||
IconButton,
|
||||
Image,
|
||||
Link,
|
||||
Modal,
|
||||
PrimaryButton,
|
||||
Separator,
|
||||
Spinner,
|
||||
Stack,
|
||||
Text,
|
||||
} from "@fluentui/react";
|
||||
import { QueryCopilotSampleDatabaseId } from "Common/Constants";
|
||||
import { handleError } from "Common/ErrorHandlingUtils";
|
||||
import { StyleConstants } from "Common/StyleConstants";
|
||||
import { createCollection } from "Common/dataAccess/createCollection";
|
||||
import * as DataModels from "Contracts/DataModels";
|
||||
import { ContainerSampleGenerator } from "Explorer/DataSamples/ContainerSampleGenerator";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { AllPropertiesIndexed } from "Explorer/Panes/AddCollectionPanel/AddCollectionPanelUtility";
|
||||
import { PromptCard } from "Explorer/QueryCopilot/PromptCard";
|
||||
import { useDatabases } from "Explorer/useDatabases";
|
||||
import { useCarousel } from "hooks/useCarousel";
|
||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||
import React, { useState } from "react";
|
||||
import YoutubePlaceholder from "../../../images/YoutubePlaceholder.svg";
|
||||
|
||||
interface QueryCopilotCarouselProps {
|
||||
isOpen: boolean;
|
||||
explorer: Explorer;
|
||||
}
|
||||
|
||||
const separatorStyles: Partial<ISeparatorStyles> = {
|
||||
root: {
|
||||
selectors: {
|
||||
"::before": {
|
||||
background: StyleConstants.BaseMedium,
|
||||
},
|
||||
},
|
||||
padding: "16px 0",
|
||||
height: 1,
|
||||
},
|
||||
};
|
||||
|
||||
export const QueryCopilotCarousel: React.FC<QueryCopilotCarouselProps> = ({
|
||||
isOpen,
|
||||
explorer,
|
||||
}: QueryCopilotCarouselProps): JSX.Element => {
|
||||
const [page, setPage] = useState<number>(1);
|
||||
const [isCreatingDatabase, setIsCreatingDatabase] = useState<boolean>(false);
|
||||
const [spinnerText, setSpinnerText] = useState<string>("");
|
||||
const [selectedPrompt, setSelectedPrompt] = useState<number>(1);
|
||||
|
||||
const getHeaderText = (): string => {
|
||||
switch (page) {
|
||||
case 1:
|
||||
return "What exactly is copilot?";
|
||||
case 2:
|
||||
return "Setting up your Sample database";
|
||||
case 3:
|
||||
return "Sample prompts to help you";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
const getQueryCopilotInitialInput = (): string => {
|
||||
switch (selectedPrompt) {
|
||||
case 1:
|
||||
return "Write a query to return all records in this table";
|
||||
case 2:
|
||||
return "Write a query to return all records in this table created in the last thirty days";
|
||||
case 3:
|
||||
return 'Write a query to return all records in this table created in the last thirty days which also have the record owner as "Contoso"';
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
const createSampleDatabase = async (): Promise<void> => {
|
||||
const database = useDatabases.getState().findDatabaseWithId(QueryCopilotSampleDatabaseId);
|
||||
if (database) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsCreatingDatabase(true);
|
||||
setSpinnerText("Setting up your database...");
|
||||
const params: DataModels.CreateCollectionParams = {
|
||||
createNewDatabase: true,
|
||||
collectionId: "SampleContainer",
|
||||
databaseId: QueryCopilotSampleDatabaseId,
|
||||
databaseLevelThroughput: true,
|
||||
autoPilotMaxThroughput: 1000,
|
||||
offerThroughput: undefined,
|
||||
indexingPolicy: AllPropertiesIndexed,
|
||||
partitionKey: {
|
||||
paths: ["/categoryId"],
|
||||
kind: "Hash",
|
||||
version: 2,
|
||||
},
|
||||
};
|
||||
await createCollection(params);
|
||||
await explorer.refreshAllDatabases();
|
||||
const database = useDatabases.getState().findDatabaseWithId(QueryCopilotSampleDatabaseId);
|
||||
await database.loadCollections();
|
||||
const collection = database.findCollectionWithId("SampleContainer");
|
||||
|
||||
setSpinnerText("Adding sample data set...");
|
||||
const sampleGenerator = await ContainerSampleGenerator.createSampleGeneratorAsync(explorer, true);
|
||||
await sampleGenerator.populateContainerAsync(collection);
|
||||
await database.expandDatabase();
|
||||
collection.expandCollection();
|
||||
useDatabases.getState().updateDatabase(database);
|
||||
} catch (error) {
|
||||
//TODO: show error in UI
|
||||
handleError(error, "QueryCopilotCreateSampleDB");
|
||||
throw error;
|
||||
} finally {
|
||||
setIsCreatingDatabase(false);
|
||||
setSpinnerText("");
|
||||
}
|
||||
};
|
||||
|
||||
const getContent = (): JSX.Element => {
|
||||
switch (page) {
|
||||
case 1:
|
||||
return (
|
||||
<Stack style={{ marginTop: 8 }}>
|
||||
<Text style={{ fontSize: 13 }}>
|
||||
A couple of lines about copilot and the background about it. The idea is to have some text to give context
|
||||
to the user.
|
||||
</Text>
|
||||
<Text style={{ fontSize: 14, fontWeight: 600, marginTop: 16 }}>How do you use copilot</Text>
|
||||
<Text style={{ fontSize: 13, marginTop: 8 }}>
|
||||
To generate queries , just describe the query you want and copilot will generate the query for you.Watch
|
||||
this video to learn more about how to use copilot.
|
||||
</Text>
|
||||
<Image src={YoutubePlaceholder} style={{ margin: "16px auto" }} />
|
||||
<Text style={{ fontSize: 14, fontWeight: 600 }}>What is copilot good at</Text>
|
||||
<Text style={{ fontSize: 13, marginTop: 8 }}>
|
||||
A couple of lines about what copilot can do and its capablites with a link to{" "}
|
||||
<Link href="" target="_blank">
|
||||
documentation
|
||||
</Link>{" "}
|
||||
if possible.
|
||||
</Text>
|
||||
<Text style={{ fontSize: 14, fontWeight: 600, marginTop: 16 }}>What are its limitations</Text>
|
||||
<Text style={{ fontSize: 13, marginTop: 8 }}>
|
||||
A couple of lines about what copilot cant do and its limitations.{" "}
|
||||
<Link href="" target="_blank">
|
||||
Link to documentation
|
||||
</Link>
|
||||
</Text>
|
||||
<Text style={{ fontSize: 14, fontWeight: 600, marginTop: 16 }}>Disclaimer</Text>
|
||||
<Text style={{ fontSize: 13, marginTop: 8 }}>
|
||||
AI-generated content can have mistakes. Make sure it's accurate and appropriate before using it.{" "}
|
||||
<Link href="" target="_blank">
|
||||
Read preview terms
|
||||
</Link>
|
||||
</Text>
|
||||
</Stack>
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
<Stack style={{ marginTop: 8 }}>
|
||||
<Text style={{ fontSize: 13 }}>
|
||||
Before you get started, we need to configure your sample database for you. Here is a summary of the
|
||||
database being created for your reference. Configuration values can be updated using the settings icon in
|
||||
the query builder.
|
||||
</Text>
|
||||
<Text style={{ fontSize: 13, fontWeight: 600, marginTop: 24 }}>Database Id</Text>
|
||||
<Text style={{ fontSize: 13 }}>CopilotSampleDB</Text>
|
||||
<Text style={{ fontSize: 13, fontWeight: 600, marginTop: 16 }}>Database throughput (autoscale)</Text>
|
||||
<Text style={{ fontSize: 13 }}>Autoscale</Text>
|
||||
<Text style={{ fontSize: 13, fontWeight: 600, marginTop: 16 }}>Database Max RU/s</Text>
|
||||
<Text>1000</Text>
|
||||
<Text style={{ fontSize: 10, marginTop: 8 }}>
|
||||
Your database throughput will automatically scale from{" "}
|
||||
<strong>100 RU/s (10% of max RU/s) - 1000 RU/s</strong> based on usage.
|
||||
</Text>
|
||||
<Text style={{ fontSize: 10, marginTop: 8 }}>
|
||||
Estimated monthly cost (USD): <strong>$8.76 - $87.60</strong> (1 region, 100 - 1000 RU/s, $0.00012/RU)
|
||||
</Text>
|
||||
<Text style={{ fontSize: 13, fontWeight: 600, marginTop: 16 }}>Container Id</Text>
|
||||
<Text style={{ fontSize: 13 }}>SampleContainer</Text>
|
||||
<Text style={{ fontSize: 13, fontWeight: 600, marginTop: 16 }}>Partition key</Text>
|
||||
<Text style={{ fontSize: 13 }}>categoryId</Text>
|
||||
</Stack>
|
||||
);
|
||||
case 3:
|
||||
return (
|
||||
<Stack>
|
||||
<Text>To help you get started, here are some sample prompts to get you started</Text>
|
||||
<Stack tokens={{ childrenGap: 12 }} style={{ marginTop: 16 }}>
|
||||
<PromptCard
|
||||
header="Write a query to return all records in this table"
|
||||
description="This is a basic query which returns all records in the table "
|
||||
onSelect={() => setSelectedPrompt(1)}
|
||||
isSelected={selectedPrompt === 1}
|
||||
/>
|
||||
<PromptCard
|
||||
header="Write a query to return all records in this table created in the last thirty days"
|
||||
description="This builds on the previous query which returns all records in the table which were inserted in the last thirty days. You can also modify this query to return records based upon creation date"
|
||||
onSelect={() => setSelectedPrompt(2)}
|
||||
isSelected={selectedPrompt === 2}
|
||||
/>
|
||||
<PromptCard
|
||||
header='Write a query to return all records in this table created in the last thirty days which also have the record owner as "Contoso"'
|
||||
description='This builds on the previous query which returns all records in the table which were inserted in the last thirty days but which has the record owner as "contoso"'
|
||||
onSelect={() => setSelectedPrompt(3)}
|
||||
isSelected={selectedPrompt === 3}
|
||||
/>
|
||||
</Stack>
|
||||
<Text style={{ fontSize: 13, marginTop: 32 }}>
|
||||
Interested in learning more about how to write effective prompts. Please read this article for more
|
||||
information.
|
||||
</Text>
|
||||
<Text style={{ fontSize: 13, marginTop: 16 }}>
|
||||
You can also access these prompts by selecting the Samples prompts button in the query builder page.
|
||||
</Text>
|
||||
<Text style={{ fontSize: 13, marginTop: 16 }}>
|
||||
Don't like any of the prompts? Just click Get Started and write your own prompt.
|
||||
</Text>
|
||||
</Stack>
|
||||
);
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal styles={{ main: { width: 880 } }} isOpen={isOpen && page < 4}>
|
||||
<Stack style={{ padding: 16 }}>
|
||||
<Stack horizontal horizontalAlign="space-between">
|
||||
<Text variant="xLarge">{getHeaderText()}</Text>
|
||||
<IconButton
|
||||
iconProps={{ iconName: "Cancel" }}
|
||||
onClick={() => useCarousel.getState().setShowCopilotCarousel(false)}
|
||||
/>
|
||||
</Stack>
|
||||
{getContent()}
|
||||
<Separator styles={separatorStyles} />
|
||||
<Stack horizontal horizontalAlign="start" verticalAlign="center">
|
||||
{page !== 1 && (
|
||||
<DefaultButton
|
||||
text="Previous"
|
||||
style={{ marginRight: 8 }}
|
||||
onClick={() => setPage(page - 1)}
|
||||
disabled={isCreatingDatabase}
|
||||
/>
|
||||
)}
|
||||
<PrimaryButton
|
||||
text={page === 3 ? "Get started" : "Next"}
|
||||
onClick={async () => {
|
||||
if (page === 3) {
|
||||
useCarousel.getState().setShowCopilotCarousel(false);
|
||||
useTabs.getState().setQueryCopilotTabInitialInput(getQueryCopilotInitialInput());
|
||||
useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot);
|
||||
return;
|
||||
}
|
||||
|
||||
if (page === 2) {
|
||||
await createSampleDatabase();
|
||||
}
|
||||
|
||||
setPage(page + 1);
|
||||
}}
|
||||
disabled={isCreatingDatabase}
|
||||
/>
|
||||
{isCreatingDatabase && <Spinner style={{ marginLeft: 8 }} />}
|
||||
{isCreatingDatabase && <Text style={{ marginLeft: 8, color: "#0078D4" }}>{spinnerText}</Text>}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -1,192 +0,0 @@
|
||||
import { Checkbox, DefaultButton, IconButton, PrimaryButton, TextField } from "@fluentui/react";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { QueryCopilotFeedbackModal } from "Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal";
|
||||
import { useCopilotStore } from "Explorer/QueryCopilot/QueryCopilotContext";
|
||||
import { SubmitFeedback } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||
import { getUserEmail } from "Utils/UserUtils";
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
jest.mock("Utils/UserUtils");
|
||||
(getUserEmail as jest.Mock).mockResolvedValue("test@email.com");
|
||||
|
||||
jest.mock("Explorer/QueryCopilot/Shared/QueryCopilotClient");
|
||||
SubmitFeedback as jest.Mock;
|
||||
|
||||
jest.mock("Explorer/QueryCopilot/QueryCopilotContext");
|
||||
const mockUseCopilotStore = useCopilotStore as jest.Mock;
|
||||
const mockReturnValue = {
|
||||
generatedQuery: "test query",
|
||||
userPrompt: "test prompt",
|
||||
likeQuery: false,
|
||||
showFeedbackModal: false,
|
||||
closeFeedbackModal: jest.fn,
|
||||
setHideFeedbackModalForLikedQueries: jest.fn,
|
||||
};
|
||||
|
||||
describe("Query Copilot Feedback Modal snapshot test", () => {
|
||||
beforeEach(() => {
|
||||
mockUseCopilotStore.mockReturnValue(mockReturnValue);
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it("shoud render and match snapshot", () => {
|
||||
mockUseCopilotStore.mockReturnValue({
|
||||
...mockReturnValue,
|
||||
showFeedbackModal: true,
|
||||
});
|
||||
const wrapper = shallow(
|
||||
<QueryCopilotFeedbackModal
|
||||
explorer={new Explorer()}
|
||||
databaseId="CopilotUserDb"
|
||||
containerId="CopilotUserContainer"
|
||||
mode="User"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.props().isOpen).toBeTruthy();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should close on cancel click", () => {
|
||||
const wrapper = shallow(
|
||||
<QueryCopilotFeedbackModal
|
||||
explorer={new Explorer()}
|
||||
databaseId="CopilotUserDb"
|
||||
containerId="CopilotUserContainer"
|
||||
mode="User"
|
||||
/>,
|
||||
);
|
||||
|
||||
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
|
||||
explorer={new Explorer()}
|
||||
databaseId="CopilotUserDb"
|
||||
containerId="CopilotUserContainer"
|
||||
mode="User"
|
||||
/>,
|
||||
);
|
||||
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 not render dont show again button", () => {
|
||||
const wrapper = shallow(
|
||||
<QueryCopilotFeedbackModal
|
||||
explorer={new Explorer()}
|
||||
databaseId="CopilotUserDb"
|
||||
containerId="CopilotUserContainer"
|
||||
mode="User"
|
||||
/>,
|
||||
);
|
||||
|
||||
const dontShowAgain = wrapper.find(Checkbox);
|
||||
|
||||
expect(dontShowAgain).toHaveLength(0);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render dont show again button and check it", () => {
|
||||
mockUseCopilotStore.mockReturnValue({
|
||||
...mockReturnValue,
|
||||
showFeedbackModal: true,
|
||||
likeQuery: true,
|
||||
});
|
||||
const wrapper = shallow(
|
||||
<QueryCopilotFeedbackModal
|
||||
explorer={new Explorer()}
|
||||
databaseId="CopilotUserDb"
|
||||
containerId="CopilotUserContainer"
|
||||
mode="User"
|
||||
/>,
|
||||
);
|
||||
|
||||
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
|
||||
explorer={new Explorer()}
|
||||
databaseId="CopilotUserDb"
|
||||
containerId="CopilotUserContainer"
|
||||
mode="User"
|
||||
/>,
|
||||
);
|
||||
|
||||
const cancelButton = wrapper.find(DefaultButton);
|
||||
cancelButton.simulate("click");
|
||||
wrapper.setProps({});
|
||||
|
||||
expect(wrapper.props().isOpen).toBeFalsy();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should not submit submission if required description field is null", () => {
|
||||
const explorer = new Explorer();
|
||||
const wrapper = shallow(
|
||||
<QueryCopilotFeedbackModal
|
||||
explorer={explorer}
|
||||
databaseId="CopilotUserDb"
|
||||
containerId="CopilotUserContainer"
|
||||
mode="User"
|
||||
/>,
|
||||
);
|
||||
|
||||
const submitButton = wrapper.find(PrimaryButton);
|
||||
submitButton.simulate("click");
|
||||
wrapper.setProps({});
|
||||
|
||||
expect(SubmitFeedback).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it("should submit submission", () => {
|
||||
const explorer = new Explorer();
|
||||
const wrapper = shallow(
|
||||
<QueryCopilotFeedbackModal
|
||||
explorer={explorer}
|
||||
databaseId="CopilotUserDb"
|
||||
containerId="CopilotUserContainer"
|
||||
mode="User"
|
||||
/>,
|
||||
);
|
||||
|
||||
const submitButton = wrapper.find("form");
|
||||
submitButton.simulate("submit");
|
||||
wrapper.setProps({});
|
||||
|
||||
expect(SubmitFeedback).toHaveBeenCalledTimes(1);
|
||||
expect(SubmitFeedback).toHaveBeenCalledWith({
|
||||
containerId: "CopilotUserContainer",
|
||||
databaseId: "CopilotUserDb",
|
||||
mode: "User",
|
||||
params: {
|
||||
likeQuery: false,
|
||||
generatedQuery: "test query",
|
||||
userPrompt: "test prompt",
|
||||
description: "",
|
||||
},
|
||||
explorer: explorer,
|
||||
});
|
||||
expect(wrapper.props().isOpen).toBeFalsy();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,112 +0,0 @@
|
||||
import {
|
||||
Checkbox,
|
||||
DefaultButton,
|
||||
IconButton,
|
||||
Link,
|
||||
Modal,
|
||||
PrimaryButton,
|
||||
Stack,
|
||||
Text,
|
||||
TextField,
|
||||
} from "@fluentui/react";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { useCopilotStore } from "Explorer/QueryCopilot/QueryCopilotContext";
|
||||
import { SubmitFeedback } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||
import React from "react";
|
||||
|
||||
export const QueryCopilotFeedbackModal = ({
|
||||
explorer,
|
||||
databaseId,
|
||||
containerId,
|
||||
mode,
|
||||
}: {
|
||||
explorer: Explorer;
|
||||
databaseId: string;
|
||||
containerId: string;
|
||||
mode: string;
|
||||
}): JSX.Element => {
|
||||
const {
|
||||
generatedQuery,
|
||||
userPrompt,
|
||||
likeQuery,
|
||||
showFeedbackModal,
|
||||
closeFeedbackModal,
|
||||
setHideFeedbackModalForLikedQueries,
|
||||
} = useCopilotStore();
|
||||
const [description, setDescription] = React.useState<string>("");
|
||||
const [doNotShowAgainChecked, setDoNotShowAgainChecked] = React.useState<boolean>(false);
|
||||
|
||||
const handleSubmit = () => {
|
||||
closeFeedbackModal();
|
||||
setHideFeedbackModalForLikedQueries(doNotShowAgainChecked);
|
||||
SubmitFeedback({
|
||||
params: { generatedQuery, likeQuery, description, userPrompt },
|
||||
explorer,
|
||||
databaseId,
|
||||
containerId,
|
||||
mode: mode,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal isOpen={showFeedbackModal} styles={{ main: { borderRadius: 8, maxWidth: 600 } }}>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Stack style={{ padding: 24 }}>
|
||||
<Stack horizontal horizontalAlign="space-between">
|
||||
<Text style={{ fontSize: 20, fontWeight: 600, marginBottom: 20 }}>Send feedback to Microsoft</Text>
|
||||
<IconButton iconProps={{ iconName: "Cancel" }} onClick={() => closeFeedbackModal()} />
|
||||
</Stack>
|
||||
<Text style={{ fontSize: 14, marginBottom: 14 }}>Your feedback will help improve the experience.</Text>
|
||||
<TextField
|
||||
styles={{ root: { marginBottom: 14 } }}
|
||||
label="Description"
|
||||
required
|
||||
placeholder="Provide more details"
|
||||
value={description}
|
||||
onChange={(_, newValue) => setDescription(newValue)}
|
||||
multiline
|
||||
rows={3}
|
||||
/>
|
||||
<TextField
|
||||
styles={{
|
||||
root: { marginBottom: 14 },
|
||||
fieldGroup: { backgroundColor: "#F3F2F1", borderRadius: 4, borderColor: "#D1D1D1" },
|
||||
}}
|
||||
label="Query generated"
|
||||
defaultValue={generatedQuery}
|
||||
multiline
|
||||
rows={3}
|
||||
readOnly
|
||||
/>
|
||||
<Text style={{ fontSize: 12, marginBottom: 14 }}>
|
||||
Microsoft will process the feedback you submit pursuant to your organization’s instructions in order to
|
||||
improve your and your organization’s experience with this product. If you have any questions about the use
|
||||
of feedback data, please contact your tenant administrator. Processing of feedback data is governed by the
|
||||
Microsoft Products and Services Data Protection Addendum between your organization and Microsoft, and the
|
||||
feedback you submit is considered Personal Data under that addendum. Please see the{" "}
|
||||
{
|
||||
<Link href="https://go.microsoft.com/fwlink/?LinkId=521839" target="_blank">
|
||||
Privacy statement
|
||||
</Link>
|
||||
}{" "}
|
||||
for more information.
|
||||
</Text>
|
||||
{likeQuery && (
|
||||
<Checkbox
|
||||
styles={{ label: { paddingLeft: 0 }, root: { marginBottom: 14 } }}
|
||||
label="Don't show me this next time"
|
||||
checked={doNotShowAgainChecked}
|
||||
onChange={(_, checked) => setDoNotShowAgainChecked(checked)}
|
||||
/>
|
||||
)}
|
||||
<Stack horizontal horizontalAlign="end">
|
||||
<PrimaryButton styles={{ root: { marginRight: 8 } }} type="submit">
|
||||
Submit
|
||||
</PrimaryButton>
|
||||
<DefaultButton onClick={() => closeFeedbackModal()}>Cancel</DefaultButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -1,46 +0,0 @@
|
||||
.modalContentPadding {
|
||||
padding-top: 15px;
|
||||
width: 513px;
|
||||
}
|
||||
|
||||
.exitPadding {
|
||||
padding: 0px 7px 0px 0px;
|
||||
}
|
||||
|
||||
.previewMargin {
|
||||
margin: 8px 10px 0px 0;
|
||||
}
|
||||
|
||||
.preview {
|
||||
padding: 0px 4px;
|
||||
background: #f0f0f0;
|
||||
border-radius: 8px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.exitIcon {
|
||||
margin: 3px 7px 0px 0px;
|
||||
color: #424242;
|
||||
}
|
||||
|
||||
.text {
|
||||
width: 348px;
|
||||
padding: 8px 16px 8px 16px;
|
||||
margin-left: 25px;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.imageTextPadding {
|
||||
padding: 0px 5px 0px 0px;
|
||||
}
|
||||
|
||||
.buttonPadding {
|
||||
padding: 15px 0px 15px 0px;
|
||||
}
|
||||
|
||||
.tryButton {
|
||||
border-radius: 4px;
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,110 +0,0 @@
|
||||
import { IconButton, Image, Link, Modal, PrimaryButton, Stack, StackItem, Text } from "@fluentui/react";
|
||||
import { useBoolean } from "@fluentui/react-hooks";
|
||||
import React from "react";
|
||||
import Flash from "../../../../images/CopilotFlash.svg";
|
||||
import Thumb from "../../../../images/CopilotThumb.svg";
|
||||
import CoplilotWelcomeIllustration from "../../../../images/CopliotWelcomeIllustration.svg";
|
||||
import "./WelcomeModal.css";
|
||||
|
||||
export const WelcomeModal = ({ visible }: { visible: boolean }): JSX.Element => {
|
||||
const [isModalVisible, { setFalse: hideModal }] = useBoolean(visible);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (visible) {
|
||||
window.localStorage.setItem("hideWelcomeModal", "true");
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
isOpen={isModalVisible}
|
||||
onDismiss={hideModal}
|
||||
isBlocking={false}
|
||||
styles={{
|
||||
main: {
|
||||
maxHeight: 600,
|
||||
borderRadius: 10,
|
||||
overflow: "hidden",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Stack className="modalContentPadding">
|
||||
<Stack horizontal>
|
||||
<Stack horizontal grow={4} horizontalAlign="end">
|
||||
<Stack.Item>
|
||||
<Image src={CoplilotWelcomeIllustration} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
<Stack horizontal grow={1} horizontalAlign="end" verticalAlign="start" className="exitPadding">
|
||||
<Stack.Item>
|
||||
<IconButton
|
||||
onClick={hideModal}
|
||||
iconProps={{ iconName: "Cancel" }}
|
||||
title="Exit"
|
||||
ariaLabel="Exit"
|
||||
className="exitIcon"
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack horizontalAlign="center">
|
||||
<Stack.Item align="center" style={{ textAlign: "center" }}>
|
||||
<Text className="title bold" as={"h1"}>
|
||||
Welcome to Microsoft Copilot for Azure in Cosmos DB (preview)
|
||||
</Text>
|
||||
</Stack.Item>
|
||||
<Stack.Item align="center" className="text">
|
||||
<Stack horizontal>
|
||||
<StackItem align="start" className="imageTextPadding">
|
||||
<Image src={Flash} />
|
||||
</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 />
|
||||
<Link target="_blank" href="https://aka.ms/MicrosoftCopilotForAzureInCDBHowTo">
|
||||
Learn more
|
||||
</Link>
|
||||
</Text>
|
||||
</Stack.Item>
|
||||
<Stack.Item align="center" className="text">
|
||||
<Stack horizontal>
|
||||
<StackItem align="start" className="imageTextPadding">
|
||||
<Image src={Thumb} />
|
||||
</StackItem>
|
||||
<StackItem align="start">
|
||||
<Text className="bold">
|
||||
Use your judgement
|
||||
<br />
|
||||
</Text>
|
||||
</StackItem>
|
||||
</Stack>
|
||||
<Text>
|
||||
AI-generated content can have mistakes. Make sure it is accurate and appropriate before executing the
|
||||
query.
|
||||
<br />
|
||||
<Link target="_blank" href="https://aka.ms/cdb-copilot-preview-terms">
|
||||
Read our preview terms here
|
||||
</Link>
|
||||
</Text>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
<Stack className="buttonPadding">
|
||||
<Stack.Item align="center">
|
||||
<PrimaryButton onClick={hideModal} className="tryButton">
|
||||
Try Copilot
|
||||
</PrimaryButton>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,975 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Query Copilot Feedback Modal snapshot test shoud render and match snapshot 1`] = `
|
||||
<Modal
|
||||
isOpen={true}
|
||||
styles={
|
||||
{
|
||||
"main": {
|
||||
"borderRadius": 8,
|
||||
"maxWidth": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<form
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<Stack
|
||||
style={
|
||||
{
|
||||
"padding": 24,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="space-between"
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 20,
|
||||
"fontWeight": 600,
|
||||
"marginBottom": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
Send feedback to Microsoft
|
||||
</Text>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"iconName": "Cancel",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 14,
|
||||
"marginBottom": 14,
|
||||
}
|
||||
}
|
||||
>
|
||||
Your feedback will help improve the experience.
|
||||
</Text>
|
||||
<StyledTextFieldBase
|
||||
label="Description"
|
||||
multiline={true}
|
||||
onChange={[Function]}
|
||||
placeholder="Provide more details"
|
||||
required={true}
|
||||
rows={3}
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"marginBottom": 14,
|
||||
},
|
||||
}
|
||||
}
|
||||
value=""
|
||||
/>
|
||||
<StyledTextFieldBase
|
||||
defaultValue="test query"
|
||||
label="Query generated"
|
||||
multiline={true}
|
||||
readOnly={true}
|
||||
rows={3}
|
||||
styles={
|
||||
{
|
||||
"fieldGroup": {
|
||||
"backgroundColor": "#F3F2F1",
|
||||
"borderColor": "#D1D1D1",
|
||||
"borderRadius": 4,
|
||||
},
|
||||
"root": {
|
||||
"marginBottom": 14,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 12,
|
||||
"marginBottom": 14,
|
||||
}
|
||||
}
|
||||
>
|
||||
Microsoft will process the feedback you submit pursuant to your organization’s instructions in order to improve your and your organization’s experience with this product. If you have any questions about the use of feedback data, please contact your tenant administrator. Processing of feedback data is governed by the Microsoft Products and Services Data Protection Addendum between your organization and Microsoft, and the feedback you submit is considered Personal Data under that addendum. Please see the
|
||||
|
||||
<StyledLinkBase
|
||||
href="https://go.microsoft.com/fwlink/?LinkId=521839"
|
||||
target="_blank"
|
||||
>
|
||||
Privacy statement
|
||||
</StyledLinkBase>
|
||||
|
||||
for more information.
|
||||
</Text>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="end"
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"marginRight": 8,
|
||||
},
|
||||
}
|
||||
}
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
</CustomizedPrimaryButton>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
>
|
||||
Cancel
|
||||
</CustomizedDefaultButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</form>
|
||||
</Modal>
|
||||
`;
|
||||
|
||||
exports[`Query Copilot Feedback Modal snapshot test should cancel submission 1`] = `
|
||||
<Modal
|
||||
isOpen={false}
|
||||
styles={
|
||||
{
|
||||
"main": {
|
||||
"borderRadius": 8,
|
||||
"maxWidth": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<form
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<Stack
|
||||
style={
|
||||
{
|
||||
"padding": 24,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="space-between"
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 20,
|
||||
"fontWeight": 600,
|
||||
"marginBottom": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
Send feedback to Microsoft
|
||||
</Text>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"iconName": "Cancel",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 14,
|
||||
"marginBottom": 14,
|
||||
}
|
||||
}
|
||||
>
|
||||
Your feedback will help improve the experience.
|
||||
</Text>
|
||||
<StyledTextFieldBase
|
||||
label="Description"
|
||||
multiline={true}
|
||||
onChange={[Function]}
|
||||
placeholder="Provide more details"
|
||||
required={true}
|
||||
rows={3}
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"marginBottom": 14,
|
||||
},
|
||||
}
|
||||
}
|
||||
value=""
|
||||
/>
|
||||
<StyledTextFieldBase
|
||||
defaultValue="test query"
|
||||
label="Query generated"
|
||||
multiline={true}
|
||||
readOnly={true}
|
||||
rows={3}
|
||||
styles={
|
||||
{
|
||||
"fieldGroup": {
|
||||
"backgroundColor": "#F3F2F1",
|
||||
"borderColor": "#D1D1D1",
|
||||
"borderRadius": 4,
|
||||
},
|
||||
"root": {
|
||||
"marginBottom": 14,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 12,
|
||||
"marginBottom": 14,
|
||||
}
|
||||
}
|
||||
>
|
||||
Microsoft will process the feedback you submit pursuant to your organization’s instructions in order to improve your and your organization’s experience with this product. If you have any questions about the use of feedback data, please contact your tenant administrator. Processing of feedback data is governed by the Microsoft Products and Services Data Protection Addendum between your organization and Microsoft, and the feedback you submit is considered Personal Data under that addendum. Please see the
|
||||
|
||||
<StyledLinkBase
|
||||
href="https://go.microsoft.com/fwlink/?LinkId=521839"
|
||||
target="_blank"
|
||||
>
|
||||
Privacy statement
|
||||
</StyledLinkBase>
|
||||
|
||||
for more information.
|
||||
</Text>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="end"
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"marginRight": 8,
|
||||
},
|
||||
}
|
||||
}
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
</CustomizedPrimaryButton>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
>
|
||||
Cancel
|
||||
</CustomizedDefaultButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</form>
|
||||
</Modal>
|
||||
`;
|
||||
|
||||
exports[`Query Copilot Feedback Modal snapshot test should close on cancel click 1`] = `
|
||||
<Modal
|
||||
isOpen={false}
|
||||
styles={
|
||||
{
|
||||
"main": {
|
||||
"borderRadius": 8,
|
||||
"maxWidth": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<form
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<Stack
|
||||
style={
|
||||
{
|
||||
"padding": 24,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="space-between"
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 20,
|
||||
"fontWeight": 600,
|
||||
"marginBottom": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
Send feedback to Microsoft
|
||||
</Text>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"iconName": "Cancel",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 14,
|
||||
"marginBottom": 14,
|
||||
}
|
||||
}
|
||||
>
|
||||
Your feedback will help improve the experience.
|
||||
</Text>
|
||||
<StyledTextFieldBase
|
||||
label="Description"
|
||||
multiline={true}
|
||||
onChange={[Function]}
|
||||
placeholder="Provide more details"
|
||||
required={true}
|
||||
rows={3}
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"marginBottom": 14,
|
||||
},
|
||||
}
|
||||
}
|
||||
value=""
|
||||
/>
|
||||
<StyledTextFieldBase
|
||||
defaultValue="test query"
|
||||
label="Query generated"
|
||||
multiline={true}
|
||||
readOnly={true}
|
||||
rows={3}
|
||||
styles={
|
||||
{
|
||||
"fieldGroup": {
|
||||
"backgroundColor": "#F3F2F1",
|
||||
"borderColor": "#D1D1D1",
|
||||
"borderRadius": 4,
|
||||
},
|
||||
"root": {
|
||||
"marginBottom": 14,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 12,
|
||||
"marginBottom": 14,
|
||||
}
|
||||
}
|
||||
>
|
||||
Microsoft will process the feedback you submit pursuant to your organization’s instructions in order to improve your and your organization’s experience with this product. If you have any questions about the use of feedback data, please contact your tenant administrator. Processing of feedback data is governed by the Microsoft Products and Services Data Protection Addendum between your organization and Microsoft, and the feedback you submit is considered Personal Data under that addendum. Please see the
|
||||
|
||||
<StyledLinkBase
|
||||
href="https://go.microsoft.com/fwlink/?LinkId=521839"
|
||||
target="_blank"
|
||||
>
|
||||
Privacy statement
|
||||
</StyledLinkBase>
|
||||
|
||||
for more information.
|
||||
</Text>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="end"
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"marginRight": 8,
|
||||
},
|
||||
}
|
||||
}
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
</CustomizedPrimaryButton>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
>
|
||||
Cancel
|
||||
</CustomizedDefaultButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</form>
|
||||
</Modal>
|
||||
`;
|
||||
|
||||
exports[`Query Copilot Feedback Modal snapshot test should get user unput 1`] = `
|
||||
<Modal
|
||||
isOpen={false}
|
||||
styles={
|
||||
{
|
||||
"main": {
|
||||
"borderRadius": 8,
|
||||
"maxWidth": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<form
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<Stack
|
||||
style={
|
||||
{
|
||||
"padding": 24,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="space-between"
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 20,
|
||||
"fontWeight": 600,
|
||||
"marginBottom": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
Send feedback to Microsoft
|
||||
</Text>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"iconName": "Cancel",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 14,
|
||||
"marginBottom": 14,
|
||||
}
|
||||
}
|
||||
>
|
||||
Your feedback will help improve the experience.
|
||||
</Text>
|
||||
<StyledTextFieldBase
|
||||
label="Description"
|
||||
multiline={true}
|
||||
onChange={[Function]}
|
||||
placeholder="Provide more details"
|
||||
required={true}
|
||||
rows={3}
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"marginBottom": 14,
|
||||
},
|
||||
}
|
||||
}
|
||||
value="test user input"
|
||||
/>
|
||||
<StyledTextFieldBase
|
||||
defaultValue="test query"
|
||||
label="Query generated"
|
||||
multiline={true}
|
||||
readOnly={true}
|
||||
rows={3}
|
||||
styles={
|
||||
{
|
||||
"fieldGroup": {
|
||||
"backgroundColor": "#F3F2F1",
|
||||
"borderColor": "#D1D1D1",
|
||||
"borderRadius": 4,
|
||||
},
|
||||
"root": {
|
||||
"marginBottom": 14,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 12,
|
||||
"marginBottom": 14,
|
||||
}
|
||||
}
|
||||
>
|
||||
Microsoft will process the feedback you submit pursuant to your organization’s instructions in order to improve your and your organization’s experience with this product. If you have any questions about the use of feedback data, please contact your tenant administrator. Processing of feedback data is governed by the Microsoft Products and Services Data Protection Addendum between your organization and Microsoft, and the feedback you submit is considered Personal Data under that addendum. Please see the
|
||||
|
||||
<StyledLinkBase
|
||||
href="https://go.microsoft.com/fwlink/?LinkId=521839"
|
||||
target="_blank"
|
||||
>
|
||||
Privacy statement
|
||||
</StyledLinkBase>
|
||||
|
||||
for more information.
|
||||
</Text>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="end"
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"marginRight": 8,
|
||||
},
|
||||
}
|
||||
}
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
</CustomizedPrimaryButton>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
>
|
||||
Cancel
|
||||
</CustomizedDefaultButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</form>
|
||||
</Modal>
|
||||
`;
|
||||
|
||||
exports[`Query Copilot Feedback Modal snapshot test should not render dont show again button 1`] = `
|
||||
<Modal
|
||||
isOpen={false}
|
||||
styles={
|
||||
{
|
||||
"main": {
|
||||
"borderRadius": 8,
|
||||
"maxWidth": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<form
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<Stack
|
||||
style={
|
||||
{
|
||||
"padding": 24,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="space-between"
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 20,
|
||||
"fontWeight": 600,
|
||||
"marginBottom": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
Send feedback to Microsoft
|
||||
</Text>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"iconName": "Cancel",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 14,
|
||||
"marginBottom": 14,
|
||||
}
|
||||
}
|
||||
>
|
||||
Your feedback will help improve the experience.
|
||||
</Text>
|
||||
<StyledTextFieldBase
|
||||
label="Description"
|
||||
multiline={true}
|
||||
onChange={[Function]}
|
||||
placeholder="Provide more details"
|
||||
required={true}
|
||||
rows={3}
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"marginBottom": 14,
|
||||
},
|
||||
}
|
||||
}
|
||||
value=""
|
||||
/>
|
||||
<StyledTextFieldBase
|
||||
defaultValue="test query"
|
||||
label="Query generated"
|
||||
multiline={true}
|
||||
readOnly={true}
|
||||
rows={3}
|
||||
styles={
|
||||
{
|
||||
"fieldGroup": {
|
||||
"backgroundColor": "#F3F2F1",
|
||||
"borderColor": "#D1D1D1",
|
||||
"borderRadius": 4,
|
||||
},
|
||||
"root": {
|
||||
"marginBottom": 14,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 12,
|
||||
"marginBottom": 14,
|
||||
}
|
||||
}
|
||||
>
|
||||
Microsoft will process the feedback you submit pursuant to your organization’s instructions in order to improve your and your organization’s experience with this product. If you have any questions about the use of feedback data, please contact your tenant administrator. Processing of feedback data is governed by the Microsoft Products and Services Data Protection Addendum between your organization and Microsoft, and the feedback you submit is considered Personal Data under that addendum. Please see the
|
||||
|
||||
<StyledLinkBase
|
||||
href="https://go.microsoft.com/fwlink/?LinkId=521839"
|
||||
target="_blank"
|
||||
>
|
||||
Privacy statement
|
||||
</StyledLinkBase>
|
||||
|
||||
for more information.
|
||||
</Text>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="end"
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"marginRight": 8,
|
||||
},
|
||||
}
|
||||
}
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
</CustomizedPrimaryButton>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
>
|
||||
Cancel
|
||||
</CustomizedDefaultButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</form>
|
||||
</Modal>
|
||||
`;
|
||||
|
||||
exports[`Query Copilot Feedback Modal snapshot test should render dont show again button and check it 1`] = `
|
||||
<Modal
|
||||
isOpen={true}
|
||||
styles={
|
||||
{
|
||||
"main": {
|
||||
"borderRadius": 8,
|
||||
"maxWidth": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<form
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<Stack
|
||||
style={
|
||||
{
|
||||
"padding": 24,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="space-between"
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 20,
|
||||
"fontWeight": 600,
|
||||
"marginBottom": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
Send feedback to Microsoft
|
||||
</Text>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"iconName": "Cancel",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 14,
|
||||
"marginBottom": 14,
|
||||
}
|
||||
}
|
||||
>
|
||||
Your feedback will help improve the experience.
|
||||
</Text>
|
||||
<StyledTextFieldBase
|
||||
label="Description"
|
||||
multiline={true}
|
||||
onChange={[Function]}
|
||||
placeholder="Provide more details"
|
||||
required={true}
|
||||
rows={3}
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"marginBottom": 14,
|
||||
},
|
||||
}
|
||||
}
|
||||
value=""
|
||||
/>
|
||||
<StyledTextFieldBase
|
||||
defaultValue="test query"
|
||||
label="Query generated"
|
||||
multiline={true}
|
||||
readOnly={true}
|
||||
rows={3}
|
||||
styles={
|
||||
{
|
||||
"fieldGroup": {
|
||||
"backgroundColor": "#F3F2F1",
|
||||
"borderColor": "#D1D1D1",
|
||||
"borderRadius": 4,
|
||||
},
|
||||
"root": {
|
||||
"marginBottom": 14,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 12,
|
||||
"marginBottom": 14,
|
||||
}
|
||||
}
|
||||
>
|
||||
Microsoft will process the feedback you submit pursuant to your organization’s instructions in order to improve your and your organization’s experience with this product. If you have any questions about the use of feedback data, please contact your tenant administrator. Processing of feedback data is governed by the Microsoft Products and Services Data Protection Addendum between your organization and Microsoft, and the feedback you submit is considered Personal Data under that addendum. Please see the
|
||||
|
||||
<StyledLinkBase
|
||||
href="https://go.microsoft.com/fwlink/?LinkId=521839"
|
||||
target="_blank"
|
||||
>
|
||||
Privacy statement
|
||||
</StyledLinkBase>
|
||||
|
||||
for more information.
|
||||
</Text>
|
||||
<StyledCheckboxBase
|
||||
checked={true}
|
||||
label="Don't show me this next time"
|
||||
onChange={[Function]}
|
||||
styles={
|
||||
{
|
||||
"label": {
|
||||
"paddingLeft": 0,
|
||||
},
|
||||
"root": {
|
||||
"marginBottom": 14,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="end"
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"marginRight": 8,
|
||||
},
|
||||
}
|
||||
}
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
</CustomizedPrimaryButton>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
>
|
||||
Cancel
|
||||
</CustomizedDefaultButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</form>
|
||||
</Modal>
|
||||
`;
|
||||
|
||||
exports[`Query Copilot Feedback Modal snapshot test should submit submission 1`] = `
|
||||
<Modal
|
||||
isOpen={false}
|
||||
styles={
|
||||
{
|
||||
"main": {
|
||||
"borderRadius": 8,
|
||||
"maxWidth": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<form
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<Stack
|
||||
style={
|
||||
{
|
||||
"padding": 24,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="space-between"
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 20,
|
||||
"fontWeight": 600,
|
||||
"marginBottom": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
Send feedback to Microsoft
|
||||
</Text>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"iconName": "Cancel",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 14,
|
||||
"marginBottom": 14,
|
||||
}
|
||||
}
|
||||
>
|
||||
Your feedback will help improve the experience.
|
||||
</Text>
|
||||
<StyledTextFieldBase
|
||||
label="Description"
|
||||
multiline={true}
|
||||
onChange={[Function]}
|
||||
placeholder="Provide more details"
|
||||
required={true}
|
||||
rows={3}
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"marginBottom": 14,
|
||||
},
|
||||
}
|
||||
}
|
||||
value=""
|
||||
/>
|
||||
<StyledTextFieldBase
|
||||
defaultValue="test query"
|
||||
label="Query generated"
|
||||
multiline={true}
|
||||
readOnly={true}
|
||||
rows={3}
|
||||
styles={
|
||||
{
|
||||
"fieldGroup": {
|
||||
"backgroundColor": "#F3F2F1",
|
||||
"borderColor": "#D1D1D1",
|
||||
"borderRadius": 4,
|
||||
},
|
||||
"root": {
|
||||
"marginBottom": 14,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 12,
|
||||
"marginBottom": 14,
|
||||
}
|
||||
}
|
||||
>
|
||||
Microsoft will process the feedback you submit pursuant to your organization’s instructions in order to improve your and your organization’s experience with this product. If you have any questions about the use of feedback data, please contact your tenant administrator. Processing of feedback data is governed by the Microsoft Products and Services Data Protection Addendum between your organization and Microsoft, and the feedback you submit is considered Personal Data under that addendum. Please see the
|
||||
|
||||
<StyledLinkBase
|
||||
href="https://go.microsoft.com/fwlink/?LinkId=521839"
|
||||
target="_blank"
|
||||
>
|
||||
Privacy statement
|
||||
</StyledLinkBase>
|
||||
|
||||
for more information.
|
||||
</Text>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="end"
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"marginRight": 8,
|
||||
},
|
||||
}
|
||||
}
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
</CustomizedPrimaryButton>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
>
|
||||
Cancel
|
||||
</CustomizedDefaultButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</form>
|
||||
</Modal>
|
||||
`;
|
||||
@@ -1,168 +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]}
|
||||
styles={
|
||||
{
|
||||
"main": {
|
||||
"borderRadius": 10,
|
||||
"maxHeight": 600,
|
||||
"overflow": "hidden",
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<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>
|
||||
<CustomizedIconButton
|
||||
ariaLabel="Exit"
|
||||
className="exitIcon"
|
||||
iconProps={
|
||||
{
|
||||
"iconName": "Cancel",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
title="Exit"
|
||||
/>
|
||||
</StackItem>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack
|
||||
horizontalAlign="center"
|
||||
>
|
||||
<StackItem
|
||||
align="center"
|
||||
style={
|
||||
{
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
as="h1"
|
||||
className="title bold"
|
||||
>
|
||||
Welcome to Microsoft Copilot for Azure in Cosmos DB (preview)
|
||||
</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="https://aka.ms/MicrosoftCopilotForAzureInCDBHowTo"
|
||||
target="_blank"
|
||||
>
|
||||
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 is accurate and appropriate before executing the query.
|
||||
<br />
|
||||
<StyledLinkBase
|
||||
href="https://aka.ms/cdb-copilot-preview-terms"
|
||||
target="_blank"
|
||||
>
|
||||
Read our preview terms here
|
||||
</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 +0,0 @@
|
||||
import { IconButton } from "@fluentui/react";
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { CopyPopup } from "./CopyPopup";
|
||||
|
||||
describe("Copy Popup snapshot test", () => {
|
||||
const setShowCopyPopupMock = jest.fn();
|
||||
it("should render when showCopyPopup is true", () => {
|
||||
const wrapper = shallow(<CopyPopup showCopyPopup={true} setShowCopyPopup={setShowCopyPopupMock} />);
|
||||
expect(wrapper.exists()).toBe(true);
|
||||
expect(wrapper.prop("setShowCopyPopup")).toBeUndefined();
|
||||
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,64 +0,0 @@
|
||||
import { IconButton, Image, Stack, Text } from "@fluentui/react";
|
||||
import React, { Dispatch, SetStateAction } from "react";
|
||||
import Success from "../../../../images/successfulPopup.svg";
|
||||
|
||||
export const CopyPopup = ({
|
||||
showCopyPopup,
|
||||
setShowCopyPopup,
|
||||
}: {
|
||||
showCopyPopup: boolean;
|
||||
setShowCopyPopup: Dispatch<SetStateAction<boolean>>;
|
||||
}): JSX.Element => {
|
||||
const closePopup = () => {
|
||||
setShowCopyPopup(false);
|
||||
};
|
||||
|
||||
return showCopyPopup ? (
|
||||
<Stack
|
||||
role="status"
|
||||
style={{
|
||||
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)",
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
horizontal
|
||||
verticalAlign="center"
|
||||
style={{ display: "flex", justifyContent: "space-between", padding: "5px, 2px, 0px, 0px" }}
|
||||
>
|
||||
<Stack horizontal verticalAlign="center" style={{ display: "flex", gap: 10 }}>
|
||||
<Image style={{ width: 15, height: 15 }} src={Success} />
|
||||
<Text>
|
||||
<b>Code copied successfully</b>
|
||||
</Text>
|
||||
</Stack>
|
||||
<IconButton
|
||||
styles={{
|
||||
root: {
|
||||
border: "none",
|
||||
backgroundColor: "transparent",
|
||||
padding: 0,
|
||||
selectors: {
|
||||
"&:focus": {
|
||||
outline: "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
iconProps={{ iconName: "Cancel" }}
|
||||
onClick={closePopup}
|
||||
/>
|
||||
</Stack>
|
||||
<Text style={{ marginTop: -10 }}>The query has been copied to the clipboard</Text>
|
||||
</Stack>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
};
|
||||
@@ -1,113 +0,0 @@
|
||||
import { mount, shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { DeletePopup } from "./DeletePopup";
|
||||
|
||||
describe("Delete Popup snapshot test", () => {
|
||||
const setShowDeletePopupMock = jest.fn();
|
||||
const setQueryMock = jest.fn();
|
||||
const clearFeedbackMock = jest.fn();
|
||||
const showFeedbackBarMock = jest.fn();
|
||||
|
||||
it("should render when showDeletePopup is true", () => {
|
||||
const wrapper = shallow(
|
||||
<DeletePopup
|
||||
showDeletePopup={true}
|
||||
setShowDeletePopup={setShowDeletePopupMock}
|
||||
setQuery={setQueryMock}
|
||||
clearFeedback={clearFeedbackMock}
|
||||
showFeedbackBar={showFeedbackBarMock}
|
||||
/>,
|
||||
);
|
||||
expect(wrapper.find("Modal").prop("isOpen")).toBeTruthy();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should not render when showDeletePopup is false", () => {
|
||||
const wrapper = shallow(
|
||||
<DeletePopup
|
||||
showDeletePopup={false}
|
||||
setShowDeletePopup={setShowDeletePopupMock}
|
||||
setQuery={setQueryMock}
|
||||
clearFeedback={clearFeedbackMock}
|
||||
showFeedbackBar={showFeedbackBarMock}
|
||||
/>,
|
||||
);
|
||||
expect(wrapper.props().children.props.showDeletePopup).toBeFalsy();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should call setQuery with an empty string and setShowDeletePopup(false) when delete button is clicked", () => {
|
||||
const wrapper = mount(
|
||||
<DeletePopup
|
||||
showDeletePopup={true}
|
||||
setShowDeletePopup={setShowDeletePopupMock}
|
||||
setQuery={setQueryMock}
|
||||
clearFeedback={clearFeedbackMock}
|
||||
showFeedbackBar={showFeedbackBarMock}
|
||||
/>,
|
||||
);
|
||||
|
||||
wrapper.find("PrimaryButton").simulate("click");
|
||||
|
||||
expect(setQueryMock).toHaveBeenCalledWith("");
|
||||
expect(setShowDeletePopupMock).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it("should call setShowDeletePopup(false) when close button is clicked", () => {
|
||||
const setShowDeletePopupMock = jest.fn();
|
||||
const wrapper = mount(
|
||||
<DeletePopup
|
||||
showDeletePopup={true}
|
||||
setShowDeletePopup={setShowDeletePopupMock}
|
||||
setQuery={setQueryMock}
|
||||
clearFeedback={clearFeedbackMock}
|
||||
showFeedbackBar={showFeedbackBarMock}
|
||||
/>,
|
||||
);
|
||||
|
||||
wrapper.find("DefaultButton").at(1).simulate("click");
|
||||
|
||||
expect(setShowDeletePopupMock).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it("should render the appropriate text content", () => {
|
||||
const wrapper = shallow(
|
||||
<DeletePopup
|
||||
showDeletePopup={true}
|
||||
setShowDeletePopup={setShowDeletePopupMock}
|
||||
setQuery={setQueryMock}
|
||||
clearFeedback={clearFeedbackMock}
|
||||
showFeedbackBar={showFeedbackBarMock}
|
||||
/>,
|
||||
);
|
||||
|
||||
const textContent = wrapper
|
||||
.find("Text")
|
||||
.map((text, index) => <React.Fragment key={index}>{text.props().children}</React.Fragment>);
|
||||
|
||||
expect(textContent).toEqual([
|
||||
<React.Fragment key={0}>
|
||||
<b>Delete code?</b>
|
||||
</React.Fragment>,
|
||||
<React.Fragment key={1}>
|
||||
This will clear the query from the query builder pane along with all comments and also reset the prompt pane
|
||||
</React.Fragment>,
|
||||
]);
|
||||
});
|
||||
|
||||
it("should have the correct inline style", () => {
|
||||
const wrapper = shallow(
|
||||
<DeletePopup
|
||||
showDeletePopup={true}
|
||||
setShowDeletePopup={setShowDeletePopupMock}
|
||||
setQuery={setQueryMock}
|
||||
clearFeedback={clearFeedbackMock}
|
||||
showFeedbackBar={showFeedbackBarMock}
|
||||
/>,
|
||||
);
|
||||
|
||||
const stackStyle = wrapper.find("Stack[style]").props().style;
|
||||
|
||||
expect(stackStyle).toEqual({ padding: "16px 24px", height: "auto" });
|
||||
});
|
||||
});
|
||||
@@ -1,49 +0,0 @@
|
||||
import { DefaultButton, Modal, PrimaryButton, Stack, Text } from "@fluentui/react";
|
||||
import React, { Dispatch, SetStateAction } from "react";
|
||||
|
||||
export const DeletePopup = ({
|
||||
showDeletePopup,
|
||||
setShowDeletePopup,
|
||||
setQuery,
|
||||
clearFeedback,
|
||||
showFeedbackBar,
|
||||
}: {
|
||||
showDeletePopup: boolean;
|
||||
setShowDeletePopup: Dispatch<SetStateAction<boolean>>;
|
||||
setQuery: Dispatch<SetStateAction<string>>;
|
||||
clearFeedback: Dispatch<SetStateAction<void>>;
|
||||
showFeedbackBar: Dispatch<SetStateAction<boolean>>;
|
||||
}): JSX.Element => {
|
||||
const deleteCode = () => {
|
||||
setQuery("");
|
||||
setShowDeletePopup(false);
|
||||
clearFeedback();
|
||||
showFeedbackBar(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={showDeletePopup}
|
||||
styles={{ main: { minHeight: "122px", minWidth: "880px" } }}
|
||||
titleAriaId="deleteDialogTitle"
|
||||
subtitleAriaId="deleteDialogSubTitle"
|
||||
>
|
||||
<Stack style={{ padding: "16px 24px", height: "auto" }}>
|
||||
<Text id="deleteDialogTitle" style={{ height: 24, fontSize: "18px" }}>
|
||||
<b>Delete code?</b>
|
||||
</Text>
|
||||
<Text id="deleteDialogSubTitle" style={{ marginTop: 10, marginBottom: 20 }}>
|
||||
This will clear the query from the query builder pane along with all comments and also reset the prompt pane
|
||||
</Text>
|
||||
<Stack horizontal tokens={{ childrenGap: 10 }} horizontalAlign="start">
|
||||
<PrimaryButton style={{ padding: "0px 20px", height: 24 }} onClick={deleteCode}>
|
||||
Delete
|
||||
</PrimaryButton>
|
||||
<DefaultButton style={{ padding: "0px 20px", height: 24 }} onClick={() => setShowDeletePopup(false)}>
|
||||
Close
|
||||
</DefaultButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -1,91 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Copy Popup snapshot test should render when showCopyPopup is false 1`] = `<Fragment />`;
|
||||
|
||||
exports[`Copy Popup snapshot test should render when showCopyPopup is true 1`] = `
|
||||
<Stack
|
||||
role="status"
|
||||
style={
|
||||
{
|
||||
"background": "#FFFFFF",
|
||||
"boxShadow": "0 2px 6px rgba(0, 0, 0, 0.16)",
|
||||
"gap": 5,
|
||||
"height": 66,
|
||||
"padding": 10,
|
||||
"position": "fixed",
|
||||
"right": 20,
|
||||
"top": 75,
|
||||
"width": 345,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
style={
|
||||
{
|
||||
"display": "flex",
|
||||
"justifyContent": "space-between",
|
||||
"padding": "5px, 2px, 0px, 0px",
|
||||
}
|
||||
}
|
||||
verticalAlign="center"
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
style={
|
||||
{
|
||||
"display": "flex",
|
||||
"gap": 10,
|
||||
}
|
||||
}
|
||||
verticalAlign="center"
|
||||
>
|
||||
<Image
|
||||
src={{}}
|
||||
style={
|
||||
{
|
||||
"height": 15,
|
||||
"width": 15,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text>
|
||||
<b>
|
||||
Code copied successfully
|
||||
</b>
|
||||
</Text>
|
||||
</Stack>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"iconName": "Cancel",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"backgroundColor": "transparent",
|
||||
"border": "none",
|
||||
"padding": 0,
|
||||
"selectors": {
|
||||
"&:focus": {
|
||||
"outline": "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"marginTop": -10,
|
||||
}
|
||||
}
|
||||
>
|
||||
The query has been copied to the clipboard
|
||||
</Text>
|
||||
</Stack>
|
||||
`;
|
||||
@@ -1,165 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Delete Popup snapshot test should not render when showDeletePopup is false 1`] = `
|
||||
<Modal
|
||||
isOpen={false}
|
||||
styles={
|
||||
{
|
||||
"main": {
|
||||
"minHeight": "122px",
|
||||
"minWidth": "880px",
|
||||
},
|
||||
}
|
||||
}
|
||||
subtitleAriaId="deleteDialogSubTitle"
|
||||
titleAriaId="deleteDialogTitle"
|
||||
>
|
||||
<Stack
|
||||
style={
|
||||
{
|
||||
"height": "auto",
|
||||
"padding": "16px 24px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
id="deleteDialogTitle"
|
||||
style={
|
||||
{
|
||||
"fontSize": "18px",
|
||||
"height": 24,
|
||||
}
|
||||
}
|
||||
>
|
||||
<b>
|
||||
Delete code?
|
||||
</b>
|
||||
</Text>
|
||||
<Text
|
||||
id="deleteDialogSubTitle"
|
||||
style={
|
||||
{
|
||||
"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={
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
onClick={[Function]}
|
||||
style={
|
||||
{
|
||||
"height": 24,
|
||||
"padding": "0px 20px",
|
||||
}
|
||||
}
|
||||
>
|
||||
Delete
|
||||
</CustomizedPrimaryButton>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
style={
|
||||
{
|
||||
"height": 24,
|
||||
"padding": "0px 20px",
|
||||
}
|
||||
}
|
||||
>
|
||||
Close
|
||||
</CustomizedDefaultButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Modal>
|
||||
`;
|
||||
|
||||
exports[`Delete Popup snapshot test should render when showDeletePopup is true 1`] = `
|
||||
<Modal
|
||||
isOpen={true}
|
||||
styles={
|
||||
{
|
||||
"main": {
|
||||
"minHeight": "122px",
|
||||
"minWidth": "880px",
|
||||
},
|
||||
}
|
||||
}
|
||||
subtitleAriaId="deleteDialogSubTitle"
|
||||
titleAriaId="deleteDialogTitle"
|
||||
>
|
||||
<Stack
|
||||
style={
|
||||
{
|
||||
"height": "auto",
|
||||
"padding": "16px 24px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
id="deleteDialogTitle"
|
||||
style={
|
||||
{
|
||||
"fontSize": "18px",
|
||||
"height": 24,
|
||||
}
|
||||
}
|
||||
>
|
||||
<b>
|
||||
Delete code?
|
||||
</b>
|
||||
</Text>
|
||||
<Text
|
||||
id="deleteDialogSubTitle"
|
||||
style={
|
||||
{
|
||||
"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={
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
onClick={[Function]}
|
||||
style={
|
||||
{
|
||||
"height": 24,
|
||||
"padding": "0px 20px",
|
||||
}
|
||||
}
|
||||
>
|
||||
Delete
|
||||
</CustomizedPrimaryButton>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
style={
|
||||
{
|
||||
"height": 24,
|
||||
"padding": "0px 20px",
|
||||
}
|
||||
}
|
||||
>
|
||||
Close
|
||||
</CustomizedDefaultButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Modal>
|
||||
`;
|
||||
@@ -1,19 +0,0 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { PromptCard } from "./PromptCard";
|
||||
|
||||
describe("Prompt card snapshot test", () => {
|
||||
it("should render properly if isSelected is true", () => {
|
||||
const wrapper = shallow(
|
||||
<PromptCard header="TestHeader" description="TestDescription" isSelected={true} onSelect={() => undefined} />,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render properly if isSelected is false", () => {
|
||||
const wrapper = shallow(
|
||||
<PromptCard header="TestHeader" description="TestDescription" isSelected={false} onSelect={() => undefined} />,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,48 +0,0 @@
|
||||
import { ChoiceGroup, Stack, Text } from "@fluentui/react";
|
||||
import React from "react";
|
||||
|
||||
interface PromptCardProps {
|
||||
header: string;
|
||||
description: string;
|
||||
isSelected: boolean;
|
||||
onSelect: () => void;
|
||||
}
|
||||
|
||||
export const PromptCard: React.FC<PromptCardProps> = ({
|
||||
header,
|
||||
description,
|
||||
isSelected,
|
||||
onSelect,
|
||||
}: PromptCardProps): JSX.Element => {
|
||||
return (
|
||||
<Stack
|
||||
horizontal
|
||||
style={{
|
||||
padding: "16px 0 16px 16px ",
|
||||
boxSizing: "border-box",
|
||||
width: 650,
|
||||
height: 100,
|
||||
border: "1px solid #F3F2F1",
|
||||
boxShadow: "0px 1.6px 3.6px rgba(0, 0, 0, 0.132), 0px 0.3px 0.9px rgba(0, 0, 0, 0.108)",
|
||||
}}
|
||||
>
|
||||
<Stack.Item grow={1}>
|
||||
<Stack horizontal>
|
||||
<div>
|
||||
<Text style={{ fontSize: 13, color: "#00A2AD", background: "#F8FFF0" }}>Prompt</Text>
|
||||
</div>
|
||||
<Text style={{ fontSize: 13, marginLeft: 16 }}>{header}</Text>
|
||||
</Stack>
|
||||
<Text style={{ fontSize: 10, marginTop: 16 }}>{description}</Text>
|
||||
</Stack.Item>
|
||||
<Stack.Item style={{ marginLeft: 16 }}>
|
||||
<ChoiceGroup
|
||||
styles={{ flexContainer: { width: 36 } }}
|
||||
options={[{ key: "selected", text: "" }]}
|
||||
selectedKey={isSelected ? "selected" : ""}
|
||||
onChange={onSelect}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -1,139 +0,0 @@
|
||||
import { MinimalQueryIterator } from "Common/IteratorUtilities";
|
||||
import QueryError from "Common/QueryError";
|
||||
import { QueryResults } from "Contracts/ViewModels";
|
||||
import { CopilotMessage } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
||||
import { guid } from "Explorer/Tables/Utilities";
|
||||
import { QueryCopilotState } from "hooks/useQueryCopilot";
|
||||
|
||||
import React, { createContext, useContext, useState } from "react";
|
||||
import create from "zustand";
|
||||
const context = createContext(null);
|
||||
const useCopilotStore = (): Partial<QueryCopilotState> => useContext(context);
|
||||
|
||||
const CopilotProvider = ({ children }: { children: React.ReactNode }): JSX.Element => {
|
||||
const [useStore] = useState(() =>
|
||||
create((set, get) => ({
|
||||
generatedQuery: "",
|
||||
likeQuery: false,
|
||||
userPrompt: "",
|
||||
showFeedbackModal: false,
|
||||
hideFeedbackModalForLikedQueries: false,
|
||||
correlationId: "",
|
||||
query: "SELECT * FROM c",
|
||||
selectedQuery: "",
|
||||
isGeneratingQuery: false,
|
||||
isGeneratingExplanation: false,
|
||||
isExecuting: false,
|
||||
dislikeQuery: undefined,
|
||||
showCallout: false,
|
||||
showSamplePrompts: false,
|
||||
queryIterator: undefined,
|
||||
queryResults: undefined,
|
||||
errors: [],
|
||||
isSamplePromptsOpen: false,
|
||||
showPromptTeachingBubble: true,
|
||||
showDeletePopup: false,
|
||||
showFeedbackBar: false,
|
||||
showCopyPopup: false,
|
||||
showErrorMessageBar: false,
|
||||
showInvalidQueryMessageBar: false,
|
||||
generatedQueryComments: "",
|
||||
wasCopilotUsed: false,
|
||||
showWelcomeSidebar: true,
|
||||
showCopilotSidebar: false,
|
||||
chatMessages: [],
|
||||
shouldIncludeInMessages: true,
|
||||
showExplanationBubble: false,
|
||||
isAllocatingContainer: false,
|
||||
|
||||
openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) =>
|
||||
set({ generatedQuery, likeQuery, userPrompt, showFeedbackModal: true }),
|
||||
closeFeedbackModal: () => set({ showFeedbackModal: false }),
|
||||
setHideFeedbackModalForLikedQueries: (hideFeedbackModalForLikedQueries: boolean) =>
|
||||
set({ hideFeedbackModalForLikedQueries }),
|
||||
refreshCorrelationId: () => set({ correlationId: guid() }),
|
||||
setUserPrompt: (userPrompt: string) => set({ userPrompt }),
|
||||
setQuery: (query: string) => set({ query }),
|
||||
setGeneratedQuery: (generatedQuery: string) => set({ generatedQuery }),
|
||||
setSelectedQuery: (selectedQuery: string) => set({ selectedQuery }),
|
||||
setIsGeneratingQuery: (isGeneratingQuery: boolean) => set({ isGeneratingQuery }),
|
||||
setIsGeneratingExplanation: (isGeneratingExplanation: boolean) => set({ isGeneratingExplanation }),
|
||||
setIsExecuting: (isExecuting: boolean) => set({ isExecuting }),
|
||||
setLikeQuery: (likeQuery: boolean) => set({ likeQuery }),
|
||||
setDislikeQuery: (dislikeQuery: boolean | undefined) => set({ dislikeQuery }),
|
||||
setShowCallout: (showCallout: boolean) => set({ showCallout }),
|
||||
setShowSamplePrompts: (showSamplePrompts: boolean) => set({ showSamplePrompts }),
|
||||
setQueryIterator: (queryIterator: MinimalQueryIterator | undefined) => set({ queryIterator }),
|
||||
setQueryResults: (queryResults: QueryResults | undefined) => set({ queryResults }),
|
||||
setErrors: (errors: QueryError[]) => set({ errors }),
|
||||
setIsSamplePromptsOpen: (isSamplePromptsOpen: boolean) => set({ isSamplePromptsOpen }),
|
||||
setShowPromptTeachingBubble: (showPromptTeachingBubble: boolean) => set({ showPromptTeachingBubble }),
|
||||
setShowDeletePopup: (showDeletePopup: boolean) => set({ showDeletePopup }),
|
||||
setShowFeedbackBar: (showFeedbackBar: boolean) => set({ showFeedbackBar }),
|
||||
setshowCopyPopup: (showCopyPopup: boolean) => set({ showCopyPopup }),
|
||||
setShowErrorMessageBar: (showErrorMessageBar: boolean) => set({ showErrorMessageBar }),
|
||||
setShowInvalidQueryMessageBar: (showInvalidQueryMessageBar: boolean) => set({ showInvalidQueryMessageBar }),
|
||||
setGeneratedQueryComments: (generatedQueryComments: string) => set({ generatedQueryComments }),
|
||||
setWasCopilotUsed: (wasCopilotUsed: boolean) => set({ wasCopilotUsed }),
|
||||
setShowWelcomeSidebar: (showWelcomeSidebar: boolean) => set({ showWelcomeSidebar }),
|
||||
setShowCopilotSidebar: (showCopilotSidebar: boolean) => set({ showCopilotSidebar }),
|
||||
setChatMessages: (chatMessages: CopilotMessage[]) => set({ chatMessages }),
|
||||
setShouldIncludeInMessages: (shouldIncludeInMessages: boolean) => set({ shouldIncludeInMessages }),
|
||||
setShowExplanationBubble: (showExplanationBubble: boolean) => set({ showExplanationBubble }),
|
||||
|
||||
getState: () => {
|
||||
return get();
|
||||
},
|
||||
|
||||
resetQueryCopilotStates: () => {
|
||||
set((state) => ({
|
||||
...state,
|
||||
generatedQuery: "",
|
||||
likeQuery: false,
|
||||
userPrompt: "",
|
||||
showFeedbackModal: false,
|
||||
hideFeedbackModalForLikedQueries: false,
|
||||
correlationId: "",
|
||||
query: "SELECT * FROM c",
|
||||
selectedQuery: "",
|
||||
isGeneratingQuery: false,
|
||||
isGeneratingExplanation: false,
|
||||
isExecuting: false,
|
||||
dislikeQuery: undefined,
|
||||
showCallout: false,
|
||||
showSamplePrompts: false,
|
||||
queryIterator: undefined,
|
||||
queryResults: undefined,
|
||||
errorMessage: "",
|
||||
isSamplePromptsOpen: false,
|
||||
showPromptTeachingBubble: true,
|
||||
showDeletePopup: false,
|
||||
showFeedbackBar: false,
|
||||
showCopyPopup: false,
|
||||
showErrorMessageBar: false,
|
||||
showInvalidQueryMessageBar: false,
|
||||
generatedQueryComments: "",
|
||||
wasCopilotUsed: false,
|
||||
showCopilotSidebar: false,
|
||||
chatMessages: [],
|
||||
shouldIncludeInMessages: true,
|
||||
showExplanationBubble: false,
|
||||
notebookServerInfo: {
|
||||
notebookServerEndpoint: undefined,
|
||||
authToken: undefined,
|
||||
forwardingId: undefined,
|
||||
},
|
||||
containerStatus: {
|
||||
status: undefined,
|
||||
durationLeftInMinutes: undefined,
|
||||
phoenixServerInfo: undefined,
|
||||
},
|
||||
isAllocatingContainer: false,
|
||||
}));
|
||||
},
|
||||
})),
|
||||
);
|
||||
return <context.Provider value={useStore()}>{children}</context.Provider>;
|
||||
};
|
||||
|
||||
export { CopilotProvider, useCopilotStore };
|
||||
@@ -1,769 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable no-console */
|
||||
import {
|
||||
Callout,
|
||||
CommandBarButton,
|
||||
DefaultButton,
|
||||
DirectionalHint,
|
||||
IButtonStyles,
|
||||
IconButton,
|
||||
Image,
|
||||
Link,
|
||||
MessageBar,
|
||||
MessageBarType,
|
||||
ProgressIndicator,
|
||||
Separator,
|
||||
Stack,
|
||||
TeachingBubble,
|
||||
Text,
|
||||
TextField,
|
||||
} from "@fluentui/react";
|
||||
import { FeedbackLabels, HttpStatusCodes, NormalizedEventKey } from "Common/Constants";
|
||||
import { handleError } from "Common/ErrorHandlingUtils";
|
||||
import QueryError, { QueryErrorSeverity } from "Common/QueryError";
|
||||
import { createUri } from "Common/UrlUtility";
|
||||
import { CopyPopup } from "Explorer/QueryCopilot/Popup/CopyPopup";
|
||||
import { DeletePopup } from "Explorer/QueryCopilot/Popup/DeletePopup";
|
||||
import {
|
||||
SuggestedPrompt,
|
||||
getSampleDatabaseSuggestedPrompts,
|
||||
getSuggestedPrompts,
|
||||
readPromptHistory,
|
||||
savePromptHistory,
|
||||
} from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
||||
import { SubmitFeedback, allocatePhoenixContainer } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||
import { GenerateSQLQueryResponse, QueryCopilotProps } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
||||
import { SamplePrompts, SamplePromptsProps } from "Explorer/QueryCopilot/Shared/SamplePrompts/SamplePrompts";
|
||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||
import { userContext } from "UserContext";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React, { useMemo, useRef, useState } from "react";
|
||||
import HintIcon from "../../../images/Hint.svg";
|
||||
import RecentIcon from "../../../images/Recent.svg";
|
||||
import errorIcon from "../../../images/close-black.svg";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import { useCopilotStore } from "../QueryCopilot/QueryCopilotContext";
|
||||
import { useSelectedNode } from "../useSelectedNode";
|
||||
|
||||
type QueryCopilotPromptProps = QueryCopilotProps & {
|
||||
databaseId: string;
|
||||
containerId: string;
|
||||
toggleCopilot: (toggle: boolean) => void;
|
||||
};
|
||||
|
||||
const promptStyles: IButtonStyles = {
|
||||
root: { border: 0, selectors: { ":hover": { outline: "1px dashed #605e5c" } } },
|
||||
label: {
|
||||
fontWeight: 400,
|
||||
textAlign: "left",
|
||||
paddingLeft: 8,
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
},
|
||||
textContainer: { overflow: "hidden" },
|
||||
};
|
||||
|
||||
export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
||||
explorer,
|
||||
toggleCopilot,
|
||||
databaseId,
|
||||
containerId,
|
||||
}: QueryCopilotPromptProps): JSX.Element => {
|
||||
const [copilotTeachingBubbleVisible, setCopilotTeachingBubbleVisible] = useState<boolean>(false);
|
||||
const inputEdited = useRef(false);
|
||||
const itemRefs = useRef([]);
|
||||
const searchInputRef = useRef(null);
|
||||
const copyQueryRef = useRef(null);
|
||||
const {
|
||||
openFeedbackModal,
|
||||
hideFeedbackModalForLikedQueries,
|
||||
userPrompt,
|
||||
setUserPrompt,
|
||||
generatedQuery,
|
||||
setGeneratedQuery,
|
||||
query,
|
||||
setQuery,
|
||||
isGeneratingQuery,
|
||||
setIsGeneratingQuery,
|
||||
likeQuery,
|
||||
setLikeQuery,
|
||||
dislikeQuery,
|
||||
setDislikeQuery,
|
||||
showCallout,
|
||||
setShowCallout,
|
||||
isSamplePromptsOpen,
|
||||
setIsSamplePromptsOpen,
|
||||
showSamplePrompts,
|
||||
setShowSamplePrompts,
|
||||
showPromptTeachingBubble,
|
||||
setShowPromptTeachingBubble,
|
||||
showDeletePopup,
|
||||
setShowDeletePopup,
|
||||
showFeedbackBar,
|
||||
setShowFeedbackBar,
|
||||
showCopyPopup,
|
||||
setshowCopyPopup,
|
||||
showErrorMessageBar,
|
||||
showInvalidQueryMessageBar,
|
||||
setShowInvalidQueryMessageBar,
|
||||
setShowErrorMessageBar,
|
||||
setGeneratedQueryComments,
|
||||
setQueryResults,
|
||||
setErrors,
|
||||
errors,
|
||||
} = useCopilotStore();
|
||||
const [focusedIndex, setFocusedIndex] = useState(-1);
|
||||
const sampleProps: SamplePromptsProps = {
|
||||
isSamplePromptsOpen: isSamplePromptsOpen,
|
||||
setIsSamplePromptsOpen: setIsSamplePromptsOpen,
|
||||
setTextBox: setUserPrompt,
|
||||
};
|
||||
|
||||
const copyGeneratedCode = () => {
|
||||
if (!query) {
|
||||
return;
|
||||
}
|
||||
const queryElement = document.createElement("textarea");
|
||||
queryElement.value = query;
|
||||
document.body.appendChild(queryElement);
|
||||
queryElement.select();
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(queryElement);
|
||||
|
||||
setshowCopyPopup(true);
|
||||
copyQueryRef.current.focus();
|
||||
setTimeout(() => {
|
||||
setshowCopyPopup(false);
|
||||
}, 6000);
|
||||
};
|
||||
|
||||
const isSampleCopilotActive = useSelectedNode.getState().isQueryCopilotCollectionSelected();
|
||||
const [histories, setHistories] = useState<string[]>(() => readPromptHistory(userContext.databaseAccount));
|
||||
const suggestedPrompts: SuggestedPrompt[] = isSampleCopilotActive
|
||||
? getSampleDatabaseSuggestedPrompts()
|
||||
: getSuggestedPrompts();
|
||||
const [filteredHistories, setFilteredHistories] = useState<string[]>(histories);
|
||||
const [filteredSuggestedPrompts, setFilteredSuggestedPrompts] = useState<SuggestedPrompt[]>(suggestedPrompts);
|
||||
const { UpArrow, DownArrow, Enter } = NormalizedEventKey;
|
||||
|
||||
const handleUserPromptChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
inputEdited.current = true;
|
||||
const { value } = event.target;
|
||||
setUserPrompt(value);
|
||||
|
||||
// Filter history prompts
|
||||
const filteredHistory = histories.filter((history) => history.toLowerCase().includes(value.toLowerCase()));
|
||||
setFilteredHistories(filteredHistory);
|
||||
|
||||
// Filter suggested prompts
|
||||
const filteredSuggested = suggestedPrompts.filter((prompt) =>
|
||||
prompt.text.toLowerCase().includes(value.toLowerCase()),
|
||||
);
|
||||
setFilteredSuggestedPrompts(filteredSuggested);
|
||||
};
|
||||
|
||||
const updateHistories = (): void => {
|
||||
const formattedUserPrompt = userPrompt.replace(/\s+/g, " ").trim();
|
||||
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);
|
||||
savePromptHistory(userContext.databaseAccount, newHistories);
|
||||
};
|
||||
|
||||
const resetMessageStates = (): void => {
|
||||
setShowErrorMessageBar(false);
|
||||
setShowInvalidQueryMessageBar(false);
|
||||
setShowFeedbackBar(false);
|
||||
};
|
||||
|
||||
const resetQueryResults = (): void => {
|
||||
setQueryResults(null);
|
||||
setErrors([]);
|
||||
};
|
||||
|
||||
const generateSQLQuery = async (): Promise<void> => {
|
||||
try {
|
||||
resetMessageStates();
|
||||
setIsGeneratingQuery(true);
|
||||
setShowDeletePopup(false);
|
||||
useTabs.getState().setIsTabExecuting(true);
|
||||
useTabs.getState().setIsQueryErrorThrown(false);
|
||||
const mode: string = isSampleCopilotActive ? "Sample" : "User";
|
||||
|
||||
await allocatePhoenixContainer({ explorer, databaseId, containerId, mode });
|
||||
|
||||
const payload = {
|
||||
userPrompt: userPrompt,
|
||||
};
|
||||
useQueryCopilot.getState().refreshCorrelationId();
|
||||
const serverInfo = useQueryCopilot.getState().notebookServerInfo;
|
||||
const queryUri = userContext.features.disableCopilotPhoenixGateaway
|
||||
? createUri("https://copilotorchestrater.azurewebsites.net/", "generateSQLQuery")
|
||||
: createUri(serverInfo.notebookServerEndpoint, "public/generateSQLQuery");
|
||||
const response = await fetch(queryUri, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"x-ms-correlationid": useQueryCopilot.getState().correlationId,
|
||||
Authorization: `token ${useQueryCopilot.getState().notebookServerInfo.authToken}`,
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
const generateSQLQueryResponse: GenerateSQLQueryResponse = await response?.json();
|
||||
if (response.ok) {
|
||||
if (generateSQLQueryResponse?.sql !== "N/A") {
|
||||
const queryExplanation = `-- **Explanation of query:** ${
|
||||
generateSQLQueryResponse.explanation ? generateSQLQueryResponse.explanation : "N/A"
|
||||
}\r\n`;
|
||||
const currentGeneratedQuery = queryExplanation + generateSQLQueryResponse.sql;
|
||||
const lastQuery = generatedQuery && query ? `${query}\r\n` : "";
|
||||
setQuery(`${lastQuery}${currentGeneratedQuery}`);
|
||||
setGeneratedQuery(generateSQLQueryResponse.sql);
|
||||
setGeneratedQueryComments(generateSQLQueryResponse.explanation);
|
||||
setShowFeedbackBar(true);
|
||||
resetQueryResults();
|
||||
TelemetryProcessor.traceSuccess(Action.QueryGenerationFromCopilotPrompt, {
|
||||
databaseName: databaseId,
|
||||
collectionId: containerId,
|
||||
copilotLatency:
|
||||
Date.parse(generateSQLQueryResponse?.generateEnd) - Date.parse(generateSQLQueryResponse?.generateStart),
|
||||
responseCode: response.status,
|
||||
});
|
||||
} else {
|
||||
setShowInvalidQueryMessageBar(true);
|
||||
TelemetryProcessor.traceFailure(Action.QueryGenerationFromCopilotPrompt, {
|
||||
databaseName: databaseId,
|
||||
collectionId: containerId,
|
||||
responseCode: response.status,
|
||||
});
|
||||
}
|
||||
} else if (response?.status === HttpStatusCodes.TooManyRequests) {
|
||||
handleError(JSON.stringify(generateSQLQueryResponse), "copilotTooManyRequestError");
|
||||
useTabs.getState().setIsQueryErrorThrown(true);
|
||||
setShowErrorMessageBar(true);
|
||||
setErrors([
|
||||
new QueryError(
|
||||
"Ratelimit exceeded 5 per 1 minute. Please try again after sometime",
|
||||
QueryErrorSeverity.Error,
|
||||
),
|
||||
]);
|
||||
TelemetryProcessor.traceFailure(Action.QueryGenerationFromCopilotPrompt, {
|
||||
databaseName: databaseId,
|
||||
collectionId: containerId,
|
||||
responseCode: response.status,
|
||||
});
|
||||
} else {
|
||||
handleError(JSON.stringify(generateSQLQueryResponse), "copilotInternalServerError");
|
||||
useTabs.getState().setIsQueryErrorThrown(true);
|
||||
setShowErrorMessageBar(true);
|
||||
TelemetryProcessor.traceFailure(Action.QueryGenerationFromCopilotPrompt, {
|
||||
databaseName: databaseId,
|
||||
collectionId: containerId,
|
||||
responseCode: response.status,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, "executeNaturalLanguageQuery");
|
||||
useTabs.getState().setIsQueryErrorThrown(true);
|
||||
setShowErrorMessageBar(true);
|
||||
throw error;
|
||||
} finally {
|
||||
setIsGeneratingQuery(false);
|
||||
useTabs.getState().setIsTabExecuting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleCopilotTeachingBubbleVisible = (visible: boolean): void => {
|
||||
setCopilotTeachingBubbleVisible(visible);
|
||||
setShowPromptTeachingBubble(visible);
|
||||
};
|
||||
|
||||
const clearFeedback = () => {
|
||||
resetButtonState();
|
||||
resetQueryResults();
|
||||
};
|
||||
|
||||
const resetButtonState = () => {
|
||||
setDislikeQuery(false);
|
||||
setLikeQuery(false);
|
||||
setShowCallout(false);
|
||||
};
|
||||
|
||||
const startGenerateQueryProcess = () => {
|
||||
updateHistories();
|
||||
generateSQLQuery();
|
||||
resetButtonState();
|
||||
};
|
||||
|
||||
const getAriaLabel = () => {
|
||||
if (isGeneratingQuery === null) {
|
||||
return " ";
|
||||
} else if (isGeneratingQuery) {
|
||||
return "Thinking";
|
||||
} else {
|
||||
return "Content is updated";
|
||||
}
|
||||
};
|
||||
const openSamplePrompts = () => {
|
||||
inputEdited.current = true;
|
||||
setShowSamplePrompts(true);
|
||||
};
|
||||
const totalSuggestions = useMemo(
|
||||
() => [...filteredSuggestedPrompts, ...filteredHistories],
|
||||
[filteredSuggestedPrompts, filteredHistories],
|
||||
);
|
||||
|
||||
const handleKeyDownForInput = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === DownArrow) {
|
||||
setFocusedIndex(0);
|
||||
itemRefs.current[0]?.current?.focus();
|
||||
} else if (event.key === Enter && userPrompt) {
|
||||
inputEdited.current = true;
|
||||
startGenerateQueryProcess();
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDownForItem = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === UpArrow && focusedIndex > 0) {
|
||||
itemRefs.current[focusedIndex - 1].current?.focus();
|
||||
setFocusedIndex((prevIndex) => prevIndex - 1);
|
||||
} else if (event.key === DownArrow && focusedIndex < totalSuggestions.length - 1) {
|
||||
itemRefs.current[focusedIndex + 1].current?.focus();
|
||||
setFocusedIndex((prevIndex) => prevIndex + 1);
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
itemRefs.current = totalSuggestions.map(() => React.createRef());
|
||||
}, [totalSuggestions]);
|
||||
React.useEffect(() => {
|
||||
useTabs.getState().setIsQueryErrorThrown(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
className="copilot-prompt-pane"
|
||||
styles={{ root: { backgroundColor: "#FAFAFA", padding: "8px" } }}
|
||||
id="copilot-textfield-label"
|
||||
>
|
||||
<Stack
|
||||
horizontal
|
||||
styles={{
|
||||
root: {
|
||||
width: "100%",
|
||||
borderWidth: 1,
|
||||
borderStyle: "solid",
|
||||
borderColor: "#D1D1D1",
|
||||
borderRadius: 8,
|
||||
boxShadow: "0px 4px 8px 0px #00000024",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Stack style={{ width: "100%" }}>
|
||||
<Stack horizontal verticalAlign="center" style={{ padding: "8px 8px 0px 8px" }}>
|
||||
<TextField
|
||||
id="naturalLanguageInput"
|
||||
value={userPrompt}
|
||||
onChange={handleUserPromptChange}
|
||||
onClick={openSamplePrompts}
|
||||
onFocus={() => setShowSamplePrompts(true)}
|
||||
elementRef={searchInputRef}
|
||||
onKeyDown={handleKeyDownForInput}
|
||||
style={{ lineHeight: 30 }}
|
||||
styles={{
|
||||
root: { width: "100%" },
|
||||
suffix: { background: "none", padding: 0 },
|
||||
fieldGroup: {
|
||||
borderRadius: 4,
|
||||
borderColor: "#D1D1D1",
|
||||
"::after": {
|
||||
border: "inherit",
|
||||
borderWidth: 2,
|
||||
borderBottomColor: "#464FEB",
|
||||
borderRadius: 4,
|
||||
},
|
||||
},
|
||||
}}
|
||||
disabled={isGeneratingQuery}
|
||||
autoComplete="off"
|
||||
placeholder="Ask a question in natural language and we’ll generate the query for you."
|
||||
aria-labelledby="copilot-textfield-label"
|
||||
onRenderSuffix={() => {
|
||||
return (
|
||||
<IconButton
|
||||
iconProps={{ iconName: "Send" }}
|
||||
disabled={isGeneratingQuery || !userPrompt.trim()}
|
||||
allowDisabledFocus={true}
|
||||
style={{ background: "none" }}
|
||||
onClick={() => startGenerateQueryProcess()}
|
||||
aria-label="Send"
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{showPromptTeachingBubble && copilotTeachingBubbleVisible && (
|
||||
<TeachingBubble
|
||||
calloutProps={{ directionalHint: DirectionalHint.bottomCenter }}
|
||||
target="#naturalLanguageInput"
|
||||
hasCloseButton={true}
|
||||
closeButtonAriaLabel="Close"
|
||||
onDismiss={() => toggleCopilotTeachingBubbleVisible(false)}
|
||||
hasSmallHeadline={true}
|
||||
headline="Write a prompt"
|
||||
>
|
||||
Write a prompt here and Query Advisor will generate the query for you. You can also choose from our{" "}
|
||||
<Link
|
||||
onClick={() => {
|
||||
setShowSamplePrompts(true);
|
||||
toggleCopilotTeachingBubbleVisible(false);
|
||||
}}
|
||||
style={{ color: "white", fontWeight: 600 }}
|
||||
>
|
||||
sample prompts
|
||||
</Link>{" "}
|
||||
or write your own query
|
||||
</TeachingBubble>
|
||||
)}
|
||||
{showSamplePrompts && (
|
||||
<Callout
|
||||
styles={{ root: { minWidth: 400, maxWidth: "70vw" } }}
|
||||
target="#naturalLanguageInput"
|
||||
isBeakVisible={false}
|
||||
onDismiss={() => setShowSamplePrompts(false)}
|
||||
directionalHintFixed={true}
|
||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||
alignTargetEdge={true}
|
||||
gapSpace={4}
|
||||
>
|
||||
<Stack>
|
||||
{filteredHistories?.length > 0 && (
|
||||
<Stack>
|
||||
<Text
|
||||
style={{
|
||||
width: "100%",
|
||||
fontSize: 14,
|
||||
fontWeight: 600,
|
||||
color: "#0078D4",
|
||||
marginLeft: 16,
|
||||
padding: "4px 0",
|
||||
}}
|
||||
>
|
||||
Recent
|
||||
</Text>
|
||||
{filteredHistories.map((history, i) => (
|
||||
<DefaultButton
|
||||
key={i}
|
||||
onClick={() => {
|
||||
setUserPrompt(history);
|
||||
setShowSamplePrompts(false);
|
||||
inputEdited.current = true;
|
||||
}}
|
||||
elementRef={itemRefs.current[i]}
|
||||
onKeyDown={handleKeyDownForItem}
|
||||
onRenderIcon={() => <Image src={RecentIcon} styles={{ root: { overflow: "unset" } }} />}
|
||||
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, index) => (
|
||||
<DefaultButton
|
||||
key={prompt.id}
|
||||
elementRef={itemRefs.current[filteredHistories.length + index]}
|
||||
onClick={() => {
|
||||
setUserPrompt(prompt.text);
|
||||
setShowSamplePrompts(false);
|
||||
inputEdited.current = true;
|
||||
}}
|
||||
onKeyDown={handleKeyDownForItem}
|
||||
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="https://aka.ms/cdb-copilot-writing">
|
||||
writing effective prompts
|
||||
</Link>
|
||||
</Text>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
</Callout>
|
||||
)}
|
||||
</Stack>
|
||||
{!isGeneratingQuery && (
|
||||
<Stack style={{ padding: 8 }}>
|
||||
{!showFeedbackBar && (
|
||||
<Text style={{ fontSize: 12 }}>
|
||||
AI-generated content can have mistakes. Make sure it's accurate and appropriate before using it.{" "}
|
||||
<Link
|
||||
href="https://aka.ms/cdb-copilot-preview-terms"
|
||||
target="_blank"
|
||||
style={{ color: "#0072D4" }}
|
||||
className="underlinedLink"
|
||||
>
|
||||
Read preview terms
|
||||
</Link>
|
||||
{showErrorMessageBar && (
|
||||
<MessageBar messageBarType={MessageBarType.error}>
|
||||
{errors.length > 0
|
||||
? errors[0].message
|
||||
: "We ran into an error and were not able to execute query."}
|
||||
</MessageBar>
|
||||
)}
|
||||
{showInvalidQueryMessageBar && (
|
||||
<MessageBar
|
||||
messageBarType={MessageBarType.info}
|
||||
styles={{ root: { backgroundColor: "#F0F6FF" }, icon: { color: "#015CDA" } }}
|
||||
>
|
||||
We were unable to generate a query based upon the prompt provided. Please modify the prompt and
|
||||
try again. For examples of how to write a good prompt, please read
|
||||
<Link href="https://aka.ms/cdb-copilot-writing" target="_blank">
|
||||
this article.
|
||||
</Link>{" "}
|
||||
Our content guidelines can be found
|
||||
<Link href="https://aka.ms/cdb-query-copilot" target="_blank">
|
||||
here.
|
||||
</Link>
|
||||
</MessageBar>
|
||||
)}
|
||||
</Text>
|
||||
)}
|
||||
{showFeedbackBar && (
|
||||
<Stack horizontal verticalAlign="center" style={{ maxHeight: 20 }}>
|
||||
{userContext.feedbackPolicies?.policyAllowFeedback && (
|
||||
<Stack horizontal verticalAlign="center">
|
||||
<Text style={{ fontSize: 12 }}>{FeedbackLabels.provideFeedback}</Text>
|
||||
{showCallout && !hideFeedbackModalForLikedQueries && (
|
||||
<Callout
|
||||
role="status"
|
||||
style={{ padding: "6px 12px" }}
|
||||
styles={{
|
||||
root: {
|
||||
borderRadius: 8,
|
||||
},
|
||||
beakCurtain: {
|
||||
borderRadius: 8,
|
||||
},
|
||||
calloutMain: {
|
||||
borderRadius: 8,
|
||||
},
|
||||
}}
|
||||
target="#likeBtn"
|
||||
onDismiss={() => {
|
||||
setShowCallout(false);
|
||||
SubmitFeedback({
|
||||
params: {
|
||||
generatedQuery: generatedQuery,
|
||||
likeQuery: likeQuery,
|
||||
description: "",
|
||||
userPrompt: userPrompt,
|
||||
},
|
||||
explorer,
|
||||
databaseId,
|
||||
containerId,
|
||||
mode: isSampleCopilotActive ? "Sample" : "User",
|
||||
});
|
||||
}}
|
||||
directionalHint={DirectionalHint.topCenter}
|
||||
>
|
||||
<Text>
|
||||
Thank you. Need to give{" "}
|
||||
<Link
|
||||
onClick={() => {
|
||||
setShowCallout(false);
|
||||
openFeedbackModal(generatedQuery, true, userPrompt);
|
||||
}}
|
||||
>
|
||||
more feedback?
|
||||
</Link>
|
||||
</Text>
|
||||
</Callout>
|
||||
)}
|
||||
<IconButton
|
||||
id="likeBtn"
|
||||
style={{ marginLeft: 10 }}
|
||||
aria-label={FeedbackLabels.provideFeedback}
|
||||
role="button"
|
||||
title="Like"
|
||||
iconProps={{ iconName: likeQuery === true ? "LikeSolid" : "Like" }}
|
||||
onClick={() => {
|
||||
setShowCallout(!likeQuery);
|
||||
setLikeQuery(!likeQuery);
|
||||
if (likeQuery === true) {
|
||||
document.getElementById("likeStatus").innerHTML = "Unpressed";
|
||||
}
|
||||
if (likeQuery === false) {
|
||||
document.getElementById("likeStatus").innerHTML = "Liked";
|
||||
}
|
||||
if (dislikeQuery) {
|
||||
setDislikeQuery(!dislikeQuery);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
style={{ margin: "0 4px" }}
|
||||
role="button"
|
||||
aria-label={FeedbackLabels.provideFeedback}
|
||||
title="Dislike"
|
||||
iconProps={{ iconName: dislikeQuery === true ? "DislikeSolid" : "Dislike" }}
|
||||
onClick={() => {
|
||||
let toggleStatusValue = "Unpressed";
|
||||
if (!dislikeQuery) {
|
||||
openFeedbackModal(generatedQuery, false, userPrompt);
|
||||
setLikeQuery(false);
|
||||
toggleStatusValue = "Disliked";
|
||||
}
|
||||
setDislikeQuery(!dislikeQuery);
|
||||
setShowCallout(false);
|
||||
document.getElementById("likeStatus").innerHTML = toggleStatusValue;
|
||||
}}
|
||||
/>
|
||||
<span role="status" style={{ position: "absolute", left: "-9999px" }} id="likeStatus"></span>
|
||||
<Separator
|
||||
vertical
|
||||
styles={{
|
||||
root: {
|
||||
"::after": {
|
||||
backgroundColor: "#767676",
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
<CommandBarButton
|
||||
className="copyQuery"
|
||||
elementRef={copyQueryRef}
|
||||
onClick={copyGeneratedCode}
|
||||
iconProps={{ iconName: "Copy" }}
|
||||
style={{ fontSize: 12, transition: "background-color 0.3s ease", height: "100%" }}
|
||||
styles={{
|
||||
root: {
|
||||
backgroundColor: "inherit",
|
||||
},
|
||||
}}
|
||||
>
|
||||
Copy code
|
||||
</CommandBarButton>
|
||||
<CommandBarButton
|
||||
className="deleteQuery"
|
||||
onClick={() => {
|
||||
setShowDeletePopup(true);
|
||||
}}
|
||||
iconProps={{ iconName: "Delete" }}
|
||||
style={{ fontSize: 12, transition: "background-color 0.3s ease", height: "100%" }}
|
||||
styles={{
|
||||
root: {
|
||||
backgroundColor: "inherit",
|
||||
},
|
||||
}}
|
||||
>
|
||||
Clear editor
|
||||
</CommandBarButton>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
{(showFeedbackBar || isGeneratingQuery) && (
|
||||
<span role="alert" className="screenReaderOnly" aria-label={getAriaLabel()} />
|
||||
)}
|
||||
{isGeneratingQuery && (
|
||||
<ProgressIndicator
|
||||
label="Thinking..."
|
||||
ariaLabel={getAriaLabel()}
|
||||
barHeight={4}
|
||||
styles={{
|
||||
root: {
|
||||
fontSize: 12,
|
||||
width: "100%",
|
||||
bottom: 0,
|
||||
},
|
||||
itemName: {
|
||||
padding: "0px 8px",
|
||||
},
|
||||
itemProgress: {
|
||||
borderBottomLeftRadius: 8,
|
||||
borderBottomRightRadius: 8,
|
||||
padding: 0,
|
||||
},
|
||||
progressBar: {
|
||||
backgroundImage:
|
||||
"linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, rgb(24, 90, 189) 35%, rgb(71, 207, 250) 70%, rgb(180, 124, 248) 92%, rgba(0, 0, 0, 0))",
|
||||
animationDuration: "5s",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
<IconButton
|
||||
iconProps={{ imageProps: { src: errorIcon } }}
|
||||
onClick={() => {
|
||||
toggleCopilot(false);
|
||||
clearFeedback();
|
||||
resetMessageStates();
|
||||
}}
|
||||
ariaLabel="Close"
|
||||
title="Close copilot"
|
||||
/>
|
||||
</Stack>
|
||||
{isSamplePromptsOpen && <SamplePrompts sampleProps={sampleProps} />}
|
||||
{query !== "" && query.trim().length !== 0 && (
|
||||
<DeletePopup
|
||||
showDeletePopup={showDeletePopup}
|
||||
setShowDeletePopup={setShowDeletePopup}
|
||||
setQuery={setQuery}
|
||||
clearFeedback={clearFeedback}
|
||||
showFeedbackBar={setShowFeedbackBar}
|
||||
/>
|
||||
)}
|
||||
<CopyPopup showCopyPopup={showCopyPopup} setShowCopyPopup={setshowCopyPopup} />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
import { shallow } from "enzyme";
|
||||
import { CopilotSubComponentNames } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
||||
import React from "react";
|
||||
import { AppStateComponentNames, StorePath } from "Shared/AppStatePersistenceUtility";
|
||||
import { updateUserContext } from "UserContext";
|
||||
import Explorer from "../Explorer";
|
||||
import { QueryCopilotTab } from "./QueryCopilotTab";
|
||||
|
||||
describe("Query copilot tab snapshot test", () => {
|
||||
it("should render with initial input", () => {
|
||||
updateUserContext({
|
||||
databaseAccount: {
|
||||
name: "name",
|
||||
properties: undefined,
|
||||
id: "",
|
||||
location: "",
|
||||
type: "",
|
||||
kind: "",
|
||||
},
|
||||
});
|
||||
|
||||
const loadState = (path: StorePath) => {
|
||||
if (
|
||||
path.componentName === AppStateComponentNames.QueryCopilot &&
|
||||
path.subComponentName === CopilotSubComponentNames.toggleStatus
|
||||
) {
|
||||
return { enabled: true };
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
jest.mock("Shared/AppStatePersistenceUtility", () => ({
|
||||
loadState,
|
||||
}));
|
||||
|
||||
const wrapper = shallow(<QueryCopilotTab explorer={new Explorer()} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,119 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
import { Stack } from "@fluentui/react";
|
||||
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
|
||||
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
|
||||
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
||||
import { SaveQueryPane } from "Explorer/Panes/SaveQueryPane/SaveQueryPane";
|
||||
import { readCopilotToggleStatus, saveCopilotToggleStatus } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
||||
import { OnExecuteQueryClick } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||
import { QueryCopilotProps } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
||||
import { QueryCopilotResults } from "Explorer/QueryCopilot/Shared/QueryCopilotResults";
|
||||
import { userContext } from "UserContext";
|
||||
import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import { useSidePanel } from "hooks/useSidePanel";
|
||||
import React, { useState } from "react";
|
||||
import SplitterLayout from "react-splitter-layout";
|
||||
import QueryCommandIcon from "../../../images/CopilotCommand.svg";
|
||||
import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg";
|
||||
import SaveQueryIcon from "../../../images/save-cosmos.svg";
|
||||
|
||||
export const QueryCopilotTab: React.FC<QueryCopilotProps> = ({ explorer }: QueryCopilotProps): JSX.Element => {
|
||||
const { query, setQuery, selectedQuery, setSelectedQuery, isGeneratingQuery } = useQueryCopilot();
|
||||
|
||||
const [copilotActive, setCopilotActive] = useState<boolean>(() =>
|
||||
readCopilotToggleStatus(userContext.databaseAccount),
|
||||
);
|
||||
//TODO: Uncomment this useState when query copilot is reinstated in DE
|
||||
// const [tabActive, setTabActive] = useState<boolean>(true);
|
||||
|
||||
const getCommandbarButtons = (): CommandButtonComponentProps[] => {
|
||||
const executeQueryBtnLabel = selectedQuery ? "Execute Selection" : "Execute Query";
|
||||
const executeQueryBtn = {
|
||||
iconSrc: ExecuteQueryIcon,
|
||||
iconAlt: executeQueryBtnLabel,
|
||||
onCommandClick: () => OnExecuteQueryClick(useQueryCopilot as Partial<QueryCopilotState>),
|
||||
commandButtonLabel: executeQueryBtnLabel,
|
||||
ariaLabel: executeQueryBtnLabel,
|
||||
hasPopup: false,
|
||||
disabled: query?.trim() === "",
|
||||
};
|
||||
|
||||
const saveQueryBtn = {
|
||||
iconSrc: SaveQueryIcon,
|
||||
iconAlt: "Save Query",
|
||||
onCommandClick: () =>
|
||||
useSidePanel.getState().openSidePanel("Save Query", <SaveQueryPane explorer={explorer} queryToSave={query} />),
|
||||
commandButtonLabel: "Save Query",
|
||||
ariaLabel: "Save Query",
|
||||
hasPopup: false,
|
||||
disabled: true,
|
||||
};
|
||||
|
||||
const toggleCopilotButton = {
|
||||
iconSrc: QueryCommandIcon,
|
||||
iconAlt: "Query Advisor",
|
||||
onCommandClick: () => {
|
||||
toggleCopilot(true);
|
||||
},
|
||||
commandButtonLabel: "Query Advisor",
|
||||
ariaLabel: "Query Advisor",
|
||||
hasPopup: false,
|
||||
disabled: copilotActive,
|
||||
};
|
||||
|
||||
return [executeQueryBtn, saveQueryBtn, toggleCopilotButton];
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
useCommandBar.getState().setContextButtons(getCommandbarButtons());
|
||||
}, [query, selectedQuery, copilotActive]);
|
||||
|
||||
//TODO: Uncomment this effect when query copilot is reinstated in DE
|
||||
// React.useEffect(() => {
|
||||
// return () => {
|
||||
// useTabs.subscribe((state: TabsState) => {
|
||||
// if (state.activeReactTab === ReactTabKind.QueryCopilot) {
|
||||
// setTabActive(true);
|
||||
// } else {
|
||||
// setTabActive(false);
|
||||
// }
|
||||
// });
|
||||
// };
|
||||
// }, []);
|
||||
|
||||
const toggleCopilot = (toggle: boolean) => {
|
||||
setCopilotActive(toggle);
|
||||
saveCopilotToggleStatus(userContext.databaseAccount, toggle);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack className="tab-pane" style={{ width: "100%" }}>
|
||||
<div style={isGeneratingQuery ? { height: "100%" } : { overflowY: "auto", height: "100%" }}>
|
||||
{/*TODO: Uncomment this section when query copilot is reinstated in DE
|
||||
{tabActive && copilotActive && (
|
||||
<QueryCopilotPromptbar
|
||||
explorer={explorer}
|
||||
toggleCopilot={toggleCopilot}
|
||||
databaseId={QueryCopilotSampleDatabaseId}
|
||||
containerId={QueryCopilotSampleContainerId}
|
||||
></QueryCopilotPromptbar>
|
||||
)} */}
|
||||
<Stack className="tabPaneContentContainer">
|
||||
<SplitterLayout percentage={true} vertical={true} primaryIndex={0} primaryMinSize={30} secondaryMinSize={70}>
|
||||
<EditorReact
|
||||
language={"sql"}
|
||||
content={query}
|
||||
isReadOnly={false}
|
||||
wordWrap={"on"}
|
||||
ariaLabel={"Editing Query"}
|
||||
lineNumbers={"on"}
|
||||
onContentChanged={(newQuery: string) => setQuery(newQuery)}
|
||||
onContentSelected={(selectedQuery: string) => setSelectedQuery(selectedQuery)}
|
||||
/>
|
||||
<QueryCopilotResults />
|
||||
</SplitterLayout>
|
||||
</Stack>
|
||||
</div>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -1,155 +0,0 @@
|
||||
import { FeedOptions } from "@azure/cosmos";
|
||||
import { handleError } from "Common/ErrorHandlingUtils";
|
||||
import { sampleDataClient } from "Common/SampleDataClient";
|
||||
import * as commonUtils from "Common/dataAccess/queryDocuments";
|
||||
import DocumentId from "Explorer/Tree/DocumentId";
|
||||
import { querySampleDocuments, readSampleDocument } 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().mockReturnValue((): void => undefined),
|
||||
logConsoleError: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("@azure/cosmos", () => ({
|
||||
FeedOptions: jest.fn(),
|
||||
QueryIterator: jest.fn(),
|
||||
Constants: {
|
||||
HttpHeaders: {},
|
||||
},
|
||||
}));
|
||||
jest.mock("Common/ErrorHandlingUtils", () => ({
|
||||
handleError: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("Common/dataAccess/queryDocuments", () => ({
|
||||
getCommonQueryOptions: jest.fn((options) => options),
|
||||
}));
|
||||
|
||||
jest.mock("Common/SampleDataClient");
|
||||
|
||||
jest.mock("node-fetch");
|
||||
|
||||
jest.mock("Explorer/Explorer", () => {
|
||||
class MockExplorer {
|
||||
allocateContainer = jest.fn().mockResolvedValueOnce({});
|
||||
}
|
||||
return MockExplorer;
|
||||
});
|
||||
|
||||
jest.mock("hooks/useQueryCopilot", () => {
|
||||
const mockQueryCopilotStore = {
|
||||
shouldAllocateContainer: true,
|
||||
setShouldAllocateContainer: jest.fn(),
|
||||
correlationId: "mocked-correlation-id",
|
||||
};
|
||||
|
||||
return {
|
||||
useQueryCopilot: jest.fn(() => mockQueryCopilotStore),
|
||||
};
|
||||
});
|
||||
|
||||
describe("QueryCopilotUtilities", () => {
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
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,166 +0,0 @@
|
||||
import { FeedOptions, Item, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||
import { QueryCopilotSampleContainerId, QueryCopilotSampleDatabaseId } from "Common/Constants";
|
||||
import { handleError } from "Common/ErrorHandlingUtils";
|
||||
import { sampleDataClient } from "Common/SampleDataClient";
|
||||
import { getPartitionKeyValue } from "Common/dataAccess/getPartitionKeyValue";
|
||||
import { getCommonQueryOptions } from "Common/dataAccess/queryDocuments";
|
||||
import { DatabaseAccount } from "Contracts/DataModels";
|
||||
import DocumentId from "Explorer/Tree/DocumentId";
|
||||
import { AppStateComponentNames, loadState, saveState } from "Shared/AppStatePersistenceUtility";
|
||||
import { logConsoleProgress } from "Utils/NotificationConsoleUtils";
|
||||
import * as StringUtility from "../../Shared/StringUtility";
|
||||
|
||||
export interface SuggestedPrompt {
|
||||
id: number;
|
||||
text: string;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
export const getSampleDatabaseSuggestedPrompts = (): SuggestedPrompt[] => {
|
||||
return [
|
||||
{ id: 1, text: 'Show all products that have the word "ultra" in the name or description' },
|
||||
{ id: 2, text: "What are all of the possible categories for the products, and their counts?" },
|
||||
{ id: 3, text: 'Show me all products that have been reviewed by someone with a username that contains "bob"' },
|
||||
];
|
||||
};
|
||||
|
||||
export const getSuggestedPrompts = (): SuggestedPrompt[] => {
|
||||
return [
|
||||
{ id: 1, text: "Show the first 10 items" },
|
||||
{ id: 2, text: 'Count all the items in my data as "numItems"' },
|
||||
{ id: 3, text: "Find the oldest item added to my collection" },
|
||||
];
|
||||
};
|
||||
|
||||
// Prompt history persistence
|
||||
export enum CopilotSubComponentNames {
|
||||
promptHistory = "PromptHistory",
|
||||
toggleStatus = "ToggleStatus",
|
||||
}
|
||||
|
||||
const getLegacyHistoryKey = (databaseAccount: DatabaseAccount): string =>
|
||||
`${databaseAccount?.id}-queryCopilotHistories`;
|
||||
const getLegacyToggleStatusKey = (databaseAccount: DatabaseAccount): string =>
|
||||
`${databaseAccount?.id}-queryCopilotToggleStatus`;
|
||||
|
||||
// Migration only needs to run once
|
||||
let hasMigrated = false;
|
||||
// Migrate old prompt history to new format
|
||||
export const migrateCopilotPersistence = (databaseAccount: DatabaseAccount): void => {
|
||||
if (hasMigrated) {
|
||||
return;
|
||||
}
|
||||
|
||||
let key = getLegacyHistoryKey(databaseAccount);
|
||||
let item = localStorage.getItem(key);
|
||||
if (item !== undefined && item !== null) {
|
||||
const historyItems = item.split("|");
|
||||
saveState(
|
||||
{
|
||||
componentName: AppStateComponentNames.QueryCopilot,
|
||||
subComponentName: CopilotSubComponentNames.promptHistory,
|
||||
globalAccountName: databaseAccount.name,
|
||||
databaseName: undefined,
|
||||
containerName: undefined,
|
||||
},
|
||||
historyItems,
|
||||
);
|
||||
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
|
||||
key = getLegacyToggleStatusKey(databaseAccount);
|
||||
item = localStorage.getItem(key);
|
||||
if (item !== undefined && item !== null) {
|
||||
saveState(
|
||||
{
|
||||
componentName: AppStateComponentNames.QueryCopilot,
|
||||
subComponentName: CopilotSubComponentNames.toggleStatus,
|
||||
globalAccountName: databaseAccount.name,
|
||||
databaseName: undefined,
|
||||
containerName: undefined,
|
||||
},
|
||||
StringUtility.toBoolean(item),
|
||||
);
|
||||
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
|
||||
hasMigrated = true;
|
||||
};
|
||||
|
||||
export const readPromptHistory = (databaseAccount: DatabaseAccount): string[] => {
|
||||
migrateCopilotPersistence(databaseAccount);
|
||||
return (
|
||||
(loadState({
|
||||
componentName: AppStateComponentNames.QueryCopilot,
|
||||
subComponentName: CopilotSubComponentNames.promptHistory,
|
||||
globalAccountName: databaseAccount.name,
|
||||
databaseName: undefined,
|
||||
containerName: undefined,
|
||||
}) as string[]) || []
|
||||
);
|
||||
};
|
||||
|
||||
export const savePromptHistory = (databaseAccount: DatabaseAccount, historyItems: string[]): void => {
|
||||
saveState(
|
||||
{
|
||||
componentName: AppStateComponentNames.QueryCopilot,
|
||||
subComponentName: CopilotSubComponentNames.promptHistory,
|
||||
globalAccountName: databaseAccount.name,
|
||||
databaseName: undefined,
|
||||
containerName: undefined,
|
||||
},
|
||||
historyItems,
|
||||
);
|
||||
};
|
||||
|
||||
export const readCopilotToggleStatus = (databaseAccount: DatabaseAccount): boolean => {
|
||||
migrateCopilotPersistence(databaseAccount);
|
||||
return !!loadState({
|
||||
componentName: AppStateComponentNames.QueryCopilot,
|
||||
subComponentName: CopilotSubComponentNames.toggleStatus,
|
||||
globalAccountName: databaseAccount.name,
|
||||
databaseName: undefined,
|
||||
containerName: undefined,
|
||||
}) as boolean;
|
||||
};
|
||||
|
||||
export const saveCopilotToggleStatus = (databaseAccount: DatabaseAccount, status: boolean): void => {
|
||||
saveState(
|
||||
{
|
||||
componentName: AppStateComponentNames.QueryCopilot,
|
||||
subComponentName: CopilotSubComponentNames.toggleStatus,
|
||||
globalAccountName: databaseAccount.name,
|
||||
databaseName: undefined,
|
||||
containerName: undefined,
|
||||
},
|
||||
status,
|
||||
);
|
||||
};
|
||||
@@ -1,145 +0,0 @@
|
||||
import { handleError } from "Common/ErrorHandlingUtils";
|
||||
import { createUri } from "Common/UrlUtility";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { SubmitFeedback } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||
import { userContext } from "UserContext";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
|
||||
jest.mock("@azure/cosmos", () => ({
|
||||
Constants: {
|
||||
HttpHeaders: {},
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock("Common/ErrorHandlingUtils", () => ({
|
||||
handleError: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("Common/SampleDataClient");
|
||||
|
||||
jest.mock("node-fetch");
|
||||
|
||||
jest.mock("Explorer/Explorer", () => {
|
||||
class MockExplorer {
|
||||
allocateContainer = jest.fn().mockResolvedValueOnce({});
|
||||
}
|
||||
return MockExplorer;
|
||||
});
|
||||
|
||||
describe("Query Copilot Client", () => {
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
describe("SubmitFeedback", () => {
|
||||
const payload = {
|
||||
like: "like",
|
||||
generatedSql: "GeneratedQuery",
|
||||
userPrompt: "UserPrompt",
|
||||
description: "Description",
|
||||
contact: "Contact",
|
||||
};
|
||||
|
||||
const mockStore = useQueryCopilot.getState();
|
||||
mockStore.correlationId = "mocked-correlation-id";
|
||||
mockStore.notebookServerInfo = {
|
||||
notebookServerEndpoint: "mocked-endpoint",
|
||||
authToken: "mocked-token",
|
||||
forwardingId: "mocked-forwarding-id",
|
||||
};
|
||||
|
||||
const feedbackUri = userContext.features.disableCopilotPhoenixGateaway
|
||||
? createUri("https://copilotorchestrater.azurewebsites.net/", "feedback")
|
||||
: createUri(useQueryCopilot.getState().notebookServerInfo.notebookServerEndpoint, "public/feedback");
|
||||
|
||||
it("should call fetch with the payload with like", async () => {
|
||||
const mockFetch = jest.fn().mockResolvedValueOnce({});
|
||||
|
||||
globalThis.fetch = mockFetch;
|
||||
await SubmitFeedback({
|
||||
databaseId: "test",
|
||||
containerId: "test",
|
||||
mode: "User",
|
||||
params: {
|
||||
likeQuery: true,
|
||||
generatedQuery: "GeneratedQuery",
|
||||
userPrompt: "UserPrompt",
|
||||
description: "Description",
|
||||
contact: "Contact",
|
||||
},
|
||||
explorer: new Explorer(),
|
||||
});
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
feedbackUri,
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
"x-ms-correlationid": "mocked-correlation-id",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
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;
|
||||
|
||||
await SubmitFeedback({
|
||||
databaseId: "test",
|
||||
containerId: "test",
|
||||
mode: "User",
|
||||
params: {
|
||||
likeQuery: false,
|
||||
generatedQuery: "GeneratedQuery",
|
||||
userPrompt: "UserPrompt",
|
||||
description: undefined,
|
||||
contact: undefined,
|
||||
},
|
||||
explorer: new Explorer(),
|
||||
});
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
feedbackUri,
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"x-ms-correlationid": "mocked-correlation-id",
|
||||
Authorization: "token mocked-token",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
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({
|
||||
databaseId: "test",
|
||||
containerId: "test",
|
||||
mode: "User",
|
||||
params: {
|
||||
likeQuery: true,
|
||||
generatedQuery: "GeneratedQuery",
|
||||
userPrompt: "UserPrompt",
|
||||
description: "Description",
|
||||
contact: "Contact",
|
||||
},
|
||||
explorer: new Explorer(),
|
||||
}).catch((error) => {
|
||||
// eslint-disable-next-line jest/no-conditional-expect
|
||||
expect(error.message).toEqual("Mock error");
|
||||
});
|
||||
|
||||
expect(handleError).toHaveBeenCalledWith(new Error("Mock error"), expect.any(String));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,390 +0,0 @@
|
||||
import { FeedOptions } from "@azure/cosmos";
|
||||
import {
|
||||
Areas,
|
||||
ConnectionStatusType,
|
||||
ContainerStatusType,
|
||||
HttpStatusCodes,
|
||||
PoolIdType,
|
||||
QueryCopilotSampleContainerId,
|
||||
QueryCopilotSampleContainerSchema,
|
||||
ShortenedQueryCopilotSampleContainerSchema,
|
||||
} from "Common/Constants";
|
||||
import { getErrorMessage, getErrorStack, handleError } from "Common/ErrorHandlingUtils";
|
||||
import { shouldEnableCrossPartitionKey } from "Common/HeadersUtility";
|
||||
import { MinimalQueryIterator } from "Common/IteratorUtilities";
|
||||
import QueryError from "Common/QueryError";
|
||||
import { createUri } from "Common/UrlUtility";
|
||||
import { queryDocumentsPage } from "Common/dataAccess/queryDocumentsPage";
|
||||
import { configContext } from "ConfigContext";
|
||||
import {
|
||||
ContainerConnectionInfo,
|
||||
CopilotEnabledConfiguration,
|
||||
FeatureRegistration,
|
||||
IProvisionData,
|
||||
} from "Contracts/DataModels";
|
||||
import { AuthorizationTokenHeaderMetadata, QueryResults } from "Contracts/ViewModels";
|
||||
import { useDialog } from "Explorer/Controls/Dialog";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { querySampleDocuments, readCopilotToggleStatus } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
||||
import { FeedbackParams, GenerateSQLQueryResponse } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||
import { traceFailure, traceStart, traceSuccess } from "Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "UserContext";
|
||||
import { getAuthorizationHeader } from "Utils/AuthorizationUtils";
|
||||
import { queryPagesUntilContentPresent } from "Utils/QueryUtils";
|
||||
import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import { useTabs } from "hooks/useTabs";
|
||||
|
||||
async function fetchWithTimeout(
|
||||
url: string,
|
||||
headers: {
|
||||
[x: string]: string;
|
||||
},
|
||||
) {
|
||||
const timeout = 10000;
|
||||
const options = { timeout };
|
||||
|
||||
const controller = new AbortController();
|
||||
const id = setTimeout(() => controller.abort(), timeout);
|
||||
|
||||
const response = await window.fetch(url, {
|
||||
headers,
|
||||
...options,
|
||||
signal: controller.signal,
|
||||
});
|
||||
clearTimeout(id);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
export const isCopilotFeatureRegistered = async (subscriptionId: string): Promise<boolean> => {
|
||||
const api_version = "2021-07-01";
|
||||
const url = `${configContext.ARM_ENDPOINT}/subscriptions/${subscriptionId}/providers/Microsoft.Features/featureProviders/Microsoft.DocumentDB/subscriptionFeatureRegistrations/MicrosoftCopilotForAzureInCDB?api-version=${api_version}`;
|
||||
const authorizationHeader: AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
|
||||
const headers = { [authorizationHeader.header]: authorizationHeader.token };
|
||||
|
||||
const startKey = traceStart(Action.CheckCopilotFeatureRegistration, {
|
||||
dataExplorerArea: Areas.Copilot,
|
||||
});
|
||||
let response;
|
||||
|
||||
try {
|
||||
response = await fetchWithTimeout(url, headers);
|
||||
} catch (error) {
|
||||
traceFailure(Action.CheckCopilotFeatureRegistration, { error: String(error) }, startKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!response?.ok) {
|
||||
traceFailure(Action.CheckCopilotFeatureRegistration, { status: response?.status }, startKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
const featureRegistration = (await response?.json()) as FeatureRegistration;
|
||||
const registered = featureRegistration?.properties?.state === "Registered";
|
||||
traceSuccess(Action.CheckCopilotFeatureRegistration, { registered }, startKey);
|
||||
return registered;
|
||||
};
|
||||
|
||||
export const getCopilotEnabled = async (): Promise<boolean> => {
|
||||
const backendEndpoint: string = configContext.PORTAL_BACKEND_ENDPOINT;
|
||||
|
||||
const url = `${backendEndpoint}/api/portalsettings/querycopilot`;
|
||||
const authorizationHeader: AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
|
||||
const headers = { [authorizationHeader.header]: authorizationHeader.token };
|
||||
|
||||
const startKey = traceStart(Action.GetCopilotEnabled, {
|
||||
dataExplorerArea: Areas.Copilot,
|
||||
});
|
||||
let response;
|
||||
|
||||
try {
|
||||
response = await fetchWithTimeout(url, headers);
|
||||
} catch (error) {
|
||||
traceFailure(Action.GetCopilotEnabled, { error: String(error) }, startKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!response?.ok) {
|
||||
traceFailure(Action.GetCopilotEnabled, { status: response?.status }, startKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
const copilotPortalConfiguration = (await response?.json()) as CopilotEnabledConfiguration;
|
||||
const isEnabled = copilotPortalConfiguration?.isEnabled;
|
||||
traceSuccess(Action.GetCopilotEnabled, { isEnabled }, startKey);
|
||||
return isEnabled;
|
||||
};
|
||||
|
||||
export const allocatePhoenixContainer = async ({
|
||||
explorer,
|
||||
databaseId,
|
||||
containerId,
|
||||
mode,
|
||||
}: {
|
||||
explorer: Explorer;
|
||||
databaseId: string;
|
||||
containerId: string;
|
||||
mode: string;
|
||||
}): Promise<void> => {
|
||||
try {
|
||||
if (
|
||||
useQueryCopilot.getState().containerStatus.status !== ContainerStatusType.Active &&
|
||||
!userContext.features.disableCopilotPhoenixGateaway
|
||||
) {
|
||||
await explorer.allocateContainer(PoolIdType.QueryCopilot, mode);
|
||||
} else {
|
||||
const currentAllocatedSchemaInfo = useQueryCopilot.getState().schemaAllocationInfo;
|
||||
if (
|
||||
currentAllocatedSchemaInfo.databaseId !== databaseId ||
|
||||
currentAllocatedSchemaInfo.containerId !== containerId
|
||||
) {
|
||||
await resetPhoenixContainerSchema({ explorer, databaseId, containerId, mode });
|
||||
}
|
||||
}
|
||||
useQueryCopilot.getState().setSchemaAllocationInfo({
|
||||
databaseId,
|
||||
containerId,
|
||||
});
|
||||
} catch (error) {
|
||||
traceFailure(Action.PhoenixConnection, {
|
||||
dataExplorerArea: Areas.Copilot,
|
||||
status: error.status,
|
||||
error: getErrorMessage(error),
|
||||
errorStack: getErrorStack(error),
|
||||
});
|
||||
useQueryCopilot.getState().resetContainerConnection();
|
||||
if (error?.status === HttpStatusCodes.Forbidden && error.message) {
|
||||
useDialog.getState().showOkModalDialog("Connection Failed", `${error.message}`);
|
||||
} else {
|
||||
useDialog
|
||||
.getState()
|
||||
.showOkModalDialog(
|
||||
"Connection Failed",
|
||||
"We are unable to connect to the temporary workspace. Please try again in a few minutes. If the error persists, file a support ticket.",
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
useTabs.getState().setIsTabExecuting(false);
|
||||
}
|
||||
};
|
||||
|
||||
export const resetPhoenixContainerSchema = async ({
|
||||
explorer,
|
||||
databaseId,
|
||||
containerId,
|
||||
mode,
|
||||
}: {
|
||||
explorer: Explorer;
|
||||
databaseId: string;
|
||||
containerId: string;
|
||||
mode: string;
|
||||
}): Promise<void> => {
|
||||
try {
|
||||
const provisionData: IProvisionData = {
|
||||
poolId: PoolIdType.QueryCopilot,
|
||||
databaseId: databaseId,
|
||||
containerId: containerId,
|
||||
mode: mode,
|
||||
};
|
||||
const connectionInfo = await explorer.phoenixClient.allocateContainer(provisionData);
|
||||
const connectionStatus: ContainerConnectionInfo = {
|
||||
status: ConnectionStatusType.Connecting,
|
||||
};
|
||||
await explorer.setNotebookInfo(false, connectionInfo, connectionStatus);
|
||||
} catch (error) {
|
||||
traceFailure(Action.PhoenixConnection, {
|
||||
dataExplorerArea: Areas.Copilot,
|
||||
status: error.status,
|
||||
error: getErrorMessage(error),
|
||||
errorStack: getErrorStack(error),
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const SendQueryRequest = async ({
|
||||
userPrompt,
|
||||
explorer,
|
||||
}: {
|
||||
userPrompt: string;
|
||||
explorer: Explorer;
|
||||
}): Promise<void> => {
|
||||
if (userPrompt.trim() !== "") {
|
||||
useQueryCopilot
|
||||
.getState()
|
||||
.setChatMessages([...useQueryCopilot.getState().chatMessages, { source: 0, message: userPrompt }]);
|
||||
useQueryCopilot.getState().setIsGeneratingQuery(true);
|
||||
useQueryCopilot.getState().setShouldIncludeInMessages(true);
|
||||
useTabs.getState().setIsTabExecuting(true);
|
||||
useTabs.getState().setIsQueryErrorThrown(false);
|
||||
try {
|
||||
if (
|
||||
useQueryCopilot.getState().containerStatus.status !== ContainerStatusType.Active &&
|
||||
!userContext.features.disableCopilotPhoenixGateaway
|
||||
) {
|
||||
await explorer.allocateContainer(PoolIdType.QueryCopilot);
|
||||
}
|
||||
|
||||
useQueryCopilot.getState().refreshCorrelationId();
|
||||
const serverInfo = useQueryCopilot.getState().notebookServerInfo;
|
||||
|
||||
const queryUri = userContext.features.disableCopilotPhoenixGateaway
|
||||
? createUri("https://copilotorchestrater.azurewebsites.net/", "generateSQLQuery")
|
||||
: createUri(serverInfo.notebookServerEndpoint, "public/generateSQLQuery");
|
||||
|
||||
const payload = {
|
||||
containerSchema: userContext.features.enableCopilotFullSchema
|
||||
? QueryCopilotSampleContainerSchema
|
||||
: ShortenedQueryCopilotSampleContainerSchema,
|
||||
userPrompt: userPrompt,
|
||||
};
|
||||
const response = await fetch(queryUri, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"x-ms-correlationid": useQueryCopilot.getState().correlationId,
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
const generateSQLQueryResponse: GenerateSQLQueryResponse = await response?.json();
|
||||
if (response.ok) {
|
||||
if (generateSQLQueryResponse?.sql) {
|
||||
const bubbleMessage = `Here is a query which will help you with provided prompt.\r\n **Prompt:** "${userPrompt}"`;
|
||||
if (useQueryCopilot.getState().shouldIncludeInMessages) {
|
||||
useQueryCopilot.getState().setChatMessages([
|
||||
...useQueryCopilot.getState().chatMessages,
|
||||
{
|
||||
source: 1,
|
||||
message: bubbleMessage,
|
||||
sqlQuery: generateSQLQueryResponse.sql,
|
||||
explanation: generateSQLQueryResponse.explanation,
|
||||
},
|
||||
]);
|
||||
useQueryCopilot.getState().setShowExplanationBubble(true);
|
||||
useQueryCopilot.getState().setGeneratedQuery(generateSQLQueryResponse.sql);
|
||||
useQueryCopilot.getState().setGeneratedQueryComments(generateSQLQueryResponse.explanation);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
handleError(JSON.stringify(generateSQLQueryResponse), "copilotInternalServerError");
|
||||
useTabs.getState().setIsQueryErrorThrown(true);
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, "executeNaturalLanguageQuery");
|
||||
useTabs.getState().setIsQueryErrorThrown(true);
|
||||
throw error;
|
||||
} finally {
|
||||
useQueryCopilot.getState().setUserPrompt("");
|
||||
useQueryCopilot.getState().setIsGeneratingQuery(false);
|
||||
useTabs.getState().setIsTabExecuting(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const SubmitFeedback = async ({
|
||||
params,
|
||||
explorer,
|
||||
databaseId,
|
||||
containerId,
|
||||
mode,
|
||||
}: {
|
||||
params: FeedbackParams;
|
||||
explorer: Explorer;
|
||||
databaseId: string;
|
||||
containerId: string;
|
||||
mode: string;
|
||||
}): Promise<void> => {
|
||||
try {
|
||||
const { likeQuery, generatedQuery, userPrompt, description, contact } = params;
|
||||
const payload = {
|
||||
like: likeQuery ? "like" : "dislike",
|
||||
generatedSql: generatedQuery,
|
||||
userPrompt,
|
||||
description: description || "",
|
||||
contact: contact || "",
|
||||
};
|
||||
if (
|
||||
useQueryCopilot.getState().containerStatus.status !== ContainerStatusType.Active &&
|
||||
!userContext.features.disableCopilotPhoenixGateaway
|
||||
) {
|
||||
await allocatePhoenixContainer({ explorer, databaseId, containerId, mode });
|
||||
}
|
||||
const serverInfo = useQueryCopilot.getState().notebookServerInfo;
|
||||
const feedbackUri = userContext.features.disableCopilotPhoenixGateaway
|
||||
? createUri("https://copilotorchestrater.azurewebsites.net/", "feedback")
|
||||
: createUri(serverInfo.notebookServerEndpoint, "public/feedback");
|
||||
await fetch(feedbackUri, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"x-ms-correlationid": useQueryCopilot.getState().correlationId,
|
||||
Authorization: `token ${useQueryCopilot.getState().notebookServerInfo.authToken}`,
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, "copilotSubmitFeedback");
|
||||
}
|
||||
};
|
||||
|
||||
export const OnExecuteQueryClick = async (useQueryCopilot: Partial<QueryCopilotState>): Promise<void> => {
|
||||
traceStart(Action.ExecuteQueryGeneratedFromQueryCopilot, {
|
||||
correlationId: useQueryCopilot.getState().correlationId,
|
||||
userPrompt: useQueryCopilot.getState().userPrompt,
|
||||
generatedQuery: useQueryCopilot.getState().generatedQuery,
|
||||
generatedQueryComments: useQueryCopilot.getState().generatedQueryComments,
|
||||
executedQuery: useQueryCopilot.getState().selectedQuery || useQueryCopilot.getState().query,
|
||||
});
|
||||
const queryToExecute = useQueryCopilot.getState().selectedQuery || useQueryCopilot.getState().query;
|
||||
const queryIterator = querySampleDocuments(queryToExecute, {
|
||||
enableCrossPartitionQuery: shouldEnableCrossPartitionKey(),
|
||||
} as FeedOptions);
|
||||
useQueryCopilot.getState().setQueryIterator(queryIterator);
|
||||
|
||||
setTimeout(async () => {
|
||||
await QueryDocumentsPerPage(0, queryIterator, useQueryCopilot);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
export const QueryDocumentsPerPage = async (
|
||||
firstItemIndex: number,
|
||||
queryIterator: MinimalQueryIterator,
|
||||
useQueryCopilot: Partial<QueryCopilotState>,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
useQueryCopilot.getState().setIsExecuting(true);
|
||||
useTabs.getState().setIsTabExecuting(true);
|
||||
useTabs.getState().setIsQueryErrorThrown(false);
|
||||
const queryResults: QueryResults = await queryPagesUntilContentPresent(
|
||||
firstItemIndex,
|
||||
async (firstItemIndex: number) =>
|
||||
queryDocumentsPage(QueryCopilotSampleContainerId, queryIterator, firstItemIndex),
|
||||
);
|
||||
|
||||
useQueryCopilot.getState().setQueryResults(queryResults);
|
||||
useQueryCopilot.getState().setErrors([]);
|
||||
useQueryCopilot.getState().setShowErrorMessageBar(false);
|
||||
traceSuccess(Action.ExecuteQueryGeneratedFromQueryCopilot, {
|
||||
correlationId: useQueryCopilot.getState().correlationId,
|
||||
});
|
||||
} catch (error) {
|
||||
const isCopilotActive = readCopilotToggleStatus(userContext.databaseAccount);
|
||||
const errorMessage = getErrorMessage(error);
|
||||
traceFailure(Action.ExecuteQueryGeneratedFromQueryCopilot, {
|
||||
correlationId: useQueryCopilot.getState().correlationId,
|
||||
errorMessage,
|
||||
});
|
||||
handleError(errorMessage, "executeQueryCopilotTab");
|
||||
useTabs.getState().setIsQueryErrorThrown(true);
|
||||
if (isCopilotActive) {
|
||||
const queryErrors = QueryError.tryParse(error);
|
||||
useQueryCopilot.getState().setErrors(queryErrors);
|
||||
useQueryCopilot.getState().setShowErrorMessageBar(true);
|
||||
}
|
||||
} finally {
|
||||
useQueryCopilot.getState().setIsExecuting(false);
|
||||
useTabs.getState().setIsTabExecuting(false);
|
||||
}
|
||||
};
|
||||
@@ -1,39 +0,0 @@
|
||||
import Explorer from "Explorer/Explorer";
|
||||
|
||||
export interface GenerateSQLQueryResponse {
|
||||
apiVersion: string;
|
||||
sql: string;
|
||||
explanation: string;
|
||||
generateStart: string;
|
||||
generateEnd: string;
|
||||
}
|
||||
|
||||
enum MessageSource {
|
||||
User,
|
||||
AI,
|
||||
AIExplanation,
|
||||
}
|
||||
|
||||
export interface CopilotMessage {
|
||||
source: MessageSource;
|
||||
message: string;
|
||||
sqlQuery?: string;
|
||||
explanation?: string;
|
||||
}
|
||||
|
||||
export interface FeedbackParams {
|
||||
likeQuery: boolean;
|
||||
generatedQuery: string;
|
||||
userPrompt: string;
|
||||
description?: string;
|
||||
contact?: string;
|
||||
}
|
||||
|
||||
export interface QueryCopilotProps {
|
||||
explorer: Explorer;
|
||||
}
|
||||
|
||||
export interface CopilotSchemaAllocationInfo {
|
||||
databaseId: string;
|
||||
containerId: string;
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { QueryDocumentsPerPage } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||
import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection";
|
||||
import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React from "react";
|
||||
|
||||
export const QueryCopilotResults: React.FC = (): JSX.Element => {
|
||||
return (
|
||||
<QueryResultSection
|
||||
isMongoDB={false}
|
||||
queryEditorContent={useQueryCopilot.getState().selectedQuery || useQueryCopilot.getState().query}
|
||||
errors={useQueryCopilot.getState().errors}
|
||||
queryResults={useQueryCopilot.getState().queryResults}
|
||||
isExecuting={useQueryCopilot.getState().isExecuting}
|
||||
executeQueryDocumentsPage={(firstItemIndex: number) =>
|
||||
QueryDocumentsPerPage(
|
||||
firstItemIndex,
|
||||
useQueryCopilot.getState().queryIterator,
|
||||
useQueryCopilot as Partial<QueryCopilotState>,
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,91 +0,0 @@
|
||||
import { DefaultButton, IconButton } from "@fluentui/react";
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { SamplePrompts, SamplePromptsProps } from "./SamplePrompts";
|
||||
|
||||
describe("Sample Prompts snapshot test", () => {
|
||||
const setTextBoxMock = jest.fn();
|
||||
const setIsSamplePromptsOpenMock = jest.fn();
|
||||
const sampleProps: SamplePromptsProps = {
|
||||
isSamplePromptsOpen: true,
|
||||
setIsSamplePromptsOpen: setIsSamplePromptsOpenMock,
|
||||
setTextBox: setTextBoxMock,
|
||||
};
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it("should render properly if isSamplePromptsOpen is true", () => {
|
||||
const wrapper = shallow(<SamplePrompts sampleProps={sampleProps} />);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render properly if isSamplePromptsOpen is false", () => {
|
||||
sampleProps.isSamplePromptsOpen = false;
|
||||
|
||||
const wrapper = shallow(<SamplePrompts sampleProps={sampleProps} />);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should call setTextBox and setIsSamplePromptsOpen(false) when a button is clicked", () => {
|
||||
const wrapper = shallow(<SamplePrompts sampleProps={sampleProps} />);
|
||||
|
||||
wrapper.find(DefaultButton).at(0).simulate("click");
|
||||
expect(setTextBoxMock).toHaveBeenCalledWith("Show me products less than 100 dolars");
|
||||
expect(setIsSamplePromptsOpenMock).toHaveBeenCalledWith(false);
|
||||
|
||||
wrapper.find(DefaultButton).at(3).simulate("click");
|
||||
expect(setTextBoxMock).toHaveBeenCalledWith(
|
||||
"Write a query to return all records in this table created in the last thirty days",
|
||||
);
|
||||
expect(setIsSamplePromptsOpenMock).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it("should call setIsSamplePromptsOpen(false) when the close button is clicked", () => {
|
||||
const wrapper = shallow(<SamplePrompts sampleProps={sampleProps} />);
|
||||
|
||||
wrapper.find(IconButton).first().simulate("click");
|
||||
|
||||
expect(setIsSamplePromptsOpenMock).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it("should call setTextBox and setIsSamplePromptsOpen(false) when a simple prompt button is clicked", () => {
|
||||
const wrapper = shallow(<SamplePrompts sampleProps={sampleProps} />);
|
||||
|
||||
wrapper.find(DefaultButton).at(0).simulate("click");
|
||||
expect(setTextBoxMock).toHaveBeenCalledWith("Show me products less than 100 dolars");
|
||||
expect(setIsSamplePromptsOpenMock).toHaveBeenCalledWith(false);
|
||||
|
||||
wrapper.find(DefaultButton).at(1).simulate("click");
|
||||
expect(setTextBoxMock).toHaveBeenCalledWith("Show schema");
|
||||
expect(setIsSamplePromptsOpenMock).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it("should call setTextBox and setIsSamplePromptsOpen(false) when an intermediate prompt button is clicked", () => {
|
||||
const wrapper = shallow(<SamplePrompts sampleProps={sampleProps} />);
|
||||
|
||||
wrapper.find(DefaultButton).at(2).simulate("click");
|
||||
expect(setTextBoxMock).toHaveBeenCalledWith(
|
||||
"Show items with a description that contains a number between 0 and 99 inclusive.",
|
||||
);
|
||||
expect(setIsSamplePromptsOpenMock).toHaveBeenCalledWith(false);
|
||||
|
||||
wrapper.find(DefaultButton).at(3).simulate("click");
|
||||
expect(setTextBoxMock).toHaveBeenCalledWith(
|
||||
"Write a query to return all records in this table created in the last thirty days",
|
||||
);
|
||||
expect(setIsSamplePromptsOpenMock).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it("should call setTextBox and setIsSamplePromptsOpen(false) when a complex prompt button is clicked", () => {
|
||||
const wrapper = shallow(<SamplePrompts sampleProps={sampleProps} />);
|
||||
|
||||
wrapper.find(DefaultButton).at(4).simulate("click");
|
||||
expect(setTextBoxMock).toHaveBeenCalledWith("Show all the products that customer Bob has reviewed.");
|
||||
expect(setIsSamplePromptsOpenMock).toHaveBeenCalledWith(false);
|
||||
|
||||
wrapper.find(DefaultButton).at(5).simulate("click");
|
||||
expect(setTextBoxMock).toHaveBeenCalledWith("Which computers are more than 300 dollars and less than 400 dollars?");
|
||||
expect(setIsSamplePromptsOpenMock).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
@@ -1,138 +0,0 @@
|
||||
import { DefaultButton, FontIcon, IconButton, Image, Modal, Stack, Text } from "@fluentui/react";
|
||||
import React, { Dispatch, SetStateAction } from "react";
|
||||
import ComplexPrompts from "../../../../../images/ComplexPrompts.svg";
|
||||
import IntermediatePrompts from "../../../../../images/IntermediatePrompts.svg";
|
||||
import SimplePrompts from "../../../../../images/SimplePrompts.svg";
|
||||
|
||||
export interface SamplePromptsProps {
|
||||
isSamplePromptsOpen: boolean;
|
||||
setIsSamplePromptsOpen: Dispatch<SetStateAction<boolean>>;
|
||||
setTextBox: Dispatch<SetStateAction<string>>;
|
||||
}
|
||||
|
||||
const SampleUserInputs: string[] = [
|
||||
"Show me products less than 100 dolars",
|
||||
"Show schema",
|
||||
"Show items with a description that contains a number between 0 and 99 inclusive.",
|
||||
"Write a query to return all records in this table created in the last thirty days",
|
||||
"Show all the products that customer Bob has reviewed.",
|
||||
"Which computers are more than 300 dollars and less than 400 dollars?",
|
||||
];
|
||||
|
||||
export const SamplePrompts = ({ sampleProps }: { sampleProps: SamplePromptsProps }): JSX.Element => {
|
||||
const updateTextBox = (userInput: string) => {
|
||||
sampleProps.setTextBox(userInput);
|
||||
sampleProps.setIsSamplePromptsOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal isOpen={sampleProps.isSamplePromptsOpen}>
|
||||
<Stack
|
||||
style={{ padding: "16px 24px", overflowY: "auto", maxHeight: "calc(100vh - 120px)" }}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
>
|
||||
<Stack>
|
||||
<Stack horizontal style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<Text style={{ fontSize: 24, fontWeight: 600 }}>Sample Prompts</Text>
|
||||
<IconButton
|
||||
styles={{
|
||||
root: {
|
||||
border: "none",
|
||||
backgroundColor: "transparent",
|
||||
padding: 0,
|
||||
selectors: {
|
||||
"&:hover": {
|
||||
backgroundColor: "transparent",
|
||||
color: "#000", // Set the desired color for the X button on hover
|
||||
},
|
||||
"&:focus": {
|
||||
outline: "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
iconProps={{ iconName: "Cancel" }}
|
||||
onClick={() => sampleProps.setIsSamplePromptsOpen(false)}
|
||||
/>
|
||||
</Stack>
|
||||
<Text style={{ fontWeight: 400, fontSize: 13, marginTop: 10 }}>
|
||||
Here are some sample prompts for writing queries in NoSQL, ranging from simple to complex
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
<Stack style={{ marginTop: 30, display: "flex" }}>
|
||||
<Stack horizontal verticalAlign="center">
|
||||
<Image style={{ width: 25, height: 25 }} src={SimplePrompts} />
|
||||
<Text style={{ fontSize: 14, fontWeight: 600 }}>Simple Prompts</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<Stack horizontal style={{ gap: 35 }}>
|
||||
<DefaultButton
|
||||
style={{ width: 352, height: 135, background: "#F6F6F7" }}
|
||||
onClick={() => updateTextBox(SampleUserInputs[0])}
|
||||
>
|
||||
<Text style={{ height: 80, fontSize: 13 }}>{SampleUserInputs[0]}</Text>
|
||||
<FontIcon style={{ position: "absolute", left: "92.61%" }} aria-label="Forward" iconName="Forward" />
|
||||
</DefaultButton>
|
||||
<DefaultButton
|
||||
style={{ width: 352, height: 135, background: "#F6F6F7" }}
|
||||
onClick={() => updateTextBox(SampleUserInputs[1])}
|
||||
>
|
||||
<Text style={{ height: 80, fontSize: 13 }}>{SampleUserInputs[1]}</Text>
|
||||
<FontIcon style={{ position: "absolute", left: "92.61%" }} aria-label="Forward" iconName="Forward" />
|
||||
</DefaultButton>
|
||||
</Stack>
|
||||
|
||||
<Stack style={{ marginTop: 30, display: "flex" }}>
|
||||
<Stack horizontal verticalAlign="center">
|
||||
<Image style={{ width: 25, height: 25 }} src={IntermediatePrompts} />
|
||||
<Text style={{ fontSize: 14, fontWeight: 600 }}>Intermediate Prompts</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<Stack horizontal style={{ gap: 35 }}>
|
||||
<DefaultButton
|
||||
style={{ width: 352, height: 135, background: "#F6F6F7" }}
|
||||
onClick={() => updateTextBox(SampleUserInputs[2])}
|
||||
>
|
||||
<Text style={{ height: 80, fontSize: 13 }}>{SampleUserInputs[2]}</Text>
|
||||
<FontIcon style={{ position: "absolute", left: "92.61%" }} aria-label="Forward" iconName="Forward" />
|
||||
</DefaultButton>
|
||||
<DefaultButton
|
||||
style={{ width: 352, height: 135, background: "#F6F6F7" }}
|
||||
onClick={() => updateTextBox(SampleUserInputs[3])}
|
||||
>
|
||||
<Text style={{ height: 80, fontSize: 13 }}>{SampleUserInputs[3]}</Text>
|
||||
<FontIcon style={{ position: "absolute", left: "92.61%" }} aria-label="Forward" iconName="Forward" />
|
||||
</DefaultButton>
|
||||
</Stack>
|
||||
|
||||
<Stack style={{ marginTop: 30, display: "flex" }}>
|
||||
<Stack horizontal verticalAlign="center">
|
||||
<Image style={{ width: 25, height: 25 }} src={ComplexPrompts} />
|
||||
<Text style={{ fontSize: 14, fontWeight: 600 }}>Complex Prompts</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<Stack horizontal style={{ gap: 35 }}>
|
||||
<DefaultButton
|
||||
style={{ width: 352, height: 135, background: "#F6F6F7" }}
|
||||
onClick={() => updateTextBox(SampleUserInputs[4])}
|
||||
>
|
||||
<Text style={{ height: 80, fontSize: 13 }}>{SampleUserInputs[4]}</Text>
|
||||
<FontIcon style={{ position: "absolute", left: "92.61%" }} aria-label="Forward" iconName="Forward" />
|
||||
</DefaultButton>
|
||||
<DefaultButton
|
||||
style={{ width: 352, height: 135, background: "#F6F6F7" }}
|
||||
onClick={() => updateTextBox(SampleUserInputs[5])}
|
||||
>
|
||||
<Text style={{ height: 80, fontSize: 13 }}>{SampleUserInputs[5]}</Text>
|
||||
<FontIcon style={{ position: "absolute", left: "92.61%" }} aria-label="Forward" iconName="Forward" />
|
||||
</DefaultButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -1,781 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Sample Prompts snapshot test should render properly if isSamplePromptsOpen is false 1`] = `
|
||||
<Modal
|
||||
isOpen={false}
|
||||
>
|
||||
<Stack
|
||||
aria-modal="true"
|
||||
role="dialog"
|
||||
style={
|
||||
{
|
||||
"maxHeight": "calc(100vh - 120px)",
|
||||
"overflowY": "auto",
|
||||
"padding": "16px 24px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
style={
|
||||
{
|
||||
"display": "flex",
|
||||
"justifyContent": "space-between",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 24,
|
||||
"fontWeight": 600,
|
||||
}
|
||||
}
|
||||
>
|
||||
Sample Prompts
|
||||
</Text>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"iconName": "Cancel",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"backgroundColor": "transparent",
|
||||
"border": "none",
|
||||
"padding": 0,
|
||||
"selectors": {
|
||||
"&:focus": {
|
||||
"outline": "none",
|
||||
},
|
||||
"&:hover": {
|
||||
"backgroundColor": "transparent",
|
||||
"color": "#000",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 13,
|
||||
"fontWeight": 400,
|
||||
"marginTop": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
Here are some sample prompts for writing queries in NoSQL, ranging from simple to complex
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack
|
||||
style={
|
||||
{
|
||||
"display": "flex",
|
||||
"marginTop": 30,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
verticalAlign="center"
|
||||
>
|
||||
<Image
|
||||
src={{}}
|
||||
style={
|
||||
{
|
||||
"height": 25,
|
||||
"width": 25,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 14,
|
||||
"fontWeight": 600,
|
||||
}
|
||||
}
|
||||
>
|
||||
Simple Prompts
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
style={
|
||||
{
|
||||
"gap": 35,
|
||||
}
|
||||
}
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
style={
|
||||
{
|
||||
"background": "#F6F6F7",
|
||||
"height": 135,
|
||||
"width": 352,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 13,
|
||||
"height": 80,
|
||||
}
|
||||
}
|
||||
>
|
||||
Show me products less than 100 dolars
|
||||
</Text>
|
||||
<FontIcon
|
||||
aria-label="Forward"
|
||||
iconName="Forward"
|
||||
style={
|
||||
{
|
||||
"left": "92.61%",
|
||||
"position": "absolute",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</CustomizedDefaultButton>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
style={
|
||||
{
|
||||
"background": "#F6F6F7",
|
||||
"height": 135,
|
||||
"width": 352,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 13,
|
||||
"height": 80,
|
||||
}
|
||||
}
|
||||
>
|
||||
Show schema
|
||||
</Text>
|
||||
<FontIcon
|
||||
aria-label="Forward"
|
||||
iconName="Forward"
|
||||
style={
|
||||
{
|
||||
"left": "92.61%",
|
||||
"position": "absolute",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</CustomizedDefaultButton>
|
||||
</Stack>
|
||||
<Stack
|
||||
style={
|
||||
{
|
||||
"display": "flex",
|
||||
"marginTop": 30,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
verticalAlign="center"
|
||||
>
|
||||
<Image
|
||||
src={{}}
|
||||
style={
|
||||
{
|
||||
"height": 25,
|
||||
"width": 25,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 14,
|
||||
"fontWeight": 600,
|
||||
}
|
||||
}
|
||||
>
|
||||
Intermediate Prompts
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
style={
|
||||
{
|
||||
"gap": 35,
|
||||
}
|
||||
}
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
style={
|
||||
{
|
||||
"background": "#F6F6F7",
|
||||
"height": 135,
|
||||
"width": 352,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 13,
|
||||
"height": 80,
|
||||
}
|
||||
}
|
||||
>
|
||||
Show items with a description that contains a number between 0 and 99 inclusive.
|
||||
</Text>
|
||||
<FontIcon
|
||||
aria-label="Forward"
|
||||
iconName="Forward"
|
||||
style={
|
||||
{
|
||||
"left": "92.61%",
|
||||
"position": "absolute",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</CustomizedDefaultButton>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
style={
|
||||
{
|
||||
"background": "#F6F6F7",
|
||||
"height": 135,
|
||||
"width": 352,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 13,
|
||||
"height": 80,
|
||||
}
|
||||
}
|
||||
>
|
||||
Write a query to return all records in this table created in the last thirty days
|
||||
</Text>
|
||||
<FontIcon
|
||||
aria-label="Forward"
|
||||
iconName="Forward"
|
||||
style={
|
||||
{
|
||||
"left": "92.61%",
|
||||
"position": "absolute",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</CustomizedDefaultButton>
|
||||
</Stack>
|
||||
<Stack
|
||||
style={
|
||||
{
|
||||
"display": "flex",
|
||||
"marginTop": 30,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
verticalAlign="center"
|
||||
>
|
||||
<Image
|
||||
src={{}}
|
||||
style={
|
||||
{
|
||||
"height": 25,
|
||||
"width": 25,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 14,
|
||||
"fontWeight": 600,
|
||||
}
|
||||
}
|
||||
>
|
||||
Complex Prompts
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
style={
|
||||
{
|
||||
"gap": 35,
|
||||
}
|
||||
}
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
style={
|
||||
{
|
||||
"background": "#F6F6F7",
|
||||
"height": 135,
|
||||
"width": 352,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 13,
|
||||
"height": 80,
|
||||
}
|
||||
}
|
||||
>
|
||||
Show all the products that customer Bob has reviewed.
|
||||
</Text>
|
||||
<FontIcon
|
||||
aria-label="Forward"
|
||||
iconName="Forward"
|
||||
style={
|
||||
{
|
||||
"left": "92.61%",
|
||||
"position": "absolute",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</CustomizedDefaultButton>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
style={
|
||||
{
|
||||
"background": "#F6F6F7",
|
||||
"height": 135,
|
||||
"width": 352,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 13,
|
||||
"height": 80,
|
||||
}
|
||||
}
|
||||
>
|
||||
Which computers are more than 300 dollars and less than 400 dollars?
|
||||
</Text>
|
||||
<FontIcon
|
||||
aria-label="Forward"
|
||||
iconName="Forward"
|
||||
style={
|
||||
{
|
||||
"left": "92.61%",
|
||||
"position": "absolute",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</CustomizedDefaultButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Modal>
|
||||
`;
|
||||
|
||||
exports[`Sample Prompts snapshot test should render properly if isSamplePromptsOpen is true 1`] = `
|
||||
<Modal
|
||||
isOpen={true}
|
||||
>
|
||||
<Stack
|
||||
aria-modal="true"
|
||||
role="dialog"
|
||||
style={
|
||||
{
|
||||
"maxHeight": "calc(100vh - 120px)",
|
||||
"overflowY": "auto",
|
||||
"padding": "16px 24px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
style={
|
||||
{
|
||||
"display": "flex",
|
||||
"justifyContent": "space-between",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 24,
|
||||
"fontWeight": 600,
|
||||
}
|
||||
}
|
||||
>
|
||||
Sample Prompts
|
||||
</Text>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"iconName": "Cancel",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"backgroundColor": "transparent",
|
||||
"border": "none",
|
||||
"padding": 0,
|
||||
"selectors": {
|
||||
"&:focus": {
|
||||
"outline": "none",
|
||||
},
|
||||
"&:hover": {
|
||||
"backgroundColor": "transparent",
|
||||
"color": "#000",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 13,
|
||||
"fontWeight": 400,
|
||||
"marginTop": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
Here are some sample prompts for writing queries in NoSQL, ranging from simple to complex
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack
|
||||
style={
|
||||
{
|
||||
"display": "flex",
|
||||
"marginTop": 30,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
verticalAlign="center"
|
||||
>
|
||||
<Image
|
||||
src={{}}
|
||||
style={
|
||||
{
|
||||
"height": 25,
|
||||
"width": 25,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 14,
|
||||
"fontWeight": 600,
|
||||
}
|
||||
}
|
||||
>
|
||||
Simple Prompts
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
style={
|
||||
{
|
||||
"gap": 35,
|
||||
}
|
||||
}
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
style={
|
||||
{
|
||||
"background": "#F6F6F7",
|
||||
"height": 135,
|
||||
"width": 352,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 13,
|
||||
"height": 80,
|
||||
}
|
||||
}
|
||||
>
|
||||
Show me products less than 100 dolars
|
||||
</Text>
|
||||
<FontIcon
|
||||
aria-label="Forward"
|
||||
iconName="Forward"
|
||||
style={
|
||||
{
|
||||
"left": "92.61%",
|
||||
"position": "absolute",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</CustomizedDefaultButton>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
style={
|
||||
{
|
||||
"background": "#F6F6F7",
|
||||
"height": 135,
|
||||
"width": 352,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 13,
|
||||
"height": 80,
|
||||
}
|
||||
}
|
||||
>
|
||||
Show schema
|
||||
</Text>
|
||||
<FontIcon
|
||||
aria-label="Forward"
|
||||
iconName="Forward"
|
||||
style={
|
||||
{
|
||||
"left": "92.61%",
|
||||
"position": "absolute",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</CustomizedDefaultButton>
|
||||
</Stack>
|
||||
<Stack
|
||||
style={
|
||||
{
|
||||
"display": "flex",
|
||||
"marginTop": 30,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
verticalAlign="center"
|
||||
>
|
||||
<Image
|
||||
src={{}}
|
||||
style={
|
||||
{
|
||||
"height": 25,
|
||||
"width": 25,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 14,
|
||||
"fontWeight": 600,
|
||||
}
|
||||
}
|
||||
>
|
||||
Intermediate Prompts
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
style={
|
||||
{
|
||||
"gap": 35,
|
||||
}
|
||||
}
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
style={
|
||||
{
|
||||
"background": "#F6F6F7",
|
||||
"height": 135,
|
||||
"width": 352,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 13,
|
||||
"height": 80,
|
||||
}
|
||||
}
|
||||
>
|
||||
Show items with a description that contains a number between 0 and 99 inclusive.
|
||||
</Text>
|
||||
<FontIcon
|
||||
aria-label="Forward"
|
||||
iconName="Forward"
|
||||
style={
|
||||
{
|
||||
"left": "92.61%",
|
||||
"position": "absolute",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</CustomizedDefaultButton>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
style={
|
||||
{
|
||||
"background": "#F6F6F7",
|
||||
"height": 135,
|
||||
"width": 352,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 13,
|
||||
"height": 80,
|
||||
}
|
||||
}
|
||||
>
|
||||
Write a query to return all records in this table created in the last thirty days
|
||||
</Text>
|
||||
<FontIcon
|
||||
aria-label="Forward"
|
||||
iconName="Forward"
|
||||
style={
|
||||
{
|
||||
"left": "92.61%",
|
||||
"position": "absolute",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</CustomizedDefaultButton>
|
||||
</Stack>
|
||||
<Stack
|
||||
style={
|
||||
{
|
||||
"display": "flex",
|
||||
"marginTop": 30,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
verticalAlign="center"
|
||||
>
|
||||
<Image
|
||||
src={{}}
|
||||
style={
|
||||
{
|
||||
"height": 25,
|
||||
"width": 25,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 14,
|
||||
"fontWeight": 600,
|
||||
}
|
||||
}
|
||||
>
|
||||
Complex Prompts
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
style={
|
||||
{
|
||||
"gap": 35,
|
||||
}
|
||||
}
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
style={
|
||||
{
|
||||
"background": "#F6F6F7",
|
||||
"height": 135,
|
||||
"width": 352,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 13,
|
||||
"height": 80,
|
||||
}
|
||||
}
|
||||
>
|
||||
Show all the products that customer Bob has reviewed.
|
||||
</Text>
|
||||
<FontIcon
|
||||
aria-label="Forward"
|
||||
iconName="Forward"
|
||||
style={
|
||||
{
|
||||
"left": "92.61%",
|
||||
"position": "absolute",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</CustomizedDefaultButton>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
style={
|
||||
{
|
||||
"background": "#F6F6F7",
|
||||
"height": 135,
|
||||
"width": 352,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 13,
|
||||
"height": 80,
|
||||
}
|
||||
}
|
||||
>
|
||||
Which computers are more than 300 dollars and less than 400 dollars?
|
||||
</Text>
|
||||
<FontIcon
|
||||
aria-label="Forward"
|
||||
iconName="Forward"
|
||||
style={
|
||||
{
|
||||
"left": "92.61%",
|
||||
"position": "absolute",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</CustomizedDefaultButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Modal>
|
||||
`;
|
||||
@@ -1,69 +0,0 @@
|
||||
import { Text } from "@fluentui/react";
|
||||
import { shallow } from "enzyme";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React from "react";
|
||||
import { ExplanationButton } from "./ExplanationButton";
|
||||
|
||||
describe("Explanation Button", () => {
|
||||
const initialStoreState = useQueryCopilot.getState();
|
||||
beforeEach(() => {
|
||||
useQueryCopilot.setState(initialStoreState, true);
|
||||
useQueryCopilot.getState().showExplanationBubble = true;
|
||||
useQueryCopilot.getState().shouldIncludeInMessages = false;
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it("should render explanation bubble with generated comments", () => {
|
||||
useQueryCopilot.getState().shouldIncludeInMessages = true;
|
||||
|
||||
const wrapper = shallow(<ExplanationButton />);
|
||||
|
||||
expect(wrapper.find("Stack")).toHaveLength(1);
|
||||
expect(wrapper.find("Text")).toHaveLength(1);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render 'Explain this query' link", () => {
|
||||
useQueryCopilot.getState().shouldIncludeInMessages = true;
|
||||
const mockSetChatMessages = jest.fn();
|
||||
const mockSetIsGeneratingExplanation = jest.fn();
|
||||
const mockSetShouldIncludeInMessages = jest.fn();
|
||||
const mockSetShowExplanationBubble = jest.fn();
|
||||
useQueryCopilot.getState().setChatMessages = mockSetChatMessages;
|
||||
useQueryCopilot.getState().setIsGeneratingExplanation = mockSetIsGeneratingExplanation;
|
||||
useQueryCopilot.getState().setShouldIncludeInMessages = mockSetShouldIncludeInMessages;
|
||||
useQueryCopilot.getState().setShowExplanationBubble = mockSetShowExplanationBubble;
|
||||
|
||||
const wrapper = shallow(<ExplanationButton />);
|
||||
|
||||
const textElement = wrapper.find(Text);
|
||||
textElement.simulate("click");
|
||||
|
||||
expect(mockSetChatMessages).toHaveBeenCalledWith([
|
||||
...initialStoreState.chatMessages,
|
||||
{ source: 0, message: "Explain this query to me" },
|
||||
]);
|
||||
expect(mockSetIsGeneratingExplanation).toHaveBeenCalledWith(true);
|
||||
expect(mockSetShouldIncludeInMessages).toHaveBeenCalledWith(true);
|
||||
expect(mockSetShowExplanationBubble).toHaveBeenCalledWith(false);
|
||||
|
||||
jest.advanceTimersByTime(3000);
|
||||
|
||||
expect(mockSetIsGeneratingExplanation).toHaveBeenCalledWith(false);
|
||||
expect(mockSetChatMessages).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should render nothing when conditions are not met", () => {
|
||||
useQueryCopilot.getState().showExplanationBubble = false;
|
||||
|
||||
const wrapper = shallow(<ExplanationButton />);
|
||||
|
||||
expect(wrapper.isEmptyRender()).toBe(true);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,61 +0,0 @@
|
||||
import { Stack, Text } from "@fluentui/react";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React from "react";
|
||||
|
||||
export const ExplanationButton: React.FC = (): JSX.Element => {
|
||||
const {
|
||||
showExplanationBubble,
|
||||
isGeneratingQuery,
|
||||
chatMessages,
|
||||
setChatMessages,
|
||||
generatedQuery,
|
||||
generatedQueryComments,
|
||||
isGeneratingExplanation,
|
||||
setIsGeneratingExplanation,
|
||||
setShouldIncludeInMessages,
|
||||
setShowExplanationBubble,
|
||||
} = useQueryCopilot();
|
||||
|
||||
const showExplanation = () => {
|
||||
setChatMessages([...chatMessages, { source: 0, message: "Explain this query to me" }]);
|
||||
setIsGeneratingExplanation(true);
|
||||
setShouldIncludeInMessages(true);
|
||||
setShowExplanationBubble(false);
|
||||
|
||||
setTimeout(() => {
|
||||
if (useQueryCopilot.getState().shouldIncludeInMessages) {
|
||||
setIsGeneratingExplanation(false);
|
||||
setChatMessages([...chatMessages, { source: 2, message: generatedQueryComments, sqlQuery: generatedQuery }]);
|
||||
}
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
return (
|
||||
showExplanationBubble &&
|
||||
!isGeneratingQuery &&
|
||||
!isGeneratingExplanation && (
|
||||
<Stack
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: "5px 5px 5px 50px",
|
||||
margin: "5px",
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
onClick={showExplanation}
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
border: "1.5px solid #B0BEFF",
|
||||
width: "100%",
|
||||
padding: "2px",
|
||||
borderRadius: "4px",
|
||||
marginBottom: "5px",
|
||||
}}
|
||||
>
|
||||
Explain this query to me
|
||||
</Text>
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Explanation Button should render explanation bubble with generated comments 1`] = `
|
||||
<Stack
|
||||
style={
|
||||
{
|
||||
"alignItems": "center",
|
||||
"display": "flex",
|
||||
"margin": "5px",
|
||||
"padding": "5px 5px 5px 50px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
onClick={[Function]}
|
||||
style={
|
||||
{
|
||||
"border": "1.5px solid #B0BEFF",
|
||||
"borderRadius": "4px",
|
||||
"cursor": "pointer",
|
||||
"marginBottom": "5px",
|
||||
"padding": "2px",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
Explain this query to me
|
||||
</Text>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Explanation Button should render nothing when conditions are not met 1`] = `""`;
|
||||
@@ -1,26 +0,0 @@
|
||||
import { Stack, Text } from "@fluentui/react";
|
||||
import { CopilotMessage } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
||||
import { FeedbackButtons } from "Explorer/QueryCopilot/V2/Bubbles/Output/Buttons/Feedback/FeedbackButtons";
|
||||
import React from "react";
|
||||
|
||||
export const ExplanationBubble = ({ copilotMessage }: { copilotMessage: CopilotMessage }): JSX.Element => {
|
||||
return (
|
||||
<Stack
|
||||
horizontalAlign="start"
|
||||
verticalAlign="start"
|
||||
tokens={{ padding: 8, childrenGap: 8 }}
|
||||
style={{
|
||||
backgroundColor: "white",
|
||||
borderRadius: "8px",
|
||||
margin: "5px 10px",
|
||||
textAlign: "start",
|
||||
}}
|
||||
>
|
||||
<Text>{copilotMessage.message}</Text>
|
||||
<FeedbackButtons sqlQuery={copilotMessage.sqlQuery} />
|
||||
<Text style={{ fontWeight: 400, fontSize: "10px", lineHeight: "14px" }}>
|
||||
AI-generated content may be incorrect
|
||||
</Text>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -1,17 +0,0 @@
|
||||
import { CopilotMessage } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
||||
import { ExplanationBubble } from "Explorer/QueryCopilot/V2/Bubbles/Explanation/ExplainationBubble";
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
describe("Explanation Bubble snapshot tests", () => {
|
||||
it("should render", () => {
|
||||
const mockCopilotMessage: CopilotMessage = {
|
||||
source: 2,
|
||||
message: "Mock message",
|
||||
};
|
||||
|
||||
const wrapper = shallow(<ExplanationBubble copilotMessage={mockCopilotMessage} />);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,38 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Explanation Bubble snapshot tests should render 1`] = `
|
||||
<Stack
|
||||
horizontalAlign="start"
|
||||
style={
|
||||
{
|
||||
"backgroundColor": "white",
|
||||
"borderRadius": "8px",
|
||||
"margin": "5px 10px",
|
||||
"textAlign": "start",
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
{
|
||||
"childrenGap": 8,
|
||||
"padding": 8,
|
||||
}
|
||||
}
|
||||
verticalAlign="start"
|
||||
>
|
||||
<Text>
|
||||
Mock message
|
||||
</Text>
|
||||
<FeedbackButtons />
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": "10px",
|
||||
"fontWeight": 400,
|
||||
"lineHeight": "14px",
|
||||
}
|
||||
}
|
||||
>
|
||||
AI-generated content may be incorrect
|
||||
</Text>
|
||||
</Stack>
|
||||
`;
|
||||
@@ -1,21 +0,0 @@
|
||||
import { IconButton } from "@fluentui/react";
|
||||
import { CopyButton } from "Explorer/QueryCopilot/V2/Bubbles/Output/Buttons/Copy/CopyButton";
|
||||
import { shallow } from "enzyme";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React from "react";
|
||||
|
||||
document.execCommand = jest.fn();
|
||||
|
||||
describe("Copy button snapshot tests", () => {
|
||||
it("should render and click copy", async () => {
|
||||
const testInput = "test input query";
|
||||
useQueryCopilot.getState().setGeneratedQuery(testInput);
|
||||
const wrapper = shallow(<CopyButton sqlQuery={""} />);
|
||||
|
||||
const button = wrapper.find(IconButton).first();
|
||||
button.simulate("click", {});
|
||||
|
||||
expect(document.execCommand).toHaveBeenCalledWith("copy");
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,22 +0,0 @@
|
||||
import { IconButton } from "@fluentui/react";
|
||||
import React from "react";
|
||||
import CopilotCopy from "../../../../../../../../images/CopilotCopy.svg";
|
||||
|
||||
export const CopyButton = ({ sqlQuery }: { sqlQuery: string }): JSX.Element => {
|
||||
const copyGeneratedCode = (): void => {
|
||||
const queryElement = document.createElement("textarea");
|
||||
queryElement.value = sqlQuery;
|
||||
document.body.appendChild(queryElement);
|
||||
queryElement.select();
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(queryElement);
|
||||
};
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
iconProps={{ imageProps: { src: CopilotCopy } }}
|
||||
ariaLabel="Copy"
|
||||
onClick={copyGeneratedCode}
|
||||
></IconButton>
|
||||
);
|
||||
};
|
||||
@@ -1,15 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Copy button snapshot tests should render and click copy 1`] = `
|
||||
<CustomizedIconButton
|
||||
ariaLabel="Copy"
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": {},
|
||||
},
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
`;
|
||||
@@ -1,205 +0,0 @@
|
||||
import { Callout, IconButton, Link } from "@fluentui/react";
|
||||
import { useId } from "@fluentui/react-hooks";
|
||||
import { FeedbackButtons } from "Explorer/QueryCopilot/V2/Bubbles/Output/Buttons/Feedback/FeedbackButtons";
|
||||
import { shallow } from "enzyme";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React from "react";
|
||||
import LikeHover from "../../../../../../../../images/CopilotLikeHover.svg";
|
||||
import LikePressed from "../../../../../../../../images/CopilotLikePressed.svg";
|
||||
import LikeRest from "../../../../../../../../images/CopilotLikeRest.svg";
|
||||
|
||||
useId as jest.Mock;
|
||||
|
||||
jest.mock("../../../../../../../../images/CopilotLikeHover.svg", () => "LikeHover");
|
||||
jest.mock("../../../../../../../../images/CopilotLikePressed.svg", () => "LikePressed");
|
||||
jest.mock("../../../../../../../../images/CopilotLikeRest.svg", () => "LikeRest");
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe("Feedback buttons snapshot tests", () => {
|
||||
it("should click like and show callout", () => {
|
||||
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||
|
||||
let likeButton = wrapper.find(IconButton).first();
|
||||
const dislikeButton = wrapper.find(IconButton).last();
|
||||
likeButton.simulate("click");
|
||||
likeButton = wrapper.find(IconButton).first();
|
||||
const callout = wrapper.find(Callout).first();
|
||||
|
||||
expect(likeButton.props().iconProps.imageProps.src).toEqual(LikePressed);
|
||||
expect(dislikeButton.props().iconProps.imageProps.src).toEqual(LikeRest);
|
||||
expect(callout.exists()).toBeTruthy();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should click like and dismiss callout", () => {
|
||||
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||
|
||||
const likeButton = wrapper.find(IconButton).first();
|
||||
likeButton.simulate("click");
|
||||
let callout = wrapper.find(Callout).first();
|
||||
callout.simulate("dismiss");
|
||||
callout = wrapper.find(Callout).first();
|
||||
|
||||
expect(callout.exists()).toBeFalsy();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should click like and submit feedback", () => {
|
||||
const spy = jest.spyOn(useQueryCopilot.getState(), "openFeedbackModal");
|
||||
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||
|
||||
const likeButton = wrapper.find(IconButton).first();
|
||||
likeButton.simulate("click");
|
||||
const link = wrapper.find(Link).first();
|
||||
link.simulate("click");
|
||||
|
||||
expect(spy).toHaveBeenNthCalledWith(1, "", true, "");
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should hover over like", () => {
|
||||
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||
|
||||
let likeButton = wrapper.find(IconButton).first();
|
||||
likeButton.simulate("mouseover");
|
||||
likeButton = wrapper.find(IconButton).first();
|
||||
|
||||
expect(likeButton.props().iconProps.imageProps.src).toEqual(LikeHover);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should hover over rest like and leave", () => {
|
||||
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||
|
||||
let likeButton = wrapper.find(IconButton).first();
|
||||
likeButton.simulate("mouseover");
|
||||
likeButton.simulate("mouseleave");
|
||||
likeButton = wrapper.find(IconButton).first();
|
||||
|
||||
expect(likeButton.props().iconProps.imageProps.src).toEqual(LikeRest);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should hover over pressed like and leave", () => {
|
||||
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||
|
||||
let likeButton = wrapper.find(IconButton).first();
|
||||
likeButton.simulate("click");
|
||||
likeButton = wrapper.find(IconButton).first();
|
||||
likeButton.simulate("mouseover");
|
||||
likeButton.simulate("mouseleave");
|
||||
|
||||
expect(likeButton.props().iconProps.imageProps.src).toEqual(LikePressed);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should hover over like and click", () => {
|
||||
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||
|
||||
let likeButton = wrapper.find(IconButton).first();
|
||||
likeButton.simulate("mouseover");
|
||||
likeButton.simulate("click");
|
||||
likeButton = wrapper.find(IconButton).first();
|
||||
|
||||
expect(likeButton.props().iconProps.imageProps.src).toEqual(LikePressed);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should dobule click on like", () => {
|
||||
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||
|
||||
let likeButton = wrapper.find(IconButton).first();
|
||||
likeButton.simulate("click");
|
||||
likeButton = wrapper.find(IconButton).first();
|
||||
expect(likeButton.props().iconProps.imageProps.src).toEqual(LikePressed);
|
||||
|
||||
likeButton.simulate("click");
|
||||
likeButton = wrapper.find(IconButton).first();
|
||||
expect(likeButton.props().iconProps.imageProps.src).toEqual(LikeRest);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should click dislike and show popup", () => {
|
||||
const spy = jest.spyOn(useQueryCopilot.getState(), "openFeedbackModal");
|
||||
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||
|
||||
const likeButton = wrapper.find(IconButton).first();
|
||||
let dislikeButton = wrapper.find(IconButton).last();
|
||||
dislikeButton.simulate("click");
|
||||
const callout = wrapper.find(Callout).first();
|
||||
dislikeButton = wrapper.find(IconButton).last();
|
||||
|
||||
expect(likeButton.props().iconProps.imageProps.src).toEqual(LikeRest);
|
||||
expect(dislikeButton.props().iconProps.imageProps.src).toEqual(LikePressed);
|
||||
expect(spy).toHaveBeenNthCalledWith(1, "", false, "");
|
||||
expect(callout.exists()).toBeFalsy();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should hover over dislike", () => {
|
||||
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||
|
||||
let dislikeButton = wrapper.find(IconButton).last();
|
||||
dislikeButton.simulate("mouseover");
|
||||
dislikeButton = wrapper.find(IconButton).last();
|
||||
|
||||
expect(dislikeButton.props().iconProps.imageProps.src).toEqual(LikeHover);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should hover over rest dislike and leave", () => {
|
||||
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||
|
||||
let dislikeButton = wrapper.find(IconButton).last();
|
||||
dislikeButton.simulate("mouseover");
|
||||
dislikeButton.simulate("mouseleave");
|
||||
dislikeButton = wrapper.find(IconButton).last();
|
||||
|
||||
expect(dislikeButton.props().iconProps.imageProps.src).toEqual(LikeRest);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should hover over pressed dislike and leave", () => {
|
||||
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||
|
||||
let dislikeButton = wrapper.find(IconButton).last();
|
||||
dislikeButton.simulate("click");
|
||||
dislikeButton = wrapper.find(IconButton).last();
|
||||
expect(dislikeButton.props().iconProps.imageProps.src).toEqual(LikePressed);
|
||||
dislikeButton.simulate("mouseover");
|
||||
dislikeButton.simulate("mouseleave");
|
||||
dislikeButton = wrapper.find(IconButton).last();
|
||||
|
||||
expect(dislikeButton.props().iconProps.imageProps.src).toEqual(LikePressed);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should hover over dislike and click", () => {
|
||||
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||
|
||||
let dislikeButton = wrapper.find(IconButton).last();
|
||||
dislikeButton.simulate("mouseover");
|
||||
dislikeButton.simulate("click");
|
||||
dislikeButton = wrapper.find(IconButton).last();
|
||||
|
||||
expect(dislikeButton.props().iconProps.imageProps.src).toEqual(LikePressed);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should dobule click on dislike", () => {
|
||||
const wrapper = shallow(<FeedbackButtons sqlQuery={""} />);
|
||||
|
||||
let dislikeButton = wrapper.find(IconButton).last();
|
||||
dislikeButton.simulate("click");
|
||||
dislikeButton = wrapper.find(IconButton).last();
|
||||
expect(dislikeButton.props().iconProps.imageProps.src).toEqual(LikePressed);
|
||||
|
||||
dislikeButton.simulate("click");
|
||||
dislikeButton = wrapper.find(IconButton).last();
|
||||
expect(dislikeButton.props().iconProps.imageProps.src).toEqual(LikeRest);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,93 +0,0 @@
|
||||
import { Callout, DirectionalHint, IconButton, Link, Stack, Text } from "@fluentui/react";
|
||||
import { useId } from "@fluentui/react-hooks";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React, { useState } from "react";
|
||||
import LikeHover from "../../../../../../../../images/CopilotLikeHover.svg";
|
||||
import LikePressed from "../../../../../../../../images/CopilotLikePressed.svg";
|
||||
import LikeRest from "../../../../../../../../images/CopilotLikeRest.svg";
|
||||
|
||||
export const FeedbackButtons = ({ sqlQuery }: { sqlQuery: string }): JSX.Element => {
|
||||
const { userPrompt } = useQueryCopilot();
|
||||
|
||||
const [likeQuery, setLikeQuery] = useState<boolean>(false);
|
||||
const [dislikeQuery, setDislikeQuery] = useState<boolean>(false);
|
||||
const [likeImageLink, setLikeImageLink] = useState<string>(LikeRest);
|
||||
const [dislikeImageLink, setDislikeImageLink] = useState<string>(LikeRest);
|
||||
const [calloutVisible, setCalloutVisible] = useState<boolean>(false);
|
||||
const likeBtnId = useId("likeBtn");
|
||||
const dislikeBtnId = useId("dislikeBtn");
|
||||
|
||||
return (
|
||||
<Stack horizontal>
|
||||
{calloutVisible && (
|
||||
<Callout
|
||||
target={`#${likeBtnId}`}
|
||||
onDismiss={() => setCalloutVisible(false)}
|
||||
directionalHint={DirectionalHint.topCenter}
|
||||
role="dialog"
|
||||
style={{ padding: "5px 12px 5px 12px", borderRadius: "4px" }}
|
||||
styles={{ beakCurtain: { borderRadius: "4px" }, root: { borderRadius: "4px" } }}
|
||||
>
|
||||
<Text>
|
||||
{" "}
|
||||
<Text>
|
||||
Thank you. Need to give{" "}
|
||||
<Link
|
||||
onClick={() => {
|
||||
setCalloutVisible(false);
|
||||
useQueryCopilot.getState().openFeedbackModal(sqlQuery, true, userPrompt);
|
||||
}}
|
||||
>
|
||||
more feedback?
|
||||
</Link>
|
||||
</Text>
|
||||
</Text>
|
||||
</Callout>
|
||||
)}
|
||||
|
||||
<IconButton
|
||||
id={likeBtnId}
|
||||
iconProps={{
|
||||
imageProps: { src: likeImageLink },
|
||||
style: { minHeight: "18px" },
|
||||
}}
|
||||
onClick={() => {
|
||||
if (likeQuery) {
|
||||
setLikeQuery(false);
|
||||
setLikeImageLink(LikeRest);
|
||||
setCalloutVisible(false);
|
||||
} else {
|
||||
setLikeQuery(true);
|
||||
setDislikeQuery(false);
|
||||
setLikeImageLink(LikePressed);
|
||||
setDislikeImageLink(LikeRest);
|
||||
setCalloutVisible(true);
|
||||
}
|
||||
}}
|
||||
onMouseOver={() => setLikeImageLink(LikeHover)}
|
||||
onMouseLeave={() => setLikeImageLink(likeQuery ? LikePressed : LikeRest)}
|
||||
/>
|
||||
<IconButton
|
||||
id={dislikeBtnId}
|
||||
iconProps={{
|
||||
imageProps: { src: dislikeImageLink },
|
||||
style: { minHeight: "18px", transform: "rotate(180deg)" },
|
||||
}}
|
||||
onClick={() => {
|
||||
if (dislikeQuery) {
|
||||
setDislikeQuery(false);
|
||||
setDislikeImageLink(LikeRest);
|
||||
} else {
|
||||
setDislikeQuery(true);
|
||||
setLikeQuery(false);
|
||||
setDislikeImageLink(LikePressed);
|
||||
setLikeImageLink(LikeRest);
|
||||
useQueryCopilot.getState().openFeedbackModal(sqlQuery, false, userPrompt);
|
||||
}
|
||||
}}
|
||||
onMouseOver={() => setDislikeImageLink(LikeHover)}
|
||||
onMouseLeave={() => setDislikeImageLink(dislikeQuery ? LikePressed : LikeRest)}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -1,666 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Feedback buttons snapshot tests should click dislike and show popup 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikeRest",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="likeBtn16"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikePressed",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
"transform": "rotate(180deg)",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="dislikeBtn17"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Feedback buttons snapshot tests should click like and dismiss callout 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikePressed",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="likeBtn2"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikeRest",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
"transform": "rotate(180deg)",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="dislikeBtn3"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Feedback buttons snapshot tests should click like and show callout 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
>
|
||||
<Callout
|
||||
directionalHint={1}
|
||||
onDismiss={[Function]}
|
||||
role="dialog"
|
||||
style={
|
||||
{
|
||||
"borderRadius": "4px",
|
||||
"padding": "5px 12px 5px 12px",
|
||||
}
|
||||
}
|
||||
styles={
|
||||
{
|
||||
"beakCurtain": {
|
||||
"borderRadius": "4px",
|
||||
},
|
||||
"root": {
|
||||
"borderRadius": "4px",
|
||||
},
|
||||
}
|
||||
}
|
||||
target="#likeBtn0"
|
||||
>
|
||||
<Text>
|
||||
|
||||
<Text>
|
||||
Thank you. Need to give
|
||||
|
||||
<StyledLinkBase
|
||||
onClick={[Function]}
|
||||
>
|
||||
more feedback?
|
||||
</StyledLinkBase>
|
||||
</Text>
|
||||
</Text>
|
||||
</Callout>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikePressed",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="likeBtn0"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikeRest",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
"transform": "rotate(180deg)",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="dislikeBtn1"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Feedback buttons snapshot tests should click like and submit feedback 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikePressed",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="likeBtn4"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikeRest",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
"transform": "rotate(180deg)",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="dislikeBtn5"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Feedback buttons snapshot tests should dobule click on dislike 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikeRest",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="likeBtn26"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikeRest",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
"transform": "rotate(180deg)",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="dislikeBtn27"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Feedback buttons snapshot tests should dobule click on like 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikeRest",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="likeBtn14"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikeRest",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
"transform": "rotate(180deg)",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="dislikeBtn15"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Feedback buttons snapshot tests should hover over dislike 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikeRest",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="likeBtn18"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikeHover",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
"transform": "rotate(180deg)",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="dislikeBtn19"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Feedback buttons snapshot tests should hover over dislike and click 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikeRest",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="likeBtn24"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikePressed",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
"transform": "rotate(180deg)",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="dislikeBtn25"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Feedback buttons snapshot tests should hover over like 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikeHover",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="likeBtn6"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikeRest",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
"transform": "rotate(180deg)",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="dislikeBtn7"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Feedback buttons snapshot tests should hover over like and click 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
>
|
||||
<Callout
|
||||
directionalHint={1}
|
||||
onDismiss={[Function]}
|
||||
role="dialog"
|
||||
style={
|
||||
{
|
||||
"borderRadius": "4px",
|
||||
"padding": "5px 12px 5px 12px",
|
||||
}
|
||||
}
|
||||
styles={
|
||||
{
|
||||
"beakCurtain": {
|
||||
"borderRadius": "4px",
|
||||
},
|
||||
"root": {
|
||||
"borderRadius": "4px",
|
||||
},
|
||||
}
|
||||
}
|
||||
target="#likeBtn12"
|
||||
>
|
||||
<Text>
|
||||
|
||||
<Text>
|
||||
Thank you. Need to give
|
||||
|
||||
<StyledLinkBase
|
||||
onClick={[Function]}
|
||||
>
|
||||
more feedback?
|
||||
</StyledLinkBase>
|
||||
</Text>
|
||||
</Text>
|
||||
</Callout>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikePressed",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="likeBtn12"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikeRest",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
"transform": "rotate(180deg)",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="dislikeBtn13"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Feedback buttons snapshot tests should hover over pressed dislike and leave 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikeRest",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="likeBtn22"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikePressed",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
"transform": "rotate(180deg)",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="dislikeBtn23"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Feedback buttons snapshot tests should hover over pressed like and leave 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
>
|
||||
<Callout
|
||||
directionalHint={1}
|
||||
onDismiss={[Function]}
|
||||
role="dialog"
|
||||
style={
|
||||
{
|
||||
"borderRadius": "4px",
|
||||
"padding": "5px 12px 5px 12px",
|
||||
}
|
||||
}
|
||||
styles={
|
||||
{
|
||||
"beakCurtain": {
|
||||
"borderRadius": "4px",
|
||||
},
|
||||
"root": {
|
||||
"borderRadius": "4px",
|
||||
},
|
||||
}
|
||||
}
|
||||
target="#likeBtn10"
|
||||
>
|
||||
<Text>
|
||||
|
||||
<Text>
|
||||
Thank you. Need to give
|
||||
|
||||
<StyledLinkBase
|
||||
onClick={[Function]}
|
||||
>
|
||||
more feedback?
|
||||
</StyledLinkBase>
|
||||
</Text>
|
||||
</Text>
|
||||
</Callout>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikePressed",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="likeBtn10"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikeRest",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
"transform": "rotate(180deg)",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="dislikeBtn11"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Feedback buttons snapshot tests should hover over rest dislike and leave 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikeRest",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="likeBtn20"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikeRest",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
"transform": "rotate(180deg)",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="dislikeBtn21"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Feedback buttons snapshot tests should hover over rest like and leave 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikeRest",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="likeBtn8"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": "LikeRest",
|
||||
},
|
||||
"style": {
|
||||
"minHeight": "18px",
|
||||
"transform": "rotate(180deg)",
|
||||
},
|
||||
}
|
||||
}
|
||||
id="dislikeBtn9"
|
||||
onClick={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
@@ -1,10 +0,0 @@
|
||||
import { InsertButton } from "Explorer/QueryCopilot/V2/Bubbles/Output/Buttons/Insert/InsertButton";
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
describe("Insert button snapshot tests", () => {
|
||||
it("should click and update state", () => {
|
||||
const wrapper = shallow(<InsertButton sqlQuery={""} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
import { ActionButton } from "@fluentui/react";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React from "react";
|
||||
import CopilotInsert from "../../../../../../../../images/CopilotInsert.svg";
|
||||
|
||||
export const InsertButton = ({ sqlQuery }: { sqlQuery: string }): JSX.Element => {
|
||||
return (
|
||||
<ActionButton
|
||||
iconProps={{ imageProps: { src: CopilotInsert } }}
|
||||
style={{ borderRadius: "4px", borderWidth: "1px", borderColor: "#D1D1D1", height: "24px", paddingBottom: "2px" }}
|
||||
onClick={() => useQueryCopilot.getState().setQuery(sqlQuery)}
|
||||
>
|
||||
Insert
|
||||
</ActionButton>
|
||||
);
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Insert button snapshot tests should click and update state 1`] = `
|
||||
<CustomizedActionButton
|
||||
iconProps={
|
||||
{
|
||||
"imageProps": {
|
||||
"src": {},
|
||||
},
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
style={
|
||||
{
|
||||
"borderColor": "#D1D1D1",
|
||||
"borderRadius": "4px",
|
||||
"borderWidth": "1px",
|
||||
"height": "24px",
|
||||
"paddingBottom": "2px",
|
||||
}
|
||||
}
|
||||
>
|
||||
Insert
|
||||
</CustomizedActionButton>
|
||||
`;
|
||||
@@ -1,11 +0,0 @@
|
||||
import { MoreButton } from "Explorer/QueryCopilot/V2/Bubbles/Output/Buttons/More/MoreButton";
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
describe("More button snapshot tests", () => {
|
||||
it("should render", () => {
|
||||
const wrapper = shallow(<MoreButton />);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,41 +0,0 @@
|
||||
import { DirectionalHint, IContextualMenuProps, IconButton } from "@fluentui/react";
|
||||
import React from "react";
|
||||
import ExplainIcon from "../../../../../../../../images/CopilotExplain.svg";
|
||||
import OptimizeIcon from "../../../../../../../../images/CopilotOptimize.svg";
|
||||
import RegenerateIcon from "../../../../../../../../images/CopilotRegenerate.svg";
|
||||
import SimplifyIcon from "../../../../../../../../images/CopilotSimplify.svg";
|
||||
|
||||
export const MoreButton: React.FC = (): JSX.Element => {
|
||||
const menuProps: IContextualMenuProps = {
|
||||
items: [
|
||||
{
|
||||
key: "regenerate",
|
||||
text: "Regenerate code",
|
||||
iconProps: { imageProps: { src: RegenerateIcon } },
|
||||
},
|
||||
{
|
||||
key: "explain",
|
||||
text: "Explain code",
|
||||
iconProps: { imageProps: { src: ExplainIcon } },
|
||||
},
|
||||
{
|
||||
key: "optimize",
|
||||
text: "Optimize",
|
||||
iconProps: { imageProps: { src: OptimizeIcon } },
|
||||
},
|
||||
{
|
||||
key: "simplify",
|
||||
text: "Simplify",
|
||||
iconProps: { imageProps: { src: SimplifyIcon } },
|
||||
},
|
||||
],
|
||||
directionalHint: DirectionalHint.topRightEdge,
|
||||
calloutProps: {
|
||||
styles: { calloutMain: { borderRadius: "4px" }, root: { borderRadius: "4px" } },
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<IconButton iconProps={{ iconName: "More" }} menuProps={menuProps} menuIconProps={{ hidden: true }}></IconButton>
|
||||
);
|
||||
};
|
||||
@@ -1,69 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`More button snapshot tests should render 1`] = `
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"iconName": "More",
|
||||
}
|
||||
}
|
||||
menuIconProps={
|
||||
{
|
||||
"hidden": true,
|
||||
}
|
||||
}
|
||||
menuProps={
|
||||
{
|
||||
"calloutProps": {
|
||||
"styles": {
|
||||
"calloutMain": {
|
||||
"borderRadius": "4px",
|
||||
},
|
||||
"root": {
|
||||
"borderRadius": "4px",
|
||||
},
|
||||
},
|
||||
},
|
||||
"directionalHint": 2,
|
||||
"items": [
|
||||
{
|
||||
"iconProps": {
|
||||
"imageProps": {
|
||||
"src": {},
|
||||
},
|
||||
},
|
||||
"key": "regenerate",
|
||||
"text": "Regenerate code",
|
||||
},
|
||||
{
|
||||
"iconProps": {
|
||||
"imageProps": {
|
||||
"src": {},
|
||||
},
|
||||
},
|
||||
"key": "explain",
|
||||
"text": "Explain code",
|
||||
},
|
||||
{
|
||||
"iconProps": {
|
||||
"imageProps": {
|
||||
"src": {},
|
||||
},
|
||||
},
|
||||
"key": "optimize",
|
||||
"text": "Optimize",
|
||||
},
|
||||
{
|
||||
"iconProps": {
|
||||
"imageProps": {
|
||||
"src": {},
|
||||
},
|
||||
},
|
||||
"key": "simplify",
|
||||
"text": "Simplify",
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
@@ -1,11 +0,0 @@
|
||||
import { OutputBubbleButtons } from "Explorer/QueryCopilot/V2/Bubbles/Output/Buttons/OutputBubbleButtons";
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
describe("Output Bubble Buttons snapshot tests", () => {
|
||||
it("should render", () => {
|
||||
const wrapper = shallow(<OutputBubbleButtons sqlQuery={""} />);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
import { Stack } from "@fluentui/react";
|
||||
import { CopyButton } from "Explorer/QueryCopilot/V2/Bubbles/Output/Buttons/Copy/CopyButton";
|
||||
import { FeedbackButtons } from "Explorer/QueryCopilot/V2/Bubbles/Output/Buttons/Feedback/FeedbackButtons";
|
||||
import { InsertButton } from "Explorer/QueryCopilot/V2/Bubbles/Output/Buttons/Insert/InsertButton";
|
||||
import { MoreButton } from "Explorer/QueryCopilot/V2/Bubbles/Output/Buttons/More/MoreButton";
|
||||
import React from "react";
|
||||
|
||||
export const OutputBubbleButtons = ({ sqlQuery }: { sqlQuery: string }): JSX.Element => {
|
||||
return (
|
||||
<Stack horizontal>
|
||||
<Stack.Item style={{ paddingTop: "5px" }}>
|
||||
<InsertButton sqlQuery={sqlQuery} />
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<CopyButton sqlQuery={sqlQuery} />
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<FeedbackButtons sqlQuery={sqlQuery} />
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<MoreButton />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Output Bubble Buttons snapshot tests should render 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
>
|
||||
<StackItem
|
||||
style={
|
||||
{
|
||||
"paddingTop": "5px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<InsertButton
|
||||
sqlQuery=""
|
||||
/>
|
||||
</StackItem>
|
||||
<StackItem>
|
||||
<CopyButton
|
||||
sqlQuery=""
|
||||
/>
|
||||
</StackItem>
|
||||
<StackItem>
|
||||
<FeedbackButtons
|
||||
sqlQuery=""
|
||||
/>
|
||||
</StackItem>
|
||||
<StackItem>
|
||||
<MoreButton />
|
||||
</StackItem>
|
||||
</Stack>
|
||||
`;
|
||||
@@ -1,22 +0,0 @@
|
||||
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
|
||||
import { OutputBubble } from "Explorer/QueryCopilot/V2/Bubbles/Output/OutputBubble";
|
||||
import { shallow } from "enzyme";
|
||||
import { withHooks } from "jest-react-hooks-shallow";
|
||||
import React from "react";
|
||||
|
||||
describe("Output Bubble snapshot tests", () => {
|
||||
it("should render and update height", () => {
|
||||
withHooks(() => {
|
||||
const wrapper = shallow(
|
||||
<OutputBubble
|
||||
copilotMessage={{ message: "testMessage", source: 1, explanation: "testExplanation", sqlQuery: "testSQL" }}
|
||||
/>,
|
||||
);
|
||||
|
||||
const editor = wrapper.find(EditorReact).first();
|
||||
|
||||
expect(editor.props().monacoContainerStyles).not.toHaveProperty("height", undefined);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,71 +0,0 @@
|
||||
import { Stack, Text } from "@fluentui/react";
|
||||
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
|
||||
import { CopilotMessage } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
||||
import { OutputBubbleButtons } from "Explorer/QueryCopilot/V2/Bubbles/Output/Buttons/OutputBubbleButtons";
|
||||
import { userContext } from "UserContext";
|
||||
import React, { useState } from "react";
|
||||
|
||||
export const OutputBubble = ({ copilotMessage }: { copilotMessage: CopilotMessage }): JSX.Element => {
|
||||
const [windowHeight, setWindowHeight] = useState<string>();
|
||||
const textHeightWithPadding = 16;
|
||||
|
||||
const calculateQueryWindowHeight = (): string => {
|
||||
const outputWidth = document.getElementById("outputBubble")?.clientWidth;
|
||||
const responseLength = copilotMessage.sqlQuery.length;
|
||||
|
||||
if (outputWidth > responseLength) {
|
||||
return `${textHeightWithPadding * 3}px`;
|
||||
} else {
|
||||
const neededLines = Math.ceil(responseLength / outputWidth);
|
||||
return `${neededLines * textHeightWithPadding}px`;
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (userContext.features.copilotChatFixedMonacoEditorHeight) {
|
||||
setWindowHeight(`${textHeightWithPadding * 5}px`);
|
||||
} else {
|
||||
setWindowHeight(calculateQueryWindowHeight());
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
id="outputBubble"
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: "10px",
|
||||
margin: "10px",
|
||||
backgroundColor: "white",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
tokens={{ padding: 8, childrenGap: 8 }}
|
||||
>
|
||||
<Stack.Item style={{ alignSelf: "flex-start", paddingLeft: "2px" }}>{copilotMessage.message}</Stack.Item>
|
||||
<Stack.Item style={{ alignSelf: "stretch", flexGrow: 4 }}>
|
||||
<EditorReact
|
||||
language={"sql"}
|
||||
content={copilotMessage.sqlQuery}
|
||||
isReadOnly={true}
|
||||
ariaLabel={"AI Response"}
|
||||
wordWrap="on"
|
||||
lineNumbers="on"
|
||||
lineNumbersMinChars={2}
|
||||
lineDecorationsWidth={0}
|
||||
minimap={{ enabled: false }}
|
||||
scrollBeyondLastLine={false}
|
||||
monacoContainerStyles={{ height: windowHeight, borderRadius: "4px" }}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item style={{ alignSelf: "flex-start" }}>
|
||||
<OutputBubbleButtons sqlQuery={copilotMessage.sqlQuery} />
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Text style={{ fontWeight: 400, fontSize: "10px", lineHeight: "14px" }}>
|
||||
AI-generated content may be incorrect
|
||||
</Text>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -1,89 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Output Bubble snapshot tests should render and update height 1`] = `
|
||||
<Stack
|
||||
id="outputBubble"
|
||||
style={
|
||||
{
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "white",
|
||||
"borderRadius": "8px",
|
||||
"display": "flex",
|
||||
"margin": "10px",
|
||||
"padding": "10px",
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
{
|
||||
"childrenGap": 8,
|
||||
"padding": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
<StackItem
|
||||
style={
|
||||
{
|
||||
"alignSelf": "flex-start",
|
||||
"paddingLeft": "2px",
|
||||
}
|
||||
}
|
||||
>
|
||||
testMessage
|
||||
</StackItem>
|
||||
<StackItem
|
||||
style={
|
||||
{
|
||||
"alignSelf": "stretch",
|
||||
"flexGrow": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<EditorReact
|
||||
ariaLabel="AI Response"
|
||||
content="testSQL"
|
||||
isReadOnly={true}
|
||||
language="sql"
|
||||
lineDecorationsWidth={0}
|
||||
lineNumbers="on"
|
||||
lineNumbersMinChars={2}
|
||||
minimap={
|
||||
{
|
||||
"enabled": false,
|
||||
}
|
||||
}
|
||||
monacoContainerStyles={
|
||||
{
|
||||
"borderRadius": "4px",
|
||||
"height": "NaNpx",
|
||||
}
|
||||
}
|
||||
scrollBeyondLastLine={false}
|
||||
wordWrap="on"
|
||||
/>
|
||||
</StackItem>
|
||||
<StackItem
|
||||
style={
|
||||
{
|
||||
"alignSelf": "flex-start",
|
||||
}
|
||||
}
|
||||
>
|
||||
<OutputBubbleButtons
|
||||
sqlQuery="testSQL"
|
||||
/>
|
||||
</StackItem>
|
||||
<StackItem>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": "10px",
|
||||
"fontWeight": 400,
|
||||
"lineHeight": "14px",
|
||||
}
|
||||
}
|
||||
>
|
||||
AI-generated content may be incorrect
|
||||
</Text>
|
||||
</StackItem>
|
||||
</Stack>
|
||||
`;
|
||||
@@ -1,8 +0,0 @@
|
||||
@keyframes loadingAnimation {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import { DefaultButton } from "@fluentui/react";
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { RetrievingBubble } from "./RetrievingBubble";
|
||||
|
||||
const mockUseQueryCopilot = {
|
||||
isGeneratingQuery: false,
|
||||
setIsGeneratingQuery: jest.fn(),
|
||||
isGeneratingExplanation: false,
|
||||
setIsGeneratingExplanation: jest.fn(),
|
||||
shouldIncludeInMessages: true,
|
||||
setShouldIncludeInMessages: jest.fn(),
|
||||
};
|
||||
|
||||
jest.mock("hooks/useQueryCopilot", () => ({
|
||||
useQueryCopilot: jest.fn(() => mockUseQueryCopilot),
|
||||
}));
|
||||
|
||||
describe("RetrievingBubble", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockUseQueryCopilot.isGeneratingQuery = false;
|
||||
mockUseQueryCopilot.isGeneratingExplanation = false;
|
||||
mockUseQueryCopilot.setIsGeneratingQuery.mockClear();
|
||||
mockUseQueryCopilot.setIsGeneratingExplanation.mockClear();
|
||||
mockUseQueryCopilot.setShouldIncludeInMessages.mockClear();
|
||||
});
|
||||
|
||||
it("should render properly when isGeneratingQuery is true", () => {
|
||||
mockUseQueryCopilot.isGeneratingQuery = true;
|
||||
const wrapper = shallow(<RetrievingBubble />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render properly when isGeneratingExplanation is true", () => {
|
||||
mockUseQueryCopilot.isGeneratingExplanation = true;
|
||||
const wrapper = shallow(<RetrievingBubble />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("when isGeneratingQuery is true clicking stop generating button invokes the correct callbacks", () => {
|
||||
mockUseQueryCopilot.isGeneratingQuery = true;
|
||||
|
||||
const wrapper = shallow(<RetrievingBubble />);
|
||||
wrapper.find(DefaultButton).at(0).simulate("click");
|
||||
|
||||
expect(mockUseQueryCopilot.setIsGeneratingQuery).toHaveBeenCalledWith(false);
|
||||
expect(mockUseQueryCopilot.setIsGeneratingExplanation).toHaveBeenCalledTimes(0);
|
||||
expect(mockUseQueryCopilot.setShouldIncludeInMessages).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it("when isGeneratingExplanation is true clicking stop generating button invokes the correct callbacks", () => {
|
||||
mockUseQueryCopilot.isGeneratingExplanation = true;
|
||||
|
||||
const wrapper = shallow(<RetrievingBubble />);
|
||||
wrapper.find(DefaultButton).at(0).simulate("click");
|
||||
|
||||
expect(mockUseQueryCopilot.setIsGeneratingQuery).toHaveBeenCalledTimes(0);
|
||||
expect(mockUseQueryCopilot.setIsGeneratingExplanation).toHaveBeenCalledWith(false);
|
||||
expect(mockUseQueryCopilot.setShouldIncludeInMessages).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
@@ -1,98 +0,0 @@
|
||||
import { DefaultButton, Image, Stack, Text } from "@fluentui/react";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React from "react";
|
||||
import StopGeneratingIcon from "../../../../../../images/StopGenerating.svg";
|
||||
import "./RetrievingBubble.css";
|
||||
|
||||
export const RetrievingBubble = (): JSX.Element => {
|
||||
const {
|
||||
isGeneratingQuery,
|
||||
setIsGeneratingQuery,
|
||||
isGeneratingExplanation,
|
||||
setIsGeneratingExplanation,
|
||||
shouldIncludeInMessages,
|
||||
setShouldIncludeInMessages,
|
||||
} = useQueryCopilot();
|
||||
|
||||
const stopGenerating = () => {
|
||||
if (isGeneratingQuery) {
|
||||
setIsGeneratingQuery(false);
|
||||
}
|
||||
if (isGeneratingExplanation) {
|
||||
setIsGeneratingExplanation(false);
|
||||
}
|
||||
if (shouldIncludeInMessages) {
|
||||
setShouldIncludeInMessages(false);
|
||||
}
|
||||
};
|
||||
|
||||
const bubbleContent = (bubbleType: string) => {
|
||||
return (
|
||||
<Stack
|
||||
horizontalAlign="end"
|
||||
verticalAlign="end"
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: "10px",
|
||||
margin: "10px",
|
||||
backgroundColor: "#FAFAFA",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "46px",
|
||||
backgroundColor: "white",
|
||||
padding: "12px 16px 16px 16px",
|
||||
gap: "12px",
|
||||
borderRadius: "8px",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
Retriveing {bubbleType}
|
||||
</Text>
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "4px",
|
||||
backgroundColor: "#E6E6E6",
|
||||
borderRadius: "4px",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: "50%",
|
||||
height: "100%",
|
||||
backgroundColor: "#0078D4",
|
||||
animation: "loadingAnimation 2s linear infinite",
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
<Stack
|
||||
horizontalAlign="center"
|
||||
verticalAlign="center"
|
||||
style={{ marginTop: "8px", gap: "8px", alignItems: "center" }}
|
||||
>
|
||||
<DefaultButton
|
||||
onClick={stopGenerating}
|
||||
styles={{ root: { border: "none", background: "none", padding: 0, color: "#424242" } }}
|
||||
style={{ color: "#424242" }}
|
||||
onRenderIcon={() => <Image src={StopGeneratingIcon} />}
|
||||
>
|
||||
Stop generating
|
||||
</DefaultButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isGeneratingQuery && bubbleContent("queries")}
|
||||
{isGeneratingExplanation && bubbleContent("explanation")}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,183 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`RetrievingBubble should render properly when isGeneratingExplanation is true 1`] = `
|
||||
<Fragment>
|
||||
<Stack
|
||||
horizontalAlign="end"
|
||||
style={
|
||||
{
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#FAFAFA",
|
||||
"borderRadius": "8px",
|
||||
"display": "flex",
|
||||
"margin": "10px",
|
||||
"padding": "10px",
|
||||
}
|
||||
}
|
||||
verticalAlign="end"
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"backgroundColor": "white",
|
||||
"borderRadius": "8px",
|
||||
"fontWeight": "bold",
|
||||
"gap": "12px",
|
||||
"height": "46px",
|
||||
"padding": "12px 16px 16px 16px",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
Retriveing
|
||||
explanation
|
||||
</Text>
|
||||
<div
|
||||
style={
|
||||
{
|
||||
"backgroundColor": "#E6E6E6",
|
||||
"borderRadius": "4px",
|
||||
"height": "4px",
|
||||
"overflow": "hidden",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
{
|
||||
"animation": "loadingAnimation 2s linear infinite",
|
||||
"backgroundColor": "#0078D4",
|
||||
"height": "100%",
|
||||
"width": "50%",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Stack
|
||||
horizontalAlign="center"
|
||||
style={
|
||||
{
|
||||
"alignItems": "center",
|
||||
"gap": "8px",
|
||||
"marginTop": "8px",
|
||||
}
|
||||
}
|
||||
verticalAlign="center"
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
onRenderIcon={[Function]}
|
||||
style={
|
||||
{
|
||||
"color": "#424242",
|
||||
}
|
||||
}
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"background": "none",
|
||||
"border": "none",
|
||||
"color": "#424242",
|
||||
"padding": 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
Stop generating
|
||||
</CustomizedDefaultButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`RetrievingBubble should render properly when isGeneratingQuery is true 1`] = `
|
||||
<Fragment>
|
||||
<Stack
|
||||
horizontalAlign="end"
|
||||
style={
|
||||
{
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#FAFAFA",
|
||||
"borderRadius": "8px",
|
||||
"display": "flex",
|
||||
"margin": "10px",
|
||||
"padding": "10px",
|
||||
}
|
||||
}
|
||||
verticalAlign="end"
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"backgroundColor": "white",
|
||||
"borderRadius": "8px",
|
||||
"fontWeight": "bold",
|
||||
"gap": "12px",
|
||||
"height": "46px",
|
||||
"padding": "12px 16px 16px 16px",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
Retriveing
|
||||
queries
|
||||
</Text>
|
||||
<div
|
||||
style={
|
||||
{
|
||||
"backgroundColor": "#E6E6E6",
|
||||
"borderRadius": "4px",
|
||||
"height": "4px",
|
||||
"overflow": "hidden",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
{
|
||||
"animation": "loadingAnimation 2s linear infinite",
|
||||
"backgroundColor": "#0078D4",
|
||||
"height": "100%",
|
||||
"width": "50%",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Stack
|
||||
horizontalAlign="center"
|
||||
style={
|
||||
{
|
||||
"alignItems": "center",
|
||||
"gap": "8px",
|
||||
"marginTop": "8px",
|
||||
}
|
||||
}
|
||||
verticalAlign="center"
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
onRenderIcon={[Function]}
|
||||
style={
|
||||
{
|
||||
"color": "#424242",
|
||||
}
|
||||
}
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"background": "none",
|
||||
"border": "none",
|
||||
"color": "#424242",
|
||||
"padding": 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
Stop generating
|
||||
</CustomizedDefaultButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Fragment>
|
||||
`;
|
||||
@@ -1,26 +0,0 @@
|
||||
import { Text } from "@fluentui/react";
|
||||
import { shallow } from "enzyme";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React from "react";
|
||||
import { SampleBubble } from "./SampleBubble";
|
||||
|
||||
describe("Sample Bubble snapshot test", () => {
|
||||
it("should render", () => {
|
||||
const wrapper = shallow(<SampleBubble />);
|
||||
|
||||
const sampleInputs = wrapper.find(Text);
|
||||
|
||||
expect(sampleInputs.length).toEqual(2);
|
||||
expect(useQueryCopilot.getState().userPrompt).toEqual("");
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
it("should render and be clicked", () => {
|
||||
const wrapper = shallow(<SampleBubble />);
|
||||
|
||||
const firstSampleInput = wrapper.find(Text).first();
|
||||
firstSampleInput.simulate("click", {}, "");
|
||||
|
||||
expect(useQueryCopilot.getState().userPrompt).toEqual(expect.any(String));
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,42 +0,0 @@
|
||||
import { Stack, Text } from "@fluentui/react";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React from "react";
|
||||
|
||||
export const SampleBubble: React.FC = (): JSX.Element => {
|
||||
const { setUserPrompt } = useQueryCopilot();
|
||||
|
||||
const sampleChatMessages: string[] = [
|
||||
"Write a query to return last 10 records in the database",
|
||||
'Write a query to return all records in this table created in the last thirty days which also have the record owner as "Contoso"',
|
||||
];
|
||||
|
||||
return (
|
||||
<Stack
|
||||
horizontalAlign="end"
|
||||
verticalAlign="end"
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: "10px",
|
||||
margin: "10px",
|
||||
}}
|
||||
>
|
||||
{sampleChatMessages.map((text, index) => (
|
||||
<Text
|
||||
key={index}
|
||||
onClick={() => setUserPrompt(text)}
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
border: "1.5px solid #B0BEFF",
|
||||
width: "100%",
|
||||
padding: "2px",
|
||||
borderRadius: "4px",
|
||||
marginBottom: "5px",
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</Text>
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -1,97 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Sample Bubble snapshot test should render 1`] = `
|
||||
<Stack
|
||||
horizontalAlign="end"
|
||||
style={
|
||||
{
|
||||
"alignItems": "center",
|
||||
"display": "flex",
|
||||
"margin": "10px",
|
||||
"padding": "10px",
|
||||
}
|
||||
}
|
||||
verticalAlign="end"
|
||||
>
|
||||
<Text
|
||||
key="0"
|
||||
onClick={[Function]}
|
||||
style={
|
||||
{
|
||||
"border": "1.5px solid #B0BEFF",
|
||||
"borderRadius": "4px",
|
||||
"cursor": "pointer",
|
||||
"marginBottom": "5px",
|
||||
"padding": "2px",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
Write a query to return last 10 records in the database
|
||||
</Text>
|
||||
<Text
|
||||
key="1"
|
||||
onClick={[Function]}
|
||||
style={
|
||||
{
|
||||
"border": "1.5px solid #B0BEFF",
|
||||
"borderRadius": "4px",
|
||||
"cursor": "pointer",
|
||||
"marginBottom": "5px",
|
||||
"padding": "2px",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
Write a query to return all records in this table created in the last thirty days which also have the record owner as "Contoso"
|
||||
</Text>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Sample Bubble snapshot test should render and be clicked 1`] = `
|
||||
<Stack
|
||||
horizontalAlign="end"
|
||||
style={
|
||||
{
|
||||
"alignItems": "center",
|
||||
"display": "flex",
|
||||
"margin": "10px",
|
||||
"padding": "10px",
|
||||
}
|
||||
}
|
||||
verticalAlign="end"
|
||||
>
|
||||
<Text
|
||||
key="0"
|
||||
onClick={[Function]}
|
||||
style={
|
||||
{
|
||||
"border": "1.5px solid #B0BEFF",
|
||||
"borderRadius": "4px",
|
||||
"cursor": "pointer",
|
||||
"marginBottom": "5px",
|
||||
"padding": "2px",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
Write a query to return last 10 records in the database
|
||||
</Text>
|
||||
<Text
|
||||
key="1"
|
||||
onClick={[Function]}
|
||||
style={
|
||||
{
|
||||
"border": "1.5px solid #B0BEFF",
|
||||
"borderRadius": "4px",
|
||||
"cursor": "pointer",
|
||||
"marginBottom": "5px",
|
||||
"padding": "2px",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
Write a query to return all records in this table created in the last thirty days which also have the record owner as "Contoso"
|
||||
</Text>
|
||||
</Stack>
|
||||
`;
|
||||
@@ -1,13 +0,0 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { WelcomeBubble } from "./WelcomeBubble";
|
||||
|
||||
const mockedDate = new Date(2023, 7, 15);
|
||||
jest.spyOn(global, "Date").mockImplementation(() => mockedDate);
|
||||
|
||||
describe("Welcome Bubble snapshot test", () => {
|
||||
it("should render", () => {
|
||||
const wrapper = shallow(<WelcomeBubble />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,52 +0,0 @@
|
||||
import { Stack, Text } from "@fluentui/react";
|
||||
import React from "react";
|
||||
|
||||
export const WelcomeBubble: React.FC = (): JSX.Element => {
|
||||
return (
|
||||
<Stack>
|
||||
<Stack horizontalAlign="center" style={{ color: "#707070" }} tokens={{ padding: 8, childrenGap: 8 }}>
|
||||
{new Date().toLocaleDateString("en-US", {
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
})}{" "}
|
||||
{new Date().toLocaleTimeString("en-US", {
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
hour12: true,
|
||||
})}
|
||||
</Stack>
|
||||
<Stack
|
||||
tokens={{ padding: 16, childrenGap: 12 }}
|
||||
style={{
|
||||
backgroundColor: "white",
|
||||
margin: "5px 10px",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
>
|
||||
<Text variant="medium">
|
||||
Hello, I am Cosmos Db's copilot assistant. I can help you do the following things:
|
||||
</Text>
|
||||
<Stack tokens={{ childrenGap: 8 }}>
|
||||
<Stack horizontal style={{ marginLeft: "15px" }} tokens={{ childrenGap: 8 }}>
|
||||
<Text style={{ fontSize: "16px", lineHeight: "16px", verticalAlign: "middle" }}>•</Text>
|
||||
<Text style={{ verticalAlign: "middle" }}>Generate queries based upon prompt you suggest</Text>
|
||||
</Stack>
|
||||
<Stack horizontal style={{ marginLeft: "15px" }} tokens={{ childrenGap: 8 }}>
|
||||
<Text style={{ fontSize: "16px", lineHeight: "16px", verticalAlign: "middle" }}>•</Text>
|
||||
<Text style={{ verticalAlign: "middle" }}>
|
||||
Explain and provide alternate queries for a query suggested by you
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack horizontal style={{ marginLeft: "15px" }} tokens={{ childrenGap: 8 }}>
|
||||
<Text style={{ fontSize: "16px", lineHeight: "16px", verticalAlign: "middle" }}>•</Text>
|
||||
<Text style={{ verticalAlign: "middle" }}>Help answer questions about Cosmos DB</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Text variant="medium">
|
||||
To get started, ask me a question or use one of the sample prompts to generate a query. AI-generated content
|
||||
may be incorrect.
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -1,160 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Welcome Bubble snapshot test should render 1`] = `
|
||||
<Stack>
|
||||
<Stack
|
||||
horizontalAlign="center"
|
||||
style={
|
||||
{
|
||||
"color": "#707070",
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
{
|
||||
"childrenGap": 8,
|
||||
"padding": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
August 15
|
||||
|
||||
12:00 AM
|
||||
</Stack>
|
||||
<Stack
|
||||
style={
|
||||
{
|
||||
"backgroundColor": "white",
|
||||
"borderRadius": "8px",
|
||||
"margin": "5px 10px",
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
{
|
||||
"childrenGap": 12,
|
||||
"padding": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
variant="medium"
|
||||
>
|
||||
Hello, I am Cosmos Db's copilot assistant. I can help you do the following things:
|
||||
</Text>
|
||||
<Stack
|
||||
tokens={
|
||||
{
|
||||
"childrenGap": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
style={
|
||||
{
|
||||
"marginLeft": "15px",
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
{
|
||||
"childrenGap": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": "16px",
|
||||
"lineHeight": "16px",
|
||||
"verticalAlign": "middle",
|
||||
}
|
||||
}
|
||||
>
|
||||
•
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"verticalAlign": "middle",
|
||||
}
|
||||
}
|
||||
>
|
||||
Generate queries based upon prompt you suggest
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
style={
|
||||
{
|
||||
"marginLeft": "15px",
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
{
|
||||
"childrenGap": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": "16px",
|
||||
"lineHeight": "16px",
|
||||
"verticalAlign": "middle",
|
||||
}
|
||||
}
|
||||
>
|
||||
•
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"verticalAlign": "middle",
|
||||
}
|
||||
}
|
||||
>
|
||||
Explain and provide alternate queries for a query suggested by you
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
style={
|
||||
{
|
||||
"marginLeft": "15px",
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
{
|
||||
"childrenGap": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": "16px",
|
||||
"lineHeight": "16px",
|
||||
"verticalAlign": "middle",
|
||||
}
|
||||
}
|
||||
>
|
||||
•
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"verticalAlign": "middle",
|
||||
}
|
||||
}
|
||||
>
|
||||
Help answer questions about Cosmos DB
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Text
|
||||
variant="medium"
|
||||
>
|
||||
To get started, ask me a question or use one of the sample prompts to generate a query. AI-generated content may be incorrect.
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
`;
|
||||
@@ -1,112 +0,0 @@
|
||||
import { IconButton, Image, TextField } from "@fluentui/react";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { SendQueryRequest } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||
import { shallow } from "enzyme";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React from "react";
|
||||
import { act } from "react-dom/test-utils";
|
||||
import { Footer } from "./Footer";
|
||||
|
||||
jest.mock("@azure/cosmos", () => ({
|
||||
Constants: {
|
||||
HttpHeaders: {},
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock("Common/SampleDataClient");
|
||||
|
||||
jest.mock("Explorer/Explorer");
|
||||
|
||||
jest.mock("node-fetch");
|
||||
|
||||
jest.mock("Common/ErrorHandlingUtils", () => ({
|
||||
handleError: jest.fn(),
|
||||
getErrorMessage: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("Explorer/QueryCopilot/Shared/QueryCopilotClient");
|
||||
|
||||
describe("Footer snapshot test", () => {
|
||||
const testMessage = "test message";
|
||||
|
||||
const initialStoreState = useQueryCopilot.getState();
|
||||
beforeEach(() => {
|
||||
useQueryCopilot.setState(initialStoreState, true);
|
||||
});
|
||||
|
||||
it("should open sample prompts on button click", () => {
|
||||
const wrapper = shallow(<Footer explorer={new Explorer()} />);
|
||||
|
||||
const samplePromptsImage = wrapper.find(Image).first();
|
||||
samplePromptsImage.simulate("click", {});
|
||||
|
||||
expect(useQueryCopilot.getState().isSamplePromptsOpen).toBeTruthy();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should update user input", () => {
|
||||
const wrapper = shallow(<Footer explorer={new Explorer()} />);
|
||||
const newInput = "some new input";
|
||||
|
||||
const textInput = wrapper.find(TextField).first();
|
||||
textInput.simulate("change", {}, newInput);
|
||||
|
||||
expect(useQueryCopilot.getState().userPrompt).toEqual(newInput);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should pass text with enter key", async () => {
|
||||
useQueryCopilot.getState().setUserPrompt(testMessage);
|
||||
const wrapper = shallow(<Footer explorer={new Explorer()} />);
|
||||
|
||||
const textInput = wrapper.find(TextField).first();
|
||||
await act(async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
textInput.simulate("keydown", { key: "Enter", shiftKey: false, preventDefault: () => {} });
|
||||
});
|
||||
|
||||
await Promise.resolve();
|
||||
|
||||
expect(SendQueryRequest).toHaveBeenCalledWith({ userPrompt: testMessage, explorer: expect.any(Explorer) });
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should not pass text with non enter key", () => {
|
||||
const wrapper = shallow(<Footer explorer={new Explorer()} />);
|
||||
|
||||
const textInput = wrapper.find(TextField).first();
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
textInput.simulate("keydown", { key: "K", shiftKey: false, preventDefault: () => {} });
|
||||
|
||||
expect(useQueryCopilot.getState().chatMessages).toEqual([]);
|
||||
expect(useQueryCopilot.getState().userPrompt).toEqual("");
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should not pass if no text", () => {
|
||||
const wrapper = shallow(<Footer explorer={new Explorer()} />);
|
||||
|
||||
const textInput = wrapper.find(TextField).first();
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
textInput.simulate("keydown", { key: "Enter", shiftKey: false, preventDefault: () => {} });
|
||||
|
||||
expect(useQueryCopilot.getState().chatMessages).toEqual([]);
|
||||
expect(useQueryCopilot.getState().userPrompt).toEqual("");
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should pass text with icon button", async () => {
|
||||
useQueryCopilot.getState().setUserPrompt(testMessage);
|
||||
const wrapper = shallow(<Footer explorer={new Explorer()} />);
|
||||
|
||||
const iconButton = wrapper.find(IconButton).first();
|
||||
await act(async () => {
|
||||
iconButton.simulate("click", {});
|
||||
});
|
||||
|
||||
await Promise.resolve();
|
||||
|
||||
expect(SendQueryRequest).toHaveBeenCalledWith({ userPrompt: testMessage, explorer: expect.any(Explorer) });
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,77 +0,0 @@
|
||||
import { IButtonStyles, IconButton, Image, Stack, TextField } from "@fluentui/react";
|
||||
import { SendQueryRequest } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||
import { QueryCopilotProps } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React from "react";
|
||||
import HintIcon from "../../../../../images/Hint.svg";
|
||||
import { SamplePrompts, SamplePromptsProps } from "../../Shared/SamplePrompts/SamplePrompts";
|
||||
|
||||
export const Footer: React.FC<QueryCopilotProps> = ({ explorer }: QueryCopilotProps): JSX.Element => {
|
||||
const { userPrompt, setUserPrompt, isSamplePromptsOpen, setIsSamplePromptsOpen, isGeneratingQuery } =
|
||||
useQueryCopilot();
|
||||
|
||||
const promptStyles: IButtonStyles = {
|
||||
root: { border: "5px", selectors: { ":hover": { outline: "1px dashed #605e5c" } } },
|
||||
label: { fontWeight: 400, textAlign: "left", paddingLeft: 8 },
|
||||
};
|
||||
|
||||
const sampleProps: SamplePromptsProps = {
|
||||
isSamplePromptsOpen: isSamplePromptsOpen,
|
||||
setIsSamplePromptsOpen: setIsSamplePromptsOpen,
|
||||
setTextBox: setUserPrompt,
|
||||
};
|
||||
|
||||
const handleEnterKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
startSentMessageProcess();
|
||||
}
|
||||
};
|
||||
|
||||
const startSentMessageProcess = async () => {
|
||||
await SendQueryRequest({ userPrompt, explorer });
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack
|
||||
horizontal
|
||||
horizontalAlign="end"
|
||||
verticalAlign="end"
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
borderRadius: "20px",
|
||||
background: "white",
|
||||
padding: "5px",
|
||||
margin: "5px",
|
||||
}}
|
||||
>
|
||||
<Stack>
|
||||
<Image src={HintIcon} styles={promptStyles} onClick={() => setIsSamplePromptsOpen(true)} />
|
||||
<SamplePrompts sampleProps={sampleProps} />
|
||||
</Stack>
|
||||
<TextField
|
||||
placeholder="Write your own prompt or ask a question"
|
||||
value={userPrompt}
|
||||
onChange={(_, newValue) => setUserPrompt(newValue)}
|
||||
onKeyDown={handleEnterKeyPress}
|
||||
multiline
|
||||
resizable={false}
|
||||
disabled={isGeneratingQuery}
|
||||
styles={{
|
||||
root: {
|
||||
width: "100%",
|
||||
height: "80px",
|
||||
borderRadius: "20px",
|
||||
padding: "8px",
|
||||
border: "none",
|
||||
outline: "none",
|
||||
marginLeft: "10px",
|
||||
},
|
||||
fieldGroup: { border: "none" },
|
||||
}}
|
||||
/>
|
||||
<IconButton disabled={isGeneratingQuery} iconProps={{ iconName: "Send" }} onClick={startSentMessageProcess} />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -1,511 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Footer snapshot test should not pass if no text 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="end"
|
||||
style={
|
||||
{
|
||||
"alignItems": "center",
|
||||
"background": "white",
|
||||
"borderRadius": "20px",
|
||||
"display": "flex",
|
||||
"margin": "5px",
|
||||
"padding": "5px",
|
||||
}
|
||||
}
|
||||
verticalAlign="end"
|
||||
>
|
||||
<Stack>
|
||||
<Image
|
||||
onClick={[Function]}
|
||||
src={{}}
|
||||
styles={
|
||||
{
|
||||
"label": {
|
||||
"fontWeight": 400,
|
||||
"paddingLeft": 8,
|
||||
"textAlign": "left",
|
||||
},
|
||||
"root": {
|
||||
"border": "5px",
|
||||
"selectors": {
|
||||
":hover": {
|
||||
"outline": "1px dashed #605e5c",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<SamplePrompts
|
||||
sampleProps={
|
||||
{
|
||||
"isSamplePromptsOpen": false,
|
||||
"setIsSamplePromptsOpen": [Function],
|
||||
"setTextBox": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<StyledTextFieldBase
|
||||
disabled={null}
|
||||
multiline={true}
|
||||
onChange={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
placeholder="Write your own prompt or ask a question"
|
||||
resizable={false}
|
||||
styles={
|
||||
{
|
||||
"fieldGroup": {
|
||||
"border": "none",
|
||||
},
|
||||
"root": {
|
||||
"border": "none",
|
||||
"borderRadius": "20px",
|
||||
"height": "80px",
|
||||
"marginLeft": "10px",
|
||||
"outline": "none",
|
||||
"padding": "8px",
|
||||
"width": "100%",
|
||||
},
|
||||
}
|
||||
}
|
||||
value=""
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
disabled={null}
|
||||
iconProps={
|
||||
{
|
||||
"iconName": "Send",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Footer snapshot test should not pass text with non enter key 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="end"
|
||||
style={
|
||||
{
|
||||
"alignItems": "center",
|
||||
"background": "white",
|
||||
"borderRadius": "20px",
|
||||
"display": "flex",
|
||||
"margin": "5px",
|
||||
"padding": "5px",
|
||||
}
|
||||
}
|
||||
verticalAlign="end"
|
||||
>
|
||||
<Stack>
|
||||
<Image
|
||||
onClick={[Function]}
|
||||
src={{}}
|
||||
styles={
|
||||
{
|
||||
"label": {
|
||||
"fontWeight": 400,
|
||||
"paddingLeft": 8,
|
||||
"textAlign": "left",
|
||||
},
|
||||
"root": {
|
||||
"border": "5px",
|
||||
"selectors": {
|
||||
":hover": {
|
||||
"outline": "1px dashed #605e5c",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<SamplePrompts
|
||||
sampleProps={
|
||||
{
|
||||
"isSamplePromptsOpen": false,
|
||||
"setIsSamplePromptsOpen": [Function],
|
||||
"setTextBox": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<StyledTextFieldBase
|
||||
disabled={null}
|
||||
multiline={true}
|
||||
onChange={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
placeholder="Write your own prompt or ask a question"
|
||||
resizable={false}
|
||||
styles={
|
||||
{
|
||||
"fieldGroup": {
|
||||
"border": "none",
|
||||
},
|
||||
"root": {
|
||||
"border": "none",
|
||||
"borderRadius": "20px",
|
||||
"height": "80px",
|
||||
"marginLeft": "10px",
|
||||
"outline": "none",
|
||||
"padding": "8px",
|
||||
"width": "100%",
|
||||
},
|
||||
}
|
||||
}
|
||||
value=""
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
disabled={null}
|
||||
iconProps={
|
||||
{
|
||||
"iconName": "Send",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Footer snapshot test should open sample prompts on button click 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="end"
|
||||
style={
|
||||
{
|
||||
"alignItems": "center",
|
||||
"background": "white",
|
||||
"borderRadius": "20px",
|
||||
"display": "flex",
|
||||
"margin": "5px",
|
||||
"padding": "5px",
|
||||
}
|
||||
}
|
||||
verticalAlign="end"
|
||||
>
|
||||
<Stack>
|
||||
<Image
|
||||
onClick={[Function]}
|
||||
src={{}}
|
||||
styles={
|
||||
{
|
||||
"label": {
|
||||
"fontWeight": 400,
|
||||
"paddingLeft": 8,
|
||||
"textAlign": "left",
|
||||
},
|
||||
"root": {
|
||||
"border": "5px",
|
||||
"selectors": {
|
||||
":hover": {
|
||||
"outline": "1px dashed #605e5c",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<SamplePrompts
|
||||
sampleProps={
|
||||
{
|
||||
"isSamplePromptsOpen": false,
|
||||
"setIsSamplePromptsOpen": [Function],
|
||||
"setTextBox": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<StyledTextFieldBase
|
||||
disabled={null}
|
||||
multiline={true}
|
||||
onChange={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
placeholder="Write your own prompt or ask a question"
|
||||
resizable={false}
|
||||
styles={
|
||||
{
|
||||
"fieldGroup": {
|
||||
"border": "none",
|
||||
},
|
||||
"root": {
|
||||
"border": "none",
|
||||
"borderRadius": "20px",
|
||||
"height": "80px",
|
||||
"marginLeft": "10px",
|
||||
"outline": "none",
|
||||
"padding": "8px",
|
||||
"width": "100%",
|
||||
},
|
||||
}
|
||||
}
|
||||
value=""
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
disabled={null}
|
||||
iconProps={
|
||||
{
|
||||
"iconName": "Send",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Footer snapshot test should pass text with enter key 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="end"
|
||||
style={
|
||||
{
|
||||
"alignItems": "center",
|
||||
"background": "white",
|
||||
"borderRadius": "20px",
|
||||
"display": "flex",
|
||||
"margin": "5px",
|
||||
"padding": "5px",
|
||||
}
|
||||
}
|
||||
verticalAlign="end"
|
||||
>
|
||||
<Stack>
|
||||
<Image
|
||||
onClick={[Function]}
|
||||
src={{}}
|
||||
styles={
|
||||
{
|
||||
"label": {
|
||||
"fontWeight": 400,
|
||||
"paddingLeft": 8,
|
||||
"textAlign": "left",
|
||||
},
|
||||
"root": {
|
||||
"border": "5px",
|
||||
"selectors": {
|
||||
":hover": {
|
||||
"outline": "1px dashed #605e5c",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<SamplePrompts
|
||||
sampleProps={
|
||||
{
|
||||
"isSamplePromptsOpen": false,
|
||||
"setIsSamplePromptsOpen": [Function],
|
||||
"setTextBox": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<StyledTextFieldBase
|
||||
disabled={null}
|
||||
multiline={true}
|
||||
onChange={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
placeholder="Write your own prompt or ask a question"
|
||||
resizable={false}
|
||||
styles={
|
||||
{
|
||||
"fieldGroup": {
|
||||
"border": "none",
|
||||
},
|
||||
"root": {
|
||||
"border": "none",
|
||||
"borderRadius": "20px",
|
||||
"height": "80px",
|
||||
"marginLeft": "10px",
|
||||
"outline": "none",
|
||||
"padding": "8px",
|
||||
"width": "100%",
|
||||
},
|
||||
}
|
||||
}
|
||||
value="test message"
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
disabled={null}
|
||||
iconProps={
|
||||
{
|
||||
"iconName": "Send",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Footer snapshot test should pass text with icon button 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="end"
|
||||
style={
|
||||
{
|
||||
"alignItems": "center",
|
||||
"background": "white",
|
||||
"borderRadius": "20px",
|
||||
"display": "flex",
|
||||
"margin": "5px",
|
||||
"padding": "5px",
|
||||
}
|
||||
}
|
||||
verticalAlign="end"
|
||||
>
|
||||
<Stack>
|
||||
<Image
|
||||
onClick={[Function]}
|
||||
src={{}}
|
||||
styles={
|
||||
{
|
||||
"label": {
|
||||
"fontWeight": 400,
|
||||
"paddingLeft": 8,
|
||||
"textAlign": "left",
|
||||
},
|
||||
"root": {
|
||||
"border": "5px",
|
||||
"selectors": {
|
||||
":hover": {
|
||||
"outline": "1px dashed #605e5c",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<SamplePrompts
|
||||
sampleProps={
|
||||
{
|
||||
"isSamplePromptsOpen": false,
|
||||
"setIsSamplePromptsOpen": [Function],
|
||||
"setTextBox": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<StyledTextFieldBase
|
||||
disabled={null}
|
||||
multiline={true}
|
||||
onChange={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
placeholder="Write your own prompt or ask a question"
|
||||
resizable={false}
|
||||
styles={
|
||||
{
|
||||
"fieldGroup": {
|
||||
"border": "none",
|
||||
},
|
||||
"root": {
|
||||
"border": "none",
|
||||
"borderRadius": "20px",
|
||||
"height": "80px",
|
||||
"marginLeft": "10px",
|
||||
"outline": "none",
|
||||
"padding": "8px",
|
||||
"width": "100%",
|
||||
},
|
||||
}
|
||||
}
|
||||
value="test message"
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
disabled={null}
|
||||
iconProps={
|
||||
{
|
||||
"iconName": "Send",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Footer snapshot test should update user input 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="end"
|
||||
style={
|
||||
{
|
||||
"alignItems": "center",
|
||||
"background": "white",
|
||||
"borderRadius": "20px",
|
||||
"display": "flex",
|
||||
"margin": "5px",
|
||||
"padding": "5px",
|
||||
}
|
||||
}
|
||||
verticalAlign="end"
|
||||
>
|
||||
<Stack>
|
||||
<Image
|
||||
onClick={[Function]}
|
||||
src={{}}
|
||||
styles={
|
||||
{
|
||||
"label": {
|
||||
"fontWeight": 400,
|
||||
"paddingLeft": 8,
|
||||
"textAlign": "left",
|
||||
},
|
||||
"root": {
|
||||
"border": "5px",
|
||||
"selectors": {
|
||||
":hover": {
|
||||
"outline": "1px dashed #605e5c",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<SamplePrompts
|
||||
sampleProps={
|
||||
{
|
||||
"isSamplePromptsOpen": false,
|
||||
"setIsSamplePromptsOpen": [Function],
|
||||
"setTextBox": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<StyledTextFieldBase
|
||||
disabled={null}
|
||||
multiline={true}
|
||||
onChange={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
placeholder="Write your own prompt or ask a question"
|
||||
resizable={false}
|
||||
styles={
|
||||
{
|
||||
"fieldGroup": {
|
||||
"border": "none",
|
||||
},
|
||||
"root": {
|
||||
"border": "none",
|
||||
"borderRadius": "20px",
|
||||
"height": "80px",
|
||||
"marginLeft": "10px",
|
||||
"outline": "none",
|
||||
"padding": "8px",
|
||||
"width": "100%",
|
||||
},
|
||||
}
|
||||
}
|
||||
value=""
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
disabled={null}
|
||||
iconProps={
|
||||
{
|
||||
"iconName": "Send",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
@@ -1,17 +0,0 @@
|
||||
import { IconButton } from "@fluentui/react";
|
||||
import { shallow } from "enzyme";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React from "react";
|
||||
import { Header } from "./Header";
|
||||
|
||||
describe("Header snapshot test", () => {
|
||||
it("should close on button click", () => {
|
||||
const wrapper = shallow(<Header />);
|
||||
|
||||
const iconButton = wrapper.find(IconButton).first();
|
||||
iconButton.simulate("click", {});
|
||||
|
||||
expect(useQueryCopilot.getState().showCopilotSidebar).toBeFalsy();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,39 +0,0 @@
|
||||
import { IconButton, Image, Stack, Text } from "@fluentui/react";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React from "react";
|
||||
import CopilotIcon from "../../../../../images/CopilotSidebarLogo.svg";
|
||||
|
||||
export const Header: React.FC = (): JSX.Element => {
|
||||
const { setShowCopilotSidebar } = useQueryCopilot();
|
||||
|
||||
return (
|
||||
<Stack
|
||||
style={{ margin: "15px 0px 0px 0px", padding: "5px", display: "flex", justifyContent: "space-between" }}
|
||||
horizontal
|
||||
verticalAlign="center"
|
||||
>
|
||||
<Stack horizontal verticalAlign="center">
|
||||
<Image src={CopilotIcon} />
|
||||
<Text style={{ marginLeft: "5px", fontWeight: "bold" }}>Copilot</Text>
|
||||
<Text
|
||||
style={{
|
||||
background: "#f0f0f0",
|
||||
fontSize: "10px",
|
||||
padding: "2px 4px",
|
||||
marginLeft: "5px",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
>
|
||||
Preview
|
||||
</Text>
|
||||
</Stack>
|
||||
<IconButton
|
||||
onClick={() => setShowCopilotSidebar(false)}
|
||||
iconProps={{ iconName: "Cancel" }}
|
||||
title="Exit"
|
||||
ariaLabel="Exit"
|
||||
style={{ color: "#424242", verticalAlign: "middle" }}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -1,64 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Header snapshot test should close on button click 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
style={
|
||||
{
|
||||
"display": "flex",
|
||||
"justifyContent": "space-between",
|
||||
"margin": "15px 0px 0px 0px",
|
||||
"padding": "5px",
|
||||
}
|
||||
}
|
||||
verticalAlign="center"
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
verticalAlign="center"
|
||||
>
|
||||
<Image
|
||||
src={{}}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontWeight": "bold",
|
||||
"marginLeft": "5px",
|
||||
}
|
||||
}
|
||||
>
|
||||
Copilot
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"background": "#f0f0f0",
|
||||
"borderRadius": "8px",
|
||||
"fontSize": "10px",
|
||||
"marginLeft": "5px",
|
||||
"padding": "2px 4px",
|
||||
}
|
||||
}
|
||||
>
|
||||
Preview
|
||||
</Text>
|
||||
</Stack>
|
||||
<CustomizedIconButton
|
||||
ariaLabel="Exit"
|
||||
iconProps={
|
||||
{
|
||||
"iconName": "Cancel",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
style={
|
||||
{
|
||||
"color": "#424242",
|
||||
"verticalAlign": "middle",
|
||||
}
|
||||
}
|
||||
title="Exit"
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
@@ -1,32 +0,0 @@
|
||||
import { PrimaryButton } from "@fluentui/react";
|
||||
import { shallow } from "enzyme";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import { withHooks } from "jest-react-hooks-shallow";
|
||||
import React from "react";
|
||||
import { WelcomeSidebarModal } from "./WelcomeSidebarModal";
|
||||
|
||||
describe("Welcome Sidebar Modal snapshot test", () => {
|
||||
it("should close on button click", () => {
|
||||
withHooks(() => {
|
||||
const wrapper = shallow(<WelcomeSidebarModal />);
|
||||
const spy = jest.spyOn(localStorage, "setItem");
|
||||
spy.mockClear();
|
||||
|
||||
const button = wrapper.find(PrimaryButton).first();
|
||||
button.simulate("click", {});
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
expect(spy).toHaveBeenLastCalledWith("showWelcomeSidebar", "false");
|
||||
expect(useQueryCopilot.getState().showWelcomeSidebar).toBeFalsy();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
it("should not reneder with local storage key", () => {
|
||||
withHooks(() => {
|
||||
window.localStorage.setItem("showWelcomeSidebar", "false");
|
||||
const wrapper = shallow(<WelcomeSidebarModal />);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,136 +0,0 @@
|
||||
import { Image, Link, PrimaryButton, Stack, Text } from "@fluentui/react";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React from "react";
|
||||
import Database from "../../../../../images/CopilotDatabase.svg";
|
||||
import Flash from "../../../../../images/CopilotFlash.svg";
|
||||
import CopilotSidebarWelcomeIllustration from "../../../../../images/CopilotSidebarWelcomeIllustration.svg";
|
||||
import Thumb from "../../../../../images/CopilotThumb.svg";
|
||||
|
||||
export const WelcomeSidebarModal: React.FC = (): JSX.Element => {
|
||||
const { showWelcomeSidebar, setShowWelcomeSidebar } = useQueryCopilot();
|
||||
|
||||
const hideModal = (): void => {
|
||||
setShowWelcomeSidebar(false);
|
||||
window.localStorage.setItem("showWelcomeSidebar", "false");
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
const showWelcomeSidebar = window.localStorage.getItem("showWelcomeSidebar");
|
||||
setShowWelcomeSidebar(showWelcomeSidebar && showWelcomeSidebar === "false" ? false : true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
showWelcomeSidebar && (
|
||||
<Stack
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
overflow: "auto",
|
||||
backgroundColor: "#FAFAFA",
|
||||
flex: "1 0 100%",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
margin: "20px 10px",
|
||||
padding: "20px",
|
||||
maxHeight: "100%",
|
||||
boxSizing: "border-box",
|
||||
borderRadius: "20px",
|
||||
backgroundColor: "white",
|
||||
}}
|
||||
>
|
||||
<Stack horizontalAlign="center" verticalAlign="center">
|
||||
<Image src={CopilotSidebarWelcomeIllustration} />
|
||||
</Stack>
|
||||
|
||||
<Stack>
|
||||
<Stack.Item align="center" style={{ marginBottom: "10px" }}>
|
||||
<Text className="title bold">Welcome to Copilot in CosmosDB</Text>
|
||||
</Stack.Item>
|
||||
<Stack.Item style={{ marginBottom: "15px" }}>
|
||||
<Stack>
|
||||
<Stack horizontal>
|
||||
<Stack.Item align="start">
|
||||
<Image src={Flash} />
|
||||
</Stack.Item>
|
||||
<Stack.Item align="center" style={{ marginLeft: "10px" }}>
|
||||
<Text style={{ fontWeight: 600 }}>
|
||||
Let copilot do the work for you
|
||||
<br />
|
||||
</Text>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
<Stack.Item style={{ textAlign: "start", marginLeft: "25px" }}>
|
||||
<Text>
|
||||
Ask Copilot to generate a query by describing the query in your words.
|
||||
<br />
|
||||
<Link target="_blank" href="https://aka.ms/cdb-copilot-learn-more">
|
||||
Learn more
|
||||
</Link>
|
||||
</Text>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item style={{ marginBottom: "15px" }}>
|
||||
<Stack>
|
||||
<Stack horizontal>
|
||||
<Stack.Item align="start">
|
||||
<Image src={Thumb} />
|
||||
</Stack.Item>
|
||||
<Stack.Item align="center" style={{ marginLeft: "10px" }}>
|
||||
<Text style={{ fontWeight: 600 }}>
|
||||
Use your judgement
|
||||
<br />
|
||||
</Text>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
<Stack.Item style={{ textAlign: "start", marginLeft: "25px" }}>
|
||||
<Text>
|
||||
AI-generated content can have mistakes. Make sure it’s accurate and appropriate before using it.
|
||||
<br />
|
||||
<Link target="_blank" href="https://aka.ms/cdb-copilot-preview-terms">
|
||||
Read preview terms
|
||||
</Link>
|
||||
</Text>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item style={{ marginBottom: "15px" }}>
|
||||
<Stack>
|
||||
<Stack horizontal>
|
||||
<Stack.Item align="start">
|
||||
<Image src={Database} />
|
||||
</Stack.Item>
|
||||
<Stack.Item align="center" style={{ marginLeft: "10px" }}>
|
||||
<Text style={{ fontWeight: 600 }}>
|
||||
Copilot currently works only a sample database
|
||||
<br />
|
||||
</Text>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
<Stack.Item style={{ textAlign: "start", marginLeft: "25px" }}>
|
||||
<Text>
|
||||
Copilot is setup on a sample database we have configured for you at no cost
|
||||
<br />
|
||||
<Link target="_blank" href="https://aka.ms/cdb-copilot-learn-more">
|
||||
Learn more
|
||||
</Link>
|
||||
</Text>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
|
||||
<Stack>
|
||||
<Stack.Item align="center">
|
||||
<PrimaryButton style={{ width: "224px", height: "32px" }} onClick={hideModal}>
|
||||
Get Started
|
||||
</PrimaryButton>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</div>
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
};
|
||||
@@ -1,5 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Welcome Sidebar Modal snapshot test should close on button click 1`] = `""`;
|
||||
|
||||
exports[`Welcome Sidebar Modal snapshot test should not reneder with local storage key 1`] = `""`;
|
||||
@@ -1,55 +0,0 @@
|
||||
import { Stack } from "@fluentui/react";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { SampleBubble } from "Explorer/QueryCopilot/V2/Bubbles/Sample/SampleBubble";
|
||||
import { shallow } from "enzyme";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import { withHooks } from "jest-react-hooks-shallow";
|
||||
import React from "react";
|
||||
import { QueryCopilotSidebar } from "./QueryCopilotSidebar";
|
||||
|
||||
describe("Query Copilot Sidebar snapshot test", () => {
|
||||
const initialState = useQueryCopilot.getState();
|
||||
|
||||
beforeEach(() => {
|
||||
useQueryCopilot.setState(initialState, true);
|
||||
});
|
||||
|
||||
it("should render and set copilot used flag", () => {
|
||||
withHooks(() => {
|
||||
useQueryCopilot.getState().setShowCopilotSidebar(true);
|
||||
const wrapper = shallow(<QueryCopilotSidebar explorer={new Explorer()} />);
|
||||
|
||||
expect(useQueryCopilot.getState().wasCopilotUsed).toBeTruthy();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
it("should render and not set copilot used flag", () => {
|
||||
withHooks(() => {
|
||||
const wrapper = shallow(<QueryCopilotSidebar explorer={new Explorer()} />);
|
||||
|
||||
expect(useQueryCopilot.getState().wasCopilotUsed).toBeFalsy();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
it("should render with chat messages", () => {
|
||||
const message = "some test message";
|
||||
useQueryCopilot.getState().setChatMessages([{ source: 0, message: message }]);
|
||||
const wrapper = shallow(<QueryCopilotSidebar explorer={new Explorer()} />);
|
||||
|
||||
const messageContainer = wrapper.find(Stack).findWhere((x) => x.text() === message);
|
||||
|
||||
expect(messageContainer).toBeDefined();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render samples without messages", () => {
|
||||
const wrapper = shallow(<QueryCopilotSidebar explorer={new Explorer()} />);
|
||||
|
||||
const sampleBubble = wrapper.find(SampleBubble);
|
||||
|
||||
expect(sampleBubble).toBeDefined();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,77 +0,0 @@
|
||||
import { Stack } from "@fluentui/react";
|
||||
import { QueryCopilotProps } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
||||
import { ExplanationButton } from "Explorer/QueryCopilot/V2/Bubbles/Explanation/Button/ExplanationButton";
|
||||
import { ExplanationBubble } from "Explorer/QueryCopilot/V2/Bubbles/Explanation/ExplainationBubble";
|
||||
import { OutputBubble } from "Explorer/QueryCopilot/V2/Bubbles/Output/OutputBubble";
|
||||
import { RetrievingBubble } from "Explorer/QueryCopilot/V2/Bubbles/Retriveing/RetrievingBubble";
|
||||
import { SampleBubble } from "Explorer/QueryCopilot/V2/Bubbles/Sample/SampleBubble";
|
||||
import { WelcomeBubble } from "Explorer/QueryCopilot/V2/Bubbles/Welcome/WelcomeBubble";
|
||||
import { Footer } from "Explorer/QueryCopilot/V2/Footer/Footer";
|
||||
import { Header } from "Explorer/QueryCopilot/V2/Header/Header";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React from "react";
|
||||
import { WelcomeSidebarModal } from "../Modal/WelcomeSidebarModal";
|
||||
|
||||
export const QueryCopilotSidebar: React.FC<QueryCopilotProps> = ({ explorer }: QueryCopilotProps): JSX.Element => {
|
||||
const { setWasCopilotUsed, showCopilotSidebar, chatMessages, isGeneratingQuery, showWelcomeSidebar } =
|
||||
useQueryCopilot();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (showCopilotSidebar) {
|
||||
setWasCopilotUsed(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Stack style={{ width: "100%", height: "100%", backgroundColor: "#FAFAFA" }}>
|
||||
<Header />
|
||||
{showWelcomeSidebar ? (
|
||||
<WelcomeSidebarModal />
|
||||
) : (
|
||||
<>
|
||||
<Stack
|
||||
style={{
|
||||
flexGrow: 1,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
overflowY: "auto",
|
||||
}}
|
||||
>
|
||||
<WelcomeBubble />
|
||||
{chatMessages.map((message, index) => {
|
||||
switch (message.source) {
|
||||
case 0:
|
||||
return (
|
||||
<Stack
|
||||
key={index}
|
||||
horizontalAlign="center"
|
||||
tokens={{ padding: 8, childrenGap: 8 }}
|
||||
style={{
|
||||
backgroundColor: "#E0E7FF",
|
||||
borderRadius: "8px",
|
||||
margin: "5px 10px",
|
||||
textAlign: "start",
|
||||
}}
|
||||
>
|
||||
{message.message}
|
||||
</Stack>
|
||||
);
|
||||
case 1:
|
||||
return <OutputBubble key={index} copilotMessage={message} />;
|
||||
case 2:
|
||||
return <ExplanationBubble key={index} copilotMessage={message} />;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
})}
|
||||
<RetrievingBubble />
|
||||
<ExplanationButton />
|
||||
|
||||
{chatMessages.length === 0 && !isGeneratingQuery && <SampleBubble />}
|
||||
</Stack>
|
||||
<Footer explorer={explorer} />
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -1,61 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Query Copilot Sidebar snapshot test should render and not set copilot used flag 1`] = `
|
||||
<Stack
|
||||
style={
|
||||
{
|
||||
"backgroundColor": "#FAFAFA",
|
||||
"height": "100%",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Header />
|
||||
<WelcomeSidebarModal />
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Query Copilot Sidebar snapshot test should render and set copilot used flag 1`] = `
|
||||
<Stack
|
||||
style={
|
||||
{
|
||||
"backgroundColor": "#FAFAFA",
|
||||
"height": "100%",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Header />
|
||||
<WelcomeSidebarModal />
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Query Copilot Sidebar snapshot test should render samples without messages 1`] = `
|
||||
<Stack
|
||||
style={
|
||||
{
|
||||
"backgroundColor": "#FAFAFA",
|
||||
"height": "100%",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Header />
|
||||
<WelcomeSidebarModal />
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Query Copilot Sidebar snapshot test should render with chat messages 1`] = `
|
||||
<Stack
|
||||
style={
|
||||
{
|
||||
"backgroundColor": "#FAFAFA",
|
||||
"height": "100%",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Header />
|
||||
<WelcomeSidebarModal />
|
||||
</Stack>
|
||||
`;
|
||||
@@ -1,198 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Query Copilot Carousel snapshot test should render when isOpen is true 1`] = `
|
||||
<Modal
|
||||
isOpen={true}
|
||||
styles={
|
||||
{
|
||||
"main": {
|
||||
"width": 880,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
style={
|
||||
{
|
||||
"padding": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="space-between"
|
||||
>
|
||||
<Text
|
||||
variant="xLarge"
|
||||
>
|
||||
What exactly is copilot?
|
||||
</Text>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
{
|
||||
"iconName": "Cancel",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack
|
||||
style={
|
||||
{
|
||||
"marginTop": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 13,
|
||||
}
|
||||
}
|
||||
>
|
||||
A couple of lines about copilot and the background about it. The idea is to have some text to give context to the user.
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 14,
|
||||
"fontWeight": 600,
|
||||
"marginTop": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
How do you use copilot
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 13,
|
||||
"marginTop": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
To generate queries , just describe the query you want and copilot will generate the query for you.Watch this video to learn more about how to use copilot.
|
||||
</Text>
|
||||
<Image
|
||||
src={{}}
|
||||
style={
|
||||
{
|
||||
"margin": "16px auto",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 14,
|
||||
"fontWeight": 600,
|
||||
}
|
||||
}
|
||||
>
|
||||
What is copilot good at
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 13,
|
||||
"marginTop": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
A couple of lines about what copilot can do and its capablites with a link to
|
||||
|
||||
<StyledLinkBase
|
||||
href=""
|
||||
target="_blank"
|
||||
>
|
||||
documentation
|
||||
</StyledLinkBase>
|
||||
|
||||
if possible.
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 14,
|
||||
"fontWeight": 600,
|
||||
"marginTop": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
What are its limitations
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 13,
|
||||
"marginTop": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
A couple of lines about what copilot cant do and its limitations.
|
||||
|
||||
<StyledLinkBase
|
||||
href=""
|
||||
target="_blank"
|
||||
>
|
||||
Link to documentation
|
||||
</StyledLinkBase>
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 14,
|
||||
"fontWeight": 600,
|
||||
"marginTop": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
Disclaimer
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 13,
|
||||
"marginTop": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
AI-generated content can have mistakes. Make sure it's accurate and appropriate before using it.
|
||||
|
||||
<StyledLinkBase
|
||||
href=""
|
||||
target="_blank"
|
||||
>
|
||||
Read preview terms
|
||||
</StyledLinkBase>
|
||||
</Text>
|
||||
</Stack>
|
||||
<Separator
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"height": 1,
|
||||
"padding": "16px 0",
|
||||
"selectors": {
|
||||
"::before": {
|
||||
"background": undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="start"
|
||||
verticalAlign="center"
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
text="Next"
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Modal>
|
||||
`;
|
||||
@@ -1,171 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Prompt card snapshot test should render properly if isSelected is false 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
style={
|
||||
{
|
||||
"border": "1px solid #F3F2F1",
|
||||
"boxShadow": "0px 1.6px 3.6px rgba(0, 0, 0, 0.132), 0px 0.3px 0.9px rgba(0, 0, 0, 0.108)",
|
||||
"boxSizing": "border-box",
|
||||
"height": 100,
|
||||
"padding": "16px 0 16px 16px ",
|
||||
"width": 650,
|
||||
}
|
||||
}
|
||||
>
|
||||
<StackItem
|
||||
grow={1}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
>
|
||||
<div>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"background": "#F8FFF0",
|
||||
"color": "#00A2AD",
|
||||
"fontSize": 13,
|
||||
}
|
||||
}
|
||||
>
|
||||
Prompt
|
||||
</Text>
|
||||
</div>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 13,
|
||||
"marginLeft": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
TestHeader
|
||||
</Text>
|
||||
</Stack>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 10,
|
||||
"marginTop": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
TestDescription
|
||||
</Text>
|
||||
</StackItem>
|
||||
<StackItem
|
||||
style={
|
||||
{
|
||||
"marginLeft": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
<StyledChoiceGroup
|
||||
onChange={[Function]}
|
||||
options={
|
||||
[
|
||||
{
|
||||
"key": "selected",
|
||||
"text": "",
|
||||
},
|
||||
]
|
||||
}
|
||||
selectedKey=""
|
||||
styles={
|
||||
{
|
||||
"flexContainer": {
|
||||
"width": 36,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
</StackItem>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Prompt card snapshot test should render properly if isSelected is true 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
style={
|
||||
{
|
||||
"border": "1px solid #F3F2F1",
|
||||
"boxShadow": "0px 1.6px 3.6px rgba(0, 0, 0, 0.132), 0px 0.3px 0.9px rgba(0, 0, 0, 0.108)",
|
||||
"boxSizing": "border-box",
|
||||
"height": 100,
|
||||
"padding": "16px 0 16px 16px ",
|
||||
"width": 650,
|
||||
}
|
||||
}
|
||||
>
|
||||
<StackItem
|
||||
grow={1}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
>
|
||||
<div>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"background": "#F8FFF0",
|
||||
"color": "#00A2AD",
|
||||
"fontSize": 13,
|
||||
}
|
||||
}
|
||||
>
|
||||
Prompt
|
||||
</Text>
|
||||
</div>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 13,
|
||||
"marginLeft": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
TestHeader
|
||||
</Text>
|
||||
</Stack>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"fontSize": 10,
|
||||
"marginTop": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
TestDescription
|
||||
</Text>
|
||||
</StackItem>
|
||||
<StackItem
|
||||
style={
|
||||
{
|
||||
"marginLeft": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
<StyledChoiceGroup
|
||||
onChange={[Function]}
|
||||
options={
|
||||
[
|
||||
{
|
||||
"key": "selected",
|
||||
"text": "",
|
||||
},
|
||||
]
|
||||
}
|
||||
selectedKey="selected"
|
||||
styles={
|
||||
{
|
||||
"flexContainer": {
|
||||
"width": 36,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
</StackItem>
|
||||
</Stack>
|
||||
`;
|
||||
@@ -1,49 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Query copilot tab snapshot test should render with initial input 1`] = `
|
||||
<Stack
|
||||
className="tab-pane"
|
||||
style={
|
||||
{
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
{
|
||||
"height": "100%",
|
||||
"overflowY": "auto",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
className="tabPaneContentContainer"
|
||||
>
|
||||
<t
|
||||
customClassName=""
|
||||
onDragEnd={null}
|
||||
onDragStart={null}
|
||||
onSecondaryPaneSizeChange={null}
|
||||
percentage={true}
|
||||
primaryIndex={0}
|
||||
primaryMinSize={30}
|
||||
secondaryMinSize={70}
|
||||
vertical={true}
|
||||
>
|
||||
<EditorReact
|
||||
ariaLabel="Editing Query"
|
||||
content="SELECT * FROM c"
|
||||
isReadOnly={false}
|
||||
language="sql"
|
||||
lineNumbers="on"
|
||||
onContentChanged={[Function]}
|
||||
onContentSelected={[Function]}
|
||||
wordWrap="on"
|
||||
/>
|
||||
<QueryCopilotResults />
|
||||
</t>
|
||||
</Stack>
|
||||
</div>
|
||||
</Stack>
|
||||
`;
|
||||
@@ -31,7 +31,6 @@ export const checkContainerExists = (databaseName: string, containerName: string
|
||||
hasContainer(databaseName, containerName, useDatabases.getState().databases);
|
||||
|
||||
export enum SampleDataFile {
|
||||
COPILOT = "Copilot",
|
||||
FABRIC_SAMPLE_DATA = "FabricSampleData",
|
||||
FABRIC_SAMPLE_VECTOR_DATA = "FabricSampleVectorData",
|
||||
}
|
||||
@@ -43,9 +42,6 @@ const containerSettings: {
|
||||
indexingPolicy?: DataModels.IndexingPolicy;
|
||||
};
|
||||
} = {
|
||||
[SampleDataFile.COPILOT]: {
|
||||
partitionKeyString: "category",
|
||||
},
|
||||
[SampleDataFile.FABRIC_SAMPLE_DATA]: {
|
||||
partitionKeyString: "categoryName",
|
||||
},
|
||||
@@ -120,11 +116,6 @@ export const createContainer = async (
|
||||
export const importData = async (sampleDataFile: SampleDataFile, collection: ViewModels.Collection): Promise<void> => {
|
||||
let documents: JSONObject[] = undefined;
|
||||
switch (sampleDataFile) {
|
||||
case SampleDataFile.COPILOT:
|
||||
documents = (
|
||||
await import(/* webpackChunkName: "queryCopilotSampleData" */ "../../../sampleData/queryCopilotSampleData.json")
|
||||
).data;
|
||||
break;
|
||||
case SampleDataFile.FABRIC_SAMPLE_DATA:
|
||||
documents = (await import(/* webpackChunkName: "fabricSampleData" */ "../../../sampleData/fabricSampleData.json"))
|
||||
.default;
|
||||
|
||||
@@ -21,7 +21,6 @@ import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
|
||||
import { useCarousel } from "hooks/useCarousel";
|
||||
import { usePostgres } from "hooks/usePostgres";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||
import * as React from "react";
|
||||
import ConnectIcon from "../../../images/Connect_color.svg";
|
||||
@@ -215,12 +214,6 @@ export const SplashScreen: React.FC<SplashScreenProps> = ({ explorer }) => {
|
||||
(state) => state.sampleDataResourceTokenCollection,
|
||||
),
|
||||
},
|
||||
{
|
||||
dispose: useQueryCopilot.subscribe(
|
||||
() => setState({}),
|
||||
(state) => state.copilotEnabled,
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
return () => {
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
makeStyles,
|
||||
shorthands,
|
||||
} from "@fluentui/react-components";
|
||||
import { QueryCopilotSampleContainerId, QueryCopilotSampleDatabaseId } from "Common/Constants";
|
||||
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
|
||||
import MongoUtility from "Common/MongoUtility";
|
||||
import { createDocument } from "Common/dataAccess/createDocument";
|
||||
@@ -27,7 +26,7 @@ import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
|
||||
import { InputDataList, InputDatalistDropdownOptionSection } from "Explorer/Controls/InputDataList/InputDataList";
|
||||
import { ProgressModalDialog } from "Explorer/Controls/ProgressModalDialog";
|
||||
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
||||
import { querySampleDocuments, readSampleDocument } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
||||
|
||||
import {
|
||||
ColumnsSelection,
|
||||
FilterHistory,
|
||||
@@ -360,10 +359,7 @@ export const getTabsButtons = ({
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: false,
|
||||
disabled:
|
||||
!getNewDocumentButtonState(editorState).enabled ||
|
||||
!clientWriteEnabled ||
|
||||
useSelectedNode.getState().isQueryCopilotCollectionSelected(),
|
||||
disabled: !getNewDocumentButtonState(editorState).enabled || !clientWriteEnabled,
|
||||
id: NEW_DOCUMENT_BUTTON_ID,
|
||||
});
|
||||
}
|
||||
@@ -378,10 +374,7 @@ export const getTabsButtons = ({
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: false,
|
||||
disabled:
|
||||
!getSaveNewDocumentButtonState(editorState).enabled ||
|
||||
!clientWriteEnabled ||
|
||||
useSelectedNode.getState().isQueryCopilotCollectionSelected(),
|
||||
disabled: !getSaveNewDocumentButtonState(editorState).enabled || !clientWriteEnabled,
|
||||
id: SAVE_BUTTON_ID,
|
||||
});
|
||||
}
|
||||
@@ -396,9 +389,7 @@ export const getTabsButtons = ({
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: false,
|
||||
disabled:
|
||||
!getDiscardNewDocumentChangesButtonState(editorState).enabled ||
|
||||
useSelectedNode.getState().isQueryCopilotCollectionSelected(),
|
||||
disabled: !getDiscardNewDocumentChangesButtonState(editorState).enabled,
|
||||
id: DISCARD_BUTTON_ID,
|
||||
});
|
||||
}
|
||||
@@ -413,10 +404,7 @@ export const getTabsButtons = ({
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: false,
|
||||
disabled:
|
||||
!getSaveExistingDocumentButtonState(editorState).enabled ||
|
||||
!clientWriteEnabled ||
|
||||
useSelectedNode.getState().isQueryCopilotCollectionSelected(),
|
||||
disabled: !getSaveExistingDocumentButtonState(editorState).enabled || !clientWriteEnabled,
|
||||
id: UPDATE_BUTTON_ID,
|
||||
});
|
||||
}
|
||||
@@ -431,9 +419,7 @@ export const getTabsButtons = ({
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: false,
|
||||
disabled:
|
||||
!getDiscardExistingDocumentChangesButtonState(editorState).enabled ||
|
||||
useSelectedNode.getState().isQueryCopilotCollectionSelected(),
|
||||
disabled: !getDiscardExistingDocumentChangesButtonState(editorState).enabled,
|
||||
id: DISCARD_BUTTON_ID,
|
||||
});
|
||||
}
|
||||
@@ -448,7 +434,7 @@ export const getTabsButtons = ({
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: false,
|
||||
disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected() || !clientWriteEnabled,
|
||||
disabled: !clientWriteEnabled,
|
||||
id: DELETE_BUTTON_ID,
|
||||
});
|
||||
}
|
||||
@@ -465,8 +451,7 @@ export const getTabsButtons = ({
|
||||
hasPopup: true,
|
||||
disabled:
|
||||
useSelectedNode.getState().isDatabaseNodeOrNoneSelected() ||
|
||||
!useClientWriteEnabled.getState().clientWriteEnabled ||
|
||||
useSelectedNode.getState().isQueryCopilotCollectionSelected(),
|
||||
!useClientWriteEnabled.getState().clientWriteEnabled,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -642,11 +627,6 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
||||
}),
|
||||
);
|
||||
|
||||
const isQueryCopilotSampleContainer =
|
||||
_collection?.isSampleCollection &&
|
||||
_collection?.databaseId === QueryCopilotSampleDatabaseId &&
|
||||
_collection?.id() === QueryCopilotSampleContainerId;
|
||||
|
||||
// For Mongo only
|
||||
const [continuationToken, setContinuationToken] = useState<string>(undefined);
|
||||
|
||||
@@ -1400,16 +1380,13 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
||||
// Fixes compile error error TS2741: Property 'throwIfAborted' is missing in type 'AbortSignal' but required in type 'import("/home/runner/work/cosmos-explorer/cosmos-explorer/node_modules/node-abort-controller/index").AbortSignal'.
|
||||
options.abortSignal = _queryAbortController.signal;
|
||||
|
||||
return isQueryCopilotSampleContainer
|
||||
? querySampleDocuments(query, options)
|
||||
: queryDocuments(_collection.databaseId, _collection.id(), query, options);
|
||||
return queryDocuments(_collection.databaseId, _collection.id(), query, options);
|
||||
}, [
|
||||
filterContent,
|
||||
isPreferredApiMongoDB,
|
||||
partitionKeyProperties,
|
||||
partitionKey,
|
||||
resourceTokenPartitionKey,
|
||||
isQueryCopilotSampleContainer,
|
||||
_collection,
|
||||
selectedColumnIds,
|
||||
]);
|
||||
@@ -1567,11 +1544,6 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
||||
}
|
||||
};
|
||||
|
||||
const _isQueryCopilotSampleContainer =
|
||||
_collection?.isSampleCollection &&
|
||||
_collection?.databaseId === QueryCopilotSampleDatabaseId &&
|
||||
_collection?.id() === QueryCopilotSampleContainerId;
|
||||
|
||||
// Table config here
|
||||
const tableItems: DocumentsTableComponentItem[] = documentIds.map((documentId) => {
|
||||
const item: DocumentsTableComponentItem = documentId.tableFields || { id: documentId.id() };
|
||||
@@ -1635,14 +1607,12 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
||||
};
|
||||
|
||||
let loadDocument = (documentId: DocumentId) =>
|
||||
(_isQueryCopilotSampleContainer ? readSampleDocument(documentId) : readDocument(_collection, documentId)).then(
|
||||
(content) => {
|
||||
initDocumentEditor(documentId, content);
|
||||
readDocument(_collection, documentId).then((content) => {
|
||||
initDocumentEditor(documentId, content);
|
||||
|
||||
// Update columns
|
||||
setColumnDefinitionsFromDocument(content);
|
||||
},
|
||||
);
|
||||
// Update columns
|
||||
setColumnDefinitionsFromDocument(content);
|
||||
});
|
||||
|
||||
const initDocumentEditor = (documentId: DocumentId, documentContent: unknown): void => {
|
||||
if (documentId) {
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
import { sendMessage } from "Common/MessageHandler";
|
||||
import { ActionType, OpenQueryTab, TabKind } from "Contracts/ActionContracts";
|
||||
import { MessageTypes } from "Contracts/MessageTypes";
|
||||
import { CopilotProvider } from "Explorer/QueryCopilot/QueryCopilotContext";
|
||||
import { userContext } from "UserContext";
|
||||
import React from "react";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import type { QueryTabOptions } from "../../../Contracts/ViewModels";
|
||||
import { useTabs } from "../../../hooks/useTabs";
|
||||
import Explorer from "../../Explorer";
|
||||
import {
|
||||
IQueryTabComponentProps,
|
||||
ITabAccessor,
|
||||
QueryTabComponent,
|
||||
QueryTabCopilotComponent,
|
||||
} from "../../Tabs/QueryTab/QueryTabComponent";
|
||||
import { IQueryTabComponentProps, ITabAccessor, QueryTabComponent } from "../../Tabs/QueryTab/QueryTabComponent";
|
||||
import TabsBase from "../TabsBase";
|
||||
|
||||
export interface IQueryTabProps {
|
||||
@@ -80,13 +73,7 @@ export class NewQueryTab extends TabsBase {
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
return userContext.apiType === "SQL" ? (
|
||||
<CopilotProvider>
|
||||
<QueryTabCopilotComponent {...this.iQueryTabComponentProps} />
|
||||
</CopilotProvider>
|
||||
) : (
|
||||
<QueryTabComponent {...this.iQueryTabComponentProps} />
|
||||
);
|
||||
return <QueryTabComponent {...this.iQueryTabComponentProps} />;
|
||||
}
|
||||
|
||||
public onActivate(): void {
|
||||
|
||||
@@ -1,60 +1,21 @@
|
||||
import { fireEvent, render } from "@testing-library/react";
|
||||
import { CollectionTabKind } from "Contracts/ViewModels";
|
||||
import { CopilotProvider } from "Explorer/QueryCopilot/QueryCopilotContext";
|
||||
import { QueryCopilotPromptbar } from "Explorer/QueryCopilot/QueryCopilotPromptbar";
|
||||
import { CopilotSubComponentNames } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
||||
import {
|
||||
IQueryTabComponentProps,
|
||||
QueryTabComponent,
|
||||
QueryTabCopilotComponent,
|
||||
} from "Explorer/Tabs/QueryTab/QueryTabComponent";
|
||||
import TabsBase from "Explorer/Tabs/TabsBase";
|
||||
import { AppStateComponentNames, StorePath } from "Shared/AppStatePersistenceUtility";
|
||||
import { updateUserContext, userContext } from "UserContext";
|
||||
import { mount } from "enzyme";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import { useTabs } from "hooks/useTabs";
|
||||
import { render } from "@testing-library/react";
|
||||
import { IQueryTabComponentProps, QueryTabComponent } from "Explorer/Tabs/QueryTab/QueryTabComponent";
|
||||
import React from "react";
|
||||
|
||||
jest.mock("Explorer/Controls/Editor/EditorReact");
|
||||
|
||||
const loadState = (path: StorePath) => {
|
||||
if (
|
||||
path.componentName === AppStateComponentNames.QueryCopilot &&
|
||||
path.subComponentName === CopilotSubComponentNames.toggleStatus
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
jest.mock("Shared/AppStatePersistenceUtility", () => ({
|
||||
loadState,
|
||||
AppStateComponentNames: {
|
||||
QueryCopilot: "QueryCopilot",
|
||||
},
|
||||
loadState: jest.fn(),
|
||||
AppStateComponentNames: {},
|
||||
readSubComponentState: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("QueryTabComponent", () => {
|
||||
const mockStore = useQueryCopilot.getState();
|
||||
beforeEach(() => {
|
||||
mockStore.showCopilotSidebar = false;
|
||||
mockStore.setShowCopilotSidebar = jest.fn();
|
||||
});
|
||||
afterEach(() => jest.clearAllMocks());
|
||||
|
||||
it("should launch conversational Copilot when ALT+C is pressed and when copilot version is 3", () => {
|
||||
updateUserContext({
|
||||
features: {
|
||||
...userContext.features,
|
||||
copilotVersion: "v3.0",
|
||||
},
|
||||
});
|
||||
|
||||
it("should render without crashing", () => {
|
||||
const propsMock: Readonly<IQueryTabComponentProps> = {
|
||||
collection: { databaseId: "CopilotSampleDB", id: () => "CopilotContainer" },
|
||||
collection: { databaseId: "testDb", id: () => "testContainer" },
|
||||
onTabAccessor: () => jest.fn(),
|
||||
isExecutionError: false,
|
||||
tabId: "mockTabId",
|
||||
@@ -64,49 +25,6 @@ describe("QueryTabComponent", () => {
|
||||
} as unknown as IQueryTabComponentProps;
|
||||
|
||||
const { container } = render(<QueryTabComponent {...propsMock} />);
|
||||
|
||||
const launchCopilotButton = container.querySelector('[data-test="QueryTab/ResultsPane/ExecuteCTA"]');
|
||||
fireEvent.keyDown(launchCopilotButton, { key: "c", altKey: true });
|
||||
|
||||
expect(mockStore.setShowCopilotSidebar).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it("copilot should be enabled by default when tab is active", () => {
|
||||
updateUserContext({
|
||||
databaseAccount: {
|
||||
name: "name",
|
||||
properties: undefined,
|
||||
id: "",
|
||||
location: "",
|
||||
type: "",
|
||||
kind: "",
|
||||
},
|
||||
});
|
||||
|
||||
useQueryCopilot.getState().setCopilotEnabled(true);
|
||||
useQueryCopilot.getState().setCopilotUserDBEnabled(true);
|
||||
const activeTab = new TabsBase({
|
||||
tabKind: CollectionTabKind.Query,
|
||||
title: "Query",
|
||||
tabPath: "",
|
||||
});
|
||||
activeTab.tabId = "mockTabId";
|
||||
useTabs.getState().activeTab = activeTab;
|
||||
const propsMock: Readonly<IQueryTabComponentProps> = {
|
||||
collection: { databaseId: "CopilotUserDb", id: () => "CopilotUserContainer" },
|
||||
onTabAccessor: () => jest.fn(),
|
||||
isExecutionError: false,
|
||||
tabId: "mockTabId",
|
||||
tabsBaseInstance: {
|
||||
updateNavbarWithTabsButtons: () => jest.fn(),
|
||||
},
|
||||
} as unknown as IQueryTabComponentProps;
|
||||
|
||||
const container = mount(
|
||||
<CopilotProvider>
|
||||
<QueryTabCopilotComponent {...propsMock} />
|
||||
</CopilotProvider>,
|
||||
);
|
||||
expect(container.find(QueryCopilotPromptbar).exists()).toBe(false);
|
||||
expect(container).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,32 +7,21 @@ import { SplitterDirection } from "Common/Splitter";
|
||||
import { Platform, configContext } from "ConfigContext";
|
||||
import { useDialog } from "Explorer/Controls/Dialog";
|
||||
import { monaco } from "Explorer/LazyMonaco";
|
||||
import { QueryCopilotFeedbackModal } from "Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal";
|
||||
import { useCopilotStore } from "Explorer/QueryCopilot/QueryCopilotContext";
|
||||
import { readCopilotToggleStatus, saveCopilotToggleStatus } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
||||
import { OnExecuteQueryClick, QueryDocumentsPerPage } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||
import { QueryCopilotSidebar } from "Explorer/QueryCopilot/V2/Sidebar/QueryCopilotSidebar";
|
||||
import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection";
|
||||
import { QueryTabStyles, useQueryTabStyles } from "Explorer/Tabs/QueryTab/Styles";
|
||||
import { CosmosFluentProvider } from "Explorer/Theme/ThemeUtil";
|
||||
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||
import { KeyboardAction } from "KeyboardShortcuts";
|
||||
import { Keys, t } from "Localization";
|
||||
import { QueryConstants } from "Shared/Constants";
|
||||
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||
import { Allotment } from "allotment";
|
||||
import { useClientWriteEnabled } from "hooks/useClientWriteEnabled";
|
||||
import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import { TabsState, useTabs } from "hooks/useTabs";
|
||||
import { useMonacoTheme } from "hooks/useTheme";
|
||||
import React, { Fragment, createRef } from "react";
|
||||
import "react-splitter-layout/lib/index.css";
|
||||
import { format } from "react-string-format";
|
||||
import create from "zustand";
|
||||
//TODO: Uncomment next two lines when query copilot is reinstated in DE
|
||||
// import QueryCommandIcon from "../../../../images/CopilotCommand.svg";
|
||||
// import LaunchCopilot from "../../../../images/CopilotTabIcon.svg";
|
||||
import DownloadQueryIcon from "../../../../images/DownloadQuery.svg";
|
||||
import CancelQueryIcon from "../../../../images/Entity_cancel.svg";
|
||||
import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg";
|
||||
@@ -46,7 +35,6 @@ import { queryDocuments } from "../../../Common/dataAccess/queryDocuments";
|
||||
import { queryDocumentsPage } from "../../../Common/dataAccess/queryDocumentsPage";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import * as QueryUtils from "../../../Utils/QueryUtils";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
@@ -109,9 +97,6 @@ export interface IQueryTabComponentProps {
|
||||
isPreferredApiMongoDB?: boolean;
|
||||
monacoEditorSetting?: string;
|
||||
viewModelcollection?: ViewModels.Collection;
|
||||
copilotEnabled?: boolean;
|
||||
isSampleCopilotActive?: boolean;
|
||||
copilotStore?: Partial<QueryCopilotState>;
|
||||
splitterDirection?: "horizontal" | "vertical";
|
||||
queryViewSizePercent?: number;
|
||||
onUpdatePersistedState: (state: {
|
||||
@@ -130,10 +115,7 @@ interface IQueryTabStates {
|
||||
queryResults: ViewModels.QueryResults;
|
||||
isExecutionError: boolean;
|
||||
isExecuting: boolean;
|
||||
showCopilotSidebar: boolean;
|
||||
queryCopilotGeneratedQuery: string;
|
||||
cancelQueryTimeoutID: NodeJS.Timeout;
|
||||
copilotActive: boolean;
|
||||
currentTabActive: boolean;
|
||||
queryResultsView: SplitterDirection;
|
||||
errors?: QueryError[];
|
||||
@@ -141,23 +123,6 @@ interface IQueryTabStates {
|
||||
queryViewSizePercent: number;
|
||||
}
|
||||
|
||||
export const QueryTabCopilotComponent = (props: IQueryTabComponentProps): any => {
|
||||
const styles = useQueryTabStyles();
|
||||
const monacoTheme = useMonacoTheme();
|
||||
const copilotStore = useCopilotStore();
|
||||
|
||||
const isSampleCopilotActive = useSelectedNode.getState().isQueryCopilotCollectionSelected();
|
||||
const queryTabProps = {
|
||||
...props,
|
||||
copilotEnabled:
|
||||
useQueryCopilot().copilotEnabled &&
|
||||
(useQueryCopilot().copilotUserDBEnabled || (isSampleCopilotActive && !!userContext.sampleDataConnectionInfo)),
|
||||
isSampleCopilotActive: isSampleCopilotActive,
|
||||
copilotStore: copilotStore,
|
||||
};
|
||||
return <QueryTabComponentImpl styles={styles} monacoTheme={monacoTheme} {...queryTabProps} />;
|
||||
};
|
||||
|
||||
export const QueryTabComponent = (props: IQueryTabComponentProps): any => {
|
||||
const styles = useQueryTabStyles();
|
||||
const monacoTheme = useMonacoTheme();
|
||||
@@ -176,11 +141,9 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
||||
public queryEditorId: string;
|
||||
public executeQueryButton: Button;
|
||||
public saveQueryButton: Button;
|
||||
public launchCopilotButton: Button;
|
||||
public splitterId: string;
|
||||
public isPreferredApiMongoDB: boolean;
|
||||
public isCloseClicked: boolean;
|
||||
public isCopilotTabActive: boolean;
|
||||
private _iterator: MinimalQueryIterator;
|
||||
private queryAbortController: AbortController;
|
||||
queryEditor: React.RefObject<EditorReact>;
|
||||
@@ -198,10 +161,7 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
||||
errors: [],
|
||||
isExecutionError: this.props.isExecutionError,
|
||||
isExecuting: false,
|
||||
showCopilotSidebar: useQueryCopilot.getState().showCopilotSidebar,
|
||||
queryCopilotGeneratedQuery: useQueryCopilot.getState().query,
|
||||
cancelQueryTimeoutID: undefined,
|
||||
copilotActive: this._queryCopilotActive(),
|
||||
currentTabActive: true,
|
||||
queryResultsView:
|
||||
props.splitterDirection === "vertical" ? SplitterDirection.Vertical : SplitterDirection.Horizontal,
|
||||
@@ -211,7 +171,6 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
||||
this.splitterId = this.props.tabId + "_splitter";
|
||||
this.queryEditorId = `queryeditor${this.props.tabId}`;
|
||||
this.isPreferredApiMongoDB = this.props.isPreferredApiMongoDB;
|
||||
this.isCopilotTabActive = userContext.features.copilotVersion === "v3.0";
|
||||
this.executeQueryButton = {
|
||||
enabled: !!this.state.sqlQueryEditorContent && this.state.sqlQueryEditorContent.length > 0,
|
||||
visible: true,
|
||||
@@ -223,11 +182,6 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
||||
visible: isSaveQueryBtnEnabled,
|
||||
};
|
||||
|
||||
this.launchCopilotButton = {
|
||||
enabled: userContext.apiType === "SQL" && true,
|
||||
visible: userContext.apiType === "SQL" && true,
|
||||
};
|
||||
|
||||
this.props.tabsBaseInstance.updateNavbarWithTabsButtons();
|
||||
props.onTabAccessor({
|
||||
onTabClickEvent: this.onTabClick.bind(this),
|
||||
@@ -253,18 +207,8 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
||||
}, QueryTabComponentImpl.DEBOUNCE_DELAY_MS);
|
||||
};
|
||||
|
||||
private _queryCopilotActive(): boolean {
|
||||
if (this.props.copilotEnabled) {
|
||||
return readCopilotToggleStatus(userContext.databaseAccount);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public onCloseClick(isClicked: boolean): void {
|
||||
this.isCloseClicked = isClicked;
|
||||
if (useQueryCopilot.getState().wasCopilotUsed && this.isCopilotTabActive) {
|
||||
useQueryCopilot.getState().resetQueryCopilotStates();
|
||||
}
|
||||
}
|
||||
|
||||
public getCurrentEditorQuery(): string {
|
||||
@@ -289,16 +233,6 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
||||
setTimeout(async () => {
|
||||
await this._executeQueryDocumentsPage(0);
|
||||
}, 100); // TODO: Revert this
|
||||
if (this.state.copilotActive) {
|
||||
const query = this.state.sqlQueryEditorContent.split("\r\n")?.pop();
|
||||
const isqueryEdited = this.props.copilotStore.generatedQuery && this.props.copilotStore.generatedQuery !== query;
|
||||
if (isqueryEdited) {
|
||||
TelemetryProcessor.traceMark(Action.QueryEdited, {
|
||||
databaseName: this.props.collection.databaseId,
|
||||
collectionId: this.props.collection.id(),
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public onDownloadQueryClick = (): void => {
|
||||
@@ -321,10 +255,6 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
||||
.openSidePanel(t(Keys.tabs.query.saveQuery), <SaveQueryPane explorer={this.props.collection.container} />);
|
||||
};
|
||||
|
||||
public launchQueryCopilotChat = (): void => {
|
||||
useQueryCopilot.getState().setShowCopilotSidebar(!useQueryCopilot.getState().showCopilotSidebar);
|
||||
};
|
||||
|
||||
public onSavedQueriesClick = (): void => {
|
||||
useSidePanel
|
||||
.getState()
|
||||
@@ -346,12 +276,6 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
||||
});
|
||||
}
|
||||
|
||||
public handleCopilotKeyDown = (event: KeyboardEvent): void => {
|
||||
if (this.isCopilotTabActive && event.altKey && event.key === "c") {
|
||||
this.launchQueryCopilotChat();
|
||||
}
|
||||
};
|
||||
|
||||
public onToggleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): boolean => {
|
||||
if (event.key === NormalizedEventKey.LeftArrow) {
|
||||
this.toggleResult();
|
||||
@@ -485,9 +409,7 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
||||
iconSrc: ExecuteQueryIcon,
|
||||
iconAlt: label,
|
||||
keyboardAction: KeyboardAction.EXECUTE_ITEM,
|
||||
onCommandClick: this.props.isSampleCopilotActive
|
||||
? () => OnExecuteQueryClick(this.props.copilotStore)
|
||||
: this.onExecuteQueryClick,
|
||||
onCommandClick: this.onExecuteQueryClick,
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: false,
|
||||
@@ -524,56 +446,6 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
||||
});
|
||||
}
|
||||
|
||||
//TODO: Uncomment next section when query copilot is reinstated in DE
|
||||
// if (this.launchCopilotButton.visible && this.isCopilotTabActive) {
|
||||
// const mainButtonLabel = "Launch Copilot";
|
||||
// const chatPaneLabel = "Open Copilot in chat pane (ALT+C)";
|
||||
// const copilotSettingLabel = "Copilot settings";
|
||||
|
||||
// const openCopilotChatButton: CommandButtonComponentProps = {
|
||||
// iconAlt: chatPaneLabel,
|
||||
// onCommandClick: this.launchQueryCopilotChat,
|
||||
// commandButtonLabel: chatPaneLabel,
|
||||
// ariaLabel: chatPaneLabel,
|
||||
// hasPopup: false,
|
||||
// };
|
||||
|
||||
// const copilotSettingsButton: CommandButtonComponentProps = {
|
||||
// iconAlt: copilotSettingLabel,
|
||||
// onCommandClick: () => undefined,
|
||||
// commandButtonLabel: copilotSettingLabel,
|
||||
// ariaLabel: copilotSettingLabel,
|
||||
// hasPopup: false,
|
||||
// };
|
||||
|
||||
// const launchCopilotButton: CommandButtonComponentProps = {
|
||||
// iconSrc: LaunchCopilot,
|
||||
// iconAlt: mainButtonLabel,
|
||||
// onCommandClick: this.launchQueryCopilotChat,
|
||||
// commandButtonLabel: mainButtonLabel,
|
||||
// ariaLabel: mainButtonLabel,
|
||||
// hasPopup: false,
|
||||
// children: [openCopilotChatButton, copilotSettingsButton],
|
||||
// };
|
||||
// buttons.push(launchCopilotButton);
|
||||
// }
|
||||
|
||||
//TODO: Uncomment next section when query copilot is reinstated in DE
|
||||
// if (this.props.copilotEnabled) {
|
||||
// const toggleCopilotButton: CommandButtonComponentProps = {
|
||||
// iconSrc: QueryCommandIcon,
|
||||
// iconAlt: "Query Advisor",
|
||||
// keyboardAction: KeyboardAction.TOGGLE_COPILOT,
|
||||
// onCommandClick: () => {
|
||||
// this._toggleCopilot(!this.state.copilotActive);
|
||||
// },
|
||||
// commandButtonLabel: this.state.copilotActive ? "Disable Query Advisor" : "Enable Query Advisor",
|
||||
// ariaLabel: this.state.copilotActive ? "Disable Query Advisor" : "Enable Query Advisor",
|
||||
// hasPopup: false,
|
||||
// };
|
||||
// buttons.push(toggleCopilotButton);
|
||||
// }
|
||||
|
||||
if (!this.props.isPreferredApiMongoDB && this.state.isExecuting) {
|
||||
const label = t(Keys.tabs.query.cancelQuery);
|
||||
buttons.push({
|
||||
@@ -626,34 +498,10 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
||||
}, 100);
|
||||
}
|
||||
|
||||
private _toggleCopilot = (active: boolean) => {
|
||||
this.setState({ copilotActive: active });
|
||||
useQueryCopilot.getState().setCopilotEnabledforExecution(active);
|
||||
saveCopilotToggleStatus(userContext.databaseAccount, active);
|
||||
|
||||
TelemetryProcessor.traceSuccess(active ? Action.ActivateQueryCopilot : Action.DeactivateQueryCopilot, {
|
||||
databaseName: this.props.collection.databaseId,
|
||||
collectionId: this.props.collection.id(),
|
||||
});
|
||||
};
|
||||
|
||||
componentDidUpdate = (_prevProps: IQueryTabComponentProps, prevState: IQueryTabStates): void => {
|
||||
if (prevState.copilotActive !== this.state.copilotActive) {
|
||||
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||
}
|
||||
};
|
||||
|
||||
public onChangeContent(newContent: string): void {
|
||||
// The copilot store's active query takes precedence over the local state,
|
||||
// and we can't update both states in a single operation.
|
||||
// So, we update the copilot store's state first, then update the local state.
|
||||
if (this.state.copilotActive) {
|
||||
this.props.copilotStore?.setQuery(newContent);
|
||||
}
|
||||
this.setState(
|
||||
{
|
||||
sqlQueryEditorContent: newContent,
|
||||
queryCopilotGeneratedQuery: "",
|
||||
|
||||
// Clear the markers when the user edits the document.
|
||||
modelMarkers: [],
|
||||
@@ -692,28 +540,10 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.isCopilotTabActive) {
|
||||
selectedContent.trim().length > 0
|
||||
? useQueryCopilot.getState().setSelectedQuery(selectedContent)
|
||||
: useQueryCopilot.getState().setSelectedQuery("");
|
||||
}
|
||||
|
||||
if (this.state.copilotActive) {
|
||||
this.props.copilotStore?.setSelectedQuery(selectedContent);
|
||||
}
|
||||
|
||||
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||
}
|
||||
|
||||
public getEditorContent(): string {
|
||||
if (this.isCopilotTabActive && this.state.queryCopilotGeneratedQuery) {
|
||||
return this.state.queryCopilotGeneratedQuery;
|
||||
}
|
||||
|
||||
if (this.state.copilotActive) {
|
||||
return this.props.copilotStore?.query;
|
||||
}
|
||||
|
||||
return this.state.sqlQueryEditorContent;
|
||||
}
|
||||
|
||||
@@ -721,7 +551,6 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
||||
return !this.isPreferredApiMongoDB && LocalStorageUtility.getEntryBoolean(StorageKey.QueryTimeoutEnabled);
|
||||
}
|
||||
|
||||
private unsubscribeCopilotSidebar: () => void;
|
||||
private unsubscribeClientWriteEnabled: () => void;
|
||||
|
||||
componentDidMount(): void {
|
||||
@@ -738,7 +567,6 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
||||
});
|
||||
|
||||
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||
document.addEventListener("keydown", this.handleCopilotKeyDown);
|
||||
|
||||
this.unsubscribeClientWriteEnabled = useClientWriteEnabled.subscribe(() => {
|
||||
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||
@@ -746,7 +574,6 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
document.removeEventListener("keydown", this.handleCopilotKeyDown);
|
||||
if (this.unsubscribeClientWriteEnabled) {
|
||||
this.unsubscribeClientWriteEnabled();
|
||||
}
|
||||
@@ -757,15 +584,6 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
||||
return (
|
||||
<Fragment>
|
||||
<CosmosFluentProvider id={this.props.tabId} className={this.props.styles.queryTab} role="tabpanel">
|
||||
{/*TODO: Uncomment this section when query copilot is reinstated in DE
|
||||
{this.props.copilotEnabled && this.state.currentTabActive && this.state.copilotActive && (
|
||||
<QueryCopilotPromptbar
|
||||
explorer={this.props.collection.container}
|
||||
toggleCopilot={this._toggleCopilot}
|
||||
databaseId={this.props.collection.databaseId}
|
||||
containerId={this.props.collection.id()}
|
||||
></QueryCopilotPromptbar>
|
||||
)} */}
|
||||
{/* Set 'key' to the value of vertical to force re-rendering when vertical changes, to work around https://github.com/johnwalley/allotment/issues/457 */}
|
||||
<Allotment
|
||||
key={vertical.toString()}
|
||||
@@ -799,64 +617,27 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
||||
/>
|
||||
</Allotment.Pane>
|
||||
<Allotment.Pane>
|
||||
{this.props.isSampleCopilotActive ? (
|
||||
<QueryResultSection
|
||||
isMongoDB={this.props.isPreferredApiMongoDB}
|
||||
queryEditorContent={this.state.sqlQueryEditorContent}
|
||||
errors={this.props.copilotStore?.errors}
|
||||
isExecuting={this.props.copilotStore?.isExecuting}
|
||||
queryResults={this.props.copilotStore?.queryResults}
|
||||
databaseId={this.props.collection.databaseId}
|
||||
containerId={this.props.collection.id()}
|
||||
executeQueryDocumentsPage={(firstItemIndex: number) =>
|
||||
QueryDocumentsPerPage(
|
||||
firstItemIndex,
|
||||
this.props.copilotStore.queryIterator,
|
||||
this.props.copilotStore,
|
||||
)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<QueryResultSection
|
||||
isMongoDB={this.props.isPreferredApiMongoDB}
|
||||
queryEditorContent={this.state.sqlQueryEditorContent}
|
||||
errors={this.state.errors}
|
||||
isExecuting={this.state.isExecuting}
|
||||
queryResults={this.state.queryResults}
|
||||
databaseId={this.props.collection.databaseId}
|
||||
containerId={this.props.collection.id()}
|
||||
executeQueryDocumentsPage={(firstItemIndex: number) =>
|
||||
this._executeQueryDocumentsPage(firstItemIndex)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<QueryResultSection
|
||||
isMongoDB={this.props.isPreferredApiMongoDB}
|
||||
queryEditorContent={this.state.sqlQueryEditorContent}
|
||||
errors={this.state.errors}
|
||||
isExecuting={this.state.isExecuting}
|
||||
queryResults={this.state.queryResults}
|
||||
databaseId={this.props.collection.databaseId}
|
||||
containerId={this.props.collection.id()}
|
||||
executeQueryDocumentsPage={(firstItemIndex: number) => this._executeQueryDocumentsPage(firstItemIndex)}
|
||||
/>
|
||||
</Allotment.Pane>
|
||||
</Allotment>
|
||||
</CosmosFluentProvider>
|
||||
{this.props.copilotEnabled && this.props.copilotStore?.showFeedbackModal && (
|
||||
<QueryCopilotFeedbackModal
|
||||
explorer={this.props.collection.container}
|
||||
databaseId={this.props.collection.databaseId}
|
||||
containerId={this.props.collection.id()}
|
||||
mode={this.props.isSampleCopilotActive ? "Sample" : "User"}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
const shouldScaleElements = this.state.showCopilotSidebar && this.isCopilotTabActive;
|
||||
return (
|
||||
<div data-test="QueryTab" style={{ display: "flex", flexDirection: "row", height: "100%" }}>
|
||||
<div style={{ width: shouldScaleElements ? "70%" : "100%", height: "100%" }}>
|
||||
{this.getEditorAndQueryResult()}
|
||||
</div>
|
||||
{shouldScaleElements && (
|
||||
<div style={{ width: "30%", height: "100%" }}>
|
||||
<QueryCopilotSidebar explorer={this.props.collection.container} />
|
||||
</div>
|
||||
)}
|
||||
<div style={{ width: "100%", height: "100%" }}>{this.getEditorAndQueryResult()}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ import { logConsoleProgress } from "Utils/NotificationConsoleUtils";
|
||||
import create from "zustand";
|
||||
import { client } from "../../../Common/CosmosClient";
|
||||
import { handleError } from "../../../Common/ErrorHandlingUtils";
|
||||
import { sampleDataClient } from "../../../Common/SampleDataClient";
|
||||
import { ResultsViewProps } from "./QueryResultSection";
|
||||
import { useIndexAdvisorStyles } from "./StylesAdvisor";
|
||||
enum ResultsTabs {
|
||||
@@ -583,8 +582,7 @@ export const IndexAdvisorTab: React.FC<{
|
||||
query: queryEditorContent,
|
||||
};
|
||||
|
||||
// Use sampleDataClient for CopilotSampleDB, regular client for other databases
|
||||
const cosmosClient = databaseId === "CopilotSampleDB" ? sampleDataClient() : client();
|
||||
const cosmosClient = client();
|
||||
|
||||
const sdkResponse = await cosmosClient
|
||||
.database(databaseId)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user