mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-10 13:08:20 +00:00
Compare commits
4 Commits
users/sind
...
2819674
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b38f558248 | ||
|
|
3c9325ecbc | ||
|
|
309fcd112e | ||
|
|
eba617f53f |
@@ -147,7 +147,6 @@
|
|||||||
|
|
||||||
// CommandBar
|
// CommandBar
|
||||||
@CommandBarButtonHeight: 40px;
|
@CommandBarButtonHeight: 40px;
|
||||||
@FabricCommandBarButtonHeight: 34px;
|
|
||||||
|
|
||||||
/**********************************************************************************
|
/**********************************************************************************
|
||||||
Portal Consts
|
Portal Consts
|
||||||
@@ -165,7 +164,7 @@
|
|||||||
@FabricFont: "Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
|
@FabricFont: "Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
|
||||||
|
|
||||||
@FabricBoxBorderRadius: 8px;
|
@FabricBoxBorderRadius: 8px;
|
||||||
@FabricBoxBorderShadow: rgba(0, 0, 0, 0.133) 0px 1.6px 3.6px 0px, rgba(0, 0, 0, 0.11) 0px 0.3px 0.9px 0px;
|
@FabricBoxBorderShadow: 0 0 2px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.14);
|
||||||
@FabricBoxMargin: 4px 3px 4px 3px;
|
@FabricBoxMargin: 4px 3px 4px 3px;
|
||||||
|
|
||||||
@FabricAccentMediumHigh: #0c695a;
|
@FabricAccentMediumHigh: #0c695a;
|
||||||
|
|||||||
@@ -2897,21 +2897,9 @@ a:link {
|
|||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settingsSectionInlineCheckbox {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row-reverse;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settingsSectionLabel {
|
.settingsSectionLabel {
|
||||||
margin-bottom: @DefaultSpace;
|
margin-bottom: @DefaultSpace;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
|
|
||||||
.panelInfoIcon {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.pageOptionsPart {
|
.pageOptionsPart {
|
||||||
|
|||||||
@@ -47,15 +47,11 @@ a:focus {
|
|||||||
border-radius: @FabricBoxBorderRadius;
|
border-radius: @FabricBoxBorderRadius;
|
||||||
box-shadow: @FabricBoxBorderShadow;
|
box-shadow: @FabricBoxBorderShadow;
|
||||||
margin: @FabricBoxMargin;
|
margin: @FabricBoxMargin;
|
||||||
margin-top: 0px;
|
|
||||||
padding-top: 2px;
|
padding-top: 2px;
|
||||||
padding: 0px;
|
|
||||||
height: 40px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dividerContainer {
|
.dividerContainer {
|
||||||
padding: @SmallSpace 0px @SmallSpace 0px;
|
padding: @SmallSpace 0px @SmallSpace 0px;
|
||||||
height: @FabricCommandBarButtonHeight;
|
|
||||||
.flex-display();
|
.flex-display();
|
||||||
|
|
||||||
span {
|
span {
|
||||||
|
|||||||
1412
package-lock.json
generated
1412
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@@ -55,8 +55,8 @@
|
|||||||
"crossroads": "0.12.2",
|
"crossroads": "0.12.2",
|
||||||
"css-element-queries": "1.1.1",
|
"css-element-queries": "1.1.1",
|
||||||
"d3": "6.1.1",
|
"d3": "6.1.1",
|
||||||
"datatables.net-colreorder-dt": "1.7.0",
|
"datatables.net-colreorder-dt": "1.5.1",
|
||||||
"datatables.net-dt": "1.13.8",
|
"datatables.net-dt": "1.10.19",
|
||||||
"date-fns": "1.29.0",
|
"date-fns": "1.29.0",
|
||||||
"dayjs": "1.8.19",
|
"dayjs": "1.8.19",
|
||||||
"dom-to-image": "2.6.0",
|
"dom-to-image": "2.6.0",
|
||||||
@@ -71,14 +71,13 @@
|
|||||||
"iframe-resizer-react": "1.1.0",
|
"iframe-resizer-react": "1.1.0",
|
||||||
"immutable": "4.0.0-rc.12",
|
"immutable": "4.0.0-rc.12",
|
||||||
"is-ci": "2.0.0",
|
"is-ci": "2.0.0",
|
||||||
"jquery": "3.7.1",
|
"jquery": "3.5.1",
|
||||||
"jquery-typeahead": "2.11.1",
|
"jquery-typeahead": "2.10.6",
|
||||||
"jquery-ui-dist": "1.13.2",
|
"jquery-ui-dist": "1.12.1",
|
||||||
"knockout": "3.5.1",
|
"knockout": "3.5.1",
|
||||||
"mkdirp": "1.0.4",
|
"mkdirp": "1.0.4",
|
||||||
"monaco-editor": "0.44.0",
|
"monaco-editor": "0.44.0",
|
||||||
"ms": "2.1.3",
|
"ms": "2.1.3",
|
||||||
"patch-package": "8.0.0",
|
|
||||||
"p-retry": "4.6.2",
|
"p-retry": "4.6.2",
|
||||||
"plotly.js-cartesian-dist-min": "1.52.3",
|
"plotly.js-cartesian-dist-min": "1.52.3",
|
||||||
"post-robot": "10.0.42",
|
"post-robot": "10.0.42",
|
||||||
@@ -115,14 +114,11 @@
|
|||||||
"@types/codemirror": "0.0.56",
|
"@types/codemirror": "0.0.56",
|
||||||
"@types/crossroads": "0.0.30",
|
"@types/crossroads": "0.0.30",
|
||||||
"@types/d3": "5.9.2",
|
"@types/d3": "5.9.2",
|
||||||
"@types/datatables.net": "1.10.28",
|
|
||||||
"@types/datatables.net-colreorder": "1.4.5",
|
|
||||||
"@types/dom-to-image": "2.6.2",
|
"@types/dom-to-image": "2.6.2",
|
||||||
"@types/enzyme": "3.10.7",
|
"@types/enzyme": "3.10.7",
|
||||||
"@types/enzyme-adapter-react-16": "1.0.6",
|
"@types/enzyme-adapter-react-16": "1.0.6",
|
||||||
"@types/hasher": "0.0.31",
|
"@types/hasher": "0.0.31",
|
||||||
"@types/jest": "26.0.20",
|
"@types/jest": "26.0.20",
|
||||||
"@types/jquery": "3.5.29",
|
|
||||||
"@types/node": "12.11.1",
|
"@types/node": "12.11.1",
|
||||||
"@types/post-robot": "10.0.1",
|
"@types/post-robot": "10.0.1",
|
||||||
"@types/q": "1.5.1",
|
"@types/q": "1.5.1",
|
||||||
@@ -191,7 +187,6 @@
|
|||||||
"webpack-dev-server": "4.15.1"
|
"webpack-dev-server": "4.15.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "patch-package",
|
|
||||||
"start": "webpack serve --mode development",
|
"start": "webpack serve --mode development",
|
||||||
"dev": "echo \"WARNING: npm run dev has been deprecated\" && npm run build",
|
"dev": "echo \"WARNING: npm run dev has been deprecated\" && npm run build",
|
||||||
"build:dataExplorer:ci": "npm run build:ci",
|
"build:dataExplorer:ci": "npm run build:ci",
|
||||||
@@ -238,4 +233,4 @@
|
|||||||
"printWidth": 120,
|
"printWidth": 120,
|
||||||
"endOfLine": "auto"
|
"endOfLine": "auto"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
diff --git a/node_modules/datatables.net-colreorder/types/types.d.ts b/node_modules/datatables.net-colreorder/types/types.d.ts
|
|
||||||
index e5dc283..1930c2b 100644
|
|
||||||
--- a/node_modules/datatables.net-colreorder/types/types.d.ts
|
|
||||||
+++ b/node_modules/datatables.net-colreorder/types/types.d.ts
|
|
||||||
@@ -7,7 +7,7 @@
|
|
||||||
|
|
||||||
/// <reference types="jquery" />
|
|
||||||
|
|
||||||
-import DataTables, {Api} from 'datatables.net';
|
|
||||||
+import DataTables, { Api } from 'datatables.net';
|
|
||||||
|
|
||||||
export default DataTables;
|
|
||||||
|
|
||||||
@@ -40,6 +40,8 @@ declare module 'datatables.net' {
|
|
||||||
/**
|
|
||||||
* Create a new ColReorder instance for the target DataTable
|
|
||||||
*/
|
|
||||||
+ // Ignore this error: error TS7013: Construct signature, which lacks return-type annotation, implicitly has an 'any' return type.
|
|
||||||
+ // @ts-ignore
|
|
||||||
new (dt: Api<any>, settings: boolean | ConfigColReorder);
|
|
||||||
|
|
||||||
/**
|
|
||||||
@@ -40,10 +40,9 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
|
|||||||
case Cosmos.ResourceType.item:
|
case Cosmos.ResourceType.item:
|
||||||
case Cosmos.ResourceType.pkranges:
|
case Cosmos.ResourceType.pkranges:
|
||||||
// User resource tokens
|
// User resource tokens
|
||||||
// TODO userContext.fabricContext.databaseConnectionInfo can be undefined
|
|
||||||
headers[HttpHeaders.msDate] = new Date().toUTCString();
|
headers[HttpHeaders.msDate] = new Date().toUTCString();
|
||||||
const resourceTokens = userContext.fabricContext.databaseConnectionInfo.resourceTokens;
|
const resourceTokens = userContext.fabricDatabaseConnectionInfo.resourceTokens;
|
||||||
checkDatabaseResourceTokensValidity(userContext.fabricContext.databaseConnectionInfo.resourceTokensTimestamp);
|
checkDatabaseResourceTokensValidity(userContext.fabricDatabaseConnectionInfo.resourceTokensTimestamp);
|
||||||
return getAuthorizationTokenUsingResourceTokens(resourceTokens, requestInfo.path, requestInfo.resourceId);
|
return getAuthorizationTokenUsingResourceTokens(resourceTokens, requestInfo.path, requestInfo.resourceId);
|
||||||
|
|
||||||
case Cosmos.ResourceType.none:
|
case Cosmos.ResourceType.none:
|
||||||
@@ -52,11 +51,9 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
|
|||||||
case Cosmos.ResourceType.user:
|
case Cosmos.ResourceType.user:
|
||||||
case Cosmos.ResourceType.permission:
|
case Cosmos.ResourceType.permission:
|
||||||
// User master tokens
|
// User master tokens
|
||||||
const authorizationToken = await sendCachedDataMessage<AuthorizationToken>(
|
const authorizationToken = await sendCachedDataMessage<AuthorizationToken>(MessageTypes.GetAuthorizationToken, [
|
||||||
MessageTypes.GetAuthorizationToken,
|
requestInfo,
|
||||||
[requestInfo],
|
]);
|
||||||
userContext.fabricContext.connectionId,
|
|
||||||
);
|
|
||||||
console.log("Response from Fabric: ", authorizationToken);
|
console.log("Response from Fabric: ", authorizationToken);
|
||||||
headers[HttpHeaders.msDate] = authorizationToken.XDate;
|
headers[HttpHeaders.msDate] = authorizationToken.XDate;
|
||||||
return decodeURIComponent(authorizationToken.PrimaryReadWriteToken);
|
return decodeURIComponent(authorizationToken.PrimaryReadWriteToken);
|
||||||
|
|||||||
@@ -27,24 +27,15 @@ export function handleCachedDataMessage(message: any): void {
|
|||||||
runGarbageCollector();
|
runGarbageCollector();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param messageType
|
|
||||||
* @param params
|
|
||||||
* @param scope Use this string to identify request Useful to distinguish response from different senders
|
|
||||||
* @param timeoutInMs
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function sendCachedDataMessage<TResponseDataModel>(
|
export function sendCachedDataMessage<TResponseDataModel>(
|
||||||
messageType: MessageTypes,
|
messageType: MessageTypes,
|
||||||
params: Object[],
|
params: Object[],
|
||||||
scope?: string,
|
|
||||||
timeoutInMs?: number,
|
timeoutInMs?: number,
|
||||||
): Q.Promise<TResponseDataModel> {
|
): Q.Promise<TResponseDataModel> {
|
||||||
let cachedDataPromise: CachedDataPromise<TResponseDataModel> = {
|
let cachedDataPromise: CachedDataPromise<TResponseDataModel> = {
|
||||||
deferred: Q.defer<TResponseDataModel>(),
|
deferred: Q.defer<TResponseDataModel>(),
|
||||||
startTime: new Date(),
|
startTime: new Date(),
|
||||||
id: _.uniqueId(scope),
|
id: _.uniqueId(),
|
||||||
};
|
};
|
||||||
RequestMap[cachedDataPromise.id] = cachedDataPromise;
|
RequestMap[cachedDataPromise.id] = cachedDataPromise;
|
||||||
sendMessage({ type: messageType, params: params, id: cachedDataPromise.id });
|
sendMessage({ type: messageType, params: params, id: cachedDataPromise.id });
|
||||||
@@ -56,10 +47,6 @@ export function sendCachedDataMessage<TResponseDataModel>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param data Overwrite the data property of the message
|
|
||||||
*/
|
|
||||||
export function sendMessage(data: any): void {
|
export function sendMessage(data: any): void {
|
||||||
_sendMessage({
|
_sendMessage({
|
||||||
signature: "pcIframe",
|
signature: "pcIframe",
|
||||||
|
|||||||
@@ -18,13 +18,13 @@ export async function readCollections(databaseId: string): Promise<DataModels.Co
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
configContext.platform === Platform.Fabric &&
|
configContext.platform === Platform.Fabric &&
|
||||||
userContext.fabricContext &&
|
userContext.fabricDatabaseConnectionInfo &&
|
||||||
userContext.fabricContext.databaseConnectionInfo.databaseId === databaseId
|
userContext.fabricDatabaseConnectionInfo.databaseId === databaseId
|
||||||
) {
|
) {
|
||||||
const collections: DataModels.Collection[] = [];
|
const collections: DataModels.Collection[] = [];
|
||||||
const promises: Promise<ContainerResponse>[] = [];
|
const promises: Promise<ContainerResponse>[] = [];
|
||||||
|
|
||||||
for (const collectionResourceId in userContext.fabricContext.databaseConnectionInfo.resourceTokens) {
|
for (const collectionResourceId in userContext.fabricDatabaseConnectionInfo.resourceTokens) {
|
||||||
// Dictionary key looks like this: dbs/SampleDB/colls/Container
|
// Dictionary key looks like this: dbs/SampleDB/colls/Container
|
||||||
const resourceIdObj = collectionResourceId.split("/");
|
const resourceIdObj = collectionResourceId.split("/");
|
||||||
const tokenDatabaseId = resourceIdObj[1];
|
const tokenDatabaseId = resourceIdObj[1];
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
|
|||||||
let databases: DataModels.Database[];
|
let databases: DataModels.Database[];
|
||||||
const clearMessage = logConsoleProgress(`Querying databases`);
|
const clearMessage = logConsoleProgress(`Querying databases`);
|
||||||
|
|
||||||
if (configContext.platform === Platform.Fabric && userContext.fabricContext?.databaseConnectionInfo.resourceTokens) {
|
if (configContext.platform === Platform.Fabric && userContext.fabricDatabaseConnectionInfo?.resourceTokens) {
|
||||||
const tokensData = userContext.fabricContext.databaseConnectionInfo;
|
const tokensData = userContext.fabricDatabaseConnectionInfo;
|
||||||
|
|
||||||
const databaseIdsSet = new Set<string>(); // databaseId
|
const databaseIdsSet = new Set<string>(); // databaseId
|
||||||
|
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
import { MessageTypes } from "./MessageTypes";
|
|
||||||
|
|
||||||
// This is the current version of these messages
|
|
||||||
export const DATA_EXPLORER_RPC_VERSION = "2";
|
|
||||||
|
|
||||||
// Data Explorer to Fabric
|
|
||||||
|
|
||||||
// TODO Remove when upgrading to Fabric v2
|
|
||||||
export type DataExploreMessageV1 =
|
|
||||||
| "ready"
|
|
||||||
| {
|
|
||||||
type: MessageTypes.GetAuthorizationToken;
|
|
||||||
id: string;
|
|
||||||
params: GetCosmosTokenMessageOptions[];
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: MessageTypes.GetAllResourceTokens;
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
// -----------------------------
|
|
||||||
|
|
||||||
export type DataExploreMessageV2 =
|
|
||||||
| {
|
|
||||||
type: MessageTypes.Ready;
|
|
||||||
id: string;
|
|
||||||
params: [string]; // version
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: MessageTypes.GetAuthorizationToken;
|
|
||||||
id: string;
|
|
||||||
params: GetCosmosTokenMessageOptions[];
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: MessageTypes.GetAllResourceTokens;
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type GetCosmosTokenMessageOptions = {
|
|
||||||
verb: "connect" | "delete" | "get" | "head" | "options" | "patch" | "post" | "put" | "trace";
|
|
||||||
resourceType: "" | "dbs" | "colls" | "docs" | "sprocs" | "pkranges";
|
|
||||||
resourceId: string;
|
|
||||||
};
|
|
||||||
@@ -1,12 +1,6 @@
|
|||||||
import { AuthorizationToken } from "./MessageTypes";
|
import { AuthorizationToken, MessageTypes } from "./MessageTypes";
|
||||||
|
|
||||||
// This is the version of these messages
|
export type FabricMessage =
|
||||||
export const FABRIC_RPC_VERSION = "2";
|
|
||||||
|
|
||||||
// Fabric to Data Explorer
|
|
||||||
|
|
||||||
// TODO Deprecated. Remove this section once DE is updated
|
|
||||||
export type FabricMessageV1 =
|
|
||||||
| {
|
| {
|
||||||
type: "newContainer";
|
type: "newContainer";
|
||||||
databaseName: string;
|
databaseName: string;
|
||||||
@@ -32,52 +26,38 @@ export type FabricMessageV1 =
|
|||||||
| {
|
| {
|
||||||
type: "allResourceTokens";
|
type: "allResourceTokens";
|
||||||
message: {
|
message: {
|
||||||
id: string;
|
|
||||||
error: string | undefined;
|
|
||||||
endpoint: string | undefined;
|
endpoint: string | undefined;
|
||||||
databaseId: string | undefined;
|
databaseId: string | undefined;
|
||||||
resourceTokens: unknown | undefined;
|
resourceTokens: unknown | undefined;
|
||||||
resourceTokensTimestamp: number | undefined;
|
resourceTokensTimestamp: number | undefined;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
// -----------------------------
|
|
||||||
|
|
||||||
export type FabricMessageV2 =
|
export type DataExploreMessage =
|
||||||
|
| "ready"
|
||||||
| {
|
| {
|
||||||
type: "newContainer";
|
type: MessageTypes.TelemetryInfo;
|
||||||
databaseName: string;
|
data: {
|
||||||
|
action: "LoadDatabases";
|
||||||
|
actionModifier: "success" | "start";
|
||||||
|
defaultExperience: "SQL";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: "initialize";
|
type: MessageTypes.GetAuthorizationToken;
|
||||||
version: string;
|
|
||||||
id: string;
|
id: string;
|
||||||
message: {
|
params: GetCosmosTokenMessageOptions[];
|
||||||
connectionId: string;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: "authorizationToken";
|
type: MessageTypes.GetAllResourceTokens;
|
||||||
message: {
|
|
||||||
id: string;
|
|
||||||
error: string | undefined;
|
|
||||||
data: AuthorizationToken | undefined;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "allResourceTokens_v2";
|
|
||||||
message: {
|
|
||||||
id: string;
|
|
||||||
error: string | undefined;
|
|
||||||
data: FabricDatabaseConnectionInfo | undefined;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "setToolbarStatus";
|
|
||||||
message: {
|
|
||||||
visible: boolean;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type GetCosmosTokenMessageOptions = {
|
||||||
|
verb: "connect" | "delete" | "get" | "head" | "options" | "patch" | "post" | "put" | "trace";
|
||||||
|
resourceType: "" | "dbs" | "colls" | "docs" | "sprocs" | "pkranges";
|
||||||
|
resourceId: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type CosmosDBTokenResponse = {
|
export type CosmosDBTokenResponse = {
|
||||||
token: string;
|
token: string;
|
||||||
date: string;
|
date: string;
|
||||||
@@ -86,9 +66,12 @@ export type CosmosDBTokenResponse = {
|
|||||||
export type CosmosDBConnectionInfoResponse = {
|
export type CosmosDBConnectionInfoResponse = {
|
||||||
endpoint: string;
|
endpoint: string;
|
||||||
databaseId: string;
|
databaseId: string;
|
||||||
resourceTokens: { [resourceId: string]: string };
|
resourceTokens: unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface FabricDatabaseConnectionInfo extends CosmosDBConnectionInfoResponse {
|
export interface FabricDatabaseConnectionInfo {
|
||||||
|
endpoint: string;
|
||||||
|
databaseId: string;
|
||||||
|
resourceTokens: { [resourceId: string]: string };
|
||||||
resourceTokensTimestamp: number;
|
resourceTokensTimestamp: number;
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Messaging types used with Data Explorer <-> Portal communication,
|
* Messaging types used with Data Explorer <-> Portal communication,
|
||||||
* Hosted <-> Explorer communication and Data Explorer -> Fabric communication.
|
* Hosted <-> Explorer communication and Data Explorer -> Fabric communication.
|
||||||
*
|
|
||||||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
||||||
* WARNING: !!!!!!! YOU CAN ONLY ADD NEW TYPES TO THE END OF THIS ENUM !!!!!!!
|
|
||||||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
||||||
*
|
|
||||||
* Enum are integers, so inserting or deleting a type will break the communication.
|
|
||||||
*/
|
*/
|
||||||
export enum MessageTypes {
|
export enum MessageTypes {
|
||||||
TelemetryInfo,
|
TelemetryInfo,
|
||||||
@@ -43,9 +37,10 @@ export enum MessageTypes {
|
|||||||
DisplayNPSSurvey,
|
DisplayNPSSurvey,
|
||||||
OpenVCoreMongoNetworkingBlade,
|
OpenVCoreMongoNetworkingBlade,
|
||||||
OpenVCoreMongoConnectionStringsBlade,
|
OpenVCoreMongoConnectionStringsBlade,
|
||||||
GetAuthorizationToken, // Data Explorer -> Fabric
|
|
||||||
GetAllResourceTokens, // Data Explorer -> Fabric
|
// Data Explorer -> Fabric communication
|
||||||
Ready, // Data Explorer -> Fabric
|
GetAuthorizationToken,
|
||||||
|
GetAllResourceTokens,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthorizationToken {
|
export interface AuthorizationToken {
|
||||||
|
|||||||
1954
src/Definitions/datatables.d.ts
vendored
Normal file
1954
src/Definitions/datatables.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2
src/Definitions/jquery-typescript.d.ts
vendored
2
src/Definitions/jquery-typescript.d.ts
vendored
@@ -5,7 +5,7 @@
|
|||||||
* https://github.com/running-coder/jquery-typeahead/issues/156
|
* https://github.com/running-coder/jquery-typeahead/issues/156
|
||||||
* TODO: Replace this minimum definition by the official one when it comes out.
|
* TODO: Replace this minimum definition by the official one when it comes out.
|
||||||
*/
|
*/
|
||||||
/// <reference types="jquery" />
|
/// <reference path="jquery.d.ts" />
|
||||||
|
|
||||||
interface JQueryTypeaheadParam {
|
interface JQueryTypeaheadParam {
|
||||||
input: string;
|
input: string;
|
||||||
|
|||||||
2
src/Definitions/jquery-ui.d.ts
vendored
2
src/Definitions/jquery-ui.d.ts
vendored
@@ -3,7 +3,7 @@
|
|||||||
// Definitions by: Boris Yankov <https://github.com/borisyankov/>, John Reilly <https://github.com/johnnyreilly>
|
// Definitions by: Boris Yankov <https://github.com/borisyankov/>, John Reilly <https://github.com/johnnyreilly>
|
||||||
// Definitions: https://github.com/borisyankov/DefinitelyTyped
|
// Definitions: https://github.com/borisyankov/DefinitelyTyped
|
||||||
|
|
||||||
/// <reference types="jquery"/>
|
/// <reference path="jquery.d.ts"/>
|
||||||
|
|
||||||
declare namespace JQueryUI {
|
declare namespace JQueryUI {
|
||||||
// Accordion //////////////////////////////////////////////////
|
// Accordion //////////////////////////////////////////////////
|
||||||
|
|||||||
1890
src/Definitions/jquery.d.ts
vendored
Normal file
1890
src/Definitions/jquery.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -23,12 +23,12 @@ describe("ThroughputInput Pane", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should switch mode properly", () => {
|
it("should switch mode properly", () => {
|
||||||
wrapper.find('[id="Manual-input"]').simulate("change");
|
wrapper.find('[aria-label="Manual database throughput"]').simulate("change");
|
||||||
expect(wrapper.find('[aria-label="Throughput header"]').at(0).text()).toBe(
|
expect(wrapper.find('[aria-label="Throughput header"]').at(0).text()).toBe(
|
||||||
"Container throughput (400 - unlimited RU/s)",
|
"Container throughput (400 - unlimited RU/s)",
|
||||||
);
|
);
|
||||||
|
|
||||||
wrapper.find('[id="Autoscale-input"]').simulate("change");
|
wrapper.find('[aria-label="Autoscale database throughput"]').simulate("change");
|
||||||
expect(wrapper.find('[aria-label="Throughput header"]').at(0).text()).toBe("Container throughput (autoscale)");
|
expect(wrapper.find('[aria-label="Throughput header"]').at(0).text()).toBe("Container throughput (autoscale)");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
<input
|
<input
|
||||||
id="Autoscale-input"
|
id="Autoscale-input"
|
||||||
className="throughputInputRadioBtn"
|
className="throughputInputRadioBtn"
|
||||||
aria-label={`${getThroughputLabelText()} Autoscale`}
|
aria-label="Autoscale database throughput"
|
||||||
aria-required={true}
|
aria-required={true}
|
||||||
checked={isAutoscaleSelected}
|
checked={isAutoscaleSelected}
|
||||||
type="radio"
|
type="radio"
|
||||||
@@ -204,7 +204,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
<input
|
<input
|
||||||
id="Manual-input"
|
id="Manual-input"
|
||||||
className="throughputInputRadioBtn"
|
className="throughputInputRadioBtn"
|
||||||
aria-label={`${getThroughputLabelText()} Manual`}
|
aria-label="Manual database throughput"
|
||||||
checked={!isAutoscaleSelected}
|
checked={!isAutoscaleSelected}
|
||||||
type="radio"
|
type="radio"
|
||||||
aria-required={true}
|
aria-required={true}
|
||||||
@@ -276,12 +276,6 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
</Link>
|
</Link>
|
||||||
.
|
.
|
||||||
</Text>
|
</Text>
|
||||||
<Stack horizontal>
|
|
||||||
<Text variant="small" style={{ lineHeight: "20px", fontWeight: 600 }} aria-label="maxRUDescription">
|
|
||||||
{isDatabase ? "Database" : getCollectionName()} Required RU/s
|
|
||||||
</Text>
|
|
||||||
<InfoTooltip>{getAutoScaleTooltip()}</InfoTooltip>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
<TooltipHost
|
<TooltipHost
|
||||||
directionalHint={DirectionalHint.topLeftEdge}
|
directionalHint={DirectionalHint.topLeftEdge}
|
||||||
@@ -302,7 +296,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
min={SharedConstants.CollectionCreation.DefaultCollectionRUs400}
|
min={SharedConstants.CollectionCreation.DefaultCollectionRUs400}
|
||||||
max={userContext.isTryCosmosDBSubscription ? Constants.TryCosmosExperience.maxRU : Infinity}
|
max={userContext.isTryCosmosDBSubscription ? Constants.TryCosmosExperience.maxRU : Infinity}
|
||||||
value={throughput.toString()}
|
value={throughput.toString()}
|
||||||
ariaLabel={`${isDatabase ? "Database" : getCollectionName()} Required RU/s`}
|
aria-label="Max request units per second"
|
||||||
required={true}
|
required={true}
|
||||||
errorMessage={throughputError}
|
errorMessage={throughputError}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -678,7 +678,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
role="radiogroup"
|
role="radiogroup"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
aria-label="Container throughput (autoscale) Autoscale"
|
aria-label="Autoscale database throughput"
|
||||||
aria-required={true}
|
aria-required={true}
|
||||||
checked={true}
|
checked={true}
|
||||||
className="throughputInputRadioBtn"
|
className="throughputInputRadioBtn"
|
||||||
@@ -695,7 +695,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
Autoscale
|
Autoscale
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
aria-label="Container throughput (autoscale) Manual"
|
aria-label="Manual database throughput"
|
||||||
aria-required={true}
|
aria-required={true}
|
||||||
checked={false}
|
checked={false}
|
||||||
className="throughputInputRadioBtn"
|
className="throughputInputRadioBtn"
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
|
|||||||
import { sendMessage } from "Common/MessageHandler";
|
import { sendMessage } from "Common/MessageHandler";
|
||||||
import { Platform, configContext } from "ConfigContext";
|
import { Platform, configContext } from "ConfigContext";
|
||||||
import { MessageTypes } from "Contracts/ExplorerContracts";
|
import { MessageTypes } from "Contracts/ExplorerContracts";
|
||||||
import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
import { getCopilotEnabled } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||||
import { IGalleryItem } from "Juno/JunoClient";
|
import { IGalleryItem } from "Juno/JunoClient";
|
||||||
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
|
import { requestDatabaseResourceTokens } from "Platform/Fabric/FabricUtil";
|
||||||
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
|
||||||
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointValidation";
|
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointValidation";
|
||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
@@ -277,32 +276,21 @@ export default class Explorer {
|
|||||||
const NINETY_DAYS_IN_MS = 7776000000;
|
const NINETY_DAYS_IN_MS = 7776000000;
|
||||||
const ONE_DAY_IN_MS = 86400000;
|
const ONE_DAY_IN_MS = 86400000;
|
||||||
const THREE_DAYS_IN_MS = 259200000;
|
const THREE_DAYS_IN_MS = 259200000;
|
||||||
|
const isAccountNewerThanNinetyDays = isAccountNewerThanThresholdInMs(
|
||||||
|
userContext.databaseAccount?.systemData?.createdAt || "",
|
||||||
|
NINETY_DAYS_IN_MS,
|
||||||
|
);
|
||||||
const lastSubmitted: string = localStorage.getItem("lastSubmitted");
|
const lastSubmitted: string = localStorage.getItem("lastSubmitted");
|
||||||
Logger.logInfo(`NPS Survey last shown date: ${lastSubmitted}`, "Explorer/openNPSSurveyDialog");
|
|
||||||
|
|
||||||
if (lastSubmitted !== null) {
|
if (lastSubmitted !== null) {
|
||||||
Logger.logInfo(`NPS Survey last shown is not empty ${lastSubmitted}`, "Explorer/openNPSSurveyDialog");
|
|
||||||
|
|
||||||
let lastSubmittedDate: number = parseInt(lastSubmitted);
|
let lastSubmittedDate: number = parseInt(lastSubmitted);
|
||||||
Logger.logInfo(`NPS Survey last shown is parsed ${lastSubmittedDate.toString()}`, "Explorer/openNPSSurveyDialog");
|
|
||||||
|
|
||||||
if (isNaN(lastSubmittedDate)) {
|
if (isNaN(lastSubmittedDate)) {
|
||||||
Logger.logInfo(
|
|
||||||
`NPS Survey last shown is not a number ${lastSubmittedDate.toString()}`,
|
|
||||||
"Explorer/openNPSSurveyDialog",
|
|
||||||
);
|
|
||||||
lastSubmittedDate = 0;
|
lastSubmittedDate = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nowMs: number = Date.now();
|
const nowMs: number = Date.now();
|
||||||
Logger.logInfo(`NPS Survey current date ${nowMs.toString()}`, "Explorer/openNPSSurveyDialog");
|
|
||||||
|
|
||||||
const millisecsSinceLastSubmitted = nowMs - lastSubmittedDate;
|
const millisecsSinceLastSubmitted = nowMs - lastSubmittedDate;
|
||||||
if (millisecsSinceLastSubmitted < NINETY_DAYS_IN_MS) {
|
if (millisecsSinceLastSubmitted < NINETY_DAYS_IN_MS) {
|
||||||
Logger.logInfo(
|
|
||||||
`NPS Survey last shown is less than ninety days ${millisecsSinceLastSubmitted.toString()}`,
|
|
||||||
"Explorer/openNPSSurveyDialog",
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -310,32 +298,26 @@ export default class Explorer {
|
|||||||
// Try Cosmos DB subscription - survey shown to 100% of users at day 1 in Data Explorer.
|
// Try Cosmos DB subscription - survey shown to 100% of users at day 1 in Data Explorer.
|
||||||
if (userContext.isTryCosmosDBSubscription) {
|
if (userContext.isTryCosmosDBSubscription) {
|
||||||
if (isAccountNewerThanThresholdInMs(userContext.databaseAccount?.systemData?.createdAt || "", ONE_DAY_IN_MS)) {
|
if (isAccountNewerThanThresholdInMs(userContext.databaseAccount?.systemData?.createdAt || "", ONE_DAY_IN_MS)) {
|
||||||
Logger.logInfo(
|
|
||||||
`Displaying NPS Survey for Try Cosmos DB ${userContext.apiType}`,
|
|
||||||
"Explorer/openNPSSurveyDialog",
|
|
||||||
);
|
|
||||||
this.sendNPSMessage();
|
this.sendNPSMessage();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Show survey when an existing account is older than 3 days
|
// An existing account is older than 3 days but less than 90 days old. For existing account show to 100% of users in Data Explorer.
|
||||||
if (
|
if (
|
||||||
!isAccountNewerThanThresholdInMs(userContext.databaseAccount?.systemData?.createdAt || "", THREE_DAYS_IN_MS)
|
!isAccountNewerThanThresholdInMs(userContext.databaseAccount?.systemData?.createdAt || "", THREE_DAYS_IN_MS) &&
|
||||||
|
isAccountNewerThanNinetyDays
|
||||||
) {
|
) {
|
||||||
Logger.logInfo(
|
|
||||||
`Displaying NPS Survey for users with existing ${userContext.apiType} account older than 3 days`,
|
|
||||||
"Explorer/openNPSSurveyDialog",
|
|
||||||
);
|
|
||||||
this.sendNPSMessage();
|
this.sendNPSMessage();
|
||||||
|
} else {
|
||||||
|
// An existing account is greater than 90 days. For existing account show to random 33% of users in Data Explorer.
|
||||||
|
if (this.getRandomInt(100) < 33) {
|
||||||
|
this.sendNPSMessage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sendNPSMessage() {
|
private sendNPSMessage() {
|
||||||
sendMessage({ type: MessageTypes.DisplayNPSSurvey });
|
sendMessage({ type: MessageTypes.DisplayNPSSurvey });
|
||||||
Logger.logInfo(
|
|
||||||
`NPS Survey logging current date when survey is shown ${Date.now().toString()}`,
|
|
||||||
"Explorer/openNPSSurveyDialog",
|
|
||||||
);
|
|
||||||
localStorage.setItem("lastSubmitted", Date.now().toString());
|
localStorage.setItem("lastSubmitted", Date.now().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,7 +383,9 @@ export default class Explorer {
|
|||||||
|
|
||||||
public onRefreshResourcesClick = (): void => {
|
public onRefreshResourcesClick = (): void => {
|
||||||
if (configContext.platform === Platform.Fabric) {
|
if (configContext.platform === Platform.Fabric) {
|
||||||
scheduleRefreshDatabaseResourceToken(true).then(() => this.refreshAllDatabases());
|
// Requesting the tokens will trigger a refresh of the databases
|
||||||
|
// TODO: Once the id is returned from Fabric, we can await this call and then refresh the databases here
|
||||||
|
requestDatabaseResourceTokens();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1405,18 +1389,9 @@ export default class Explorer {
|
|||||||
if (userContext.apiType !== "SQL" || !userContext.subscriptionId) {
|
if (userContext.apiType !== "SQL" || !userContext.subscriptionId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const copilotEnabledPromise = getCopilotEnabled();
|
const copilotEnabled = await getCopilotEnabled();
|
||||||
const copilotUserDBEnabledPromise = isCopilotFeatureRegistered(userContext.subscriptionId);
|
useQueryCopilot.getState().setCopilotEnabled(copilotEnabled);
|
||||||
const [copilotEnabled, copilotUserDBEnabled] = await Promise.all([
|
useQueryCopilot.getState().setCopilotUserDBEnabled(copilotEnabled);
|
||||||
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 async refreshSampleData(): Promise<void> {
|
public async refreshSampleData(): Promise<void> {
|
||||||
|
|||||||
@@ -1163,12 +1163,15 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
)}"`,
|
)}"`,
|
||||||
).then(
|
).then(
|
||||||
(documents: DataModels.DocumentId[]) => {
|
(documents: DataModels.DocumentId[]) => {
|
||||||
$.each(documents, (index: number, doc: any) => {
|
$.each(
|
||||||
newIconsMap[doc["_graph_icon_property_value"]] = {
|
documents,
|
||||||
data: doc["icon"],
|
(index: number, doc: { _graph_icon_property_value: string; icon: string; format: string }) => {
|
||||||
format: doc["format"],
|
newIconsMap[doc["_graph_icon_property_value"]] = {
|
||||||
};
|
data: doc["icon"],
|
||||||
});
|
format: doc["format"],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Update graph configuration
|
// Update graph configuration
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|||||||
@@ -24,21 +24,16 @@ interface Props {
|
|||||||
export interface CommandBarStore {
|
export interface CommandBarStore {
|
||||||
contextButtons: CommandButtonComponentProps[];
|
contextButtons: CommandButtonComponentProps[];
|
||||||
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => void;
|
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => void;
|
||||||
isHidden: boolean;
|
|
||||||
setIsHidden: (isHidden: boolean) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useCommandBar: UseStore<CommandBarStore> = create((set) => ({
|
export const useCommandBar: UseStore<CommandBarStore> = create((set) => ({
|
||||||
contextButtons: [],
|
contextButtons: [],
|
||||||
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => set((state) => ({ ...state, contextButtons })),
|
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => set((state) => ({ ...state, contextButtons })),
|
||||||
isHidden: false,
|
|
||||||
setIsHidden: (isHidden: boolean) => set((state) => ({ ...state, isHidden })),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
||||||
const selectedNodeState = useSelectedNode();
|
const selectedNodeState = useSelectedNode();
|
||||||
const buttons = useCommandBar((state) => state.contextButtons);
|
const buttons = useCommandBar((state) => state.contextButtons);
|
||||||
const isHidden = useCommandBar((state) => state.isHidden);
|
|
||||||
const backgroundColor = StyleConstants.BaseLight;
|
const backgroundColor = StyleConstants.BaseLight;
|
||||||
|
|
||||||
if (userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo") {
|
if (userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo") {
|
||||||
@@ -47,7 +42,7 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
|||||||
? CommandBarComponentButtonFactory.createPostgreButtons(container)
|
? CommandBarComponentButtonFactory.createPostgreButtons(container)
|
||||||
: CommandBarComponentButtonFactory.createVCoreMongoButtons(container);
|
: CommandBarComponentButtonFactory.createVCoreMongoButtons(container);
|
||||||
return (
|
return (
|
||||||
<div className="commandBarContainer" style={{ display: isHidden ? "none" : "initial" }}>
|
<div className="commandBarContainer">
|
||||||
<FluentCommandBar
|
<FluentCommandBar
|
||||||
ariaLabel="Use left and right arrow keys to navigate between commands"
|
ariaLabel="Use left and right arrow keys to navigate between commands"
|
||||||
items={CommandBarUtil.convertButton(buttons, backgroundColor)}
|
items={CommandBarUtil.convertButton(buttons, backgroundColor)}
|
||||||
@@ -96,7 +91,7 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
|||||||
? {
|
? {
|
||||||
root: {
|
root: {
|
||||||
backgroundColor: "transparent",
|
backgroundColor: "transparent",
|
||||||
padding: "2px 8px 0px 8px",
|
padding: "0px 14px 0px 14px",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
@@ -106,7 +101,7 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="commandBarContainer" style={{ display: isHidden ? "none" : "initial" }}>
|
<div className="commandBarContainer">
|
||||||
<FluentCommandBar
|
<FluentCommandBar
|
||||||
ariaLabel="Use left and right arrow keys to navigate between commands"
|
ariaLabel="Use left and right arrow keys to navigate between commands"
|
||||||
items={uiFabricStaticButtons.concat(uiFabricTabsButtons)}
|
items={uiFabricStaticButtons.concat(uiFabricTabsButtons)}
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt
|
|||||||
{
|
{
|
||||||
iconSrc: SettingsIcon,
|
iconSrc: SettingsIcon,
|
||||||
iconAlt: "Settings",
|
iconAlt: "Settings",
|
||||||
onCommandClick: () => useSidePanel.getState().openSidePanel("Settings", <SettingsPane explorer={container} />),
|
onCommandClick: () => useSidePanel.getState().openSidePanel("Settings", <SettingsPane />),
|
||||||
commandButtonLabel: undefined,
|
commandButtonLabel: undefined,
|
||||||
ariaLabel: "Settings",
|
ariaLabel: "Settings",
|
||||||
tooltipText: "Settings",
|
tooltipText: "Settings",
|
||||||
|
|||||||
@@ -25,10 +25,7 @@ import { MemoryTracker } from "./MemoryTrackerComponent";
|
|||||||
* @param btns
|
* @param btns
|
||||||
*/
|
*/
|
||||||
export const convertButton = (btns: CommandButtonComponentProps[], backgroundColor: string): ICommandBarItemProps[] => {
|
export const convertButton = (btns: CommandButtonComponentProps[], backgroundColor: string): ICommandBarItemProps[] => {
|
||||||
const buttonHeightPx =
|
const buttonHeightPx = StyleConstants.CommandBarButtonHeight;
|
||||||
configContext.platform == Platform.Fabric
|
|
||||||
? StyleConstants.FabricCommandBarButtonHeight
|
|
||||||
: StyleConstants.CommandBarButtonHeight;
|
|
||||||
|
|
||||||
const hoverColor =
|
const hoverColor =
|
||||||
configContext.platform == Platform.Fabric ? StyleConstants.FabricAccentLight : StyleConstants.AccentLight;
|
configContext.platform == Platform.Fabric ? StyleConstants.FabricAccentLight : StyleConstants.AccentLight;
|
||||||
@@ -115,7 +112,6 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
|
|||||||
splitButtonContainer: {
|
splitButtonContainer: {
|
||||||
marginLeft: 5,
|
marginLeft: 5,
|
||||||
marginRight: 5,
|
marginRight: 5,
|
||||||
height: buttonHeightPx,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
className: btn.className,
|
className: btn.className,
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ function openPane(action: ActionContracts.OpenPane, explorer: Explorer) {
|
|||||||
action.paneKind === ActionContracts.PaneKind.GlobalSettings ||
|
action.paneKind === ActionContracts.PaneKind.GlobalSettings ||
|
||||||
action.paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.GlobalSettings]
|
action.paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.GlobalSettings]
|
||||||
) {
|
) {
|
||||||
useSidePanel.getState().openSidePanel("Settings", <SettingsPane explorer={explorer} />);
|
useSidePanel.getState().openSidePanel("Settings", <SettingsPane />);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export class PanelContainerComponent extends React.Component<PanelContainerProps
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
omponentDidMount(): void {
|
||||||
window.addEventListener("resize", () => this.setState({ height: this.getPanelHeight() }));
|
window.addEventListener("resize", () => this.setState({ height: this.getPanelHeight() }));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,12 +62,12 @@ export class PanelContainerComponent extends React.Component<PanelContainerProps
|
|||||||
customWidth={this.props.panelWidth ? this.props.panelWidth : "440px"}
|
customWidth={this.props.panelWidth ? this.props.panelWidth : "440px"}
|
||||||
headerClassName="panelHeader"
|
headerClassName="panelHeader"
|
||||||
onRenderNavigationContent={this.props.onRenderNavigationContent}
|
onRenderNavigationContent={this.props.onRenderNavigationContent}
|
||||||
isFooterAtBottom={true}
|
|
||||||
styles={{
|
styles={{
|
||||||
navigation: { borderBottom: "1px solid #cccccc" },
|
navigation: { borderBottom: "1px solid #cccccc" },
|
||||||
content: { padding: 0 },
|
content: { padding: 0, height: "100%" },
|
||||||
|
scrollableContent: { height: "100%" },
|
||||||
header: { padding: "0 0 8px 34px" },
|
header: { padding: "0 0 8px 34px" },
|
||||||
commands: { marginTop: 8, paddingTop: 0 },
|
commands: { marginTop: 8 },
|
||||||
}}
|
}}
|
||||||
style={{ height: this.state.height }}
|
style={{ height: this.state.height }}
|
||||||
>
|
>
|
||||||
@@ -76,7 +76,44 @@ export class PanelContainerComponent extends React.Component<PanelContainerProps
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public isFocusable(element: HTMLElement) {
|
||||||
|
return (
|
||||||
|
element.tabIndex >= 0 ||
|
||||||
|
(element instanceof HTMLAnchorElement && element.href) ||
|
||||||
|
(element instanceof HTMLAreaElement && element.href) ||
|
||||||
|
(element instanceof HTMLInputElement && !element.disabled) ||
|
||||||
|
(element instanceof HTMLSelectElement && !element.disabled) ||
|
||||||
|
(element instanceof HTMLTextAreaElement && !element.disabled) ||
|
||||||
|
(element instanceof HTMLButtonElement && !element.disabled)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
private findFocusableParent = (element: HTMLElement) => {
|
||||||
|
while (element) {
|
||||||
|
if (this.isFocusable(element)) {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
element = element.parentNode as HTMLElement;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
private onDissmiss = (ev?: KeyboardEvent | React.SyntheticEvent<HTMLElement>): void => {
|
private onDissmiss = (ev?: KeyboardEvent | React.SyntheticEvent<HTMLElement>): void => {
|
||||||
|
const elementIdToFocus = sessionStorage.getItem("focusedElementId") || null;
|
||||||
|
if (elementIdToFocus) {
|
||||||
|
const targetElement = document.getElementById(elementIdToFocus);
|
||||||
|
|
||||||
|
if (targetElement) {
|
||||||
|
const focusableParent = this.findFocusableParent(targetElement);
|
||||||
|
if (focusableParent) {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (focusableParent) {
|
||||||
|
focusableParent.focus();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
sessionStorage.removeItem("focusedElementId");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (ev && (ev.target as HTMLElement).id === "notificationConsoleHeader") {
|
if (ev && (ev.target as HTMLElement).id === "notificationConsoleHeader") {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { SettingsPane } from "./SettingsPane";
|
|||||||
|
|
||||||
describe("Settings Pane", () => {
|
describe("Settings Pane", () => {
|
||||||
it("should render Default properly", () => {
|
it("should render Default properly", () => {
|
||||||
const wrapper = shallow(<SettingsPane explorer={null} />);
|
const wrapper = shallow(<SettingsPane />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ describe("Settings Pane", () => {
|
|||||||
},
|
},
|
||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
});
|
});
|
||||||
const wrapper = shallow(<SettingsPane explorer={null} />);
|
const wrapper = shallow(<SettingsPane />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,20 +16,13 @@ import * as StringUtility from "Shared/StringUtility";
|
|||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import { logConsoleInfo } from "Utils/NotificationConsoleUtils";
|
import { logConsoleInfo } from "Utils/NotificationConsoleUtils";
|
||||||
import * as PriorityBasedExecutionUtils from "Utils/PriorityBasedExecutionUtils";
|
import * as PriorityBasedExecutionUtils from "Utils/PriorityBasedExecutionUtils";
|
||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
|
||||||
import { useSidePanel } from "hooks/useSidePanel";
|
import { useSidePanel } from "hooks/useSidePanel";
|
||||||
import React, { FunctionComponent, useState } from "react";
|
import React, { FunctionComponent, useState } from "react";
|
||||||
import Explorer from "../../Explorer";
|
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
|
|
||||||
export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
export const SettingsPane: FunctionComponent = () => {
|
||||||
explorer,
|
|
||||||
}: {
|
|
||||||
explorer: Explorer;
|
|
||||||
}): JSX.Element => {
|
|
||||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||||
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
||||||
const [refreshExplorer, setRefreshExplorer] = useState<boolean>(false);
|
|
||||||
const [pageOption, setPageOption] = useState<string>(
|
const [pageOption, setPageOption] = useState<string>(
|
||||||
LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage) === Constants.Queries.unlimitedItemsPerPage
|
LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage) === Constants.Queries.unlimitedItemsPerPage
|
||||||
? Constants.Queries.UnlimitedPageOption
|
? Constants.Queries.UnlimitedPageOption
|
||||||
@@ -85,17 +78,13 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
? LocalStorageUtility.getEntryString(StorageKey.PriorityLevel)
|
? LocalStorageUtility.getEntryString(StorageKey.PriorityLevel)
|
||||||
: Constants.PriorityLevel.Default,
|
: Constants.PriorityLevel.Default,
|
||||||
);
|
);
|
||||||
const [copilotSampleDBEnabled, setCopilotSampleDBEnabled] = useState<boolean>(
|
|
||||||
LocalStorageUtility.getEntryString(StorageKey.CopilotSampleDBEnabled) === "true",
|
|
||||||
);
|
|
||||||
const explorerVersion = configContext.gitSha;
|
const explorerVersion = configContext.gitSha;
|
||||||
const shouldShowQueryPageOptions = userContext.apiType === "SQL";
|
const shouldShowQueryPageOptions = userContext.apiType === "SQL";
|
||||||
const shouldShowGraphAutoVizOption = userContext.apiType === "Gremlin";
|
const shouldShowGraphAutoVizOption = userContext.apiType === "Gremlin";
|
||||||
const shouldShowCrossPartitionOption = userContext.apiType !== "Gremlin";
|
const shouldShowCrossPartitionOption = userContext.apiType !== "Gremlin";
|
||||||
const shouldShowParallelismOption = userContext.apiType !== "Gremlin";
|
const shouldShowParallelismOption = userContext.apiType !== "Gremlin";
|
||||||
const shouldShowPriorityLevelOption = PriorityBasedExecutionUtils.isFeatureEnabled();
|
const shouldShowPriorityLevelOption = PriorityBasedExecutionUtils.isFeatureEnabled();
|
||||||
const shouldShowCopilotSampleDBOption = userContext.apiType === "SQL" && useQueryCopilot.getState().copilotEnabled;
|
const handlerOnSubmit = () => {
|
||||||
const handlerOnSubmit = async () => {
|
|
||||||
setIsExecuting(true);
|
setIsExecuting(true);
|
||||||
|
|
||||||
LocalStorageUtility.setEntryNumber(
|
LocalStorageUtility.setEntryNumber(
|
||||||
@@ -111,7 +100,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, crossPartitionQueryEnabled.toString());
|
LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, crossPartitionQueryEnabled.toString());
|
||||||
LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, maxDegreeOfParallelism);
|
LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, maxDegreeOfParallelism);
|
||||||
LocalStorageUtility.setEntryString(StorageKey.PriorityLevel, priorityLevel.toString());
|
LocalStorageUtility.setEntryString(StorageKey.PriorityLevel, priorityLevel.toString());
|
||||||
LocalStorageUtility.setEntryString(StorageKey.CopilotSampleDBEnabled, copilotSampleDBEnabled.toString());
|
|
||||||
|
|
||||||
if (shouldShowGraphAutoVizOption) {
|
if (shouldShowGraphAutoVizOption) {
|
||||||
LocalStorageUtility.setEntryBoolean(
|
LocalStorageUtility.setEntryBoolean(
|
||||||
@@ -151,7 +139,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
logConsoleInfo(
|
logConsoleInfo(
|
||||||
`Updated query setting to ${LocalStorageUtility.getEntryString(StorageKey.SetPartitionKeyUndefined)}`,
|
`Updated query setting to ${LocalStorageUtility.getEntryString(StorageKey.SetPartitionKeyUndefined)}`,
|
||||||
);
|
);
|
||||||
refreshExplorer && (await explorer.refreshExplorer());
|
|
||||||
closeSidePanel();
|
closeSidePanel();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -231,12 +218,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(!refreshExplorer);
|
|
||||||
};
|
|
||||||
|
|
||||||
const choiceButtonStyles = {
|
const choiceButtonStyles = {
|
||||||
root: {
|
root: {
|
||||||
clear: "both",
|
clear: "both",
|
||||||
@@ -453,7 +434,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="settingsSection">
|
<div className="settingsSection">
|
||||||
<div className="settingsSectionPart settingsSectionInlineCheckbox">
|
<div className="settingsSectionPart">
|
||||||
<div className="settingsSectionLabel">
|
<div className="settingsSectionLabel">
|
||||||
Enable container pagination
|
Enable container pagination
|
||||||
<InfoTooltip>
|
<InfoTooltip>
|
||||||
@@ -473,7 +454,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
</div>
|
</div>
|
||||||
{shouldShowCrossPartitionOption && (
|
{shouldShowCrossPartitionOption && (
|
||||||
<div className="settingsSection">
|
<div className="settingsSection">
|
||||||
<div className="settingsSectionPart settingsSectionInlineCheckbox">
|
<div className="settingsSectionPart">
|
||||||
<div className="settingsSectionLabel">
|
<div className="settingsSectionLabel">
|
||||||
Enable cross-partition query
|
Enable cross-partition query
|
||||||
<InfoTooltip>
|
<InfoTooltip>
|
||||||
@@ -564,30 +545,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{shouldShowCopilotSampleDBOption && (
|
|
||||||
<div className="settingsSection">
|
|
||||||
<div className="settingsSectionPart settingsSectionInlineCheckbox">
|
|
||||||
<div className="settingsSectionLabel">
|
|
||||||
Enable sample database
|
|
||||||
<InfoTooltip>
|
|
||||||
This is a sample database and collection with synthetic product data you can use to explore using
|
|
||||||
NoSQL queries and Copilot. This will appear as another database in the Data Explorer UI, and is
|
|
||||||
created by, and maintained by Microsoft at no cost to you.
|
|
||||||
</InfoTooltip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Checkbox
|
|
||||||
styles={{
|
|
||||||
label: { padding: 0 },
|
|
||||||
}}
|
|
||||||
className="padding"
|
|
||||||
ariaLabel="Enable sample db for Copilot"
|
|
||||||
checked={copilotSampleDBEnabled}
|
|
||||||
onChange={handleSampleDatabaseChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="settingsSection">
|
<div className="settingsSection">
|
||||||
<div className="settingsSectionPart">
|
<div className="settingsSectionPart">
|
||||||
<div className="settingsSectionLabel">Explorer Version</div>
|
<div className="settingsSectionLabel">Explorer Version</div>
|
||||||
|
|||||||
@@ -274,7 +274,7 @@ exports[`Settings Pane should render Default properly 1`] = `
|
|||||||
className="settingsSection"
|
className="settingsSection"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="settingsSectionPart settingsSectionInlineCheckbox"
|
className="settingsSectionPart"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="settingsSectionLabel"
|
className="settingsSectionLabel"
|
||||||
@@ -303,7 +303,7 @@ exports[`Settings Pane should render Default properly 1`] = `
|
|||||||
className="settingsSection"
|
className="settingsSection"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="settingsSectionPart settingsSectionInlineCheckbox"
|
className="settingsSectionPart"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="settingsSectionLabel"
|
className="settingsSectionLabel"
|
||||||
@@ -521,7 +521,7 @@ exports[`Settings Pane should render Gremlin properly 1`] = `
|
|||||||
className="settingsSection"
|
className="settingsSection"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="settingsSectionPart settingsSectionInlineCheckbox"
|
className="settingsSectionPart"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="settingsSectionLabel"
|
className="settingsSectionLabel"
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ exports[`PaneContainerComponent test should be resize if notification console is
|
|||||||
customWidth="440px"
|
customWidth="440px"
|
||||||
headerClassName="panelHeader"
|
headerClassName="panelHeader"
|
||||||
headerText="test"
|
headerText="test"
|
||||||
isFooterAtBottom={true}
|
|
||||||
isLightDismiss={true}
|
isLightDismiss={true}
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
onDismiss={[Function]}
|
onDismiss={[Function]}
|
||||||
@@ -19,9 +18,9 @@ exports[`PaneContainerComponent test should be resize if notification console is
|
|||||||
Object {
|
Object {
|
||||||
"commands": Object {
|
"commands": Object {
|
||||||
"marginTop": 8,
|
"marginTop": 8,
|
||||||
"paddingTop": 0,
|
|
||||||
},
|
},
|
||||||
"content": Object {
|
"content": Object {
|
||||||
|
"height": "100%",
|
||||||
"padding": 0,
|
"padding": 0,
|
||||||
},
|
},
|
||||||
"header": Object {
|
"header": Object {
|
||||||
@@ -30,6 +29,9 @@ exports[`PaneContainerComponent test should be resize if notification console is
|
|||||||
"navigation": Object {
|
"navigation": Object {
|
||||||
"borderBottom": "1px solid #cccccc",
|
"borderBottom": "1px solid #cccccc",
|
||||||
},
|
},
|
||||||
|
"scrollableContent": Object {
|
||||||
|
"height": "100%",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
type={7}
|
type={7}
|
||||||
@@ -46,7 +48,6 @@ exports[`PaneContainerComponent test should render with panel content and header
|
|||||||
customWidth="440px"
|
customWidth="440px"
|
||||||
headerClassName="panelHeader"
|
headerClassName="panelHeader"
|
||||||
headerText="test"
|
headerText="test"
|
||||||
isFooterAtBottom={true}
|
|
||||||
isLightDismiss={true}
|
isLightDismiss={true}
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
onDismiss={[Function]}
|
onDismiss={[Function]}
|
||||||
@@ -59,9 +60,9 @@ exports[`PaneContainerComponent test should render with panel content and header
|
|||||||
Object {
|
Object {
|
||||||
"commands": Object {
|
"commands": Object {
|
||||||
"marginTop": 8,
|
"marginTop": 8,
|
||||||
"paddingTop": 0,
|
|
||||||
},
|
},
|
||||||
"content": Object {
|
"content": Object {
|
||||||
|
"height": "100%",
|
||||||
"padding": 0,
|
"padding": 0,
|
||||||
},
|
},
|
||||||
"header": Object {
|
"header": Object {
|
||||||
@@ -70,6 +71,9 @@ exports[`PaneContainerComponent test should render with panel content and header
|
|||||||
"navigation": Object {
|
"navigation": Object {
|
||||||
"borderBottom": "1px solid #cccccc",
|
"borderBottom": "1px solid #cccccc",
|
||||||
},
|
},
|
||||||
|
"scrollableContent": Object {
|
||||||
|
"height": "100%",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
type={7}
|
type={7}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ export const CopyPopup = ({
|
|||||||
|
|
||||||
return showCopyPopup ? (
|
return showCopyPopup ? (
|
||||||
<Stack
|
<Stack
|
||||||
role="status"
|
|
||||||
style={{
|
style={{
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
width: 345,
|
width: 345,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ exports[`Copy Popup snapshot test should render when showCopyPopup is false 1`]
|
|||||||
|
|
||||||
exports[`Copy Popup snapshot test should render when showCopyPopup is true 1`] = `
|
exports[`Copy Popup snapshot test should render when showCopyPopup is true 1`] = `
|
||||||
<Stack
|
<Stack
|
||||||
role="status"
|
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"background": "#FFFFFF",
|
"background": "#FFFFFF",
|
||||||
|
|||||||
@@ -303,29 +303,15 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
resetButtonState();
|
resetButtonState();
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAriaLabel = () => {
|
|
||||||
if (isGeneratingQuery === null) {
|
|
||||||
return " ";
|
|
||||||
} else if (isGeneratingQuery) {
|
|
||||||
return "Content is loading";
|
|
||||||
} else {
|
|
||||||
return "Content is updated";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
showTeachingBubble();
|
showTeachingBubble();
|
||||||
useTabs.getState().setIsQueryErrorThrown(false);
|
useTabs.getState().setIsQueryErrorThrown(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack className="copilot-prompt-pane" styles={{ root: { backgroundColor: "#FAFAFA", padding: "16px 24px 0px" } }}>
|
||||||
className="copilot-prompt-pane"
|
|
||||||
styles={{ root: { backgroundColor: "#FAFAFA", padding: "16px 24px 0px" } }}
|
|
||||||
id="copilot-textfield-label"
|
|
||||||
>
|
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<Image src={CopilotIcon} style={{ width: 24, height: 24 }} alt="Copilot" role="none" />
|
<Image src={CopilotIcon} style={{ width: 24, height: 24 }} />
|
||||||
<Text style={{ marginLeft: 8, fontWeight: 600, fontSize: 16 }}>Copilot</Text>
|
<Text style={{ marginLeft: 8, fontWeight: 600, fontSize: 16 }}>Copilot</Text>
|
||||||
<IconButton
|
<IconButton
|
||||||
iconProps={{ imageProps: { src: errorIcon } }}
|
iconProps={{ imageProps: { src: errorIcon } }}
|
||||||
@@ -362,7 +348,6 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
disabled={isGeneratingQuery}
|
disabled={isGeneratingQuery}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
placeholder="Ask a question in natural language and we’ll generate the query for you."
|
placeholder="Ask a question in natural language and we’ll generate the query for you."
|
||||||
aria-labelledby="copilot-textfield-label"
|
|
||||||
/>
|
/>
|
||||||
{copilotTeachingBubbleVisible && (
|
{copilotTeachingBubbleVisible && (
|
||||||
<TeachingBubble
|
<TeachingBubble
|
||||||
@@ -392,11 +377,8 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
disabled={isGeneratingQuery || !userPrompt.trim()}
|
disabled={isGeneratingQuery || !userPrompt.trim()}
|
||||||
style={{ marginLeft: 8 }}
|
style={{ marginLeft: 8 }}
|
||||||
onClick={() => startGenerateQueryProcess()}
|
onClick={() => startGenerateQueryProcess()}
|
||||||
aria-label="Send"
|
|
||||||
/>
|
/>
|
||||||
<div role="alert" aria-label={getAriaLabel()}>
|
{isGeneratingQuery && <Spinner style={{ marginLeft: 8 }} />}
|
||||||
{isGeneratingQuery && <Spinner style={{ marginLeft: 8 }} />}
|
|
||||||
</div>
|
|
||||||
{showSamplePrompts && (
|
{showSamplePrompts && (
|
||||||
<Callout
|
<Callout
|
||||||
styles={{ root: { minWidth: 400, maxWidth: "70vw" } }}
|
styles={{ root: { minWidth: 400, maxWidth: "70vw" } }}
|
||||||
@@ -502,7 +484,7 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
<Stack style={{ margin: "8px 0" }}>
|
<Stack style={{ margin: "8px 0" }}>
|
||||||
<Text style={{ fontSize: 12 }}>
|
<Text style={{ fontSize: 12 }}>
|
||||||
AI-generated content can have mistakes. Make sure it's accurate and appropriate before using it.{" "}
|
AI-generated content can have mistakes. Make sure it's accurate and appropriate before using it.{" "}
|
||||||
<Link href="https://aka.ms/cdb-copilot-preview-terms" target="_blank" style={{ color: "#0072c9" }}>
|
<Link href="https://aka.ms/cdb-copilot-preview-terms" target="_blank">
|
||||||
Read preview terms
|
Read preview terms
|
||||||
</Link>
|
</Link>
|
||||||
{showErrorMessageBar && (
|
{showErrorMessageBar && (
|
||||||
@@ -570,7 +552,6 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
id="likeBtn"
|
id="likeBtn"
|
||||||
style={{ marginLeft: 20 }}
|
style={{ marginLeft: 20 }}
|
||||||
iconProps={{ iconName: likeQuery === true ? "LikeSolid" : "Like" }}
|
iconProps={{ iconName: likeQuery === true ? "LikeSolid" : "Like" }}
|
||||||
aria-label="Like"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowCallout(!likeQuery);
|
setShowCallout(!likeQuery);
|
||||||
setLikeQuery(!likeQuery);
|
setLikeQuery(!likeQuery);
|
||||||
@@ -590,7 +571,6 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
setDislikeQuery(!dislikeQuery);
|
setDislikeQuery(!dislikeQuery);
|
||||||
setShowCallout(false);
|
setShowCallout(false);
|
||||||
}}
|
}}
|
||||||
aria-label="Dislike"
|
|
||||||
/>
|
/>
|
||||||
<Separator vertical style={{ color: "#EDEBE9" }} />
|
<Separator vertical style={{ color: "#EDEBE9" }} />
|
||||||
<CommandBarButton
|
<CommandBarButton
|
||||||
|
|||||||
@@ -15,12 +15,7 @@ import { MinimalQueryIterator } from "Common/IteratorUtilities";
|
|||||||
import { createUri } from "Common/UrlUtility";
|
import { createUri } from "Common/UrlUtility";
|
||||||
import { queryDocumentsPage } from "Common/dataAccess/queryDocumentsPage";
|
import { queryDocumentsPage } from "Common/dataAccess/queryDocumentsPage";
|
||||||
import { configContext } from "ConfigContext";
|
import { configContext } from "ConfigContext";
|
||||||
import {
|
import { ContainerConnectionInfo, CopilotEnabledConfiguration, IProvisionData } from "Contracts/DataModels";
|
||||||
ContainerConnectionInfo,
|
|
||||||
CopilotEnabledConfiguration,
|
|
||||||
FeatureRegistration,
|
|
||||||
IProvisionData,
|
|
||||||
} from "Contracts/DataModels";
|
|
||||||
import { AuthorizationTokenHeaderMetadata, QueryResults } from "Contracts/ViewModels";
|
import { AuthorizationTokenHeaderMetadata, QueryResults } from "Contracts/ViewModels";
|
||||||
import { useDialog } from "Explorer/Controls/Dialog";
|
import { useDialog } from "Explorer/Controls/Dialog";
|
||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
@@ -57,28 +52,6 @@ async function fetchWithTimeout(
|
|||||||
return response;
|
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 };
|
|
||||||
|
|
||||||
let response;
|
|
||||||
|
|
||||||
try {
|
|
||||||
response = await fetchWithTimeout(url, headers);
|
|
||||||
} catch (error) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response?.ok) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const featureRegistration = (await response?.json()) as FeatureRegistration;
|
|
||||||
return featureRegistration?.properties?.state === "Registered";
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getCopilotEnabled = async (): Promise<boolean> => {
|
export const getCopilotEnabled = async (): Promise<boolean> => {
|
||||||
const url = `${configContext.BACKEND_ENDPOINT}/api/portalsettings/querycopilot`;
|
const url = `${configContext.BACKEND_ENDPOINT}/api/portalsettings/querycopilot`;
|
||||||
const authorizationHeader: AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
|
const authorizationHeader: AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ exports[`Footer snapshot test should not pass if no text 1`] = `
|
|||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
disabled={null}
|
disabled={false}
|
||||||
multiline={true}
|
multiline={true}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
@@ -74,7 +74,7 @@ exports[`Footer snapshot test should not pass if no text 1`] = `
|
|||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
<CustomizedIconButton
|
<CustomizedIconButton
|
||||||
disabled={null}
|
disabled={false}
|
||||||
iconProps={
|
iconProps={
|
||||||
Object {
|
Object {
|
||||||
"iconName": "Send",
|
"iconName": "Send",
|
||||||
@@ -134,7 +134,7 @@ exports[`Footer snapshot test should not pass text with non enter key 1`] = `
|
|||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
disabled={null}
|
disabled={false}
|
||||||
multiline={true}
|
multiline={true}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
@@ -159,7 +159,7 @@ exports[`Footer snapshot test should not pass text with non enter key 1`] = `
|
|||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
<CustomizedIconButton
|
<CustomizedIconButton
|
||||||
disabled={null}
|
disabled={false}
|
||||||
iconProps={
|
iconProps={
|
||||||
Object {
|
Object {
|
||||||
"iconName": "Send",
|
"iconName": "Send",
|
||||||
@@ -219,7 +219,7 @@ exports[`Footer snapshot test should open sample prompts on button click 1`] = `
|
|||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
disabled={null}
|
disabled={false}
|
||||||
multiline={true}
|
multiline={true}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
@@ -244,7 +244,7 @@ exports[`Footer snapshot test should open sample prompts on button click 1`] = `
|
|||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
<CustomizedIconButton
|
<CustomizedIconButton
|
||||||
disabled={null}
|
disabled={false}
|
||||||
iconProps={
|
iconProps={
|
||||||
Object {
|
Object {
|
||||||
"iconName": "Send",
|
"iconName": "Send",
|
||||||
@@ -304,7 +304,7 @@ exports[`Footer snapshot test should pass text with enter key 1`] = `
|
|||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
disabled={null}
|
disabled={false}
|
||||||
multiline={true}
|
multiline={true}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
@@ -329,7 +329,7 @@ exports[`Footer snapshot test should pass text with enter key 1`] = `
|
|||||||
value="test message"
|
value="test message"
|
||||||
/>
|
/>
|
||||||
<CustomizedIconButton
|
<CustomizedIconButton
|
||||||
disabled={null}
|
disabled={false}
|
||||||
iconProps={
|
iconProps={
|
||||||
Object {
|
Object {
|
||||||
"iconName": "Send",
|
"iconName": "Send",
|
||||||
@@ -389,7 +389,7 @@ exports[`Footer snapshot test should pass text with icon button 1`] = `
|
|||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
disabled={null}
|
disabled={false}
|
||||||
multiline={true}
|
multiline={true}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
@@ -414,7 +414,7 @@ exports[`Footer snapshot test should pass text with icon button 1`] = `
|
|||||||
value="test message"
|
value="test message"
|
||||||
/>
|
/>
|
||||||
<CustomizedIconButton
|
<CustomizedIconButton
|
||||||
disabled={null}
|
disabled={false}
|
||||||
iconProps={
|
iconProps={
|
||||||
Object {
|
Object {
|
||||||
"iconName": "Send",
|
"iconName": "Send",
|
||||||
@@ -474,7 +474,7 @@ exports[`Footer snapshot test should update user input 1`] = `
|
|||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
disabled={null}
|
disabled={false}
|
||||||
multiline={true}
|
multiline={true}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
@@ -499,7 +499,7 @@ exports[`Footer snapshot test should update user input 1`] = `
|
|||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
<CustomizedIconButton
|
<CustomizedIconButton
|
||||||
disabled={null}
|
disabled={false}
|
||||||
iconProps={
|
iconProps={
|
||||||
Object {
|
Object {
|
||||||
"iconName": "Send",
|
"iconName": "Send",
|
||||||
|
|||||||
@@ -148,25 +148,23 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack horizontal tokens={{ childrenGap: 16 }}>
|
<Stack horizontal tokens={{ childrenGap: 16 }}>
|
||||||
{useQueryCopilot.getState().copilotEnabled && useQueryCopilot.getState().copilotSampleDBEnabled && (
|
<SplashScreenButton
|
||||||
<SplashScreenButton
|
imgSrc={CopilotIcon}
|
||||||
imgSrc={CopilotIcon}
|
title={"Query faster with Copilot"}
|
||||||
title={"Query faster with Copilot"}
|
description={
|
||||||
description={
|
"Copilot is your AI buddy that helps you write Azure Cosmos DB queries like a pro. Try it using our sample data set now!"
|
||||||
"Copilot is your AI buddy that helps you write Azure Cosmos DB queries like a pro. Try it using our sample data set now!"
|
}
|
||||||
|
onClick={() => {
|
||||||
|
const copilotVersion = userContext.features.copilotVersion;
|
||||||
|
if (copilotVersion === "v1.0") {
|
||||||
|
useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot);
|
||||||
|
} else if (copilotVersion === "v2.0") {
|
||||||
|
const sampleCollection = useDatabases.getState().sampleDataResourceTokenCollection;
|
||||||
|
sampleCollection.onNewQueryClick(sampleCollection, undefined);
|
||||||
}
|
}
|
||||||
onClick={() => {
|
traceOpen(Action.OpenQueryCopilotFromSplashScreen, { apiType: userContext.apiType });
|
||||||
const copilotVersion = userContext.features.copilotVersion;
|
}}
|
||||||
if (copilotVersion === "v1.0") {
|
/>
|
||||||
useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot);
|
|
||||||
} else if (copilotVersion === "v2.0") {
|
|
||||||
const sampleCollection = useDatabases.getState().sampleDataResourceTokenCollection;
|
|
||||||
sampleCollection.onNewQueryClick(sampleCollection, undefined);
|
|
||||||
}
|
|
||||||
traceOpen(Action.OpenQueryCopilotFromSplashScreen, { apiType: userContext.apiType });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<SplashScreenButton
|
<SplashScreenButton
|
||||||
imgSrc={ConnectIcon}
|
imgSrc={ConnectIcon}
|
||||||
title={"Connect"}
|
title={"Connect"}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as _ from "underscore";
|
import * as _ from "underscore";
|
||||||
|
|
||||||
import * as DataTable from "datatables.net-dt";
|
|
||||||
import loadingIndicator3Squares from "../../../../images/LoadingIndicator_3Squares.gif";
|
|
||||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
||||||
import * as Constants from "../Constants";
|
import * as Constants from "../Constants";
|
||||||
import * as Entities from "../Entities";
|
import * as Entities from "../Entities";
|
||||||
@@ -96,7 +94,7 @@ function createDataTable(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
tableEntityListViewModel.table = DataTableBuilder.createDataTable($dataTable, <DataTable.Config>{
|
tableEntityListViewModel.table = DataTableBuilder.createDataTable($dataTable, <DataTables.Settings>{
|
||||||
// WARNING!!! SECURITY: If you add new columns, make sure you encode them if they are user strings from Azure (see encodeText)
|
// WARNING!!! SECURITY: If you add new columns, make sure you encode them if they are user strings from Azure (see encodeText)
|
||||||
// so that they don't get interpreted as HTML in our page.
|
// so that they don't get interpreted as HTML in our page.
|
||||||
colReorder: true,
|
colReorder: true,
|
||||||
@@ -118,7 +116,7 @@ function createDataTable(
|
|||||||
sPrevious: "<",
|
sPrevious: "<",
|
||||||
sLast: ">>",
|
sLast: ">>",
|
||||||
},
|
},
|
||||||
sProcessing: `<img style="width: 28px; height: 6px; " src="${loadingIndicator3Squares}">`,
|
sProcessing: '<img style="width: 28px; height: 6px; " src="images/LoadingIndicator_3Squares.gif">',
|
||||||
oAria: {
|
oAria: {
|
||||||
sSortAscending: "",
|
sSortAscending: "",
|
||||||
sSortDescending: "",
|
sSortDescending: "",
|
||||||
@@ -347,7 +345,7 @@ function updateSelectionStatus(oSettings: any): void {
|
|||||||
// TODO consider centralizing this "post-command" logic into some sort of Command Manager entity.
|
// TODO consider centralizing this "post-command" logic into some sort of Command Manager entity.
|
||||||
// See VSO:166520: "[Storage Explorer] Consider adding a 'command manager' to track command post-effects."
|
// See VSO:166520: "[Storage Explorer] Consider adding a 'command manager' to track command post-effects."
|
||||||
function updateDataTableFocus(queryTablesTabId: string): void {
|
function updateDataTableFocus(queryTablesTabId: string): void {
|
||||||
var $activeElement: JQuery<Element> = $(document.activeElement);
|
var $activeElement: JQuery = $(document.activeElement);
|
||||||
var isFocusLost: boolean = $activeElement.is("body"); // When focus is lost, "body" becomes the active element.
|
var isFocusLost: boolean = $activeElement.is("body"); // When focus is lost, "body" becomes the active element.
|
||||||
var storageExplorerFrameHasFocus: boolean = document.hasFocus();
|
var storageExplorerFrameHasFocus: boolean = document.hasFocus();
|
||||||
var operationManager = tableEntityListViewModelMap[queryTablesTabId].operationManager;
|
var operationManager = tableEntityListViewModelMap[queryTablesTabId].operationManager;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as DataTable from "datatables.net-dt";
|
|
||||||
import * as Utilities from "../Utilities";
|
import * as Utilities from "../Utilities";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -9,7 +8,7 @@ import * as Utilities from "../Utilities";
|
|||||||
* @param{$dataTableElem} JQuery data table element
|
* @param{$dataTableElem} JQuery data table element
|
||||||
* @param{$settings} Settings to use when creating the data table
|
* @param{$settings} Settings to use when creating the data table
|
||||||
*/
|
*/
|
||||||
export function createDataTable($dataTableElem: JQuery, settings: any): DataTable.Api<HTMLElement> {
|
export function createDataTable($dataTableElem: JQuery, settings: any): DataTables.DataTable {
|
||||||
return $dataTableElem.DataTable(applyDefaultRendering(settings));
|
return $dataTableElem.DataTable(applyDefaultRendering(settings));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,14 +18,14 @@ export function createDataTable($dataTableElem: JQuery, settings: any): DataTabl
|
|||||||
* @param{settings} The settings to check
|
* @param{settings} The settings to check
|
||||||
* @return The given settings with all columns having a rendering function
|
* @return The given settings with all columns having a rendering function
|
||||||
*/
|
*/
|
||||||
function applyDefaultRendering(settings: DataTable.Config): any {
|
function applyDefaultRendering(settings: any): DataTables.SettingsLegacy {
|
||||||
var tableColumns: any[] = null;
|
var tableColumns: DataTables.ColumnLegacy[] = null;
|
||||||
|
|
||||||
if (settings.columns) {
|
if (settings.aoColumns) {
|
||||||
tableColumns = settings.columns;
|
tableColumns = settings.aoColumns;
|
||||||
} else if (settings.columnDefs) {
|
} else if (settings.aoColumnDefs) {
|
||||||
// for tables we use aoColumnDefs instead of aoColumns
|
// for tables we use aoColumnDefs instead of aoColumns
|
||||||
tableColumns = settings.columnDefs;
|
tableColumns = settings.aoColumnDefs;
|
||||||
}
|
}
|
||||||
|
|
||||||
// either the settings had no columns defined, or they were called
|
// either the settings had no columns defined, or they were called
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
|
|
||||||
import * as Constants from "../Constants";
|
|
||||||
import * as Entities from "../Entities";
|
|
||||||
import * as Utilities from "../Utilities";
|
|
||||||
import * as DataTableOperations from "./DataTableOperations";
|
import * as DataTableOperations from "./DataTableOperations";
|
||||||
|
import * as Constants from "../Constants";
|
||||||
import TableCommands from "./TableCommands";
|
import TableCommands from "./TableCommands";
|
||||||
import TableEntityListViewModel from "./TableEntityListViewModel";
|
import TableEntityListViewModel from "./TableEntityListViewModel";
|
||||||
|
import * as Utilities from "../Utilities";
|
||||||
|
import * as Entities from "../Entities";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Base class for data table row selection.
|
* Base class for data table row selection.
|
||||||
@@ -13,9 +13,9 @@ import TableEntityListViewModel from "./TableEntityListViewModel";
|
|||||||
export default class DataTableOperationManager {
|
export default class DataTableOperationManager {
|
||||||
private _tableEntityListViewModel: TableEntityListViewModel;
|
private _tableEntityListViewModel: TableEntityListViewModel;
|
||||||
private _tableCommands: TableCommands;
|
private _tableCommands: TableCommands;
|
||||||
private dataTable: JQuery<Element>;
|
private dataTable: JQuery;
|
||||||
|
|
||||||
constructor(table: JQuery<Element>, viewModel: TableEntityListViewModel, tableCommands: TableCommands) {
|
constructor(table: JQuery, viewModel: TableEntityListViewModel, tableCommands: TableCommands) {
|
||||||
this.dataTable = table;
|
this.dataTable = table;
|
||||||
this._tableEntityListViewModel = viewModel;
|
this._tableEntityListViewModel = viewModel;
|
||||||
this._tableCommands = tableCommands;
|
this._tableCommands = tableCommands;
|
||||||
@@ -25,7 +25,7 @@ export default class DataTableOperationManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private click = (event: JQueryEventObject) => {
|
private click = (event: JQueryEventObject) => {
|
||||||
var elem: JQuery<Element> = $(event.currentTarget);
|
var elem: JQuery = $(event.currentTarget);
|
||||||
this.updateLastSelectedItem(elem, event.shiftKey);
|
this.updateLastSelectedItem(elem, event.shiftKey);
|
||||||
|
|
||||||
if (Utilities.isEnvironmentCtrlPressed(event)) {
|
if (Utilities.isEnvironmentCtrlPressed(event)) {
|
||||||
@@ -48,7 +48,7 @@ export default class DataTableOperationManager {
|
|||||||
|
|
||||||
if (isUpArrowKey || isDownArrowKey) {
|
if (isUpArrowKey || isDownArrowKey) {
|
||||||
var lastSelectedItem: Entities.ITableEntity = this._tableEntityListViewModel.lastSelectedItem;
|
var lastSelectedItem: Entities.ITableEntity = this._tableEntityListViewModel.lastSelectedItem;
|
||||||
var dataTableRows: JQuery<Element> = $(Constants.htmlSelectors.dataTableAllRowsSelector);
|
var dataTableRows: JQuery = $(Constants.htmlSelectors.dataTableAllRowsSelector);
|
||||||
var maximumIndex = dataTableRows.length - 1;
|
var maximumIndex = dataTableRows.length - 1;
|
||||||
|
|
||||||
// If can't find an index for lastSelectedItem, then either no item is previously selected or it goes across page.
|
// If can't find an index for lastSelectedItem, then either no item is previously selected or it goes across page.
|
||||||
@@ -60,7 +60,7 @@ export default class DataTableOperationManager {
|
|||||||
: -1;
|
: -1;
|
||||||
var nextIndex: number = isUpArrowKey ? lastSelectedItemIndex - 1 : lastSelectedItemIndex + 1;
|
var nextIndex: number = isUpArrowKey ? lastSelectedItemIndex - 1 : lastSelectedItemIndex + 1;
|
||||||
var safeIndex: number = Utilities.ensureBetweenBounds(nextIndex, 0, maximumIndex);
|
var safeIndex: number = Utilities.ensureBetweenBounds(nextIndex, 0, maximumIndex);
|
||||||
var selectedRowElement: JQuery<Element> = dataTableRows.eq(safeIndex);
|
var selectedRowElement: JQuery = dataTableRows.eq(safeIndex);
|
||||||
|
|
||||||
if (selectedRowElement) {
|
if (selectedRowElement) {
|
||||||
if (event.shiftKey) {
|
if (event.shiftKey) {
|
||||||
@@ -143,13 +143,13 @@ export default class DataTableOperationManager {
|
|||||||
return handled;
|
return handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getEntityIdentity($elem: JQuery<Element>): Entities.ITableEntityIdentity {
|
private getEntityIdentity($elem: JQuery): Entities.ITableEntityIdentity {
|
||||||
return {
|
return {
|
||||||
RowKey: $elem.attr(Constants.htmlAttributeNames.dataTableRowKeyAttr),
|
RowKey: $elem.attr(Constants.htmlAttributeNames.dataTableRowKeyAttr),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateLastSelectedItem($elem: JQuery<Element>, isShiftSelect: boolean) {
|
private updateLastSelectedItem($elem: JQuery, isShiftSelect: boolean) {
|
||||||
var entityIdentity: Entities.ITableEntityIdentity = this.getEntityIdentity($elem);
|
var entityIdentity: Entities.ITableEntityIdentity = this.getEntityIdentity($elem);
|
||||||
var entity = this._tableEntityListViewModel.getItemFromCurrentPage(
|
var entity = this._tableEntityListViewModel.getItemFromCurrentPage(
|
||||||
this._tableEntityListViewModel.getTableEntityKeys(entityIdentity.RowKey),
|
this._tableEntityListViewModel.getTableEntityKeys(entityIdentity.RowKey),
|
||||||
@@ -162,7 +162,7 @@ export default class DataTableOperationManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private applySingleSelection($elem: JQuery<Element>) {
|
private applySingleSelection($elem: JQuery) {
|
||||||
if ($elem) {
|
if ($elem) {
|
||||||
var entityIdentity: Entities.ITableEntityIdentity = this.getEntityIdentity($elem);
|
var entityIdentity: Entities.ITableEntityIdentity = this.getEntityIdentity($elem);
|
||||||
|
|
||||||
@@ -179,7 +179,7 @@ export default class DataTableOperationManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private applyCtrlSelection($elem: JQuery<Element>): void {
|
private applyCtrlSelection($elem: JQuery): void {
|
||||||
var koSelected: ko.ObservableArray<Entities.ITableEntity> = this._tableEntityListViewModel
|
var koSelected: ko.ObservableArray<Entities.ITableEntity> = this._tableEntityListViewModel
|
||||||
? this._tableEntityListViewModel.selected
|
? this._tableEntityListViewModel.selected
|
||||||
: null;
|
: null;
|
||||||
@@ -200,7 +200,7 @@ export default class DataTableOperationManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private applyShiftSelection($elem: JQuery<Element>): void {
|
private applyShiftSelection($elem: JQuery): void {
|
||||||
var anchorItem = this._tableEntityListViewModel.lastSelectedAnchorItem;
|
var anchorItem = this._tableEntityListViewModel.lastSelectedAnchorItem;
|
||||||
|
|
||||||
// If anchor item doesn't exist, use the first available item of current page instead
|
// If anchor item doesn't exist, use the first available item of current page instead
|
||||||
@@ -228,7 +228,7 @@ export default class DataTableOperationManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private applyContextMenuSelection($elem: JQuery<Element>) {
|
private applyContextMenuSelection($elem: JQuery) {
|
||||||
var entityIdentity: Entities.ITableEntityIdentity = this.getEntityIdentity($elem);
|
var entityIdentity: Entities.ITableEntityIdentity = this.getEntityIdentity($elem);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as DataTables from "datatables.net";
|
|
||||||
import Q from "q";
|
import Q from "q";
|
||||||
import _ from "underscore";
|
import _ from "underscore";
|
||||||
import * as QueryBuilderConstants from "../Constants";
|
import * as QueryBuilderConstants from "../Constants";
|
||||||
@@ -14,7 +13,7 @@ export function getRowSelector(selectorSchema: Entities.IProperty[]): string {
|
|||||||
return QueryBuilderConstants.htmlSelectors.dataTableAllRowsSelector + selector;
|
return QueryBuilderConstants.htmlSelectors.dataTableAllRowsSelector + selector;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isRowVisible(dataTableScrollBodyQuery: JQuery<Element>, element: Element): boolean {
|
export function isRowVisible(dataTableScrollBodyQuery: JQuery, element: HTMLElement): boolean {
|
||||||
let isVisible = false;
|
let isVisible = false;
|
||||||
|
|
||||||
if (dataTableScrollBodyQuery.length && element) {
|
if (dataTableScrollBodyQuery.length && element) {
|
||||||
@@ -27,18 +26,16 @@ export function isRowVisible(dataTableScrollBodyQuery: JQuery<Element>, element:
|
|||||||
return isVisible;
|
return isVisible;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function scrollToRowIfNeeded(dataTableRows: JQuery<Element>, currentIndex: number, isScrollUp: boolean): void {
|
export function scrollToRowIfNeeded(dataTableRows: JQuery, currentIndex: number, isScrollUp: boolean): void {
|
||||||
if (dataTableRows.length) {
|
if (dataTableRows.length) {
|
||||||
const dataTableScrollBodyQuery: JQuery<Element> = $(
|
const dataTableScrollBodyQuery: JQuery = $(QueryBuilderConstants.htmlSelectors.dataTableScrollBodySelector),
|
||||||
QueryBuilderConstants.htmlSelectors.dataTableScrollBodySelector,
|
selectedRowElement: HTMLElement = dataTableRows.get(currentIndex);
|
||||||
),
|
|
||||||
selectedRowElement: Element = dataTableRows.get(currentIndex);
|
|
||||||
|
|
||||||
if (dataTableScrollBodyQuery.length && selectedRowElement) {
|
if (dataTableScrollBodyQuery.length && selectedRowElement) {
|
||||||
const isVisible: boolean = isRowVisible(dataTableScrollBodyQuery, selectedRowElement);
|
const isVisible: boolean = isRowVisible(dataTableScrollBodyQuery, selectedRowElement);
|
||||||
|
|
||||||
if (!isVisible) {
|
if (!isVisible) {
|
||||||
const selectedRowQuery: JQuery<Element> = $(selectedRowElement),
|
const selectedRowQuery: JQuery = $(selectedRowElement),
|
||||||
scrollPosition: number = dataTableScrollBodyQuery.scrollTop(),
|
scrollPosition: number = dataTableScrollBodyQuery.scrollTop(),
|
||||||
selectedElementPosition: number = selectedRowQuery.position().top;
|
selectedElementPosition: number = selectedRowQuery.position().top;
|
||||||
let newScrollPosition = 0;
|
let newScrollPosition = 0;
|
||||||
@@ -57,8 +54,8 @@ export function scrollToRowIfNeeded(dataTableRows: JQuery<Element>, currentIndex
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function scrollToTopIfNeeded(): void {
|
export function scrollToTopIfNeeded(): void {
|
||||||
const $dataTableRows: JQuery<Element> = $(QueryBuilderConstants.htmlSelectors.dataTableAllRowsSelector),
|
const $dataTableRows: JQuery = $(QueryBuilderConstants.htmlSelectors.dataTableAllRowsSelector),
|
||||||
$dataTableScrollBody: JQuery<Element> = $(QueryBuilderConstants.htmlSelectors.dataTableScrollBodySelector);
|
$dataTableScrollBody: JQuery = $(QueryBuilderConstants.htmlSelectors.dataTableScrollBodySelector);
|
||||||
|
|
||||||
if ($dataTableRows.length && $dataTableScrollBody.length) {
|
if ($dataTableRows.length && $dataTableScrollBody.length) {
|
||||||
$dataTableScrollBody.scrollTop(0);
|
$dataTableScrollBody.scrollTop(0);
|
||||||
@@ -74,7 +71,7 @@ export function setPaginationButtonEventHandlers(): void {
|
|||||||
.attr("role", "button");
|
.attr("role", "button");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function filterColumns(table: DataTables.Api<HTMLElement>, settings: boolean[]): void {
|
export function filterColumns(table: DataTables.DataTable, settings: boolean[]): void {
|
||||||
settings &&
|
settings &&
|
||||||
settings.forEach((value: boolean, index: number) => {
|
settings.forEach((value: boolean, index: number) => {
|
||||||
table.column(index).visible(value, false);
|
table.column(index).visible(value, false);
|
||||||
@@ -87,7 +84,7 @@ export function filterColumns(table: DataTables.Api<HTMLElement>, settings: bool
|
|||||||
* If no current order is specified, reorder the columns based on intial order.
|
* If no current order is specified, reorder the columns based on intial order.
|
||||||
*/
|
*/
|
||||||
export function reorderColumns(
|
export function reorderColumns(
|
||||||
table: DataTables.Api<HTMLElement>,
|
table: DataTables.DataTable,
|
||||||
targetOrder: number[],
|
targetOrder: number[],
|
||||||
currentOrder?: number[],
|
currentOrder?: number[],
|
||||||
//eslint-disable-next-line
|
//eslint-disable-next-line
|
||||||
@@ -111,9 +108,7 @@ export function reorderColumns(
|
|||||||
? calculateTransformationOrder(currentOrder, targetOrder)
|
? calculateTransformationOrder(currentOrder, targetOrder)
|
||||||
: targetOrder;
|
: targetOrder;
|
||||||
try {
|
try {
|
||||||
// TODO: This possibly does not work with the new version of datatables.
|
$.fn.dataTable.ColReorder(table).fnOrder(transformationOrder);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
($.fn.dataTable as any).ColReorder(table).fnOrder(transformationOrder);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return Q.reject(err);
|
return Q.reject(err);
|
||||||
}
|
}
|
||||||
@@ -121,9 +116,9 @@ export function reorderColumns(
|
|||||||
return Q.resolve(null);
|
return Q.resolve(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// export function resetColumns(table: DataTables.DataTable): void {
|
export function resetColumns(table: DataTables.DataTable): void {
|
||||||
// $.fn.dataTable.ColReorder(table).fnReset();
|
$.fn.dataTable.ColReorder(table).fnReset();
|
||||||
// }
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A table's initial order is described in the form of a natural ascending order.
|
* A table's initial order is described in the form of a natural ascending order.
|
||||||
@@ -138,10 +133,8 @@ export function getInitialOrder(columnsCount: number): number[] {
|
|||||||
* Initial order: I = [0, 1, 2, 3, 4, 5, 6, 7, 8] <----> {prop0, prop1, prop2, prop3, prop4, prop5, prop6, prop7, prop8}
|
* Initial order: I = [0, 1, 2, 3, 4, 5, 6, 7, 8] <----> {prop0, prop1, prop2, prop3, prop4, prop5, prop6, prop7, prop8}
|
||||||
* Current order: C = [0, 1, 2, 6, 7, 3, 4, 5, 8] <----> {prop0, prop1, prop2, prop6, prop7, prop3, prop4, prop5, prop8}
|
* Current order: C = [0, 1, 2, 6, 7, 3, 4, 5, 8] <----> {prop0, prop1, prop2, prop6, prop7, prop3, prop4, prop5, prop8}
|
||||||
*/
|
*/
|
||||||
export function getCurrentOrder(table: DataTables.Api<HTMLElement>): number[] {
|
export function getCurrentOrder(table: DataTables.DataTable): number[] {
|
||||||
// TODO: This possibly does not work with the new version of datatables.
|
return $.fn.dataTable.ColReorder(table).fnOrder();
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
return ($.fn.dataTable as any).ColReorder(table).fnOrder();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -185,8 +178,8 @@ export function calculateTransformationOrder(currentOrder: number[], targetOrder
|
|||||||
return transformationOrder;
|
return transformationOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDataTableHeaders(table: DataTables.Api<HTMLElement>): string[] {
|
export function getDataTableHeaders(table: DataTables.DataTable): string[] {
|
||||||
const columns = table.columns();
|
const columns: DataTables.ColumnsMethods = table.columns();
|
||||||
let headers: string[] = [];
|
let headers: string[] = [];
|
||||||
if (columns) {
|
if (columns) {
|
||||||
// table.columns() return ColumnsMethods which is an array of arrays
|
// table.columns() return ColumnsMethods which is an array of arrays
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as _ from "underscore";
|
import * as _ from "underscore";
|
||||||
|
|
||||||
import { ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
|
||||||
import * as DataTables from "datatables.net";
|
|
||||||
import * as CommonConstants from "../../../Common/Constants";
|
|
||||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import CacheBase from "./CacheBase";
|
||||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
import * as CommonConstants from "../../../Common/Constants";
|
||||||
import * as Constants from "../Constants";
|
import * as Constants from "../Constants";
|
||||||
import * as Entities from "../Entities";
|
import * as Entities from "../Entities";
|
||||||
import CacheBase from "./CacheBase";
|
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
||||||
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import { QueryIterator, ItemDefinition, Resource } from "@azure/cosmos";
|
||||||
|
|
||||||
// This is the format of the data we will have to pass to Datatable render callback,
|
// This is the format of the data we will have to pass to Datatable render callback,
|
||||||
// and property names are defined by Datatable as well.
|
// and property names are defined by Datatable as well.
|
||||||
@@ -28,7 +27,7 @@ abstract class DataTableViewModel {
|
|||||||
public items = ko.observableArray<Entities.ITableEntity>();
|
public items = ko.observableArray<Entities.ITableEntity>();
|
||||||
public selected = ko.observableArray<Entities.ITableEntity>();
|
public selected = ko.observableArray<Entities.ITableEntity>();
|
||||||
|
|
||||||
public table: DataTables.Api<HTMLElement>;
|
public table: DataTables.DataTable;
|
||||||
|
|
||||||
// The anchor item is for shift selection. i.e., select all items between anchor item and a give item.
|
// The anchor item is for shift selection. i.e., select all items between anchor item and a give item.
|
||||||
public lastSelectedAnchorItem: Entities.ITableEntity;
|
public lastSelectedAnchorItem: Entities.ITableEntity;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as DataTables from "datatables.net";
|
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import Q from "q";
|
import Q from "q";
|
||||||
import * as _ from "underscore";
|
import * as _ from "underscore";
|
||||||
@@ -57,7 +56,7 @@ function _parse(err: any): ErrorDataModel[] {
|
|||||||
|
|
||||||
function _getInnerErrors(message: string): any[] {
|
function _getInnerErrors(message: string): any[] {
|
||||||
/*
|
/*
|
||||||
The backend error message has an inner-message which is a stringified object.
|
The backend error message has an inner-message which is a stringified object.
|
||||||
For SQL errors, the "errors" property is an array of SqlErrorDataModel.
|
For SQL errors, the "errors" property is an array of SqlErrorDataModel.
|
||||||
Example:
|
Example:
|
||||||
"Message: {"Errors":["Resource with specified id or name already exists"]}\r\nActivityId: 80005000008d40b6a, Request URI: /apps/19000c000c0a0005/services/mctestdocdbprod-MasterService-0-00066ab9937/partitions/900005f9000e676fb8/replicas/13000000000955p"
|
"Message: {"Errors":["Resource with specified id or name already exists"]}\r\nActivityId: 80005000008d40b6a, Request URI: /apps/19000c000c0a0005/services/mctestdocdbprod-MasterService-0-00066ab9937/partitions/900005f9000e676fb8/replicas/13000000000955p"
|
||||||
@@ -132,7 +131,7 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
|||||||
return [{ key: Constants.EntityKeyNames.RowKey, value: rowKey }];
|
return [{ key: Constants.EntityKeyNames.RowKey, value: rowKey }];
|
||||||
}
|
}
|
||||||
|
|
||||||
public reloadTable(useSetting: boolean = true, resetHeaders: boolean = true): DataTables.Api<Element> {
|
public reloadTable(useSetting: boolean = true, resetHeaders: boolean = true): DataTables.DataTable {
|
||||||
this.clearCache();
|
this.clearCache();
|
||||||
this.clearSelection();
|
this.clearSelection();
|
||||||
this.isCancelled = false;
|
this.isCancelled = false;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as DataTable from "datatables.net-dt";
|
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import { KeyCodes } from "../../../Common/Constants";
|
import { KeyCodes } from "../../../Common/Constants";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
@@ -644,7 +643,7 @@ export default class QueryBuilderViewModel {
|
|||||||
return groupViewModels;
|
return groupViewModels;
|
||||||
};
|
};
|
||||||
|
|
||||||
public runQuery = (): DataTable.Api<Element> => {
|
public runQuery = (): DataTables.DataTable => {
|
||||||
return this._queryViewModel.runQuery();
|
return this._queryViewModel.runQuery();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import * as DataTables from "datatables.net";
|
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import * as _ from "underscore";
|
import * as _ from "underscore";
|
||||||
import { KeyCodes } from "../../../Common/Constants";
|
import { KeyCodes } from "../../../Common/Constants";
|
||||||
import { userContext } from "../../../UserContext";
|
|
||||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
|
import { userContext } from "../../../UserContext";
|
||||||
import { TableQuerySelectPanel } from "../../Panes/Tables/TableQuerySelectPanel/TableQuerySelectPanel";
|
import { TableQuerySelectPanel } from "../../Panes/Tables/TableQuerySelectPanel/TableQuerySelectPanel";
|
||||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
||||||
import { getQuotedCqlIdentifier } from "../CqlUtilities";
|
import { getQuotedCqlIdentifier } from "../CqlUtilities";
|
||||||
@@ -159,7 +158,7 @@ export default class QueryViewModel {
|
|||||||
notify: "always",
|
notify: "always",
|
||||||
});
|
});
|
||||||
|
|
||||||
public runQuery = (): DataTables.Api<Element> => {
|
public runQuery = (): DataTables.DataTable => {
|
||||||
let filter = this.setFilter();
|
let filter = this.setFilter();
|
||||||
if (filter && userContext.apiType !== "Cassandra") {
|
if (filter && userContext.apiType !== "Cassandra") {
|
||||||
filter = filter.replace(/"/g, "'");
|
filter = filter.replace(/"/g, "'");
|
||||||
@@ -177,7 +176,7 @@ export default class QueryViewModel {
|
|||||||
return this._tableEntityListViewModel.reloadTable(/*useSetting*/ false, /*resetHeaders*/ false);
|
return this._tableEntityListViewModel.reloadTable(/*useSetting*/ false, /*resetHeaders*/ false);
|
||||||
};
|
};
|
||||||
|
|
||||||
public clearQuery = (): DataTables.Api<Element> => {
|
public clearQuery = (): DataTables.DataTable => {
|
||||||
this.queryText();
|
this.queryText();
|
||||||
this.topValue();
|
this.topValue();
|
||||||
this.selectText();
|
this.selectText();
|
||||||
|
|||||||
@@ -281,7 +281,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
query,
|
query,
|
||||||
paginationToken,
|
paginationToken,
|
||||||
},
|
},
|
||||||
beforeSend: this.setAuthorizationHeader as any,
|
beforeSend: this.setAuthorizationHeader,
|
||||||
cache: false,
|
cache: false,
|
||||||
});
|
});
|
||||||
shouldNotify &&
|
shouldNotify &&
|
||||||
@@ -423,7 +423,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
keyspaceId: collection.databaseId,
|
keyspaceId: collection.databaseId,
|
||||||
tableId: collection.id(),
|
tableId: collection.id(),
|
||||||
},
|
},
|
||||||
beforeSend: this.setAuthorizationHeader as any,
|
beforeSend: this.setAuthorizationHeader,
|
||||||
cache: false,
|
cache: false,
|
||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
@@ -463,7 +463,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
keyspaceId: collection.databaseId,
|
keyspaceId: collection.databaseId,
|
||||||
tableId: collection.id(),
|
tableId: collection.id(),
|
||||||
},
|
},
|
||||||
beforeSend: this.setAuthorizationHeader as any,
|
beforeSend: this.setAuthorizationHeader,
|
||||||
cache: false,
|
cache: false,
|
||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
@@ -496,7 +496,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
resourceId: resourceId,
|
resourceId: resourceId,
|
||||||
query: query,
|
query: query,
|
||||||
},
|
},
|
||||||
beforeSend: this.setAuthorizationHeader as any,
|
beforeSend: this.setAuthorizationHeader,
|
||||||
cache: false,
|
cache: false,
|
||||||
}).then(
|
}).then(
|
||||||
(data: any) => {
|
(data: any) => {
|
||||||
|
|||||||
@@ -224,6 +224,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
};
|
};
|
||||||
|
|
||||||
public onSaveQueryClick = (): void => {
|
public onSaveQueryClick = (): void => {
|
||||||
|
sessionStorage.setItem("focusedElementId", "savequery");
|
||||||
useSidePanel.getState().openSidePanel("Save Query", <SaveQueryPane explorer={this.props.collection.container} />);
|
useSidePanel.getState().openSidePanel("Save Query", <SaveQueryPane explorer={this.props.collection.container} />);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -394,6 +395,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
const label = "Save Query";
|
const label = "Save Query";
|
||||||
buttons.push({
|
buttons.push({
|
||||||
iconSrc: SaveQueryIcon,
|
iconSrc: SaveQueryIcon,
|
||||||
|
id: "savequery",
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
onCommandClick: this.onSaveQueryClick,
|
onCommandClick: this.onSaveQueryClick,
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
@@ -444,7 +446,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
this._toggleCopilot(!this.state.copilotActive);
|
this._toggleCopilot(!this.state.copilotActive);
|
||||||
},
|
},
|
||||||
commandButtonLabel: this.state.copilotActive ? "Disable Copilot" : "Enable Copilot",
|
commandButtonLabel: this.state.copilotActive ? "Disable Copilot" : "Enable Copilot",
|
||||||
ariaLabel: this.state.copilotActive ? "Disable Copilot" : "Enable Copilot",
|
ariaLabel: "Copilot",
|
||||||
hasPopup: false,
|
hasPopup: false,
|
||||||
};
|
};
|
||||||
buttons.push(toggleCopilotButton);
|
buttons.push(toggleCopilotButton);
|
||||||
|
|||||||
@@ -769,10 +769,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
|
|
||||||
const dataRootNode = buildDataTree();
|
const dataRootNode = buildDataTree();
|
||||||
const isSampleDataEnabled =
|
const isSampleDataEnabled =
|
||||||
useQueryCopilot().copilotEnabled &&
|
useQueryCopilot().copilotEnabled && userContext.sampleDataConnectionInfo && userContext.apiType === "SQL";
|
||||||
useQueryCopilot().copilotSampleDBEnabled &&
|
|
||||||
userContext.sampleDataConnectionInfo &&
|
|
||||||
userContext.apiType === "SQL";
|
|
||||||
const sampleDataResourceTokenCollection = useDatabases((state) => state.sampleDataResourceTokenCollection);
|
const sampleDataResourceTokenCollection = useDatabases((state) => state.sampleDataResourceTokenCollection);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,44 +1,33 @@
|
|||||||
import { sendCachedDataMessage } from "Common/MessageHandler";
|
import { sendCachedDataMessage } from "Common/MessageHandler";
|
||||||
import { FabricDatabaseConnectionInfo } from "Contracts/FabricMessagesContract";
|
import { FabricDatabaseConnectionInfo } from "Contracts/FabricContract";
|
||||||
import { MessageTypes } from "Contracts/MessageTypes";
|
import { MessageTypes } from "Contracts/MessageTypes";
|
||||||
import { updateUserContext, userContext } from "UserContext";
|
import Explorer from "Explorer/Explorer";
|
||||||
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
import { updateUserContext } from "UserContext";
|
||||||
|
|
||||||
const TOKEN_VALIDITY_MS = (3600 - 600) * 1000; // 1 hour minus 10 minutes to be safe
|
const TOKEN_VALIDITY_MS = (3600 - 600) * 1000; // 1 hour minus 10 minutes to be safe
|
||||||
const DEBOUNCE_DELAY_MS = 1000 * 20; // 20 second
|
|
||||||
let timeoutId: NodeJS.Timeout;
|
let timeoutId: NodeJS.Timeout;
|
||||||
|
|
||||||
// Prevents multiple parallel requests during DEBOUNCE_DELAY_MS
|
// Prevents multiple parallel requests
|
||||||
let lastRequestTimestamp: number = undefined;
|
let isRequestPending = false;
|
||||||
|
|
||||||
const requestDatabaseResourceTokens = async (): Promise<void> => {
|
export const requestDatabaseResourceTokens = (): void => {
|
||||||
if (lastRequestTimestamp !== undefined && lastRequestTimestamp + DEBOUNCE_DELAY_MS > Date.now()) {
|
if (isRequestPending) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastRequestTimestamp = Date.now();
|
// TODO Make Fabric return the message id so we can handle this promise
|
||||||
try {
|
isRequestPending = true;
|
||||||
const fabricDatabaseConnectionInfo = await sendCachedDataMessage<FabricDatabaseConnectionInfo>(
|
sendCachedDataMessage<FabricDatabaseConnectionInfo>(MessageTypes.GetAllResourceTokens, []);
|
||||||
MessageTypes.GetAllResourceTokens,
|
};
|
||||||
[],
|
|
||||||
userContext.fabricContext.connectionId,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!userContext.databaseAccount.properties.documentEndpoint) {
|
export const handleRequestDatabaseResourceTokensResponse = (
|
||||||
userContext.databaseAccount.properties.documentEndpoint = fabricDatabaseConnectionInfo.endpoint;
|
explorer: Explorer,
|
||||||
}
|
fabricDatabaseConnectionInfo: FabricDatabaseConnectionInfo,
|
||||||
|
): void => {
|
||||||
updateUserContext({
|
isRequestPending = false;
|
||||||
fabricContext: { ...userContext.fabricContext, databaseConnectionInfo: fabricDatabaseConnectionInfo },
|
updateUserContext({ fabricDatabaseConnectionInfo });
|
||||||
databaseAccount: { ...userContext.databaseAccount },
|
scheduleRefreshDatabaseResourceToken();
|
||||||
});
|
explorer.refreshAllDatabases();
|
||||||
scheduleRefreshDatabaseResourceToken();
|
|
||||||
} catch (error) {
|
|
||||||
logConsoleError(error);
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
lastRequestTimestamp = undefined;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,24 +35,19 @@ const requestDatabaseResourceTokens = async (): Promise<void> => {
|
|||||||
* @param tokenTimestamp
|
* @param tokenTimestamp
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const scheduleRefreshDatabaseResourceToken = (refreshNow?: boolean): Promise<void> => {
|
export const scheduleRefreshDatabaseResourceToken = (): void => {
|
||||||
return new Promise((resolve) => {
|
if (timeoutId !== undefined) {
|
||||||
if (timeoutId !== undefined) {
|
clearTimeout(timeoutId);
|
||||||
clearTimeout(timeoutId);
|
timeoutId = undefined;
|
||||||
timeoutId = undefined;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
timeoutId = setTimeout(
|
timeoutId = setTimeout(() => {
|
||||||
() => {
|
requestDatabaseResourceTokens();
|
||||||
requestDatabaseResourceTokens().then(resolve);
|
}, TOKEN_VALIDITY_MS);
|
||||||
},
|
|
||||||
refreshNow ? 0 : TOKEN_VALIDITY_MS,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const checkDatabaseResourceTokensValidity = (tokenTimestamp: number): void => {
|
export const checkDatabaseResourceTokensValidity = (tokenTimestamp: number): void => {
|
||||||
if (tokenTimestamp + TOKEN_VALIDITY_MS < Date.now()) {
|
if (tokenTimestamp + TOKEN_VALIDITY_MS < Date.now()) {
|
||||||
scheduleRefreshDatabaseResourceToken(true);
|
requestDatabaseResourceTokens();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ export enum StorageKey {
|
|||||||
MaxWaitTimeInSeconds,
|
MaxWaitTimeInSeconds,
|
||||||
AutomaticallyCancelQueryAfterTimeout,
|
AutomaticallyCancelQueryAfterTimeout,
|
||||||
ContainerPaginationEnabled,
|
ContainerPaginationEnabled,
|
||||||
CopilotSampleDBEnabled,
|
|
||||||
CustomItemPerPage,
|
CustomItemPerPage,
|
||||||
DatabaseAccountId,
|
DatabaseAccountId,
|
||||||
EncryptedKeyToken,
|
EncryptedKeyToken,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FabricDatabaseConnectionInfo } from "Contracts/FabricMessagesContract";
|
import { FabricDatabaseConnectionInfo } from "Contracts/FabricContract";
|
||||||
import { ParsedResourceTokenConnectionString } from "Platform/Hosted/Helpers/ResourceTokenUtils";
|
import { ParsedResourceTokenConnectionString } from "Platform/Hosted/Helpers/ResourceTokenUtils";
|
||||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||||
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
|
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
|
||||||
@@ -47,13 +47,8 @@ export interface VCoreMongoConnectionParams {
|
|||||||
connectionString: string;
|
connectionString: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FabricContext {
|
|
||||||
connectionId: string;
|
|
||||||
databaseConnectionInfo: FabricDatabaseConnectionInfo | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UserContext {
|
interface UserContext {
|
||||||
readonly fabricContext?: FabricContext;
|
readonly fabricDatabaseConnectionInfo?: FabricDatabaseConnectionInfo;
|
||||||
readonly authType?: AuthType;
|
readonly authType?: AuthType;
|
||||||
readonly masterKey?: string;
|
readonly masterKey?: string;
|
||||||
readonly subscriptionId?: string;
|
readonly subscriptionId?: string;
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { createUri } from "Common/UrlUtility";
|
import { createUri } from "Common/UrlUtility";
|
||||||
import { DATA_EXPLORER_RPC_VERSION } from "Contracts/DataExplorerMessagesContract";
|
import { FabricDatabaseConnectionInfo, FabricMessage } from "Contracts/FabricContract";
|
||||||
import { FABRIC_RPC_VERSION, FabricMessageV2 } from "Contracts/FabricMessagesContract";
|
|
||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
|
||||||
import { useSelectedNode } from "Explorer/useSelectedNode";
|
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||||
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
|
import {
|
||||||
|
handleRequestDatabaseResourceTokensResponse,
|
||||||
|
scheduleRefreshDatabaseResourceToken,
|
||||||
|
} from "Platform/Fabric/FabricUtil";
|
||||||
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
|
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
|
||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||||
@@ -87,9 +88,6 @@ export function useKnockoutExplorer(platform: Platform): Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function configureFabric(): Promise<Explorer> {
|
async function configureFabric(): Promise<Explorer> {
|
||||||
// These are the versions of Fabric that Data Explorer supports.
|
|
||||||
const SUPPORTED_FABRIC_VERSIONS = [FABRIC_RPC_VERSION];
|
|
||||||
|
|
||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
return new Promise<Explorer>((resolve) => {
|
return new Promise<Explorer>((resolve) => {
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
@@ -103,37 +101,38 @@ async function configureFabric(): Promise<Explorer> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data: FabricMessageV2 = event.data?.data;
|
const data: FabricMessage = event.data?.data;
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case "initialize": {
|
case "initialize": {
|
||||||
const fabricVersion = data.version;
|
const fabricDatabaseConnectionInfo: FabricDatabaseConnectionInfo = {
|
||||||
if (!SUPPORTED_FABRIC_VERSIONS.includes(fabricVersion)) {
|
endpoint: data.message.endpoint,
|
||||||
// TODO Surface error to user
|
databaseId: data.message.databaseId,
|
||||||
console.error(`Unsupported Fabric version: ${fabricVersion}`);
|
resourceTokens: data.message.resourceTokens as { [resourceId: string]: string },
|
||||||
return;
|
resourceTokensTimestamp: data.message.resourceTokensTimestamp,
|
||||||
}
|
};
|
||||||
|
explorer = await createExplorerFabric(fabricDatabaseConnectionInfo);
|
||||||
explorer = createExplorerFabric(data.message);
|
|
||||||
await scheduleRefreshDatabaseResourceToken(true);
|
|
||||||
resolve(explorer);
|
resolve(explorer);
|
||||||
await explorer.refreshAllDatabases();
|
|
||||||
openFirstContainer(explorer, userContext.fabricContext.databaseConnectionInfo.databaseId);
|
explorer.refreshAllDatabases().then(() => {
|
||||||
|
openFirstContainer(explorer, fabricDatabaseConnectionInfo.databaseId);
|
||||||
|
});
|
||||||
|
scheduleRefreshDatabaseResourceToken();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "newContainer":
|
case "newContainer":
|
||||||
explorer.onNewCollectionClicked();
|
explorer.onNewCollectionClicked();
|
||||||
break;
|
break;
|
||||||
case "authorizationToken":
|
case "authorizationToken": {
|
||||||
case "allResourceTokens_v2": {
|
|
||||||
handleCachedDataMessage(data);
|
handleCachedDataMessage(data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "setToolbarStatus": {
|
case "allResourceTokens": {
|
||||||
useCommandBar.getState().setIsHidden(data.message.visible === false);
|
// TODO call handleCachedDataMessage when Fabric echoes message id back
|
||||||
|
handleRequestDatabaseResourceTokensResponse(explorer, data.message as FabricDatabaseConnectionInfo);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -144,11 +143,7 @@ async function configureFabric(): Promise<Explorer> {
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
sendMessage({
|
sendReadyMessage();
|
||||||
type: MessageTypes.Ready,
|
|
||||||
id: "ready",
|
|
||||||
params: [DATA_EXPLORER_RPC_VERSION],
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,12 +319,9 @@ function configureHostedWithResourceToken(config: ResourceToken): Explorer {
|
|||||||
return explorer;
|
return explorer;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createExplorerFabric(params: { connectionId: string }): Explorer {
|
function createExplorerFabric(fabricDatabaseConnectionInfo: FabricDatabaseConnectionInfo): Explorer {
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
fabricContext: {
|
fabricDatabaseConnectionInfo,
|
||||||
connectionId: params.connectionId,
|
|
||||||
databaseConnectionInfo: undefined,
|
|
||||||
},
|
|
||||||
authType: AuthType.ConnectionString,
|
authType: AuthType.ConnectionString,
|
||||||
databaseAccount: {
|
databaseAccount: {
|
||||||
id: "",
|
id: "",
|
||||||
@@ -338,7 +330,7 @@ function createExplorerFabric(params: { connectionId: string }): Explorer {
|
|||||||
name: "Mounted",
|
name: "Mounted",
|
||||||
kind: AccountKind.Default,
|
kind: AccountKind.Default,
|
||||||
properties: {
|
properties: {
|
||||||
documentEndpoint: undefined,
|
documentEndpoint: fabricDatabaseConnectionInfo.endpoint,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { ContainerInfo } from "../Contracts/DataModels";
|
|||||||
export interface QueryCopilotState {
|
export interface QueryCopilotState {
|
||||||
copilotEnabled: boolean;
|
copilotEnabled: boolean;
|
||||||
copilotUserDBEnabled: boolean;
|
copilotUserDBEnabled: boolean;
|
||||||
copilotSampleDBEnabled: boolean;
|
|
||||||
generatedQuery: string;
|
generatedQuery: string;
|
||||||
likeQuery: boolean;
|
likeQuery: boolean;
|
||||||
userPrompt: string;
|
userPrompt: string;
|
||||||
@@ -51,7 +50,6 @@ export interface QueryCopilotState {
|
|||||||
|
|
||||||
setCopilotEnabled: (copilotEnabled: boolean) => void;
|
setCopilotEnabled: (copilotEnabled: boolean) => void;
|
||||||
setCopilotUserDBEnabled: (copilotUserDBEnabled: boolean) => void;
|
setCopilotUserDBEnabled: (copilotUserDBEnabled: boolean) => void;
|
||||||
setCopilotSampleDBEnabled: (copilotSampleDBEnabled: boolean) => void;
|
|
||||||
openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) => void;
|
openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) => void;
|
||||||
closeFeedbackModal: () => void;
|
closeFeedbackModal: () => void;
|
||||||
setHideFeedbackModalForLikedQueries: (hideFeedbackModalForLikedQueries: boolean) => void;
|
setHideFeedbackModalForLikedQueries: (hideFeedbackModalForLikedQueries: boolean) => void;
|
||||||
@@ -98,7 +96,6 @@ type QueryCopilotStore = UseStore<QueryCopilotState>;
|
|||||||
export const useQueryCopilot: QueryCopilotStore = create((set) => ({
|
export const useQueryCopilot: QueryCopilotStore = create((set) => ({
|
||||||
copilotEnabled: false,
|
copilotEnabled: false,
|
||||||
copilotUserDBEnabled: false,
|
copilotUserDBEnabled: false,
|
||||||
copilotSampleDBEnabled: false,
|
|
||||||
generatedQuery: "",
|
generatedQuery: "",
|
||||||
likeQuery: false,
|
likeQuery: false,
|
||||||
userPrompt: "",
|
userPrompt: "",
|
||||||
@@ -107,7 +104,7 @@ export const useQueryCopilot: QueryCopilotStore = create((set) => ({
|
|||||||
correlationId: "",
|
correlationId: "",
|
||||||
query: "SELECT * FROM c",
|
query: "SELECT * FROM c",
|
||||||
selectedQuery: "",
|
selectedQuery: "",
|
||||||
isGeneratingQuery: null,
|
isGeneratingQuery: false,
|
||||||
isGeneratingExplanation: false,
|
isGeneratingExplanation: false,
|
||||||
isExecuting: false,
|
isExecuting: false,
|
||||||
dislikeQuery: undefined,
|
dislikeQuery: undefined,
|
||||||
@@ -148,7 +145,6 @@ export const useQueryCopilot: QueryCopilotStore = create((set) => ({
|
|||||||
|
|
||||||
setCopilotEnabled: (copilotEnabled: boolean) => set({ copilotEnabled }),
|
setCopilotEnabled: (copilotEnabled: boolean) => set({ copilotEnabled }),
|
||||||
setCopilotUserDBEnabled: (copilotUserDBEnabled: boolean) => set({ copilotUserDBEnabled }),
|
setCopilotUserDBEnabled: (copilotUserDBEnabled: boolean) => set({ copilotUserDBEnabled }),
|
||||||
setCopilotSampleDBEnabled: (copilotSampleDBEnabled: boolean) => set({ copilotSampleDBEnabled }),
|
|
||||||
openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) =>
|
openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) =>
|
||||||
set({ generatedQuery, likeQuery, userPrompt, showFeedbackModal: true }),
|
set({ generatedQuery, likeQuery, userPrompt, showFeedbackModal: true }),
|
||||||
closeFeedbackModal: () => set({ showFeedbackModal: false }),
|
closeFeedbackModal: () => set({ showFeedbackModal: false }),
|
||||||
|
|||||||
Reference in New Issue
Block a user