mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-23 19:01:28 +00:00
Compare commits
2 Commits
languy-add
...
eslint/Not
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a3e6f6aef | ||
|
|
7687ed1483 |
@@ -93,11 +93,7 @@ src/Explorer/Tabs/DocumentsTab.test.ts
|
|||||||
src/Explorer/Tabs/DocumentsTab.ts
|
src/Explorer/Tabs/DocumentsTab.ts
|
||||||
src/Explorer/Tabs/GraphTab.ts
|
src/Explorer/Tabs/GraphTab.ts
|
||||||
src/Explorer/Tabs/MongoDocumentsTab.ts
|
src/Explorer/Tabs/MongoDocumentsTab.ts
|
||||||
src/Explorer/Tabs/NotebookV2Tab.ts
|
|
||||||
src/Explorer/Tabs/ScriptTabBase.ts
|
|
||||||
src/Explorer/Tabs/TabComponents.ts
|
|
||||||
src/Explorer/Tabs/TabsBase.ts
|
src/Explorer/Tabs/TabsBase.ts
|
||||||
src/Explorer/Tabs/TriggerTab.ts
|
|
||||||
src/Explorer/Tabs/UserDefinedFunctionTab.ts
|
src/Explorer/Tabs/UserDefinedFunctionTab.ts
|
||||||
src/Explorer/Tree/AccessibleVerticalList.ts
|
src/Explorer/Tree/AccessibleVerticalList.ts
|
||||||
src/Explorer/Tree/Collection.ts
|
src/Explorer/Tree/Collection.ts
|
||||||
|
|||||||
6
.vscode/launch.json
vendored
6
.vscode/launch.json
vendored
@@ -12,8 +12,7 @@
|
|||||||
"--inspect-brk",
|
"--inspect-brk",
|
||||||
"${workspaceRoot}/node_modules/jest/bin/jest.js",
|
"${workspaceRoot}/node_modules/jest/bin/jest.js",
|
||||||
"--runInBand",
|
"--runInBand",
|
||||||
"--coverage",
|
"--coverage", "false"
|
||||||
"false"
|
|
||||||
],
|
],
|
||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
"internalConsoleOptions": "neverOpen",
|
"internalConsoleOptions": "neverOpen",
|
||||||
@@ -27,8 +26,7 @@
|
|||||||
"--inspect-brk",
|
"--inspect-brk",
|
||||||
"${workspaceRoot}/node_modules/jest/bin/jest.js",
|
"${workspaceRoot}/node_modules/jest/bin/jest.js",
|
||||||
"${fileBasenameNoExtension}",
|
"${fileBasenameNoExtension}",
|
||||||
"--coverage",
|
"--coverage", "false",
|
||||||
"false",
|
|
||||||
// "--watch",
|
// "--watch",
|
||||||
// // --no-cache only used to make --watch work. Otherwise jest ignores the breakpoints.
|
// // --no-cache only used to make --watch work. Otherwise jest ignores the breakpoints.
|
||||||
// // https://github.com/facebook/jest/issues/6683
|
// // https://github.com/facebook/jest/issues/6683
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
{
|
{
|
||||||
"JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com",
|
"JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com"
|
||||||
"isTerminalEnabled" : true,
|
|
||||||
"isPhoenixEnabled" : true
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
{
|
{
|
||||||
"JUNO_ENDPOINT": "https://tools.cosmos.azure.com",
|
"JUNO_ENDPOINT": "https://tools.cosmos.azure.com"
|
||||||
"isTerminalEnabled" : false,
|
|
||||||
"isPhoenixEnabled" : false
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2077,7 +2077,7 @@ a:link {
|
|||||||
.resourceTreeAndTabs {
|
.resourceTreeAndTabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow-x: clip;
|
overflow-x: auto;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
@@ -2245,7 +2245,7 @@ a:link {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.refreshColHeader {
|
.refreshColHeader {
|
||||||
padding: 3px 6px 10px 0px !important;
|
padding: 3px 6px 6px 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.refreshColHeader:hover {
|
.refreshColHeader:hover {
|
||||||
@@ -2869,39 +2869,31 @@ a:link {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.settingsSection {
|
settings-pane {
|
||||||
border-bottom: 1px solid @BaseMedium;
|
.settingsSection {
|
||||||
margin-right: 24px;
|
border-bottom: 1px solid @BaseMedium;
|
||||||
padding: @MediumSpace 0px;
|
margin-right: 24px;
|
||||||
|
padding: @MediumSpace 0px;
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
padding-top: 0px;
|
padding-top: 0px;
|
||||||
padding-bottom: 10px;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settingsSectionPart {
|
.settingsSectionPart {
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settingsSectionLabel {
|
.settingsSectionLabel {
|
||||||
margin-bottom: @DefaultSpace;
|
margin-bottom: @DefaultSpace;
|
||||||
margin-right: 5px;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.pageOptionsPart {
|
.pageOptionsPart {
|
||||||
padding-bottom: @MediumSpace;
|
padding-bottom: @MediumSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.legendLabel {
|
|
||||||
border-bottom: 0px;
|
|
||||||
width: auto;
|
|
||||||
font-size: @mediumFontSize;
|
|
||||||
display: inline !important;
|
|
||||||
float: left;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
42
package-lock.json
generated
42
package-lock.json
generated
@@ -6866,11 +6866,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
||||||
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
|
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
|
||||||
},
|
},
|
||||||
"attr-accept": {
|
|
||||||
"version": "2.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
|
|
||||||
"integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg=="
|
|
||||||
},
|
|
||||||
"aws-sign2": {
|
"aws-sign2": {
|
||||||
"version": "0.7.0",
|
"version": "0.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
|
||||||
@@ -10820,21 +10815,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
|
||||||
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
|
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
|
||||||
},
|
},
|
||||||
"file-selector": {
|
|
||||||
"version": "0.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.4.0.tgz",
|
|
||||||
"integrity": "sha512-iACCiXeMYOvZqlF1kTiYINzgepRBymz1wwjiuup9u9nayhb6g4fSwiyJ/6adli+EPwrWtpgQAh2PoS7HukEGEg==",
|
|
||||||
"requires": {
|
|
||||||
"tslib": "^2.0.3"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": {
|
|
||||||
"version": "2.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
|
||||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"file-uri-to-path": {
|
"file-uri-to-path": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||||
@@ -20998,28 +20978,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-dropzone": {
|
|
||||||
"version": "12.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-12.0.4.tgz",
|
|
||||||
"integrity": "sha512-fcqHEYe1MzAghU6/Hz86lHDlBNsA+lO48nAcm7/wA+kIzwS6uuJbUG33tBZjksj7GAZ1iUQ6NHwjUURPmSGang==",
|
|
||||||
"requires": {
|
|
||||||
"attr-accept": "^2.2.2",
|
|
||||||
"file-selector": "^0.4.0",
|
|
||||||
"prop-types": "^15.8.1"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"prop-types": {
|
|
||||||
"version": "15.8.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
|
||||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
|
||||||
"requires": {
|
|
||||||
"loose-envify": "^1.4.0",
|
|
||||||
"object-assign": "^4.1.1",
|
|
||||||
"react-is": "^16.13.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"react-error-overlay": {
|
"react-error-overlay": {
|
||||||
"version": "6.0.9",
|
"version": "6.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz",
|
||||||
|
|||||||
@@ -86,7 +86,6 @@
|
|||||||
"react-dnd": "14.0.2",
|
"react-dnd": "14.0.2",
|
||||||
"react-dnd-html5-backend": "14.0.0",
|
"react-dnd-html5-backend": "14.0.0",
|
||||||
"react-dom": "16.13.1",
|
"react-dom": "16.13.1",
|
||||||
"react-dropzone": "12.0.4",
|
|
||||||
"react-hotkeys": "2.0.0",
|
"react-hotkeys": "2.0.0",
|
||||||
"react-i18next": "11.8.5",
|
"react-i18next": "11.8.5",
|
||||||
"react-notification-system": "0.2.17",
|
"react-notification-system": "0.2.17",
|
||||||
|
|||||||
@@ -96,10 +96,7 @@ export class Flights {
|
|||||||
public static readonly AutoscaleTest = "autoscaletest";
|
public static readonly AutoscaleTest = "autoscaletest";
|
||||||
public static readonly PartitionKeyTest = "partitionkeytest";
|
public static readonly PartitionKeyTest = "partitionkeytest";
|
||||||
public static readonly PKPartitionKeyTest = "pkpartitionkeytest";
|
public static readonly PKPartitionKeyTest = "pkpartitionkeytest";
|
||||||
public static readonly PhoenixNotebooks = "phoenixnotebooks";
|
public static readonly Phoenix = "phoenix";
|
||||||
public static readonly PhoenixFeatures = "phoenixfeatures";
|
|
||||||
public static readonly NotebooksDownBanner = "notebooksdownbanner";
|
|
||||||
public static readonly FreeTierAutoscaleThroughput = "freetierautoscalethroughput";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AfecFeatures {
|
export class AfecFeatures {
|
||||||
@@ -346,12 +343,7 @@ export enum ConnectionStatusType {
|
|||||||
Connecting = "Connecting",
|
Connecting = "Connecting",
|
||||||
Connected = "Connected",
|
Connected = "Connected",
|
||||||
Failed = "Connection Failed",
|
Failed = "Connection Failed",
|
||||||
Reconnect = "Reconnect",
|
ReConnect = "Reconnect",
|
||||||
}
|
|
||||||
|
|
||||||
export enum ContainerStatusType {
|
|
||||||
Active = "Active",
|
|
||||||
Disconnected = "Disconnected",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EmulatorMasterKey =
|
export const EmulatorMasterKey =
|
||||||
@@ -364,25 +356,20 @@ export const StyleConstants = require("less-vars-loader!../../less/Common/Consta
|
|||||||
export class Notebook {
|
export class Notebook {
|
||||||
public static readonly defaultBasePath = "./notebooks";
|
public static readonly defaultBasePath = "./notebooks";
|
||||||
public static readonly heartbeatDelayMs = 60000;
|
public static readonly heartbeatDelayMs = 60000;
|
||||||
public static readonly containerStatusHeartbeatDelayMs = 30000;
|
|
||||||
public static readonly kernelRestartInitialDelayMs = 1000;
|
public static readonly kernelRestartInitialDelayMs = 1000;
|
||||||
public static readonly kernelRestartMaxDelayMs = 20000;
|
public static readonly kernelRestartMaxDelayMs = 20000;
|
||||||
public static readonly autoSaveIntervalMs = 300000;
|
public static readonly autoSaveIntervalMs = 120000;
|
||||||
public static readonly memoryGuageToGB = 1048576;
|
public static readonly memoryGuageToGB = 1048576;
|
||||||
public static readonly lowMemoryThreshold = 0.8;
|
|
||||||
public static readonly remainingTimeForAlert = 10;
|
|
||||||
public static readonly retryAttempts = 3;
|
|
||||||
public static readonly retryAttemptDelayMs = 5000;
|
|
||||||
public static readonly temporarilyDownMsg = "Notebooks is currently not available. We are working on it.";
|
public static readonly temporarilyDownMsg = "Notebooks is currently not available. We are working on it.";
|
||||||
public static readonly mongoShellTemporarilyDownMsg =
|
public static readonly mongoShellTemporarilyDownMsg =
|
||||||
"We have identified an issue with the Mongo Shell and it is unavailable right now. We are actively working on the mitigation.";
|
"We have identified an issue with the Mongo Shell and it is unavailable right now. We are actively working on the mitigation.";
|
||||||
public static readonly cassandraShellTemporarilyDownMsg =
|
public static readonly cassandraShellTemporarilyDownMsg =
|
||||||
"We have identified an issue with the Cassandra Shell and it is unavailable right now. We are actively working on the mitigation.";
|
"We have identified an issue with the Cassandra Shell and it is unavailable right now. We are actively working on the mitigation.";
|
||||||
public static saveNotebookModalTitle = "Save notebook in temporary workspace";
|
public static saveNotebookModalTitle = "Save Notebook in temporary workspace";
|
||||||
public static saveNotebookModalContent =
|
public static saveNotebookModalContent =
|
||||||
"This notebook will be saved in the temporary workspace and will be removed when the session expires.";
|
"This notebook will be saved in the temporary workspace and will be removed when the session expires. To save your work permanently, save your notebooks to a GitHub repository or download the notebooks to your local machine before the session ends.";
|
||||||
public static newNotebookModalTitle = "Create notebook in temporary workspace";
|
public static newNotebookModalTitle = "Create Notebook in temporary workspace";
|
||||||
public static newNotebookUploadModalTitle = "Upload notebook to temporary workspace";
|
public static newNotebookUploadModalTitle = "Upload Notebook in temporary workspace";
|
||||||
public static newNotebookModalContent1 =
|
public static newNotebookModalContent1 =
|
||||||
"A temporary workspace will be created to enable you to work with notebooks. When the session expires, any notebooks in the workspace will be removed.";
|
"A temporary workspace will be created to enable you to work with notebooks. When the session expires, any notebooks in the workspace will be removed.";
|
||||||
public static newNotebookModalContent2 =
|
public static newNotebookModalContent2 =
|
||||||
@@ -414,11 +401,3 @@ export class TerminalQueryParams {
|
|||||||
public static readonly SubscriptionId = "subscriptionId";
|
public static readonly SubscriptionId = "subscriptionId";
|
||||||
public static readonly TerminalEndpoint = "terminalEndpoint";
|
public static readonly TerminalEndpoint = "terminalEndpoint";
|
||||||
}
|
}
|
||||||
|
|
||||||
export class JunoEndpoints {
|
|
||||||
public static readonly Test = "https://juno-test.documents-dev.windows-int.net";
|
|
||||||
public static readonly Test2 = "https://juno-test2.documents-dev.windows-int.net";
|
|
||||||
public static readonly Test3 = "https://juno-test3.documents-dev.windows-int.net";
|
|
||||||
public static readonly Prod = "https://tools.cosmos.azure.com";
|
|
||||||
public static readonly Stage = "https://tools-staging.cosmos.azure.com";
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import * as Cosmos from "@azure/cosmos";
|
import * as Cosmos from "@azure/cosmos";
|
||||||
import { RequestInfo, setAuthorizationTokenHeaderUsingMasterKey } from "@azure/cosmos";
|
import { RequestInfo, setAuthorizationTokenHeaderUsingMasterKey } from "@azure/cosmos";
|
||||||
import { CosmosHeaders } from "@azure/cosmos/dist-esm";
|
|
||||||
import { configContext, Platform } from "../ConfigContext";
|
import { configContext, Platform } from "../ConfigContext";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||||
@@ -78,21 +77,10 @@ export async function getTokenFromAuthService(verb: string, resourceType: string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The Capability is a bitmap, which cosmosdb backend decodes as per the below enum
|
|
||||||
enum SDKSupportedCapabilities {
|
|
||||||
None = 0,
|
|
||||||
PartitionMerge = 1 << 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
let _client: Cosmos.CosmosClient;
|
let _client: Cosmos.CosmosClient;
|
||||||
|
|
||||||
export function client(): Cosmos.CosmosClient {
|
export function client(): Cosmos.CosmosClient {
|
||||||
if (_client) return _client;
|
if (_client) return _client;
|
||||||
|
|
||||||
let _defaultHeaders: CosmosHeaders = {};
|
|
||||||
_defaultHeaders["x-ms-cosmos-sdk-supported-capabilities"] =
|
|
||||||
SDKSupportedCapabilities.None | SDKSupportedCapabilities.PartitionMerge;
|
|
||||||
|
|
||||||
const options: Cosmos.CosmosClientOptions = {
|
const options: Cosmos.CosmosClientOptions = {
|
||||||
endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called
|
endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called
|
||||||
key: userContext.masterKey,
|
key: userContext.masterKey,
|
||||||
@@ -101,7 +89,6 @@ export function client(): Cosmos.CosmosClient {
|
|||||||
enableEndpointDiscovery: false,
|
enableEndpointDiscovery: false,
|
||||||
},
|
},
|
||||||
userAgentSuffix: "Azure Portal",
|
userAgentSuffix: "Azure Portal",
|
||||||
defaultHeaders: _defaultHeaders,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (configContext.PROXY_PATH !== undefined) {
|
if (configContext.PROXY_PATH !== undefined) {
|
||||||
|
|||||||
@@ -236,12 +236,13 @@ describe("MongoProxyClient", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("returns a production endpoint", () => {
|
it("returns a production endpoint", () => {
|
||||||
const endpoint = getEndpoint("https://main.documentdb.ext.azure.com");
|
const endpoint = getEndpoint();
|
||||||
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/mongo/explorer");
|
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/mongo/explorer");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns a development endpoint", () => {
|
it("returns a development endpoint", () => {
|
||||||
const endpoint = getEndpoint("https://localhost:1234");
|
updateConfigContext({ MONGO_BACKEND_ENDPOINT: "https://localhost:1234" });
|
||||||
|
const endpoint = getEndpoint();
|
||||||
expect(endpoint).toEqual("https://localhost:1234/api/mongo/explorer");
|
expect(endpoint).toEqual("https://localhost:1234/api/mongo/explorer");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -249,7 +250,7 @@ describe("MongoProxyClient", () => {
|
|||||||
updateUserContext({
|
updateUserContext({
|
||||||
authType: AuthType.EncryptedToken,
|
authType: AuthType.EncryptedToken,
|
||||||
});
|
});
|
||||||
const endpoint = getEndpoint("https://main.documentdb.ext.azure.com");
|
const endpoint = getEndpoint();
|
||||||
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer");
|
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
|
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
|
||||||
import queryString from "querystring";
|
import queryString from "querystring";
|
||||||
import { allowedMongoProxyEndpoints, validateEndpoint } from "Utils/EndpointValidation";
|
|
||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
import { configContext } from "../ConfigContext";
|
import { configContext } from "../ConfigContext";
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
@@ -337,17 +336,14 @@ export function createMongoCollectionWithProxy(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getFeatureEndpointOrDefault(feature: string): string {
|
export function getFeatureEndpointOrDefault(feature: string): string {
|
||||||
const endpoint =
|
return hasFlag(userContext.features.mongoProxyAPIs, feature)
|
||||||
hasFlag(userContext.features.mongoProxyAPIs, feature) &&
|
? getEndpoint(userContext.features.mongoProxyEndpoint)
|
||||||
validateEndpoint(userContext.features.mongoProxyEndpoint, allowedMongoProxyEndpoints)
|
: getEndpoint();
|
||||||
? userContext.features.mongoProxyEndpoint
|
|
||||||
: configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT;
|
|
||||||
|
|
||||||
return getEndpoint(endpoint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getEndpoint(endpoint: string): string {
|
export function getEndpoint(customEndpoint?: string): string {
|
||||||
let url = endpoint + "/api/mongo/explorer";
|
let url = customEndpoint ? customEndpoint : configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT;
|
||||||
|
url += "/api/mongo/explorer";
|
||||||
|
|
||||||
if (userContext.authType === AuthType.EncryptedToken) {
|
if (userContext.authType === AuthType.EncryptedToken) {
|
||||||
url = url.replace("api/mongo", "api/guest/mongo");
|
url = url.replace("api/mongo", "api/guest/mongo");
|
||||||
|
|||||||
@@ -1,17 +1,3 @@
|
|||||||
import {
|
|
||||||
allowedAadEndpoints,
|
|
||||||
allowedArcadiaEndpoints,
|
|
||||||
allowedArmEndpoints,
|
|
||||||
allowedBackendEndpoints,
|
|
||||||
allowedEmulatorEndpoints,
|
|
||||||
allowedGraphEndpoints,
|
|
||||||
allowedHostedExplorerEndpoints,
|
|
||||||
allowedJunoOrigins,
|
|
||||||
allowedMongoBackendEndpoints,
|
|
||||||
allowedMsalRedirectEndpoints,
|
|
||||||
validateEndpoint,
|
|
||||||
} from "Utils/EndpointValidation";
|
|
||||||
|
|
||||||
export enum Platform {
|
export enum Platform {
|
||||||
Portal = "Portal",
|
Portal = "Portal",
|
||||||
Hosted = "Hosted",
|
Hosted = "Hosted",
|
||||||
@@ -20,7 +6,7 @@ export enum Platform {
|
|||||||
|
|
||||||
export interface ConfigContext {
|
export interface ConfigContext {
|
||||||
platform: Platform;
|
platform: Platform;
|
||||||
allowedParentFrameOrigins: ReadonlyArray<string>;
|
allowedParentFrameOrigins: string[];
|
||||||
gitSha?: string;
|
gitSha?: string;
|
||||||
proxyPath?: string;
|
proxyPath?: string;
|
||||||
AAD_ENDPOINT: string;
|
AAD_ENDPOINT: string;
|
||||||
@@ -37,12 +23,10 @@ export interface ConfigContext {
|
|||||||
PROXY_PATH?: string;
|
PROXY_PATH?: string;
|
||||||
JUNO_ENDPOINT: string;
|
JUNO_ENDPOINT: string;
|
||||||
GITHUB_CLIENT_ID: string;
|
GITHUB_CLIENT_ID: string;
|
||||||
GITHUB_TEST_ENV_CLIENT_ID: string;
|
|
||||||
GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it.
|
GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it.
|
||||||
isTerminalEnabled: boolean;
|
|
||||||
isPhoenixEnabled: boolean;
|
|
||||||
hostedExplorerURL: string;
|
hostedExplorerURL: string;
|
||||||
armAPIVersion?: string;
|
armAPIVersion?: string;
|
||||||
|
allowedJunoOrigins: string[];
|
||||||
msalRedirectURI?: string;
|
msalRedirectURI?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +40,8 @@ let configContext: Readonly<ConfigContext> = {
|
|||||||
`^https:\\/\\/[\\.\\w]*ext\\.azure\\.(com|cn|us)$`,
|
`^https:\\/\\/[\\.\\w]*ext\\.azure\\.(com|cn|us)$`,
|
||||||
`^https:\\/\\/[\\.\\w]*\\.ext\\.microsoftazure\\.de$`,
|
`^https:\\/\\/[\\.\\w]*\\.ext\\.microsoftazure\\.de$`,
|
||||||
`^https://cosmos-db-dataexplorer-germanycentral.azurewebsites.de$`,
|
`^https://cosmos-db-dataexplorer-germanycentral.azurewebsites.de$`,
|
||||||
], // Webpack injects this at build time
|
],
|
||||||
|
// Webpack injects this at build time
|
||||||
gitSha: process.env.GIT_SHA,
|
gitSha: process.env.GIT_SHA,
|
||||||
hostedExplorerURL: "https://cosmos.azure.com/",
|
hostedExplorerURL: "https://cosmos.azure.com/",
|
||||||
AAD_ENDPOINT: "https://login.microsoftonline.com/",
|
AAD_ENDPOINT: "https://login.microsoftonline.com/",
|
||||||
@@ -67,12 +52,16 @@ let configContext: Readonly<ConfigContext> = {
|
|||||||
GRAPH_API_VERSION: "1.6",
|
GRAPH_API_VERSION: "1.6",
|
||||||
ARCADIA_ENDPOINT: "https://workspaceartifacts.projectarcadia.net",
|
ARCADIA_ENDPOINT: "https://workspaceartifacts.projectarcadia.net",
|
||||||
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net",
|
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net",
|
||||||
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1189306
|
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/settings/applications/1189306
|
||||||
GITHUB_TEST_ENV_CLIENT_ID: "b63fc8cbf87fd3c6e2eb", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1777772
|
|
||||||
JUNO_ENDPOINT: "https://tools.cosmos.azure.com",
|
JUNO_ENDPOINT: "https://tools.cosmos.azure.com",
|
||||||
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
|
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
|
||||||
isTerminalEnabled: false,
|
allowedJunoOrigins: [
|
||||||
isPhoenixEnabled: false,
|
"https://juno-test.documents-dev.windows-int.net",
|
||||||
|
"https://juno-test2.documents-dev.windows-int.net",
|
||||||
|
"https://tools.cosmos.azure.com",
|
||||||
|
"https://tools-staging.cosmos.azure.com",
|
||||||
|
"https://localhost",
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export function resetConfigContext(): void {
|
export function resetConfigContext(): void {
|
||||||
@@ -83,50 +72,6 @@ export function resetConfigContext(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function updateConfigContext(newContext: Partial<ConfigContext>): void {
|
export function updateConfigContext(newContext: Partial<ConfigContext>): void {
|
||||||
if (!newContext) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateEndpoint(newContext.ARM_ENDPOINT, allowedArmEndpoints)) {
|
|
||||||
delete newContext.ARM_ENDPOINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateEndpoint(newContext.AAD_ENDPOINT, allowedAadEndpoints)) {
|
|
||||||
delete newContext.AAD_ENDPOINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateEndpoint(newContext.EMULATOR_ENDPOINT, allowedEmulatorEndpoints)) {
|
|
||||||
delete newContext.EMULATOR_ENDPOINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateEndpoint(newContext.GRAPH_ENDPOINT, allowedGraphEndpoints)) {
|
|
||||||
delete newContext.GRAPH_ENDPOINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateEndpoint(newContext.ARCADIA_ENDPOINT, allowedArcadiaEndpoints)) {
|
|
||||||
delete newContext.ARCADIA_ENDPOINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateEndpoint(newContext.BACKEND_ENDPOINT, allowedBackendEndpoints)) {
|
|
||||||
delete newContext.BACKEND_ENDPOINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateEndpoint(newContext.MONGO_BACKEND_ENDPOINT, allowedMongoBackendEndpoints)) {
|
|
||||||
delete newContext.MONGO_BACKEND_ENDPOINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateEndpoint(newContext.JUNO_ENDPOINT, allowedJunoOrigins)) {
|
|
||||||
delete newContext.JUNO_ENDPOINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateEndpoint(newContext.hostedExplorerURL, allowedHostedExplorerEndpoints)) {
|
|
||||||
delete newContext.hostedExplorerURL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateEndpoint(newContext.msalRedirectURI, allowedMsalRedirectEndpoints)) {
|
|
||||||
delete newContext.msalRedirectURI;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.assign(configContext, newContext);
|
Object.assign(configContext, newContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,8 +95,18 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
|
|||||||
});
|
});
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
try {
|
try {
|
||||||
const { ...externalConfig } = await response.json();
|
const { allowedParentFrameOrigins, allowedJunoOrigins, ...externalConfig } = await response.json();
|
||||||
updateConfigContext(externalConfig);
|
Object.assign(configContext, externalConfig);
|
||||||
|
if (allowedParentFrameOrigins && allowedParentFrameOrigins.length > 0) {
|
||||||
|
updateConfigContext({
|
||||||
|
allowedParentFrameOrigins: [...configContext.allowedParentFrameOrigins, ...allowedParentFrameOrigins],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (allowedJunoOrigins && allowedJunoOrigins.length > 0) {
|
||||||
|
updateConfigContext({
|
||||||
|
allowedJunoOrigins: [...configContext.allowedJunoOrigins, ...allowedJunoOrigins],
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Unable to parse json in config file");
|
console.error("Unable to parse json in config file");
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ export enum TabKind {
|
|||||||
Graph,
|
Graph,
|
||||||
SQLQuery,
|
SQLQuery,
|
||||||
ScaleSettings,
|
ScaleSettings,
|
||||||
DataUploader,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ConnectionStatusType, ContainerStatusType } from "../Common/Constants";
|
import { ConnectionStatusType } from "../Common/Constants";
|
||||||
|
|
||||||
export interface DatabaseAccount {
|
export interface DatabaseAccount {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -26,8 +26,6 @@ export interface DatabaseAccountExtendedProperties {
|
|||||||
isVirtualNetworkFilterEnabled?: boolean;
|
isVirtualNetworkFilterEnabled?: boolean;
|
||||||
ipRules?: IpRule[];
|
ipRules?: IpRule[];
|
||||||
privateEndpointConnections?: unknown[];
|
privateEndpointConnections?: unknown[];
|
||||||
capacity?: { totalThroughputLimit: number };
|
|
||||||
locations?: DatabaseAccountResponseLocation[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DatabaseAccountResponseLocation {
|
export interface DatabaseAccountResponseLocation {
|
||||||
@@ -428,32 +426,6 @@ export interface OperationStatus {
|
|||||||
export interface NotebookWorkspaceConnectionInfo {
|
export interface NotebookWorkspaceConnectionInfo {
|
||||||
authToken: string;
|
authToken: string;
|
||||||
notebookServerEndpoint: string;
|
notebookServerEndpoint: string;
|
||||||
forwardingId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ContainerInfo {
|
|
||||||
durationLeftInMinutes: number;
|
|
||||||
notebookServerInfo: NotebookWorkspaceConnectionInfo;
|
|
||||||
status: ContainerStatusType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IProvisionData {
|
|
||||||
cosmosEndpoint: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IContainerData {
|
|
||||||
forwardingId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IResponse<T> {
|
|
||||||
status: number;
|
|
||||||
data: T;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IPhoenixConnectionInfoResult {
|
|
||||||
readonly notebookAuthToken?: string;
|
|
||||||
readonly notebookServerUrl?: string;
|
|
||||||
readonly forwardingId?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NotebookWorkspaceFeedResponse {
|
export interface NotebookWorkspaceFeedResponse {
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ export enum MessageTypes {
|
|||||||
CreateWorkspace,
|
CreateWorkspace,
|
||||||
CreateSparkPool,
|
CreateSparkPool,
|
||||||
RefreshDatabaseAccount,
|
RefreshDatabaseAccount,
|
||||||
CloseTab,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Versions, ActionContracts, Diagnostics };
|
export { Versions, ActionContracts, Diagnostics };
|
||||||
|
|||||||
@@ -142,7 +142,6 @@ export interface Collection extends CollectionBase {
|
|||||||
onGraphDocumentsClick(): void;
|
onGraphDocumentsClick(): void;
|
||||||
onMongoDBDocumentsClick(): void;
|
onMongoDBDocumentsClick(): void;
|
||||||
onSchemaAnalyzerClick(): void;
|
onSchemaAnalyzerClick(): void;
|
||||||
onDataUploaderClick(): void;
|
|
||||||
openTab(): void;
|
openTab(): void;
|
||||||
|
|
||||||
onSettingsClick: () => Promise<void>;
|
onSettingsClick: () => Promise<void>;
|
||||||
@@ -365,7 +364,6 @@ export enum CollectionTabKind {
|
|||||||
CollectionSettingsV2 = 20,
|
CollectionSettingsV2 = 20,
|
||||||
DatabaseSettingsV2 = 21,
|
DatabaseSettingsV2 = 21,
|
||||||
SchemaAnalyzer = 22,
|
SchemaAnalyzer = 22,
|
||||||
DataUploader = 23,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TerminalKind {
|
export enum TerminalKind {
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ export const createCollectionContextMenuButton = (
|
|||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
iconSrc: HostedTerminalIcon,
|
iconSrc: HostedTerminalIcon,
|
||||||
|
isDisabled: useNotebook.getState().isShellEnabled && userContext.features.notebooksTemporarilyDown,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
if (useNotebook.getState().isShellEnabled) {
|
if (useNotebook.getState().isShellEnabled) {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
Link,
|
Link,
|
||||||
PrimaryButton,
|
PrimaryButton,
|
||||||
ProgressIndicator,
|
ProgressIndicator,
|
||||||
|
Text,
|
||||||
TextField,
|
TextField,
|
||||||
} from "@fluentui/react";
|
} from "@fluentui/react";
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
@@ -196,7 +197,7 @@ export const Dialog: FC = () => {
|
|||||||
{linkProps.linkText} <FontIcon iconName="NavigateExternalInline" />
|
{linkProps.linkText} <FontIcon iconName="NavigateExternalInline" />
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
{contentHtml}
|
{contentHtml && <Text>{contentHtml}</Text>}
|
||||||
{progressIndicatorProps && <ProgressIndicator {...progressIndicatorProps} />}
|
{progressIndicatorProps && <ProgressIndicator {...progressIndicatorProps} />}
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<PrimaryButton {...primaryButtonProps} />
|
<PrimaryButton {...primaryButtonProps} />
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { DefaultButton, IButtonProps, ITextFieldProps, TextField } from "@fluentui/react";
|
import { DefaultButton, IButtonProps, ITextFieldProps, TextField } from "@fluentui/react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
import * as UrlUtility from "../../../Common/UrlUtility";
|
|
||||||
import { IGitHubRepo } from "../../../GitHub/GitHubClient";
|
|
||||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
|
||||||
import Explorer from "../../Explorer";
|
|
||||||
import { RepoListItem } from "./GitHubReposComponent";
|
import { RepoListItem } from "./GitHubReposComponent";
|
||||||
import { ChildrenMargin } from "./GitHubStyleConstants";
|
import { ChildrenMargin } from "./GitHubStyleConstants";
|
||||||
|
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||||
|
import { IGitHubRepo } from "../../../GitHub/GitHubClient";
|
||||||
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import * as UrlUtility from "../../../Common/UrlUtility";
|
||||||
|
import Explorer from "../../Explorer";
|
||||||
|
|
||||||
export interface AddRepoComponentProps {
|
export interface AddRepoComponentProps {
|
||||||
container: Explorer;
|
container: Explorer;
|
||||||
@@ -27,6 +27,7 @@ export class AddRepoComponent extends React.Component<AddRepoComponentProps, Add
|
|||||||
private static readonly ButtonText = "Add";
|
private static readonly ButtonText = "Add";
|
||||||
private static readonly TextFieldPlaceholder = "https://github.com/owner/repo/tree/branch";
|
private static readonly TextFieldPlaceholder = "https://github.com/owner/repo/tree/branch";
|
||||||
private static readonly TextFieldErrorMessage = "Invalid url";
|
private static readonly TextFieldErrorMessage = "Invalid url";
|
||||||
|
private static readonly DefaultBranchName = "master";
|
||||||
|
|
||||||
constructor(props: AddRepoComponentProps) {
|
constructor(props: AddRepoComponentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -77,7 +78,7 @@ export class AddRepoComponent extends React.Component<AddRepoComponentProps, Add
|
|||||||
});
|
});
|
||||||
let enteredUrl = this.state.textFieldValue;
|
let enteredUrl = this.state.textFieldValue;
|
||||||
if (enteredUrl.indexOf("/tree/") === -1) {
|
if (enteredUrl.indexOf("/tree/") === -1) {
|
||||||
enteredUrl = UrlUtility.createUri(enteredUrl, `tree/`);
|
enteredUrl = UrlUtility.createUri(enteredUrl, `tree/${AddRepoComponent.DefaultBranchName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const repoInfo = GitHubUtils.fromRepoUri(enteredUrl);
|
const repoInfo = GitHubUtils.fromRepoUri(enteredUrl);
|
||||||
@@ -92,7 +93,11 @@ export class AddRepoComponent extends React.Component<AddRepoComponentProps, Add
|
|||||||
const item: RepoListItem = {
|
const item: RepoListItem = {
|
||||||
key: GitHubUtils.toRepoFullName(repo.owner, repo.name),
|
key: GitHubUtils.toRepoFullName(repo.owner, repo.name),
|
||||||
repo,
|
repo,
|
||||||
branches: repoInfo.branch ? [{ name: repoInfo.branch }] : [],
|
branches: [
|
||||||
|
{
|
||||||
|
name: repoInfo.branch,
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
TelemetryProcessor.traceSuccess(
|
TelemetryProcessor.traceSuccess(
|
||||||
|
|||||||
@@ -24,11 +24,11 @@ import { RepoListItem } from "./GitHubReposComponent";
|
|||||||
import {
|
import {
|
||||||
BranchesDropdownCheckboxStyles,
|
BranchesDropdownCheckboxStyles,
|
||||||
BranchesDropdownOptionContainerStyle,
|
BranchesDropdownOptionContainerStyle,
|
||||||
BranchesDropdownStyles,
|
|
||||||
BranchesDropdownWidth,
|
|
||||||
ReposListBranchesColumnWidth,
|
|
||||||
ReposListCheckboxStyles,
|
ReposListCheckboxStyles,
|
||||||
ReposListRepoColumnMinWidth,
|
ReposListRepoColumnMinWidth,
|
||||||
|
ReposListBranchesColumnWidth,
|
||||||
|
BranchesDropdownWidth,
|
||||||
|
BranchesDropdownStyles,
|
||||||
} from "./GitHubStyleConstants";
|
} from "./GitHubStyleConstants";
|
||||||
|
|
||||||
export interface ReposListComponentProps {
|
export interface ReposListComponentProps {
|
||||||
@@ -44,7 +44,6 @@ export interface BranchesProps {
|
|||||||
lastPageInfo?: IGitHubPageInfo;
|
lastPageInfo?: IGitHubPageInfo;
|
||||||
hasMore: boolean;
|
hasMore: boolean;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
defaultBranchName: string;
|
|
||||||
loadMore: () => void;
|
loadMore: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +64,7 @@ export class ReposListComponent extends React.Component<ReposListComponentProps>
|
|||||||
private static readonly BranchesColumnName = "Branches";
|
private static readonly BranchesColumnName = "Branches";
|
||||||
private static readonly LoadingText = "Loading...";
|
private static readonly LoadingText = "Loading...";
|
||||||
private static readonly LoadMoreText = "Load more";
|
private static readonly LoadMoreText = "Load more";
|
||||||
private static readonly DefaultBranchNames = "master/main";
|
private static readonly DefaultBranchName = "master";
|
||||||
private static readonly FooterIndex = -1;
|
private static readonly FooterIndex = -1;
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
@@ -156,10 +155,6 @@ export class ReposListComponent extends React.Component<ReposListComponentProps>
|
|||||||
}
|
}
|
||||||
|
|
||||||
const branchesProps = this.props.branchesProps[GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)];
|
const branchesProps = this.props.branchesProps[GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)];
|
||||||
if (item.branches.length === 0 && branchesProps.defaultBranchName) {
|
|
||||||
item.branches = [{ name: branchesProps.defaultBranchName }];
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: IDropdownOption[] = branchesProps.branches.map((branch) => ({
|
const options: IDropdownOption[] = branchesProps.branches.map((branch) => ({
|
||||||
key: branch.name,
|
key: branch.name,
|
||||||
text: branch.name,
|
text: branch.name,
|
||||||
@@ -203,7 +198,7 @@ export class ReposListComponent extends React.Component<ReposListComponentProps>
|
|||||||
const dropdownProps: IDropdownProps = {
|
const dropdownProps: IDropdownProps = {
|
||||||
styles: BranchesDropdownStyles,
|
styles: BranchesDropdownStyles,
|
||||||
options: [],
|
options: [],
|
||||||
placeholder: ReposListComponent.DefaultBranchNames,
|
placeholder: ReposListComponent.DefaultBranchName,
|
||||||
disabled: true,
|
disabled: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -277,7 +272,7 @@ export class ReposListComponent extends React.Component<ReposListComponentProps>
|
|||||||
styles: ReposListCheckboxStyles,
|
styles: ReposListCheckboxStyles,
|
||||||
onChange: () => {
|
onChange: () => {
|
||||||
const repoListItem = { ...item };
|
const repoListItem = { ...item };
|
||||||
repoListItem.branches = [];
|
repoListItem.branches = [{ name: ReposListComponent.DefaultBranchName }];
|
||||||
this.props.pinRepo(repoListItem);
|
this.props.pinRepo(repoListItem);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,19 +35,16 @@ const testCassandraAccount: DataModels.DatabaseAccount = {
|
|||||||
const testNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
const testNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||||
authToken: "authToken",
|
authToken: "authToken",
|
||||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com",
|
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com",
|
||||||
forwardingId: "Id",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const testMongoNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
const testMongoNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||||
authToken: "authToken",
|
authToken: "authToken",
|
||||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo",
|
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo",
|
||||||
forwardingId: "Id",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const testCassandraNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
const testCassandraNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||||
authToken: "authToken",
|
authToken: "authToken",
|
||||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/cassandra",
|
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/cassandra",
|
||||||
forwardingId: "Id",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("NotebookTerminalComponent", () => {
|
describe("NotebookTerminalComponent", () => {
|
||||||
@@ -55,7 +52,6 @@ describe("NotebookTerminalComponent", () => {
|
|||||||
const props: NotebookTerminalComponentProps = {
|
const props: NotebookTerminalComponentProps = {
|
||||||
databaseAccount: testAccount,
|
databaseAccount: testAccount,
|
||||||
notebookServerInfo: testNotebookServerInfo,
|
notebookServerInfo: testNotebookServerInfo,
|
||||||
tabId: undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||||
@@ -66,7 +62,6 @@ describe("NotebookTerminalComponent", () => {
|
|||||||
const props: NotebookTerminalComponentProps = {
|
const props: NotebookTerminalComponentProps = {
|
||||||
databaseAccount: testMongo32Account,
|
databaseAccount: testMongo32Account,
|
||||||
notebookServerInfo: testMongoNotebookServerInfo,
|
notebookServerInfo: testMongoNotebookServerInfo,
|
||||||
tabId: undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||||
@@ -77,7 +72,6 @@ describe("NotebookTerminalComponent", () => {
|
|||||||
const props: NotebookTerminalComponentProps = {
|
const props: NotebookTerminalComponentProps = {
|
||||||
databaseAccount: testMongo36Account,
|
databaseAccount: testMongo36Account,
|
||||||
notebookServerInfo: testMongoNotebookServerInfo,
|
notebookServerInfo: testMongoNotebookServerInfo,
|
||||||
tabId: undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||||
@@ -88,7 +82,6 @@ describe("NotebookTerminalComponent", () => {
|
|||||||
const props: NotebookTerminalComponentProps = {
|
const props: NotebookTerminalComponentProps = {
|
||||||
databaseAccount: testCassandraAccount,
|
databaseAccount: testCassandraAccount,
|
||||||
notebookServerInfo: testCassandraNotebookServerInfo,
|
notebookServerInfo: testCassandraNotebookServerInfo,
|
||||||
tabId: undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import * as StringUtils from "../../../Utils/StringUtils";
|
|||||||
export interface NotebookTerminalComponentProps {
|
export interface NotebookTerminalComponentProps {
|
||||||
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
||||||
databaseAccount: DataModels.DatabaseAccount;
|
databaseAccount: DataModels.DatabaseAccount;
|
||||||
tabId: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NotebookTerminalComponent extends React.Component<NotebookTerminalComponentProps> {
|
export class NotebookTerminalComponent extends React.Component<NotebookTerminalComponentProps> {
|
||||||
@@ -56,7 +55,6 @@ export class NotebookTerminalComponent extends React.Component<NotebookTerminalC
|
|||||||
apiType: userContext.apiType,
|
apiType: userContext.apiType,
|
||||||
authType: userContext.authType,
|
authType: userContext.authType,
|
||||||
databaseAccount: userContext.databaseAccount,
|
databaseAccount: userContext.databaseAccount,
|
||||||
tabId: this.props.tabId,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
postRobot.send(this.terminalWindow, "props", props, {
|
postRobot.send(this.terminalWindow, "props", props, {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import Explorer from "../../Explorer";
|
|||||||
import { NotebookClientV2 } from "../../Notebook/NotebookClientV2";
|
import { NotebookClientV2 } from "../../Notebook/NotebookClientV2";
|
||||||
import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper";
|
import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper";
|
||||||
import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer";
|
import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer";
|
||||||
|
import { NotebookUtil } from "../../Notebook/NotebookUtil";
|
||||||
import { useNotebook } from "../../Notebook/useNotebook";
|
import { useNotebook } from "../../Notebook/useNotebook";
|
||||||
import { Dialog, TextFieldProps, useDialog } from "../Dialog";
|
import { Dialog, TextFieldProps, useDialog } from "../Dialog";
|
||||||
import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
|
import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
|
||||||
@@ -52,7 +53,7 @@ export class NotebookViewerComponent
|
|||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.clientManager = new NotebookClientV2({
|
this.clientManager = new NotebookClientV2({
|
||||||
connectionInfo: { authToken: undefined, notebookServerEndpoint: undefined, forwardingId: undefined },
|
connectionInfo: { authToken: undefined, notebookServerEndpoint: undefined },
|
||||||
databaseAccountName: undefined,
|
databaseAccountName: undefined,
|
||||||
defaultExperience: "NotebookViewer",
|
defaultExperience: "NotebookViewer",
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
@@ -147,7 +148,9 @@ export class NotebookViewerComponent
|
|||||||
<NotebookMetadataComponent
|
<NotebookMetadataComponent
|
||||||
data={this.state.galleryItem}
|
data={this.state.galleryItem}
|
||||||
isFavorite={this.state.isFavorite}
|
isFavorite={this.state.isFavorite}
|
||||||
downloadButtonText={this.props.container && `Download to ${useNotebook.getState().notebookFolderName}`}
|
downloadButtonText={
|
||||||
|
this.props.container && NotebookUtil.getNotebookBtnTitle(useNotebook.getState().notebookFolderName)
|
||||||
|
}
|
||||||
onTagClick={this.props.onTagClick}
|
onTagClick={this.props.onTagClick}
|
||||||
onFavoriteClick={this.favoriteItem}
|
onFavoriteClick={this.favoriteItem}
|
||||||
onUnfavoriteClick={this.unfavoriteItem}
|
onUnfavoriteClick={this.unfavoriteItem}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { IPivotItemProps, IPivotProps, Pivot, PivotItem } from "@fluentui/react";
|
import { IPivotItemProps, IPivotProps, Pivot, PivotItem } from "@fluentui/react";
|
||||||
import { useDatabases } from "Explorer/useDatabases";
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import DiscardIcon from "../../../../images/discard.svg";
|
import DiscardIcon from "../../../../images/discard.svg";
|
||||||
import SaveIcon from "../../../../images/save-cosmos.svg";
|
import SaveIcon from "../../../../images/save-cosmos.svg";
|
||||||
@@ -72,7 +71,6 @@ export interface SettingsComponentState {
|
|||||||
wasAutopilotOriginallySet: boolean;
|
wasAutopilotOriginallySet: boolean;
|
||||||
isScaleSaveable: boolean;
|
isScaleSaveable: boolean;
|
||||||
isScaleDiscardable: boolean;
|
isScaleDiscardable: boolean;
|
||||||
throughputError: string;
|
|
||||||
|
|
||||||
timeToLive: TtlType;
|
timeToLive: TtlType;
|
||||||
timeToLiveBaseline: TtlType;
|
timeToLiveBaseline: TtlType;
|
||||||
@@ -126,7 +124,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
private changeFeedPolicyVisible: boolean;
|
private changeFeedPolicyVisible: boolean;
|
||||||
private isFixedContainer: boolean;
|
private isFixedContainer: boolean;
|
||||||
private shouldShowIndexingPolicyEditor: boolean;
|
private shouldShowIndexingPolicyEditor: boolean;
|
||||||
private totalThroughputUsed: number;
|
|
||||||
public mongoDBCollectionResource: MongoDBCollectionResource;
|
public mongoDBCollectionResource: MongoDBCollectionResource;
|
||||||
|
|
||||||
constructor(props: SettingsComponentProps) {
|
constructor(props: SettingsComponentProps) {
|
||||||
@@ -158,7 +155,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
wasAutopilotOriginallySet: false,
|
wasAutopilotOriginallySet: false,
|
||||||
isScaleSaveable: false,
|
isScaleSaveable: false,
|
||||||
isScaleDiscardable: false,
|
isScaleDiscardable: false,
|
||||||
throughputError: undefined,
|
|
||||||
|
|
||||||
timeToLive: undefined,
|
timeToLive: undefined,
|
||||||
timeToLiveBaseline: undefined,
|
timeToLiveBaseline: undefined,
|
||||||
@@ -212,11 +208,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
|
||||||
if (throughputCap && throughputCap !== -1) {
|
|
||||||
this.calculateTotalThroughputUsed();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
@@ -263,10 +254,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.throughputError) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
this.state.isScaleSaveable ||
|
this.state.isScaleSaveable ||
|
||||||
this.state.isSubSettingsSaveable ||
|
this.state.isSubSettingsSaveable ||
|
||||||
@@ -494,26 +481,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
private onMongoIndexingPolicyDiscardableChange = (isMongoIndexingPolicyDiscardable: boolean): void =>
|
private onMongoIndexingPolicyDiscardableChange = (isMongoIndexingPolicyDiscardable: boolean): void =>
|
||||||
this.setState({ isMongoIndexingPolicyDiscardable });
|
this.setState({ isMongoIndexingPolicyDiscardable });
|
||||||
|
|
||||||
private calculateTotalThroughputUsed = (): void => {
|
|
||||||
this.totalThroughputUsed = 0;
|
|
||||||
(useDatabases.getState().databases || []).forEach(async (database) => {
|
|
||||||
if (database.offer()) {
|
|
||||||
const dbThroughput = database.offer().autoscaleMaxThroughput || database.offer().manualThroughput;
|
|
||||||
this.totalThroughputUsed += dbThroughput;
|
|
||||||
}
|
|
||||||
|
|
||||||
(database.collections() || []).forEach(async (collection) => {
|
|
||||||
if (collection.offer()) {
|
|
||||||
const colThroughput = collection.offer().autoscaleMaxThroughput || collection.offer().manualThroughput;
|
|
||||||
this.totalThroughputUsed += colThroughput;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1;
|
|
||||||
this.totalThroughputUsed *= numberOfRegions;
|
|
||||||
};
|
|
||||||
|
|
||||||
public getAnalyticalStorageTtl = (): number => {
|
public getAnalyticalStorageTtl = (): number => {
|
||||||
if (this.isAnalyticalStorageEnabled) {
|
if (this.isAnalyticalStorageEnabled) {
|
||||||
if (this.state.analyticalStorageTtlSelection === TtlType.On) {
|
if (this.state.analyticalStorageTtlSelection === TtlType.On) {
|
||||||
@@ -676,31 +643,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
return buttons;
|
return buttons;
|
||||||
};
|
};
|
||||||
|
|
||||||
private onMaxAutoPilotThroughputChange = (newThroughput: number): void => {
|
private onMaxAutoPilotThroughputChange = (newThroughput: number): void =>
|
||||||
let throughputError = "";
|
this.setState({ autoPilotThroughput: newThroughput });
|
||||||
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
|
||||||
const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1;
|
|
||||||
const throughputDelta = (newThroughput - this.offer.autoscaleMaxThroughput) * numberOfRegions;
|
|
||||||
if (throughputCap && throughputCap !== -1 && throughputCap - this.totalThroughputUsed < throughputDelta) {
|
|
||||||
throughputError = `Your account is currently configured with a total throughput limit of ${throughputCap} RU/s. This update isn't possible because it would increase the total throughput to ${
|
|
||||||
this.totalThroughputUsed + throughputDelta
|
|
||||||
} RU/s. Change total throughput limit in cost management.`;
|
|
||||||
}
|
|
||||||
this.setState({ autoPilotThroughput: newThroughput, throughputError });
|
|
||||||
};
|
|
||||||
|
|
||||||
private onThroughputChange = (newThroughput: number): void => {
|
private onThroughputChange = (newThroughput: number): void => this.setState({ throughput: newThroughput });
|
||||||
let throughputError = "";
|
|
||||||
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
|
||||||
const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1;
|
|
||||||
const throughputDelta = (newThroughput - this.offer.manualThroughput) * numberOfRegions;
|
|
||||||
if (throughputCap && throughputCap !== -1 && throughputCap - this.totalThroughputUsed < throughputDelta) {
|
|
||||||
throughputError = `Your account is currently configured with a total throughput limit of ${throughputCap} RU/s. This update isn't possible because it would increase the total throughput to ${
|
|
||||||
this.totalThroughputUsed + throughputDelta
|
|
||||||
} RU/s. Change total throughput limit in cost management.`;
|
|
||||||
}
|
|
||||||
this.setState({ throughput: newThroughput, throughputError });
|
|
||||||
};
|
|
||||||
|
|
||||||
private onAutoPilotSelected = (isAutoPilotSelected: boolean): void =>
|
private onAutoPilotSelected = (isAutoPilotSelected: boolean): void =>
|
||||||
this.setState({ isAutoPilotSelected: isAutoPilotSelected });
|
this.setState({ isAutoPilotSelected: isAutoPilotSelected });
|
||||||
@@ -947,7 +893,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
onScaleSaveableChange: this.onScaleSaveableChange,
|
onScaleSaveableChange: this.onScaleSaveableChange,
|
||||||
onScaleDiscardableChange: this.onScaleDiscardableChange,
|
onScaleDiscardableChange: this.onScaleDiscardableChange,
|
||||||
initialNotification: this.props.settingsTab.pendingNotification(),
|
initialNotification: this.props.settingsTab.pendingNotification(),
|
||||||
throughputError: this.state.throughputError,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!this.isCollectionSettingsTab) {
|
if (!this.isCollectionSettingsTab) {
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ export interface ScaleComponentProps {
|
|||||||
onScaleSaveableChange: (isScaleSaveable: boolean) => void;
|
onScaleSaveableChange: (isScaleSaveable: boolean) => void;
|
||||||
onScaleDiscardableChange: (isScaleDiscardable: boolean) => void;
|
onScaleDiscardableChange: (isScaleDiscardable: boolean) => void;
|
||||||
initialNotification: DataModels.Notification;
|
initialNotification: DataModels.Notification;
|
||||||
throughputError?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
||||||
@@ -190,7 +189,6 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
onScaleDiscardableChange={this.props.onScaleDiscardableChange}
|
onScaleDiscardableChange={this.props.onScaleDiscardableChange}
|
||||||
getThroughputWarningMessage={this.getThroughputWarningMessage}
|
getThroughputWarningMessage={this.getThroughputWarningMessage}
|
||||||
usageSizeInKB={this.props.collection?.usageSizeInKB()}
|
usageSizeInKB={this.props.collection?.usageSizeInKB()}
|
||||||
throughputError={this.props.throughputError}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { Action, ActionModifiers } from "../../../../../Shared/Telemetry/Telemet
|
|||||||
import * as TelemetryProcessor from "../../../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "../../../../../UserContext";
|
import { userContext } from "../../../../../UserContext";
|
||||||
import * as AutoPilotUtils from "../../../../../Utils/AutoPilotUtils";
|
import * as AutoPilotUtils from "../../../../../Utils/AutoPilotUtils";
|
||||||
import { autoPilotThroughput1K, autoPilotThroughput4K } from "../../../../../Utils/AutoPilotUtils";
|
import { minAutoPilotThroughput } from "../../../../../Utils/AutoPilotUtils";
|
||||||
import { calculateEstimateNumber, usageInGB } from "../../../../../Utils/PricingUtils";
|
import { calculateEstimateNumber, usageInGB } from "../../../../../Utils/PricingUtils";
|
||||||
import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
||||||
import {
|
import {
|
||||||
@@ -75,7 +75,6 @@ export interface ThroughputInputAutoPilotV3Props {
|
|||||||
onScaleDiscardableChange: (isScaleDiscardable: boolean) => void;
|
onScaleDiscardableChange: (isScaleDiscardable: boolean) => void;
|
||||||
getThroughputWarningMessage: () => JSX.Element;
|
getThroughputWarningMessage: () => JSX.Element;
|
||||||
usageSizeInKB: number;
|
usageSizeInKB: number;
|
||||||
throughputError?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ThroughputInputAutoPilotV3State {
|
interface ThroughputInputAutoPilotV3State {
|
||||||
@@ -540,8 +539,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
step={AutoPilotUtils.autoPilotIncrementStep}
|
step={AutoPilotUtils.autoPilotIncrementStep}
|
||||||
value={this.overrideWithProvisionedThroughputSettings() ? "" : this.props.maxAutoPilotThroughput?.toString()}
|
value={this.overrideWithProvisionedThroughputSettings() ? "" : this.props.maxAutoPilotThroughput?.toString()}
|
||||||
onChange={this.onAutoPilotThroughputChange}
|
onChange={this.onAutoPilotThroughputChange}
|
||||||
min={userContext.features.freetierAutoscaleThroughput ? autoPilotThroughput1K : autoPilotThroughput4K}
|
min={minAutoPilotThroughput}
|
||||||
errorMessage={this.props.throughputError}
|
|
||||||
/>
|
/>
|
||||||
{!this.overrideWithProvisionedThroughputSettings() && this.getAutoPilotUsageCost()}
|
{!this.overrideWithProvisionedThroughputSettings() && this.getAutoPilotUsageCost()}
|
||||||
{this.minRUperGBSurvey()}
|
{this.minRUperGBSurvey()}
|
||||||
@@ -581,7 +579,6 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
}
|
}
|
||||||
onChange={this.onThroughputChange}
|
onChange={this.onThroughputChange}
|
||||||
min={this.props.minimum}
|
min={this.props.minimum}
|
||||||
errorMessage={this.props.throughputError}
|
|
||||||
/>
|
/>
|
||||||
{this.state.exceedFreeTierThroughput && (
|
{this.state.exceedFreeTierThroughput && (
|
||||||
<MessageBar
|
<MessageBar
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,10 +5,8 @@ const props = {
|
|||||||
isDatabase: false,
|
isDatabase: false,
|
||||||
showFreeTierExceedThroughputTooltip: true,
|
showFreeTierExceedThroughputTooltip: true,
|
||||||
isSharded: true,
|
isSharded: true,
|
||||||
isFreeTier: false,
|
|
||||||
setThroughputValue: () => jest.fn(),
|
setThroughputValue: () => jest.fn(),
|
||||||
setIsAutoscale: () => jest.fn(),
|
setIsAutoscale: () => jest.fn(),
|
||||||
setIsThroughputCapExceeded: () => jest.fn(),
|
|
||||||
onCostAcknowledgeChange: () => jest.fn(),
|
onCostAcknowledgeChange: () => jest.fn(),
|
||||||
};
|
};
|
||||||
describe("ThroughputInput Pane", () => {
|
describe("ThroughputInput Pane", () => {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Checkbox, DirectionalHint, Link, Stack, Text, TextField, TooltipHost } from "@fluentui/react";
|
import { Checkbox, DirectionalHint, Link, Stack, Text, TextField, TooltipHost } from "@fluentui/react";
|
||||||
import { useDatabases } from "Explorer/useDatabases";
|
import React, { FunctionComponent, useState } from "react";
|
||||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
|
||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
|
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
|
||||||
import * as SharedConstants from "../../../Shared/Constants";
|
import * as SharedConstants from "../../../Shared/Constants";
|
||||||
@@ -14,86 +13,28 @@ import "./ThroughputInput.less";
|
|||||||
export interface ThroughputInputProps {
|
export interface ThroughputInputProps {
|
||||||
isDatabase: boolean;
|
isDatabase: boolean;
|
||||||
isSharded: boolean;
|
isSharded: boolean;
|
||||||
isFreeTier: boolean;
|
|
||||||
showFreeTierExceedThroughputTooltip: boolean;
|
showFreeTierExceedThroughputTooltip: boolean;
|
||||||
setThroughputValue: (throughput: number) => void;
|
setThroughputValue: (throughput: number) => void;
|
||||||
setIsAutoscale: (isAutoscale: boolean) => void;
|
setIsAutoscale: (isAutoscale: boolean) => void;
|
||||||
setIsThroughputCapExceeded: (isThroughputCapExceeded: boolean) => void;
|
|
||||||
onCostAcknowledgeChange: (isAcknowledged: boolean) => void;
|
onCostAcknowledgeChange: (isAcknowledged: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||||
isDatabase,
|
isDatabase,
|
||||||
isSharded,
|
|
||||||
isFreeTier,
|
|
||||||
showFreeTierExceedThroughputTooltip,
|
showFreeTierExceedThroughputTooltip,
|
||||||
setThroughputValue,
|
setThroughputValue,
|
||||||
setIsAutoscale,
|
setIsAutoscale,
|
||||||
setIsThroughputCapExceeded,
|
isSharded,
|
||||||
onCostAcknowledgeChange,
|
onCostAcknowledgeChange,
|
||||||
}: ThroughputInputProps) => {
|
}: ThroughputInputProps) => {
|
||||||
const [isAutoscaleSelected, setIsAutoScaleSelected] = useState<boolean>(true);
|
const [isAutoscaleSelected, setIsAutoScaleSelected] = useState<boolean>(true);
|
||||||
const [throughput, setThroughput] = useState<number>(
|
const [throughput, setThroughput] = useState<number>(AutoPilotUtils.minAutoPilotThroughput);
|
||||||
isFreeTier && userContext.features.freetierAutoscaleThroughput
|
|
||||||
? AutoPilotUtils.autoPilotThroughput1K
|
|
||||||
: AutoPilotUtils.autoPilotThroughput4K
|
|
||||||
);
|
|
||||||
const [isCostAcknowledged, setIsCostAcknowledged] = useState<boolean>(false);
|
const [isCostAcknowledged, setIsCostAcknowledged] = useState<boolean>(false);
|
||||||
const [throughputError, setThroughputError] = useState<string>("");
|
const [throughputError, setThroughputError] = useState<string>("");
|
||||||
const [totalThroughputUsed, setTotalThroughputUsed] = useState<number>(0);
|
|
||||||
|
|
||||||
setIsAutoscale(isAutoscaleSelected);
|
setIsAutoscale(isAutoscaleSelected);
|
||||||
setThroughputValue(throughput);
|
setThroughputValue(throughput);
|
||||||
|
|
||||||
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
|
||||||
const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// throughput cap check for the initial state
|
|
||||||
let totalThroughput = 0;
|
|
||||||
(useDatabases.getState().databases || []).forEach((database) => {
|
|
||||||
if (database.offer()) {
|
|
||||||
const dbThroughput = database.offer().autoscaleMaxThroughput || database.offer().manualThroughput;
|
|
||||||
totalThroughput += dbThroughput;
|
|
||||||
}
|
|
||||||
|
|
||||||
(database.collections() || []).forEach((collection) => {
|
|
||||||
if (collection.offer()) {
|
|
||||||
const colThroughput = collection.offer().autoscaleMaxThroughput || collection.offer().manualThroughput;
|
|
||||||
totalThroughput += colThroughput;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
totalThroughput *= numberOfRegions;
|
|
||||||
setTotalThroughputUsed(totalThroughput);
|
|
||||||
|
|
||||||
if (throughputCap && throughputCap !== -1 && throughputCap - totalThroughput < throughput) {
|
|
||||||
setThroughputError(
|
|
||||||
`Your account is currently configured with a total throughput limit of ${throughputCap} RU/s. This update isn't possible because it would increase the total throughput to ${
|
|
||||||
totalThroughput + throughput * numberOfRegions
|
|
||||||
} RU/s. Change total throughput limit in cost management.`
|
|
||||||
);
|
|
||||||
|
|
||||||
setIsThroughputCapExceeded(true);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const checkThroughputCap = (newThroughput: number): boolean => {
|
|
||||||
if (throughputCap && throughputCap !== -1 && throughputCap - totalThroughputUsed < newThroughput) {
|
|
||||||
setThroughputError(
|
|
||||||
`Your account is currently configured with a total throughput limit of ${throughputCap} RU/s. This update isn't possible because it would increase the total throughput to ${
|
|
||||||
totalThroughputUsed + newThroughput * numberOfRegions
|
|
||||||
} RU/s. Change total throughput limit in cost management.`
|
|
||||||
);
|
|
||||||
setIsThroughputCapExceeded(true);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
setThroughputError("");
|
|
||||||
setIsThroughputCapExceeded(false);
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getThroughputLabelText = (): string => {
|
const getThroughputLabelText = (): string => {
|
||||||
let throughputHeaderText: string;
|
let throughputHeaderText: string;
|
||||||
if (isAutoscaleSelected) {
|
if (isAutoscaleSelected) {
|
||||||
@@ -119,17 +60,11 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
const newThroughput = parseInt(newInput);
|
const newThroughput = parseInt(newInput);
|
||||||
setThroughput(newThroughput);
|
setThroughput(newThroughput);
|
||||||
setThroughputValue(newThroughput);
|
setThroughputValue(newThroughput);
|
||||||
|
|
||||||
if (!isSharded && newThroughput > 10000) {
|
if (!isSharded && newThroughput > 10000) {
|
||||||
setThroughputError("Unsharded collections support up to 10,000 RUs");
|
setThroughputError("Unsharded collections support up to 10,000 RUs");
|
||||||
return;
|
} else {
|
||||||
|
setThroughputError("");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!checkThroughputCap(newThroughput)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setThroughputError("");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAutoScaleTooltip = (): string => {
|
const getAutoScaleTooltip = (): string => {
|
||||||
@@ -157,21 +92,15 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
|
|
||||||
const handleOnChangeMode = (event: React.ChangeEvent<HTMLInputElement>, mode: string): void => {
|
const handleOnChangeMode = (event: React.ChangeEvent<HTMLInputElement>, mode: string): void => {
|
||||||
if (mode === "Autoscale") {
|
if (mode === "Autoscale") {
|
||||||
const defaultThroughput =
|
setThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
||||||
isFreeTier && userContext.features.freetierAutoscaleThroughput
|
|
||||||
? AutoPilotUtils.autoPilotThroughput1K
|
|
||||||
: AutoPilotUtils.autoPilotThroughput4K;
|
|
||||||
setThroughput(defaultThroughput);
|
|
||||||
setIsAutoScaleSelected(true);
|
setIsAutoScaleSelected(true);
|
||||||
setThroughputValue(defaultThroughput);
|
setThroughputValue(AutoPilotUtils.minAutoPilotThroughput);
|
||||||
setIsAutoscale(true);
|
setIsAutoscale(true);
|
||||||
checkThroughputCap(defaultThroughput);
|
|
||||||
} else {
|
} else {
|
||||||
setThroughput(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
|
setThroughput(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
|
||||||
setIsAutoScaleSelected(false);
|
setIsAutoScaleSelected(false);
|
||||||
setThroughputValue(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
|
setThroughputValue(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
|
||||||
setIsAutoscale(false);
|
setIsAutoscale(false);
|
||||||
checkThroughputCap(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -236,11 +165,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
}}
|
}}
|
||||||
onChange={(event, newInput?: string) => onThroughputValueChange(newInput)}
|
onChange={(event, newInput?: string) => onThroughputValueChange(newInput)}
|
||||||
step={AutoPilotUtils.autoPilotIncrementStep}
|
step={AutoPilotUtils.autoPilotIncrementStep}
|
||||||
min={
|
min={AutoPilotUtils.minAutoPilotThroughput}
|
||||||
userContext.features.freetierAutoscaleThroughput
|
|
||||||
? AutoPilotUtils.autoPilotThroughput1K
|
|
||||||
: AutoPilotUtils.autoPilotThroughput4K
|
|
||||||
}
|
|
||||||
value={throughput.toString()}
|
value={throughput.toString()}
|
||||||
aria-label="Max request units per second"
|
aria-label="Max request units per second"
|
||||||
required={true}
|
required={true}
|
||||||
|
|||||||
@@ -3,11 +3,9 @@
|
|||||||
exports[`ThroughputInput Pane should render Default properly 1`] = `
|
exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||||
<ThroughputInput
|
<ThroughputInput
|
||||||
isDatabase={false}
|
isDatabase={false}
|
||||||
isFreeTier={false}
|
|
||||||
isSharded={true}
|
isSharded={true}
|
||||||
onCostAcknowledgeChange={[Function]}
|
onCostAcknowledgeChange={[Function]}
|
||||||
setIsAutoscale={[Function]}
|
setIsAutoscale={[Function]}
|
||||||
setIsThroughputCapExceeded={[Function]}
|
|
||||||
setThroughputValue={[Function]}
|
setThroughputValue={[Function]}
|
||||||
showFreeTierExceedThroughputTooltip={true}
|
showFreeTierExceedThroughputTooltip={true}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,31 +1,24 @@
|
|||||||
import { Link } from "@fluentui/react/lib/Link";
|
import { Link } from "@fluentui/react/lib/Link";
|
||||||
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
|
|
||||||
import { IGalleryItem } from "Juno/JunoClient";
|
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import _ from "underscore";
|
import _ from "underscore";
|
||||||
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointValidation";
|
|
||||||
import shallow from "zustand/shallow";
|
|
||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
|
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
|
||||||
import * as Constants from "../Common/Constants";
|
import * as Constants from "../Common/Constants";
|
||||||
import { Areas, ConnectionStatusType, HttpStatusCodes, Notebook } from "../Common/Constants";
|
import { ConnectionStatusType, HttpStatusCodes, Notebook } from "../Common/Constants";
|
||||||
import { readCollection } from "../Common/dataAccess/readCollection";
|
import { readCollection } from "../Common/dataAccess/readCollection";
|
||||||
import { readDatabases } from "../Common/dataAccess/readDatabases";
|
import { readDatabases } from "../Common/dataAccess/readDatabases";
|
||||||
|
import { isPublicInternetAccessAllowed } from "../Common/DatabaseAccountUtility";
|
||||||
import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHandlingUtils";
|
||||||
import * as Logger from "../Common/Logger";
|
import * as Logger from "../Common/Logger";
|
||||||
import { QueriesClient } from "../Common/QueriesClient";
|
import { QueriesClient } from "../Common/QueriesClient";
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
import {
|
import { ContainerConnectionInfo } from "../Contracts/DataModels";
|
||||||
ContainerConnectionInfo,
|
|
||||||
IPhoenixConnectionInfoResult,
|
|
||||||
IProvisionData,
|
|
||||||
IResponse,
|
|
||||||
} from "../Contracts/DataModels";
|
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
|
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
|
||||||
import { useSidePanel } from "../hooks/useSidePanel";
|
import { useSidePanel } from "../hooks/useSidePanel";
|
||||||
import { useTabs } from "../hooks/useTabs";
|
import { useTabs } from "../hooks/useTabs";
|
||||||
|
import { IGalleryItem } from "../Juno/JunoClient";
|
||||||
import { PhoenixClient } from "../Phoenix/PhoenixClient";
|
import { PhoenixClient } from "../Phoenix/PhoenixClient";
|
||||||
import * as ExplorerSettings from "../Shared/ExplorerSettings";
|
import * as ExplorerSettings from "../Shared/ExplorerSettings";
|
||||||
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
||||||
@@ -33,7 +26,12 @@ import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
|||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { getCollectionName, getUploadName } from "../Utils/APITypeUtils";
|
import { getCollectionName, getUploadName } from "../Utils/APITypeUtils";
|
||||||
import { update } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
|
import { update } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
|
||||||
import { listByDatabaseAccount } from "../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
|
import {
|
||||||
|
get as getWorkspace,
|
||||||
|
listByDatabaseAccount,
|
||||||
|
listConnectionInfo,
|
||||||
|
start,
|
||||||
|
} from "../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
|
||||||
import { stringToBlob } from "../Utils/BlobUtils";
|
import { stringToBlob } from "../Utils/BlobUtils";
|
||||||
import { isCapabilityEnabled } from "../Utils/CapabilityUtils";
|
import { isCapabilityEnabled } from "../Utils/CapabilityUtils";
|
||||||
import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils";
|
import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils";
|
||||||
@@ -47,12 +45,13 @@ import * as FileSystemUtil from "./Notebook/FileSystemUtil";
|
|||||||
import { SnapshotRequest } from "./Notebook/NotebookComponent/types";
|
import { SnapshotRequest } from "./Notebook/NotebookComponent/types";
|
||||||
import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem";
|
import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem";
|
||||||
import type NotebookManager from "./Notebook/NotebookManager";
|
import type NotebookManager from "./Notebook/NotebookManager";
|
||||||
import { NotebookPaneContent } from "./Notebook/NotebookManager";
|
import type { NotebookPaneContent } from "./Notebook/NotebookManager";
|
||||||
import { NotebookUtil } from "./Notebook/NotebookUtil";
|
import { NotebookUtil } from "./Notebook/NotebookUtil";
|
||||||
import { useNotebook } from "./Notebook/useNotebook";
|
import { useNotebook } from "./Notebook/useNotebook";
|
||||||
import { AddCollectionPanel } from "./Panes/AddCollectionPanel";
|
import { AddCollectionPanel } from "./Panes/AddCollectionPanel";
|
||||||
import { CassandraAddCollectionPane } from "./Panes/CassandraAddCollectionPane/CassandraAddCollectionPane";
|
import { CassandraAddCollectionPane } from "./Panes/CassandraAddCollectionPane/CassandraAddCollectionPane";
|
||||||
import { ExecuteSprocParamsPane } from "./Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane";
|
import { ExecuteSprocParamsPane } from "./Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane";
|
||||||
|
import { SetupNoteBooksPanel } from "./Panes/SetupNotebooksPanel/SetupNotebooksPanel";
|
||||||
import { StringInputPane } from "./Panes/StringInputPane/StringInputPane";
|
import { StringInputPane } from "./Panes/StringInputPane/StringInputPane";
|
||||||
import { UploadFilePane } from "./Panes/UploadFilePane/UploadFilePane";
|
import { UploadFilePane } from "./Panes/UploadFilePane/UploadFilePane";
|
||||||
import { UploadItemsPane } from "./Panes/UploadItemsPane/UploadItemsPane";
|
import { UploadItemsPane } from "./Panes/UploadItemsPane/UploadItemsPane";
|
||||||
@@ -166,23 +165,20 @@ export default class Explorer {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useNotebook.subscribe(
|
useNotebook.subscribe(
|
||||||
async () => this.initiateAndRefreshNotebookList(),
|
async () => {
|
||||||
(state) => [state.isNotebookEnabled, state.isRefreshed],
|
this.initiateAndRefreshNotebookList();
|
||||||
shallow
|
useNotebook.getState().setIsRefreshed(false);
|
||||||
|
},
|
||||||
|
(state) => state.isNotebookEnabled || state.isRefreshed
|
||||||
);
|
);
|
||||||
|
|
||||||
this.resourceTree = new ResourceTreeAdapter(this);
|
this.resourceTree = new ResourceTreeAdapter(this);
|
||||||
|
|
||||||
// Override notebook server parameters from URL parameters
|
// Override notebook server parameters from URL parameters
|
||||||
if (
|
if (userContext.features.notebookServerUrl && userContext.features.notebookServerToken) {
|
||||||
userContext.features.notebookServerUrl &&
|
|
||||||
validateEndpoint(userContext.features.notebookServerUrl, allowedNotebookServerUrls) &&
|
|
||||||
userContext.features.notebookServerToken
|
|
||||||
) {
|
|
||||||
useNotebook.getState().setNotebookServerInfo({
|
useNotebook.getState().setNotebookServerInfo({
|
||||||
notebookServerEndpoint: userContext.features.notebookServerUrl,
|
notebookServerEndpoint: userContext.features.notebookServerUrl,
|
||||||
authToken: userContext.features.notebookServerToken,
|
authToken: userContext.features.notebookServerToken,
|
||||||
forwardingId: undefined,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,6 +186,19 @@ export default class Explorer {
|
|||||||
useNotebook.getState().setNotebookBasePath(userContext.features.notebookBasePath);
|
useNotebook.getState().setNotebookBasePath(userContext.features.notebookBasePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (userContext.features.livyEndpoint) {
|
||||||
|
useNotebook.getState().setSparkClusterConnectionInfo({
|
||||||
|
userName: undefined,
|
||||||
|
password: undefined,
|
||||||
|
endpoints: [
|
||||||
|
{
|
||||||
|
endpoint: userContext.features.livyEndpoint,
|
||||||
|
kind: DataModels.SparkClusterEndpointKind.Livy,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.refreshExplorer();
|
this.refreshExplorer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,19 +352,35 @@ export default class Explorer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._isInitializingNotebooks = true;
|
this._isInitializingNotebooks = true;
|
||||||
|
if (userContext.features.phoenix === false) {
|
||||||
|
await this.ensureNotebookWorkspaceRunning();
|
||||||
|
const connectionInfo = await listConnectionInfo(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
databaseAccount.name,
|
||||||
|
"default"
|
||||||
|
);
|
||||||
|
|
||||||
|
useNotebook.getState().setNotebookServerInfo({
|
||||||
|
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.notebookServerEndpoint,
|
||||||
|
authToken: userContext.features.notebookServerToken || connectionInfo.authToken,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.refreshNotebookList();
|
this.refreshNotebookList();
|
||||||
|
|
||||||
this._isInitializingNotebooks = false;
|
this._isInitializingNotebooks = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async allocateContainer(): Promise<void> {
|
public async allocateContainer(): Promise<void> {
|
||||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||||
const isAllocating = useNotebook.getState().isAllocating;
|
const isAllocating = useNotebook.getState().isAllocating;
|
||||||
if (
|
if (isAllocating === false && notebookServerInfo && notebookServerInfo.notebookServerEndpoint === undefined) {
|
||||||
isAllocating === false &&
|
const provisionData = {
|
||||||
(notebookServerInfo === undefined ||
|
aadToken: userContext.authorizationToken,
|
||||||
(notebookServerInfo && notebookServerInfo.notebookServerEndpoint === undefined))
|
subscriptionId: userContext.subscriptionId,
|
||||||
) {
|
resourceGroup: userContext.resourceGroup,
|
||||||
const provisionData: IProvisionData = {
|
dbAccountName: userContext.databaseAccount.name,
|
||||||
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
|
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
|
||||||
};
|
};
|
||||||
const connectionStatus: ContainerConnectionInfo = {
|
const connectionStatus: ContainerConnectionInfo = {
|
||||||
@@ -363,70 +388,38 @@ export default class Explorer {
|
|||||||
};
|
};
|
||||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||||
try {
|
try {
|
||||||
TelemetryProcessor.traceStart(Action.PhoenixConnection, {
|
|
||||||
dataExplorerArea: Areas.Notebook,
|
|
||||||
});
|
|
||||||
useNotebook.getState().setIsAllocating(true);
|
useNotebook.getState().setIsAllocating(true);
|
||||||
const connectionInfo = await this.phoenixClient.allocateContainer(provisionData);
|
const connectionInfo = await this.phoenixClient.containerConnectionInfo(provisionData);
|
||||||
if (connectionInfo.status !== HttpStatusCodes.OK) {
|
if (
|
||||||
throw new Error(`Received status code: ${connectionInfo?.status}`);
|
connectionInfo.status === HttpStatusCodes.OK &&
|
||||||
|
connectionInfo.data &&
|
||||||
|
connectionInfo.data.notebookServerUrl
|
||||||
|
) {
|
||||||
|
connectionStatus.status = ConnectionStatusType.Connected;
|
||||||
|
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||||
|
useNotebook.getState().setNotebookServerInfo({
|
||||||
|
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.data.notebookServerUrl,
|
||||||
|
authToken: userContext.features.notebookServerToken || connectionInfo.data.notebookAuthToken,
|
||||||
|
});
|
||||||
|
this.notebookManager?.notebookClient
|
||||||
|
.getMemoryUsage()
|
||||||
|
.then((memoryUsageInfo) => useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo));
|
||||||
|
useNotebook.getState().setIsAllocating(false);
|
||||||
|
} else {
|
||||||
|
connectionStatus.status = ConnectionStatusType.Failed;
|
||||||
|
useNotebook.getState().resetConatinerConnection(connectionStatus);
|
||||||
}
|
}
|
||||||
if (!connectionInfo?.data?.notebookServerUrl) {
|
|
||||||
throw new Error(`NotebookServerUrl is invalid!`);
|
|
||||||
}
|
|
||||||
await this.setNotebookInfo(connectionInfo, connectionStatus);
|
|
||||||
TelemetryProcessor.traceSuccess(Action.PhoenixConnection, {
|
|
||||||
dataExplorerArea: Areas.Notebook,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
TelemetryProcessor.traceFailure(Action.PhoenixConnection, {
|
|
||||||
dataExplorerArea: Areas.Notebook,
|
|
||||||
error: getErrorMessage(error),
|
|
||||||
errorStack: getErrorStack(error),
|
|
||||||
});
|
|
||||||
connectionStatus.status = ConnectionStatusType.Failed;
|
connectionStatus.status = ConnectionStatusType.Failed;
|
||||||
useNotebook.getState().resetContainerConnection(connectionStatus);
|
useNotebook.getState().resetConatinerConnection(connectionStatus);
|
||||||
useDialog
|
|
||||||
.getState()
|
|
||||||
.showOkModalDialog(
|
|
||||||
"Connection Failed",
|
|
||||||
"We are unable to connect to the temporary workspace. Please try again in a few minutes. If the error persists, file a support ticket."
|
|
||||||
);
|
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
|
||||||
useNotebook.getState().setIsAllocating(false);
|
|
||||||
this.refreshCommandBarButtons();
|
|
||||||
this.refreshNotebookList();
|
|
||||||
this._isInitializingNotebooks = false;
|
|
||||||
}
|
}
|
||||||
|
this.refreshNotebookList();
|
||||||
|
|
||||||
|
this._isInitializingNotebooks = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async setNotebookInfo(
|
|
||||||
connectionInfo: IResponse<IPhoenixConnectionInfoResult>,
|
|
||||||
connectionStatus: DataModels.ContainerConnectionInfo
|
|
||||||
) {
|
|
||||||
const containerData = {
|
|
||||||
forwardingId: connectionInfo.data.forwardingId,
|
|
||||||
dbAccountName: userContext.databaseAccount.name,
|
|
||||||
};
|
|
||||||
await this.phoenixClient.initiateContainerHeartBeat(containerData);
|
|
||||||
|
|
||||||
connectionStatus.status = ConnectionStatusType.Connected;
|
|
||||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
|
||||||
useNotebook.getState().setNotebookServerInfo({
|
|
||||||
notebookServerEndpoint:
|
|
||||||
(validateEndpoint(userContext.features.notebookServerUrl, allowedNotebookServerUrls) &&
|
|
||||||
userContext.features.notebookServerUrl) ||
|
|
||||||
connectionInfo.data.notebookServerUrl,
|
|
||||||
authToken: userContext.features.notebookServerToken || connectionInfo.data.notebookAuthToken,
|
|
||||||
forwardingId: connectionInfo.data.forwardingId,
|
|
||||||
});
|
|
||||||
this.notebookManager?.notebookClient
|
|
||||||
.getMemoryUsage()
|
|
||||||
.then((memoryUsageInfo) => useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo));
|
|
||||||
}
|
|
||||||
|
|
||||||
public resetNotebookWorkspace(): void {
|
public resetNotebookWorkspace(): void {
|
||||||
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookClient) {
|
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookClient) {
|
||||||
handleError(
|
handleError(
|
||||||
@@ -435,14 +428,11 @@ export default class Explorer {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const dialogContent = useNotebook.getState().isPhoenixNotebooks
|
|
||||||
? "Notebooks saved in the temporary workspace will be deleted. Do you want to proceed?"
|
|
||||||
: "This lets you keep your notebook files and the workspace will be restored to default. Proceed anyway?";
|
|
||||||
|
|
||||||
const resetConfirmationDialogProps: DialogProps = {
|
const resetConfirmationDialogProps: DialogProps = {
|
||||||
isModal: true,
|
isModal: true,
|
||||||
title: "Reset Workspace",
|
title: "Reset Workspace",
|
||||||
subText: dialogContent,
|
subText: "This lets you keep your notebook files and the workspace will be restored to default. Proceed anyway?",
|
||||||
primaryButtonText: "OK",
|
primaryButtonText: "OK",
|
||||||
secondaryButtonText: "Cancel",
|
secondaryButtonText: "Cancel",
|
||||||
onPrimaryButtonClick: this._resetNotebookWorkspace,
|
onPrimaryButtonClick: this._resetNotebookWorkspace,
|
||||||
@@ -468,57 +458,48 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async ensureNotebookWorkspaceRunning() {
|
||||||
|
if (!userContext.databaseAccount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let clearMessage;
|
||||||
|
try {
|
||||||
|
const notebookWorkspace = await getWorkspace(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
"default"
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
notebookWorkspace &&
|
||||||
|
notebookWorkspace.properties &&
|
||||||
|
notebookWorkspace.properties.status &&
|
||||||
|
notebookWorkspace.properties.status.toLowerCase() === "stopped"
|
||||||
|
) {
|
||||||
|
clearMessage = NotificationConsoleUtils.logConsoleProgress("Initializing notebook workspace");
|
||||||
|
await start(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name, "default");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "Explorer/ensureNotebookWorkspaceRunning", "Failed to initialize notebook workspace");
|
||||||
|
} finally {
|
||||||
|
clearMessage && clearMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _resetNotebookWorkspace = async () => {
|
private _resetNotebookWorkspace = async () => {
|
||||||
useDialog.getState().closeDialog();
|
useDialog.getState().closeDialog();
|
||||||
const clearInProgressMessage = logConsoleProgress("Resetting notebook workspace");
|
const clearInProgressMessage = logConsoleProgress("Resetting notebook workspace");
|
||||||
let connectionStatus: ContainerConnectionInfo;
|
|
||||||
try {
|
try {
|
||||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
await this.notebookManager?.notebookClient.resetWorkspace();
|
||||||
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
|
|
||||||
const error = "No server endpoint detected";
|
|
||||||
Logger.logError(error, "NotebookContainerClient/resetWorkspace");
|
|
||||||
logConsoleError(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TelemetryProcessor.traceStart(Action.PhoenixResetWorkspace, {
|
|
||||||
dataExplorerArea: Areas.Notebook,
|
|
||||||
});
|
|
||||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
|
||||||
useTabs.getState().closeAllNotebookTabs(true);
|
|
||||||
connectionStatus = {
|
|
||||||
status: ConnectionStatusType.Connecting,
|
|
||||||
};
|
|
||||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
|
||||||
}
|
|
||||||
const connectionInfo = await this.notebookManager?.notebookClient.resetWorkspace();
|
|
||||||
if (connectionInfo?.status !== HttpStatusCodes.OK) {
|
|
||||||
throw new Error(`Reset Workspace: Received status code- ${connectionInfo?.status}`);
|
|
||||||
}
|
|
||||||
if (!connectionInfo?.data?.notebookServerUrl) {
|
|
||||||
throw new Error(`Reset Workspace: NotebookServerUrl is invalid!`);
|
|
||||||
}
|
|
||||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
|
||||||
await this.setNotebookInfo(connectionInfo, connectionStatus);
|
|
||||||
useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed);
|
|
||||||
}
|
|
||||||
logConsoleInfo("Successfully reset notebook workspace");
|
logConsoleInfo("Successfully reset notebook workspace");
|
||||||
TelemetryProcessor.traceSuccess(Action.PhoenixResetWorkspace, {
|
TelemetryProcessor.traceSuccess(Action.ResetNotebookWorkspace);
|
||||||
dataExplorerArea: Areas.Notebook,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Failed to reset notebook workspace: ${error}`);
|
logConsoleError(`Failed to reset notebook workspace: ${error}`);
|
||||||
TelemetryProcessor.traceFailure(Action.PhoenixResetWorkspace, {
|
TelemetryProcessor.traceFailure(Action.ResetNotebookWorkspace, {
|
||||||
dataExplorerArea: Areas.Notebook,
|
|
||||||
error: getErrorMessage(error),
|
error: getErrorMessage(error),
|
||||||
errorStack: getErrorStack(error),
|
errorStack: getErrorStack(error),
|
||||||
});
|
});
|
||||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
|
||||||
connectionStatus = {
|
|
||||||
status: ConnectionStatusType.Failed,
|
|
||||||
};
|
|
||||||
useNotebook.getState().resetContainerConnection(connectionStatus);
|
|
||||||
useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed);
|
|
||||||
}
|
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
clearInProgressMessage();
|
clearInProgressMessage();
|
||||||
@@ -710,8 +691,8 @@ export default class Explorer {
|
|||||||
if (!notebookContentItem || !notebookContentItem.path) {
|
if (!notebookContentItem || !notebookContentItem.path) {
|
||||||
throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`);
|
throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`);
|
||||||
}
|
}
|
||||||
if (notebookContentItem.type === NotebookContentItemType.Notebook && useNotebook.getState().isPhoenixNotebooks) {
|
if (notebookContentItem.type === NotebookContentItemType.Notebook && NotebookUtil.isPhoenixEnabled()) {
|
||||||
await this.allocateContainer();
|
this.allocateContainer();
|
||||||
}
|
}
|
||||||
|
|
||||||
const notebookTabs = useTabs
|
const notebookTabs = useTabs
|
||||||
@@ -928,17 +909,20 @@ export default class Explorer {
|
|||||||
/**
|
/**
|
||||||
* This creates a new notebook file, then opens the notebook
|
* This creates a new notebook file, then opens the notebook
|
||||||
*/
|
*/
|
||||||
public async onNewNotebookClicked(parent?: NotebookContentItem, isGithubTree?: boolean): Promise<void> {
|
public onNewNotebookClicked(parent?: NotebookContentItem, isGithubTree?: boolean): void {
|
||||||
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) {
|
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) {
|
||||||
const error = "Attempt to create new notebook, but notebook is not enabled";
|
const error = "Attempt to create new notebook, but notebook is not enabled";
|
||||||
handleError(error, "Explorer/onNewNotebookClicked");
|
handleError(error, "Explorer/onNewNotebookClicked");
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
const isPhoenixEnabled = NotebookUtil.isPhoenixEnabled();
|
||||||
|
if (isPhoenixEnabled) {
|
||||||
if (isGithubTree) {
|
if (isGithubTree) {
|
||||||
await this.allocateContainer();
|
async () => {
|
||||||
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
await this.allocateContainer();
|
||||||
this.createNewNoteBook(parent, isGithubTree);
|
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
||||||
|
this.createNewNoteBook(parent, isGithubTree);
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
useDialog.getState().showOkCancelModalDialog(
|
useDialog.getState().showOkCancelModalDialog(
|
||||||
Notebook.newNotebookModalTitle,
|
Notebook.newNotebookModalTitle,
|
||||||
@@ -1023,7 +1007,7 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async openNotebookTerminal(kind: ViewModels.TerminalKind): Promise<void> {
|
public async openNotebookTerminal(kind: ViewModels.TerminalKind): Promise<void> {
|
||||||
if (useNotebook.getState().isPhoenixFeatures) {
|
if (NotebookUtil.isPhoenixEnabled()) {
|
||||||
await this.allocateContainer();
|
await this.allocateContainer();
|
||||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||||
if (notebookServerInfo && notebookServerInfo.notebookServerEndpoint !== undefined) {
|
if (notebookServerInfo && notebookServerInfo.notebookServerEndpoint !== undefined) {
|
||||||
@@ -1032,8 +1016,8 @@ export default class Explorer {
|
|||||||
useDialog
|
useDialog
|
||||||
.getState()
|
.getState()
|
||||||
.showOkModalDialog(
|
.showOkModalDialog(
|
||||||
"Failed to connect",
|
"Failed to Connect",
|
||||||
"Failed to connect to temporary workspace. This could happen because of network issues. Please refresh the page and try again."
|
"Failed to connect temporary workspace, this could happen because of network issue please refresh and try again."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1063,7 +1047,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
const terminalTabs: TerminalTab[] = useTabs
|
const terminalTabs: TerminalTab[] = useTabs
|
||||||
.getState()
|
.getState()
|
||||||
.getTabs(ViewModels.CollectionTabKind.Terminal, (tab) => tab.tabTitle().startsWith(title)) as TerminalTab[];
|
.getTabs(ViewModels.CollectionTabKind.Terminal, (tab) => tab.tabTitle() === title) as TerminalTab[];
|
||||||
|
|
||||||
let index = 1;
|
let index = 1;
|
||||||
if (terminalTabs.length > 0) {
|
if (terminalTabs.length > 0) {
|
||||||
@@ -1135,10 +1119,7 @@ export default class Explorer {
|
|||||||
<CassandraAddCollectionPane explorer={this} cassandraApiClient={new CassandraAPIDataClient()} />
|
<CassandraAddCollectionPane explorer={this} cassandraApiClient={new CassandraAPIDataClient()} />
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
await useDatabases.getState().loadDatabaseOffers();
|
||||||
throughputCap && throughputCap !== -1
|
|
||||||
? await useDatabases.getState().loadAllOffers()
|
|
||||||
: await useDatabases.getState().loadDatabaseOffers();
|
|
||||||
useSidePanel
|
useSidePanel
|
||||||
.getState()
|
.getState()
|
||||||
.openSidePanel("New " + getCollectionName(), <AddCollectionPanel explorer={this} databaseId={databaseId} />);
|
.openSidePanel("New " + getCollectionName(), <AddCollectionPanel explorer={this} databaseId={databaseId} />);
|
||||||
@@ -1154,12 +1135,21 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _openSetupNotebooksPaneForQuickstart(): void {
|
||||||
|
const title = "Enable Notebooks (Preview)";
|
||||||
|
const description =
|
||||||
|
"You have not yet created a notebooks workspace for this account. To proceed and start using notebooks, we'll need to create a default notebooks workspace in this account.";
|
||||||
|
useSidePanel
|
||||||
|
.getState()
|
||||||
|
.openSidePanel(title, <SetupNoteBooksPanel explorer={this} panelTitle={title} panelDescription={description} />);
|
||||||
|
}
|
||||||
|
|
||||||
public async handleOpenFileAction(path: string): Promise<void> {
|
public async handleOpenFileAction(path: string): Promise<void> {
|
||||||
if (useNotebook.getState().isPhoenixNotebooks === undefined) {
|
if (
|
||||||
await useNotebook.getState().getPhoenixStatus();
|
userContext.features.phoenix === false &&
|
||||||
}
|
!(await this._containsDefaultNotebookWorkspace(userContext.databaseAccount))
|
||||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
) {
|
||||||
await this.allocateContainer();
|
this._openSetupNotebooksPaneForQuickstart();
|
||||||
}
|
}
|
||||||
|
|
||||||
// We still use github urls like https://github.com/Azure-Samples/cosmos-notebooks/blob/master/CSharp_quickstarts/GettingStarted_CSharp.ipynb
|
// We still use github urls like https://github.com/Azure-Samples/cosmos-notebooks/blob/master/CSharp_quickstarts/GettingStarted_CSharp.ipynb
|
||||||
@@ -1190,7 +1180,7 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public openUploadFilePanel(parent?: NotebookContentItem): void {
|
public openUploadFilePanel(parent?: NotebookContentItem): void {
|
||||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
if (NotebookUtil.isPhoenixEnabled()) {
|
||||||
useDialog.getState().showOkCancelModalDialog(
|
useDialog.getState().showOkCancelModalDialog(
|
||||||
Notebook.newNotebookUploadModalTitle,
|
Notebook.newNotebookUploadModalTitle,
|
||||||
undefined,
|
undefined,
|
||||||
@@ -1220,7 +1210,7 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getDownloadModalConent(fileName: string): JSX.Element {
|
public getDownloadModalConent(fileName: string): JSX.Element {
|
||||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
if (NotebookUtil.isPhoenixEnabled()) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p>{Notebook.galleryNotebookDownloadContent1}</p>
|
<p>{Notebook.galleryNotebookDownloadContent1}</p>
|
||||||
@@ -1242,24 +1232,28 @@ export default class Explorer {
|
|||||||
? this.refreshDatabaseForResourceToken()
|
? this.refreshDatabaseForResourceToken()
|
||||||
: this.refreshAllDatabases();
|
: this.refreshAllDatabases();
|
||||||
await useNotebook.getState().refreshNotebooksEnabledStateForAccount();
|
await useNotebook.getState().refreshNotebooksEnabledStateForAccount();
|
||||||
|
let isNotebookEnabled = true;
|
||||||
// TODO: remove reference to isNotebookEnabled and isNotebooksEnabledForAccount
|
if (!userContext.features.phoenix) {
|
||||||
const isNotebookEnabled =
|
isNotebookEnabled =
|
||||||
userContext.features.notebooksDownBanner ||
|
userContext.authType !== AuthType.ResourceToken &&
|
||||||
useNotebook.getState().isPhoenixNotebooks ||
|
((await this._containsDefaultNotebookWorkspace(userContext.databaseAccount)) ||
|
||||||
useNotebook.getState().isPhoenixFeatures;
|
userContext.features.enableNotebooks);
|
||||||
|
}
|
||||||
useNotebook.getState().setIsNotebookEnabled(isNotebookEnabled);
|
useNotebook.getState().setIsNotebookEnabled(isNotebookEnabled);
|
||||||
useNotebook
|
useNotebook.getState().setIsShellEnabled(isNotebookEnabled && isPublicInternetAccessAllowed());
|
||||||
.getState()
|
|
||||||
.setIsShellEnabled(useNotebook.getState().isPhoenixFeatures && isPublicInternetAccessAllowed());
|
|
||||||
|
|
||||||
TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, {
|
TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, {
|
||||||
isNotebookEnabled,
|
isNotebookEnabled,
|
||||||
dataExplorerArea: Constants.Areas.Notebook,
|
dataExplorerArea: Constants.Areas.Notebook,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
if (!userContext.features.notebooksTemporarilyDown) {
|
||||||
await this.initNotebooks(userContext.databaseAccount);
|
if (isNotebookEnabled) {
|
||||||
|
await this.initNotebooks(userContext.databaseAccount);
|
||||||
|
} else if (this.notebookToImport) {
|
||||||
|
// if notebooks is not enabled but the user is trying to do a quickstart setup with notebooks, open the SetupNotebooksPane
|
||||||
|
this._openSetupNotebooksPaneForQuickstart();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,15 @@
|
|||||||
* and update any knockout observables passed from the parent.
|
* and update any knockout observables passed from the parent.
|
||||||
*/
|
*/
|
||||||
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
|
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
|
||||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import create, { UseStore } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import { StyleConstants } from "../../../Common/Constants";
|
import { StyleConstants } from "../../../Common/Constants";
|
||||||
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
|
import { useTabs } from "../../../hooks/useTabs";
|
||||||
|
import { userContext } from "../../../UserContext";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
|
import { NotebookUtil } from "../../Notebook/NotebookUtil";
|
||||||
import { useSelectedNode } from "../../useSelectedNode";
|
import { useSelectedNode } from "../../useSelectedNode";
|
||||||
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
|
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
|
||||||
import * as CommandBarUtil from "./CommandBarUtil";
|
import * as CommandBarUtil from "./CommandBarUtil";
|
||||||
@@ -53,10 +56,18 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
|||||||
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
|
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
|
||||||
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
|
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
|
||||||
|
|
||||||
if (useNotebook.getState().isPhoenixNotebooks || useNotebook.getState().isPhoenixFeatures) {
|
if (NotebookUtil.isPhoenixEnabled()) {
|
||||||
uiFabricControlButtons.unshift(CommandBarUtil.createConnectionStatus(container, "connectionStatus"));
|
uiFabricControlButtons.unshift(CommandBarUtil.createConnectionStatus(container, "connectionStatus"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
userContext.features.phoenix === false &&
|
||||||
|
userContext.features.notebooksTemporarilyDown === false &&
|
||||||
|
useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2
|
||||||
|
) {
|
||||||
|
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker"));
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="commandBarContainer">
|
<div className="commandBarContainer">
|
||||||
<FluentCommandBar
|
<FluentCommandBar
|
||||||
|
|||||||
@@ -31,13 +31,28 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Button should be visible", () => {
|
it("Account is not serverless - button should be visible", () => {
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
const enableAzureSynapseLinkBtn = buttons.find(
|
const enableAzureSynapseLinkBtn = buttons.find(
|
||||||
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel
|
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel
|
||||||
);
|
);
|
||||||
expect(enableAzureSynapseLinkBtn).toBeDefined();
|
expect(enableAzureSynapseLinkBtn).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Account is serverless - button should be hidden", () => {
|
||||||
|
updateUserContext({
|
||||||
|
databaseAccount: {
|
||||||
|
properties: {
|
||||||
|
capabilities: [{ name: "EnableServerless" }],
|
||||||
|
},
|
||||||
|
} as DatabaseAccount,
|
||||||
|
});
|
||||||
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
|
const enableAzureSynapseLinkBtn = buttons.find(
|
||||||
|
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel
|
||||||
|
);
|
||||||
|
expect(enableAzureSynapseLinkBtn).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Enable notebook button", () => {
|
describe("Enable notebook button", () => {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import CosmosTerminalIcon from "../../../../images/Cosmos-Terminal.svg";
|
|||||||
import FeedbackIcon from "../../../../images/Feedback-Command.svg";
|
import FeedbackIcon from "../../../../images/Feedback-Command.svg";
|
||||||
import GitHubIcon from "../../../../images/github.svg";
|
import GitHubIcon from "../../../../images/github.svg";
|
||||||
import HostedTerminalIcon from "../../../../images/Hosted-Terminal.svg";
|
import HostedTerminalIcon from "../../../../images/Hosted-Terminal.svg";
|
||||||
|
import EnableNotebooksIcon from "../../../../images/notebook/Notebook-enable.svg";
|
||||||
import NewNotebookIcon from "../../../../images/notebook/Notebook-new.svg";
|
import NewNotebookIcon from "../../../../images/notebook/Notebook-new.svg";
|
||||||
import ResetWorkspaceIcon from "../../../../images/notebook/Notebook-reset-workspace.svg";
|
import ResetWorkspaceIcon from "../../../../images/notebook/Notebook-reset-workspace.svg";
|
||||||
import OpenInTabIcon from "../../../../images/open-in-tab.svg";
|
import OpenInTabIcon from "../../../../images/open-in-tab.svg";
|
||||||
@@ -24,6 +25,7 @@ import { useSidePanel } from "../../../hooks/useSidePanel";
|
|||||||
import { JunoClient } from "../../../Juno/JunoClient";
|
import { JunoClient } from "../../../Juno/JunoClient";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { getCollectionName, getDatabaseName } from "../../../Utils/APITypeUtils";
|
import { getCollectionName, getDatabaseName } from "../../../Utils/APITypeUtils";
|
||||||
|
import { isServerlessAccount } from "../../../Utils/CapabilityUtils";
|
||||||
import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils";
|
import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
@@ -34,6 +36,7 @@ import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPa
|
|||||||
import { GitHubReposPanel } from "../../Panes/GitHubReposPanel/GitHubReposPanel";
|
import { GitHubReposPanel } from "../../Panes/GitHubReposPanel/GitHubReposPanel";
|
||||||
import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane";
|
import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane";
|
||||||
import { SettingsPane } from "../../Panes/SettingsPane/SettingsPane";
|
import { SettingsPane } from "../../Panes/SettingsPane/SettingsPane";
|
||||||
|
import { SetupNoteBooksPanel } from "../../Panes/SetupNotebooksPanel/SetupNotebooksPanel";
|
||||||
import { useDatabases } from "../../useDatabases";
|
import { useDatabases } from "../../useDatabases";
|
||||||
import { SelectedNodeState } from "../../useSelectedNode";
|
import { SelectedNodeState } from "../../useSelectedNode";
|
||||||
|
|
||||||
@@ -75,10 +78,9 @@ export function createStaticCommandBarButtons(
|
|||||||
if (container.notebookManager?.gitHubOAuthService) {
|
if (container.notebookManager?.gitHubOAuthService) {
|
||||||
notebookButtons.push(createManageGitHubAccountButton(container));
|
notebookButtons.push(createManageGitHubAccountButton(container));
|
||||||
}
|
}
|
||||||
if (useNotebook.getState().isPhoenixFeatures && configContext.isTerminalEnabled) {
|
|
||||||
notebookButtons.push(createOpenTerminalButton(container));
|
notebookButtons.push(createOpenTerminalButton(container));
|
||||||
}
|
if (userContext.features.phoenix === false) {
|
||||||
if (useNotebook.getState().isPhoenixNotebooks && selectedNodeState.isConnectedToContainer()) {
|
|
||||||
notebookButtons.push(createNotebookWorkspaceResetButton(container));
|
notebookButtons.push(createNotebookWorkspaceResetButton(container));
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@@ -96,19 +98,22 @@ export function createStaticCommandBarButtons(
|
|||||||
}
|
}
|
||||||
|
|
||||||
notebookButtons.forEach((btn) => {
|
notebookButtons.forEach((btn) => {
|
||||||
if (btn.commandButtonLabel.indexOf("Cassandra") !== -1) {
|
if (userContext.features.notebooksTemporarilyDown) {
|
||||||
if (!useNotebook.getState().isPhoenixFeatures) {
|
if (btn.commandButtonLabel.indexOf("Cassandra") !== -1) {
|
||||||
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.cassandraShellTemporarilyDownMsg);
|
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.cassandraShellTemporarilyDownMsg);
|
||||||
}
|
} else if (btn.commandButtonLabel.indexOf("Mongo") !== -1) {
|
||||||
} else if (btn.commandButtonLabel.indexOf("Mongo") !== -1) {
|
|
||||||
if (!useNotebook.getState().isPhoenixFeatures) {
|
|
||||||
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.mongoShellTemporarilyDownMsg);
|
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.mongoShellTemporarilyDownMsg);
|
||||||
|
} else {
|
||||||
|
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.temporarilyDownMsg);
|
||||||
}
|
}
|
||||||
} else if (!useNotebook.getState().isPhoenixNotebooks) {
|
|
||||||
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.temporarilyDownMsg);
|
|
||||||
}
|
}
|
||||||
buttons.push(btn);
|
buttons.push(btn);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
if (!isRunningOnNationalCloud() && !userContext.features.notebooksTemporarilyDown) {
|
||||||
|
buttons.push(createDivider());
|
||||||
|
buttons.push(createEnableNotebooksButton(container));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!selectedNodeState.isDatabaseNodeOrNoneSelected()) {
|
if (!selectedNodeState.isDatabaseNodeOrNoneSelected()) {
|
||||||
@@ -163,7 +168,9 @@ export function createContextCommandBarButtons(
|
|||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||||
if (useNotebook.getState().isShellEnabled) {
|
if (useNotebook.getState().isShellEnabled) {
|
||||||
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
if (!userContext.features.notebooksTemporarilyDown) {
|
||||||
|
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
selectedCollection && selectedCollection.onNewMongoShellClick();
|
selectedCollection && selectedCollection.onNewMongoShellClick();
|
||||||
}
|
}
|
||||||
@@ -171,6 +178,13 @@ export function createContextCommandBarButtons(
|
|||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
hasPopup: true,
|
hasPopup: true,
|
||||||
|
tooltipText:
|
||||||
|
useNotebook.getState().isShellEnabled && userContext.features.notebooksTemporarilyDown
|
||||||
|
? Constants.Notebook.mongoShellTemporarilyDownMsg
|
||||||
|
: undefined,
|
||||||
|
disabled:
|
||||||
|
(selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") ||
|
||||||
|
(useNotebook.getState().isShellEnabled && userContext.features.notebooksTemporarilyDown),
|
||||||
};
|
};
|
||||||
buttons.push(newMongoShellBtn);
|
buttons.push(newMongoShellBtn);
|
||||||
}
|
}
|
||||||
@@ -266,6 +280,10 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isServerlessAccount()) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
if (userContext?.databaseAccount?.properties?.enableAnalyticalStorage) {
|
if (userContext?.databaseAccount?.properties?.enableAnalyticalStorage) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -289,16 +307,18 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
|
|||||||
|
|
||||||
function createNewDatabase(container: Explorer): CommandButtonComponentProps {
|
function createNewDatabase(container: Explorer): CommandButtonComponentProps {
|
||||||
const label = "New " + getDatabaseName();
|
const label = "New " + getDatabaseName();
|
||||||
|
const newDatabaseButton = document.activeElement as HTMLElement;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
iconSrc: AddDatabaseIcon,
|
iconSrc: AddDatabaseIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
onCommandClick: async () => {
|
onCommandClick: () =>
|
||||||
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
useSidePanel
|
||||||
if (throughputCap && throughputCap !== -1) {
|
.getState()
|
||||||
await useDatabases.getState().loadAllOffers();
|
.openSidePanel(
|
||||||
}
|
"New " + getDatabaseName(),
|
||||||
useSidePanel.getState().openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={container} />);
|
<AddDatabasePanel explorer={container} buttonElement={newDatabaseButton} />
|
||||||
},
|
),
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
ariaLabel: label,
|
ariaLabel: label,
|
||||||
hasPopup: true,
|
hasPopup: true,
|
||||||
@@ -459,6 +479,33 @@ function createOpenQueryFromDiskButton(): CommandButtonComponentProps {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createEnableNotebooksButton(container: Explorer): CommandButtonComponentProps {
|
||||||
|
if (configContext.platform === Platform.Emulator) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const label = "Enable Notebooks (Preview)";
|
||||||
|
const tooltip =
|
||||||
|
"Notebooks are not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks.";
|
||||||
|
const description =
|
||||||
|
"Looks like you have not yet created a notebooks workspace for this account. To proceed and start using notebooks, we'll need to create a default notebooks workspace in this account.";
|
||||||
|
return {
|
||||||
|
iconSrc: EnableNotebooksIcon,
|
||||||
|
iconAlt: label,
|
||||||
|
onCommandClick: () =>
|
||||||
|
useSidePanel
|
||||||
|
.getState()
|
||||||
|
.openSidePanel(
|
||||||
|
label,
|
||||||
|
<SetupNoteBooksPanel explorer={container} panelTitle={label} panelDescription={description} />
|
||||||
|
),
|
||||||
|
commandButtonLabel: label,
|
||||||
|
hasPopup: false,
|
||||||
|
disabled: !useNotebook.getState().isNotebooksEnabledForAccount,
|
||||||
|
ariaLabel: label,
|
||||||
|
tooltipText: useNotebook.getState().isNotebooksEnabledForAccount ? "" : tooltip,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function createOpenTerminalButton(container: Explorer): CommandButtonComponentProps {
|
function createOpenTerminalButton(container: Explorer): CommandButtonComponentProps {
|
||||||
const label = "Open Terminal";
|
const label = "Open Terminal";
|
||||||
return {
|
return {
|
||||||
@@ -476,6 +523,9 @@ function createOpenMongoTerminalButton(container: Explorer): CommandButtonCompon
|
|||||||
const label = "Open Mongo Shell";
|
const label = "Open Mongo Shell";
|
||||||
const tooltip =
|
const tooltip =
|
||||||
"This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks.";
|
"This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks.";
|
||||||
|
const title = "Set up workspace";
|
||||||
|
const description =
|
||||||
|
"Looks like you have not created a workspace for this account. To proceed and start using features including mongo shell and notebook, we will need to create a default workspace in this account.";
|
||||||
const disableButton =
|
const disableButton =
|
||||||
!useNotebook.getState().isNotebooksEnabledForAccount && !useNotebook.getState().isNotebookEnabled;
|
!useNotebook.getState().isNotebooksEnabledForAccount && !useNotebook.getState().isNotebookEnabled;
|
||||||
return {
|
return {
|
||||||
@@ -484,6 +534,13 @@ function createOpenMongoTerminalButton(container: Explorer): CommandButtonCompon
|
|||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
if (useNotebook.getState().isNotebookEnabled) {
|
if (useNotebook.getState().isNotebookEnabled) {
|
||||||
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
||||||
|
} else {
|
||||||
|
useSidePanel
|
||||||
|
.getState()
|
||||||
|
.openSidePanel(
|
||||||
|
title,
|
||||||
|
<SetupNoteBooksPanel explorer={container} panelTitle={title} panelDescription={description} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
@@ -498,6 +555,9 @@ function createOpenCassandraTerminalButton(container: Explorer): CommandButtonCo
|
|||||||
const label = "Open Cassandra Shell";
|
const label = "Open Cassandra Shell";
|
||||||
const tooltip =
|
const tooltip =
|
||||||
"This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks.";
|
"This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks.";
|
||||||
|
const title = "Set up workspace";
|
||||||
|
const description =
|
||||||
|
"Looks like you have not created a workspace for this account. To proceed and start using features including cassandra shell and notebook, we will need to create a default workspace in this account.";
|
||||||
const disableButton =
|
const disableButton =
|
||||||
!useNotebook.getState().isNotebooksEnabledForAccount && !useNotebook.getState().isNotebookEnabled;
|
!useNotebook.getState().isNotebooksEnabledForAccount && !useNotebook.getState().isNotebookEnabled;
|
||||||
return {
|
return {
|
||||||
@@ -506,6 +566,13 @@ function createOpenCassandraTerminalButton(container: Explorer): CommandButtonCo
|
|||||||
onCommandClick: () => {
|
onCommandClick: () => {
|
||||||
if (useNotebook.getState().isNotebookEnabled) {
|
if (useNotebook.getState().isNotebookEnabled) {
|
||||||
container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra);
|
container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra);
|
||||||
|
} else {
|
||||||
|
useSidePanel
|
||||||
|
.getState()
|
||||||
|
.openSidePanel(
|
||||||
|
title,
|
||||||
|
<SetupNoteBooksPanel explorer={container} panelTitle={title} panelDescription={description} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
|
|||||||
@@ -1,20 +1,8 @@
|
|||||||
import {
|
import { Icon, ProgressIndicator, Stack, TooltipHost } from "@fluentui/react";
|
||||||
FocusTrapCallout,
|
import { ActionButton } from "@fluentui/react/lib/Button";
|
||||||
FocusZone,
|
|
||||||
FocusZoneTabbableElements,
|
|
||||||
FontWeights,
|
|
||||||
Icon,
|
|
||||||
mergeStyleSets,
|
|
||||||
ProgressIndicator,
|
|
||||||
Stack,
|
|
||||||
Text,
|
|
||||||
TooltipHost,
|
|
||||||
} from "@fluentui/react";
|
|
||||||
import { useId } from "@fluentui/react-hooks";
|
|
||||||
import { ActionButton, DefaultButton } from "@fluentui/react/lib/Button";
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import "../../../../less/hostedexplorer.less";
|
import "../../../../less/hostedexplorer.less";
|
||||||
import { ConnectionStatusType, ContainerStatusType, Notebook } from "../../../Common/Constants";
|
import { ConnectionStatusType, Notebook } from "../../../Common/Constants";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { useNotebook } from "../../Notebook/useNotebook";
|
import { useNotebook } from "../../Notebook/useNotebook";
|
||||||
import "../CommandBar/ConnectionStatusComponent.less";
|
import "../CommandBar/ConnectionStatusComponent.less";
|
||||||
@@ -22,33 +10,12 @@ interface Props {
|
|||||||
container: Explorer;
|
container: Explorer;
|
||||||
}
|
}
|
||||||
export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Element => {
|
export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Element => {
|
||||||
const connectionInfo = useNotebook((state) => state.connectionInfo);
|
|
||||||
const [second, setSecond] = React.useState("00");
|
const [second, setSecond] = React.useState("00");
|
||||||
const [minute, setMinute] = React.useState("00");
|
const [minute, setMinute] = React.useState("00");
|
||||||
const [isActive, setIsActive] = React.useState(false);
|
const [isActive, setIsActive] = React.useState(false);
|
||||||
const [counter, setCounter] = React.useState(0);
|
const [counter, setCounter] = React.useState(0);
|
||||||
const [statusColor, setStatusColor] = React.useState("");
|
const [statusColor, setStatusColor] = React.useState("");
|
||||||
const [toolTipContent, setToolTipContent] = React.useState("Connect to temporary workspace.");
|
const [toolTipContent, setToolTipContent] = React.useState("Connect to temporary workspace.");
|
||||||
const [isBarDismissed, setIsBarDismissed] = React.useState<boolean>(false);
|
|
||||||
const buttonId = useId("callout-button");
|
|
||||||
const containerInfo = useNotebook((state) => state.containerStatus);
|
|
||||||
|
|
||||||
const styles = mergeStyleSets({
|
|
||||||
callout: {
|
|
||||||
width: 320,
|
|
||||||
padding: "20px 24px",
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
marginBottom: 12,
|
|
||||||
fontWeight: FontWeights.semilight,
|
|
||||||
},
|
|
||||||
buttons: {
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "flex-end",
|
|
||||||
marginTop: 20,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
let intervalId: NodeJS.Timeout;
|
let intervalId: NodeJS.Timeout;
|
||||||
|
|
||||||
@@ -68,15 +35,6 @@ export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Ele
|
|||||||
return () => clearInterval(intervalId);
|
return () => clearInterval(intervalId);
|
||||||
}, [isActive, counter]);
|
}, [isActive, counter]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (connectionInfo?.status === ConnectionStatusType.Reconnect) {
|
|
||||||
setToolTipContent("Click here to Reconnect to temporary workspace.");
|
|
||||||
} else if (connectionInfo?.status === ConnectionStatusType.Failed) {
|
|
||||||
setStatusColor("status failed is-animating");
|
|
||||||
setToolTipContent("Click here to Reconnect to temporary workspace.");
|
|
||||||
}
|
|
||||||
}, [connectionInfo.status]);
|
|
||||||
|
|
||||||
const stopTimer = () => {
|
const stopTimer = () => {
|
||||||
setIsActive(false);
|
setIsActive(false);
|
||||||
setCounter(0);
|
setCounter(0);
|
||||||
@@ -84,13 +42,15 @@ export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Ele
|
|||||||
setMinute("00");
|
setMinute("00");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const connectionInfo = useNotebook((state) => state.connectionInfo);
|
||||||
const memoryUsageInfo = useNotebook((state) => state.memoryUsageInfo);
|
const memoryUsageInfo = useNotebook((state) => state.memoryUsageInfo);
|
||||||
|
|
||||||
const totalGB = memoryUsageInfo ? memoryUsageInfo.totalKB / Notebook.memoryGuageToGB : 0;
|
const totalGB = memoryUsageInfo ? memoryUsageInfo.totalKB / Notebook.memoryGuageToGB : 0;
|
||||||
const usedGB = totalGB > 0 ? totalGB - memoryUsageInfo.freeKB / Notebook.memoryGuageToGB : 0;
|
const usedGB = totalGB > 0 ? totalGB - memoryUsageInfo.freeKB / Notebook.memoryGuageToGB : 0;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
connectionInfo &&
|
connectionInfo &&
|
||||||
(connectionInfo.status === ConnectionStatusType.Connect || connectionInfo.status === ConnectionStatusType.Reconnect)
|
(connectionInfo.status === ConnectionStatusType.Connect || connectionInfo.status === ConnectionStatusType.ReConnect)
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<ActionButton className="commandReactBtn" onClick={() => container.allocateContainer()}>
|
<ActionButton className="commandReactBtn" onClick={() => container.allocateContainer()}>
|
||||||
@@ -105,7 +65,6 @@ export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Ele
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connecting && isActive === false) {
|
if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connecting && isActive === false) {
|
||||||
stopTimer();
|
|
||||||
setIsActive(true);
|
setIsActive(true);
|
||||||
setStatusColor("status connecting is-animating");
|
setStatusColor("status connecting is-animating");
|
||||||
setToolTipContent("Connecting to temporary workspace.");
|
setToolTipContent("Connecting to temporary workspace.");
|
||||||
@@ -119,68 +78,30 @@ export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Ele
|
|||||||
setToolTipContent("Click here to Reconnect to temporary workspace.");
|
setToolTipContent("Click here to Reconnect to temporary workspace.");
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<ActionButton
|
||||||
<TooltipHost
|
className={connectionInfo.status === ConnectionStatusType.Failed ? "commandReactBtn" : "connectedReactBtn"}
|
||||||
content={
|
onClick={(e: React.MouseEvent<HTMLSpanElement>) =>
|
||||||
containerInfo?.status === ContainerStatusType.Active
|
connectionInfo.status === ConnectionStatusType.Failed ? container.allocateContainer() : e.preventDefault()
|
||||||
? `Connected to temporary workspace. This temporary workspace will get disconnected in ${Math.round(
|
}
|
||||||
containerInfo.durationLeftInMinutes
|
>
|
||||||
)} minutes.`
|
<TooltipHost content={toolTipContent}>
|
||||||
: toolTipContent
|
<Stack className="connectionStatusContainer" horizontal>
|
||||||
}
|
<i className={statusColor}></i>
|
||||||
>
|
<span className={connectionInfo.status === ConnectionStatusType.Failed ? "connectionStatusFailed" : ""}>
|
||||||
<ActionButton
|
{connectionInfo.status}
|
||||||
id={buttonId}
|
</span>
|
||||||
className={connectionInfo.status === ConnectionStatusType.Failed ? "commandReactBtn" : "connectedReactBtn"}
|
{connectionInfo.status === ConnectionStatusType.Connecting && isActive && (
|
||||||
onClick={(e: React.MouseEvent<HTMLSpanElement>) =>
|
<ProgressIndicator description={minute + ":" + second} />
|
||||||
connectionInfo.status === ConnectionStatusType.Failed ? container.allocateContainer() : e.preventDefault()
|
)}
|
||||||
}
|
{connectionInfo.status === ConnectionStatusType.Connected && !isActive && (
|
||||||
>
|
<ProgressIndicator
|
||||||
<Stack className="connectionStatusContainer" horizontal>
|
className={usedGB / totalGB > 0.8 ? "lowMemory" : ""}
|
||||||
<i className={statusColor}></i>
|
description={usedGB.toFixed(1) + " of " + totalGB.toFixed(1) + " GB"}
|
||||||
<span className={connectionInfo.status === ConnectionStatusType.Failed ? "connectionStatusFailed" : ""}>
|
percentComplete={usedGB / totalGB}
|
||||||
{connectionInfo.status}
|
/>
|
||||||
</span>
|
)}
|
||||||
{connectionInfo.status === ConnectionStatusType.Connecting && isActive && (
|
</Stack>
|
||||||
<ProgressIndicator description={minute + ":" + second} />
|
|
||||||
)}
|
|
||||||
{connectionInfo.status === ConnectionStatusType.Connected && !isActive && (
|
|
||||||
<ProgressIndicator
|
|
||||||
className={totalGB !== 0 && usedGB / totalGB > 0.8 ? "lowMemory" : ""}
|
|
||||||
description={usedGB.toFixed(1) + " of " + totalGB.toFixed(1) + " GB"}
|
|
||||||
percentComplete={totalGB !== 0 ? usedGB / totalGB : 0}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
{!isBarDismissed &&
|
|
||||||
containerInfo.status &&
|
|
||||||
containerInfo.status === ContainerStatusType.Active &&
|
|
||||||
Math.round(containerInfo.durationLeftInMinutes) <= Notebook.remainingTimeForAlert ? (
|
|
||||||
<FocusTrapCallout
|
|
||||||
role="alertdialog"
|
|
||||||
className={styles.callout}
|
|
||||||
gapSpace={0}
|
|
||||||
target={`#${buttonId}`}
|
|
||||||
onDismiss={() => setIsBarDismissed(true)}
|
|
||||||
setInitialFocus
|
|
||||||
>
|
|
||||||
<Text block variant="xLarge" className={styles.title}>
|
|
||||||
Remaining Time
|
|
||||||
</Text>
|
|
||||||
<Text block variant="small">
|
|
||||||
This temporary workspace will get disconnected in {Math.round(containerInfo.durationLeftInMinutes)}{" "}
|
|
||||||
minutes. To save your work permanently, save your notebooks to a GitHub repository or download the
|
|
||||||
notebooks to your local machine before the session ends.
|
|
||||||
</Text>
|
|
||||||
<FocusZone handleTabKey={FocusZoneTabbableElements.all} isCircularNavigation>
|
|
||||||
<Stack className={styles.buttons} gap={8} horizontal>
|
|
||||||
<DefaultButton onClick={() => setIsBarDismissed(true)}>Dimiss</DefaultButton>
|
|
||||||
</Stack>
|
|
||||||
</FocusZone>
|
|
||||||
</FocusTrapCallout>
|
|
||||||
) : undefined}
|
|
||||||
</ActionButton>
|
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
</>
|
</ActionButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
.dataUploader {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
import { ImmutableOutput } from "@nteract/commutable";
|
|
||||||
import { actions, AppState, ContentRef, KernelRef, selectors } from "@nteract/core";
|
|
||||||
import Immutable from "immutable";
|
|
||||||
import React, { CSSProperties, useCallback, useMemo } from "react";
|
|
||||||
import { useDropzone } from "react-dropzone";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { Dispatch } from "redux";
|
|
||||||
import "./DataUploader.less";
|
|
||||||
|
|
||||||
interface DataUploaderPureProps {
|
|
||||||
contentRef: ContentRef;
|
|
||||||
kernelRef: KernelRef;
|
|
||||||
databaseId: string;
|
|
||||||
collectionId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getColor = (props) => {
|
|
||||||
if (props.isDragAccept) {
|
|
||||||
return "#00e676";
|
|
||||||
}
|
|
||||||
if (props.isDragReject) {
|
|
||||||
return "#ff1744";
|
|
||||||
}
|
|
||||||
if (props.isFocused) {
|
|
||||||
return "#2196f3";
|
|
||||||
}
|
|
||||||
return "#eeeeee";
|
|
||||||
};
|
|
||||||
|
|
||||||
interface DataUploaderDispatchProps {
|
|
||||||
runCell: (contentRef: ContentRef, cellId: string) => void;
|
|
||||||
addTransform: (transform: React.ComponentType & { MIMETYPE: string }) => void;
|
|
||||||
updateCell: (text: string, id: string, contentRef: ContentRef) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
type DataUploaderProps = DataUploaderPureProps & StateProps & DataUploaderDispatchProps;
|
|
||||||
|
|
||||||
const DataUploader: React.FC<DataUploaderProps> = (props) => {
|
|
||||||
// componentDidMount(): void {
|
|
||||||
// loadTransform(this.props);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// private onAnalyzeButtonClick = (filter: string = DefaultFilter, sampleSize: string = this.state.sampleSize) => {
|
|
||||||
// const query = {
|
|
||||||
// command: "listSchema",
|
|
||||||
// database: this.props.databaseId,
|
|
||||||
// collection: this.props.collectionId,
|
|
||||||
// outputType: this.state.outputType,
|
|
||||||
// filter,
|
|
||||||
// sampleSize,
|
|
||||||
// };
|
|
||||||
|
|
||||||
// this.setState({
|
|
||||||
// isFiltering: true,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// this.props.updateCell(JSON.stringify(query), this.props.firstCellId, this.props.contentRef);
|
|
||||||
|
|
||||||
// this.clickAnalyzeTelemetryStartKey = traceStart(Action.DataUploaderClickAnalyze, {
|
|
||||||
// database: this.props.databaseId,
|
|
||||||
// collection: this.props.collectionId,
|
|
||||||
// sampleSize,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// this.props.runCell(this.props.contentRef, this.props.firstCellId);
|
|
||||||
// };
|
|
||||||
|
|
||||||
const { firstCellId: id, contentRef, kernelStatus } = props;
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log("firstCellId: id, contentRef, kernelStatus", id, contentRef, kernelStatus);
|
|
||||||
|
|
||||||
const isKernelBusy = kernelStatus === "busy";
|
|
||||||
const isKernelIdle = kernelStatus === "idle";
|
|
||||||
|
|
||||||
const baseStyle: CSSProperties = {
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
padding: "20px",
|
|
||||||
borderWidth: 2,
|
|
||||||
borderRadius: 2,
|
|
||||||
borderColor: "#eeeeee",
|
|
||||||
borderStyle: "dashed",
|
|
||||||
backgroundColor: "#fafafa",
|
|
||||||
color: "#bdbdbd",
|
|
||||||
transition: "border .3s ease-in-out",
|
|
||||||
};
|
|
||||||
|
|
||||||
const activeStyle = {
|
|
||||||
borderColor: "#2196f3",
|
|
||||||
};
|
|
||||||
|
|
||||||
const acceptStyle = {
|
|
||||||
borderColor: "#00e676",
|
|
||||||
};
|
|
||||||
|
|
||||||
const rejectStyle = {
|
|
||||||
borderColor: "#ff1744",
|
|
||||||
};
|
|
||||||
|
|
||||||
const onDrop = useCallback((acceptedFiles) => {
|
|
||||||
//eslint-disable-next-line no-console
|
|
||||||
console.log("acceptedFiles", acceptedFiles);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const { getRootProps, getInputProps, isDragActive, isDragAccept, isDragReject } = useDropzone({
|
|
||||||
onDrop,
|
|
||||||
accept: ".json",
|
|
||||||
});
|
|
||||||
|
|
||||||
const style = useMemo(
|
|
||||||
() => ({
|
|
||||||
...baseStyle,
|
|
||||||
...(isDragActive ? activeStyle : {}),
|
|
||||||
...(isDragAccept ? acceptStyle : {}),
|
|
||||||
...(isDragReject ? rejectStyle : {}),
|
|
||||||
}),
|
|
||||||
[isDragActive, isDragReject, isDragAccept]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!id) {
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div {...getRootProps({ style })}>
|
|
||||||
<input {...getInputProps()} />
|
|
||||||
<div>Drag and drop your json file here</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface StateProps {
|
|
||||||
firstCellId: string;
|
|
||||||
kernelStatus: string;
|
|
||||||
outputs: Immutable.List<ImmutableOutput>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface InitialProps {
|
|
||||||
kernelRef: string;
|
|
||||||
contentRef: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redux
|
|
||||||
const makeMapStateToProps = (state: AppState, initialProps: InitialProps) => {
|
|
||||||
const { kernelRef, contentRef } = initialProps;
|
|
||||||
const mapStateToProps = (state: AppState) => {
|
|
||||||
let kernelStatus;
|
|
||||||
let firstCellId;
|
|
||||||
let outputs;
|
|
||||||
|
|
||||||
const kernel = selectors.kernel(state, { kernelRef });
|
|
||||||
if (kernel) {
|
|
||||||
kernelStatus = kernel.status;
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = selectors.content(state, { contentRef });
|
|
||||||
if (content?.type === "notebook") {
|
|
||||||
const cellOrder = selectors.notebook.cellOrder(content.model);
|
|
||||||
if (cellOrder.size > 0) {
|
|
||||||
firstCellId = cellOrder.first() as string;
|
|
||||||
|
|
||||||
const model = selectors.model(state, { contentRef });
|
|
||||||
if (model && model.type === "notebook") {
|
|
||||||
const cell = selectors.notebook.cellById(model, { id: firstCellId });
|
|
||||||
if (cell) {
|
|
||||||
outputs = cell.get("outputs", Immutable.List());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
firstCellId,
|
|
||||||
kernelStatus,
|
|
||||||
outputs,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
return mapStateToProps;
|
|
||||||
};
|
|
||||||
|
|
||||||
const makeMapDispatchToProps = () => {
|
|
||||||
const mapDispatchToProps = (dispatch: Dispatch) => {
|
|
||||||
return {
|
|
||||||
addTransform: (transform: React.ComponentType & { MIMETYPE: string }) => {
|
|
||||||
return dispatch(
|
|
||||||
actions.addTransform({
|
|
||||||
mediaType: transform.MIMETYPE,
|
|
||||||
component: transform,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
runCell: (contentRef: ContentRef, cellId: string) => {
|
|
||||||
return dispatch(
|
|
||||||
actions.executeCell({
|
|
||||||
contentRef,
|
|
||||||
id: cellId,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
updateCell: (text: string, id: string, contentRef: ContentRef) => {
|
|
||||||
dispatch(actions.updateCellSource({ id, contentRef, value: text }));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
return mapDispatchToProps;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(makeMapStateToProps, makeMapDispatchToProps)(DataUploader);
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import { actions, createContentRef, createKernelRef, KernelRef } from "@nteract/core";
|
|
||||||
import DataUploader from "Explorer/Notebook/DataUploader/DataUploader";
|
|
||||||
import * as React from "react";
|
|
||||||
import { Provider } from "react-redux";
|
|
||||||
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
|
||||||
import {
|
|
||||||
NotebookComponentBootstrapper,
|
|
||||||
NotebookComponentBootstrapperOptions,
|
|
||||||
} from "../NotebookComponent/NotebookComponentBootstrapper";
|
|
||||||
import { DataUploaderNotebook } from "./DataUploaderUtils";
|
|
||||||
|
|
||||||
export class DataUploaderAdapter extends NotebookComponentBootstrapper implements ReactAdapter {
|
|
||||||
public parameters: unknown;
|
|
||||||
private kernelRef: KernelRef;
|
|
||||||
|
|
||||||
constructor(options: NotebookComponentBootstrapperOptions, private databaseId: string, private collectionId: string) {
|
|
||||||
super(options);
|
|
||||||
|
|
||||||
if (!this.contentRef) {
|
|
||||||
this.contentRef = createContentRef();
|
|
||||||
this.kernelRef = createKernelRef();
|
|
||||||
|
|
||||||
this.getStore().dispatch(
|
|
||||||
actions.fetchContent({
|
|
||||||
filepath: DataUploaderNotebook.path,
|
|
||||||
params: {},
|
|
||||||
kernelRef: this.kernelRef,
|
|
||||||
contentRef: this.contentRef,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public renderComponent(): JSX.Element {
|
|
||||||
const props = {
|
|
||||||
contentRef: this.contentRef,
|
|
||||||
kernelRef: this.kernelRef,
|
|
||||||
databaseId: this.databaseId,
|
|
||||||
collectionId: this.collectionId,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Provider store={this.getStore()}>
|
|
||||||
<DataUploader {...props} />;
|
|
||||||
</Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import { Notebook } from "@nteract/commutable";
|
|
||||||
import { IContent } from "@nteract/types";
|
|
||||||
import * as InMemoryContentProviderUtils from "../NotebookComponent/ContentProviders/InMemoryContentProviderUtils";
|
|
||||||
|
|
||||||
const notebookName = "data-uploader-component-notebook.ipynb";
|
|
||||||
const notebookPath = InMemoryContentProviderUtils.toContentUri(notebookName);
|
|
||||||
const notebook: Notebook = {
|
|
||||||
cells: [
|
|
||||||
{
|
|
||||||
cell_type: "code",
|
|
||||||
metadata: {},
|
|
||||||
execution_count: 0,
|
|
||||||
outputs: [],
|
|
||||||
source: "",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
metadata: {
|
|
||||||
kernelspec: {
|
|
||||||
displayName: "Mongo",
|
|
||||||
language: "mongocli",
|
|
||||||
name: "mongo",
|
|
||||||
},
|
|
||||||
language_info: {
|
|
||||||
file_extension: "ipynb",
|
|
||||||
mimetype: "application/json",
|
|
||||||
name: "mongo",
|
|
||||||
version: "1.0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
nbformat: 4,
|
|
||||||
nbformat_minor: 4,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DataUploaderNotebook: IContent<"notebook"> = {
|
|
||||||
name: notebookName,
|
|
||||||
path: notebookPath,
|
|
||||||
type: "notebook",
|
|
||||||
writable: true,
|
|
||||||
created: "",
|
|
||||||
last_modified: "",
|
|
||||||
mimetype: "application/x-ipynb+json",
|
|
||||||
content: notebook,
|
|
||||||
format: "json",
|
|
||||||
};
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { Link } from "@fluentui/react";
|
|
||||||
import { CellId, CellType, ImmutableNotebook } from "@nteract/commutable";
|
import { CellId, CellType, ImmutableNotebook } from "@nteract/commutable";
|
||||||
// Vendor modules
|
// Vendor modules
|
||||||
import {
|
import {
|
||||||
@@ -15,15 +14,13 @@ import "@nteract/styles/editor-overrides.css";
|
|||||||
import "@nteract/styles/global-variables.css";
|
import "@nteract/styles/global-variables.css";
|
||||||
import "codemirror/addon/hint/show-hint.css";
|
import "codemirror/addon/hint/show-hint.css";
|
||||||
import "codemirror/lib/codemirror.css";
|
import "codemirror/lib/codemirror.css";
|
||||||
import { Notebook } from "Common/Constants";
|
|
||||||
import { useDialog } from "Explorer/Controls/Dialog";
|
|
||||||
import * as Immutable from "immutable";
|
import * as Immutable from "immutable";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import "react-table/react-table.css";
|
import "react-table/react-table.css";
|
||||||
import { AnyAction, Store } from "redux";
|
import { AnyAction, Store } from "redux";
|
||||||
import { NotebookClientV2 } from "../NotebookClientV2";
|
import { NotebookClientV2 } from "../NotebookClientV2";
|
||||||
import { NotebookContentProviderType, NotebookUtil } from "../NotebookUtil";
|
import { NotebookUtil } from "../NotebookUtil";
|
||||||
import * as NteractUtil from "../NTeractUtil";
|
import * as NteractUtil from "../NTeractUtil";
|
||||||
import * as CdbActions from "./actions";
|
import * as CdbActions from "./actions";
|
||||||
import { NotebookComponent } from "./NotebookComponent";
|
import { NotebookComponent } from "./NotebookComponent";
|
||||||
@@ -102,10 +99,6 @@ export class NotebookComponentBootstrapper {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public getNotebookPath(): string {
|
|
||||||
return this.getStore().getState().core.entities.contents.byRef.get(this.contentRef)?.filepath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public setContent(name: string, content: unknown): void {
|
public setContent(name: string, content: unknown): void {
|
||||||
this.getStore().dispatch(
|
this.getStore().dispatch(
|
||||||
actions.fetchContentFulfilled({
|
actions.fetchContentFulfilled({
|
||||||
@@ -137,32 +130,11 @@ export class NotebookComponentBootstrapper {
|
|||||||
|
|
||||||
/* Notebook operations. See nteract/packages/connected-components/src/notebook-menu/index.tsx */
|
/* Notebook operations. See nteract/packages/connected-components/src/notebook-menu/index.tsx */
|
||||||
public notebookSave(): void {
|
public notebookSave(): void {
|
||||||
if (
|
this.getStore().dispatch(
|
||||||
NotebookUtil.getContentProviderType(this.getNotebookPath()) ===
|
actions.save({
|
||||||
NotebookContentProviderType.JupyterContentProviderType
|
contentRef: this.contentRef,
|
||||||
) {
|
})
|
||||||
useDialog.getState().showOkCancelModalDialog(
|
);
|
||||||
Notebook.saveNotebookModalTitle,
|
|
||||||
undefined,
|
|
||||||
"Save",
|
|
||||||
async () => {
|
|
||||||
this.getStore().dispatch(
|
|
||||||
actions.save({
|
|
||||||
contentRef: this.contentRef,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
"Cancel",
|
|
||||||
undefined,
|
|
||||||
this.getSaveNotebookSubText()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.getStore().dispatch(
|
|
||||||
actions.save({
|
|
||||||
contentRef: this.contentRef,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public notebookChangeKernel(kernelSpecName: string): void {
|
public notebookChangeKernel(kernelSpecName: string): void {
|
||||||
@@ -369,19 +341,4 @@ export class NotebookComponentBootstrapper {
|
|||||||
protected getStore(): Store<AppState, AnyAction> {
|
protected getStore(): Store<AppState, AnyAction> {
|
||||||
return this.notebookClient.getStore();
|
return this.notebookClient.getStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
private getSaveNotebookSubText(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<p>{Notebook.saveNotebookModalContent}</p>
|
|
||||||
<br />
|
|
||||||
<p>
|
|
||||||
{Notebook.newNotebookModalContent2}
|
|
||||||
<Link href={Notebook.cosmosNotebookHomePageUrl} target="_blank">
|
|
||||||
{Notebook.learnMore}
|
|
||||||
</Link>
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,12 +12,11 @@ import {
|
|||||||
ServerConfig as JupyterServerConfig,
|
ServerConfig as JupyterServerConfig,
|
||||||
} from "@nteract/core";
|
} from "@nteract/core";
|
||||||
import { Channels, childOf, createMessage, JupyterMessage, message, ofMessageType } from "@nteract/messaging";
|
import { Channels, childOf, createMessage, JupyterMessage, message, ofMessageType } from "@nteract/messaging";
|
||||||
import { defineConfigOption } from "@nteract/mythic-configuration";
|
|
||||||
import { RecordOf } from "immutable";
|
import { RecordOf } from "immutable";
|
||||||
import { Action, AnyAction } from "redux";
|
import { AnyAction } from "redux";
|
||||||
import { ofType, StateObservable } from "redux-observable";
|
import { ofType, StateObservable } from "redux-observable";
|
||||||
import { kernels, sessions } from "rx-jupyter";
|
import { kernels, sessions } from "rx-jupyter";
|
||||||
import { concat, EMPTY, from, interval, merge, Observable, Observer, of, Subject, Subscriber, timer } from "rxjs";
|
import { concat, EMPTY, from, merge, Observable, Observer, of, Subject, Subscriber, timer } from "rxjs";
|
||||||
import {
|
import {
|
||||||
catchError,
|
catchError,
|
||||||
concatMap,
|
concatMap,
|
||||||
@@ -42,7 +41,7 @@ import { logConsoleError, logConsoleInfo } from "../../../Utils/NotificationCons
|
|||||||
import { useDialog } from "../../Controls/Dialog";
|
import { useDialog } from "../../Controls/Dialog";
|
||||||
import * as FileSystemUtil from "../FileSystemUtil";
|
import * as FileSystemUtil from "../FileSystemUtil";
|
||||||
import * as cdbActions from "../NotebookComponent/actions";
|
import * as cdbActions from "../NotebookComponent/actions";
|
||||||
import { NotebookContentProviderType, NotebookUtil } from "../NotebookUtil";
|
import { NotebookUtil } from "../NotebookUtil";
|
||||||
import * as CdbActions from "./actions";
|
import * as CdbActions from "./actions";
|
||||||
import * as TextFile from "./contents/file/text-file";
|
import * as TextFile from "./contents/file/text-file";
|
||||||
import { CdbAppState } from "./types";
|
import { CdbAppState } from "./types";
|
||||||
@@ -949,54 +948,6 @@ const resetCellStatusOnExecuteCanceledEpic = (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { selector: autoSaveInterval } = defineConfigOption({
|
|
||||||
key: "autoSaveInterval",
|
|
||||||
label: "Auto-save interval",
|
|
||||||
defaultValue: 120_000,
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override autoSaveCurrentContentEpic to disable auto save for notebooks under temporary workspace.
|
|
||||||
* @param action$
|
|
||||||
*/
|
|
||||||
export function autoSaveCurrentContentEpic(
|
|
||||||
action$: Observable<Action>,
|
|
||||||
state$: StateObservable<AppState>
|
|
||||||
): Observable<actions.Save> {
|
|
||||||
return state$.pipe(
|
|
||||||
map((state) => autoSaveInterval(state)),
|
|
||||||
switchMap((time) => interval(time)),
|
|
||||||
mergeMap(() => {
|
|
||||||
const state = state$.value;
|
|
||||||
return from(
|
|
||||||
selectors
|
|
||||||
.contentByRef(state)
|
|
||||||
.filter(
|
|
||||||
/*
|
|
||||||
* Only save contents that are files or notebooks with
|
|
||||||
* a filepath already set.
|
|
||||||
*/
|
|
||||||
(content) => (content.type === "file" || content.type === "notebook") && content.filepath !== ""
|
|
||||||
)
|
|
||||||
.keys()
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
filter((contentRef: ContentRef) => {
|
|
||||||
const model = selectors.model(state$.value, { contentRef });
|
|
||||||
const content = selectors.content(state$.value, { contentRef });
|
|
||||||
if (
|
|
||||||
model &&
|
|
||||||
model.type === "notebook" &&
|
|
||||||
NotebookUtil.getContentProviderType(content.filepath) !== NotebookContentProviderType.JupyterContentProviderType
|
|
||||||
) {
|
|
||||||
return selectors.notebook.isDirty(model);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}),
|
|
||||||
map((contentRef: ContentRef) => actions.save({ contentRef }))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const allEpics = [
|
export const allEpics = [
|
||||||
addInitialCodeCellEpic,
|
addInitialCodeCellEpic,
|
||||||
focusInitialCodeCellEpic,
|
focusInitialCodeCellEpic,
|
||||||
@@ -1014,5 +965,4 @@ export const allEpics = [
|
|||||||
traceNotebookInfoEpic,
|
traceNotebookInfoEpic,
|
||||||
traceNotebookKernelEpic,
|
traceNotebookKernelEpic,
|
||||||
resetCellStatusOnExecuteCanceledEpic,
|
resetCellStatusOnExecuteCanceledEpic,
|
||||||
autoSaveCurrentContentEpic,
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { AppState, epics as coreEpics, IContentProvider, reducers } from "@nteract/core";
|
import { AppState, epics as coreEpics, reducers, IContentProvider } from "@nteract/core";
|
||||||
|
import { compose, Store, AnyAction, Middleware, Dispatch, MiddlewareAPI } from "redux";
|
||||||
|
import { Epic } from "redux-observable";
|
||||||
|
import { allEpics } from "./epics";
|
||||||
|
import { coreReducer, cdbReducer } from "./reducers";
|
||||||
|
import { catchError } from "rxjs/operators";
|
||||||
|
import { Observable } from "rxjs";
|
||||||
import { configuration } from "@nteract/mythic-configuration";
|
import { configuration } from "@nteract/mythic-configuration";
|
||||||
import { makeConfigureStore } from "@nteract/myths";
|
import { makeConfigureStore } from "@nteract/myths";
|
||||||
import { AnyAction, compose, Dispatch, Middleware, MiddlewareAPI, Store } from "redux";
|
|
||||||
import { Epic } from "redux-observable";
|
|
||||||
import { Observable } from "rxjs";
|
|
||||||
import { catchError } from "rxjs/operators";
|
|
||||||
import { allEpics } from "./epics";
|
|
||||||
import { cdbReducer, coreReducer } from "./reducers";
|
|
||||||
import { CdbAppState } from "./types";
|
import { CdbAppState } from "./types";
|
||||||
|
|
||||||
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||||
@@ -81,6 +81,7 @@ export const getCoreEpics = (autoStartKernelOnNotebookOpen: boolean): Epic[] =>
|
|||||||
// This list needs to be consistent and in sync with core.allEpics until we figure
|
// This list needs to be consistent and in sync with core.allEpics until we figure
|
||||||
// out how to safely filter out the ones we are overriding here.
|
// out how to safely filter out the ones we are overriding here.
|
||||||
const filteredCoreEpics = [
|
const filteredCoreEpics = [
|
||||||
|
coreEpics.autoSaveCurrentContentEpic,
|
||||||
coreEpics.executeCellEpic,
|
coreEpics.executeCellEpic,
|
||||||
coreEpics.executeFocusedCellEpic,
|
coreEpics.executeFocusedCellEpic,
|
||||||
coreEpics.executeCellAfterKernelLaunchEpic,
|
coreEpics.executeCellAfterKernelLaunchEpic,
|
||||||
|
|||||||
@@ -1,63 +1,50 @@
|
|||||||
/**
|
/**
|
||||||
* Notebook container related stuff
|
* Notebook container related stuff
|
||||||
*/
|
*/
|
||||||
import promiseRetry, { AbortError } from "p-retry";
|
|
||||||
import { PhoenixClient } from "Phoenix/PhoenixClient";
|
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import { ConnectionStatusType, HttpHeaders, HttpStatusCodes, Notebook } from "../../Common/Constants";
|
import { ConnectionStatusType } from "../../Common/Constants";
|
||||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||||
import * as Logger from "../../Common/Logger";
|
import * as Logger from "../../Common/Logger";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { IPhoenixConnectionInfoResult, IProvisionData, IResponse } from "../../Contracts/DataModels";
|
import { ContainerConnectionInfo } from "../../Contracts/DataModels";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
|
import { createOrUpdate, destroy } from "../../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
|
||||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { NotebookUtil } from "./NotebookUtil";
|
||||||
import { useNotebook } from "./useNotebook";
|
import { useNotebook } from "./useNotebook";
|
||||||
|
|
||||||
export class NotebookContainerClient {
|
export class NotebookContainerClient {
|
||||||
private clearReconnectionAttemptMessage? = () => {};
|
private clearReconnectionAttemptMessage? = () => {};
|
||||||
private isResettingWorkspace: boolean;
|
private isResettingWorkspace: boolean;
|
||||||
private phoenixClient: PhoenixClient;
|
|
||||||
private retryOptions: promiseRetry.Options;
|
|
||||||
private scheduleTimerId: NodeJS.Timeout;
|
|
||||||
|
|
||||||
constructor(private onConnectionLost: () => void) {
|
constructor(private onConnectionLost: () => void) {
|
||||||
this.phoenixClient = new PhoenixClient();
|
|
||||||
this.retryOptions = {
|
|
||||||
retries: Notebook.retryAttempts,
|
|
||||||
maxTimeout: Notebook.retryAttemptDelayMs,
|
|
||||||
minTimeout: Notebook.retryAttemptDelayMs,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.initHeartbeat(Constants.Notebook.heartbeatDelayMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
private initHeartbeat(delayMs: number): void {
|
|
||||||
this.scheduleHeartbeat(delayMs);
|
|
||||||
|
|
||||||
useNotebook.subscribe(
|
|
||||||
() => this.scheduleHeartbeat(delayMs),
|
|
||||||
(state) => state.notebookServerInfo
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private scheduleHeartbeat(delayMs: number) {
|
|
||||||
if (this.scheduleTimerId) {
|
|
||||||
clearInterval(this.scheduleTimerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||||
if (notebookServerInfo?.notebookServerEndpoint) {
|
if (notebookServerInfo?.notebookServerEndpoint) {
|
||||||
this.scheduleTimerId = setInterval(async () => {
|
this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs);
|
||||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
} else {
|
||||||
if (notebookServerInfo?.notebookServerEndpoint) {
|
const unsub = useNotebook.subscribe(
|
||||||
const memoryUsageInfo = await this.getMemoryUsage();
|
(newServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => {
|
||||||
useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo);
|
if (newServerInfo?.notebookServerEndpoint) {
|
||||||
}
|
this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs);
|
||||||
}, delayMs);
|
}
|
||||||
|
unsub();
|
||||||
|
},
|
||||||
|
(state) => state.notebookServerInfo
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Heartbeat: each ping schedules another ping
|
||||||
|
*/
|
||||||
|
private scheduleHeartbeat(delayMs: number): void {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.getMemoryUsage()
|
||||||
|
.then((memoryUsageInfo) => useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo))
|
||||||
|
.finally(() => this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs));
|
||||||
|
}, delayMs);
|
||||||
|
}
|
||||||
|
|
||||||
public async getMemoryUsage(): Promise<DataModels.MemoryUsageInfo> {
|
public async getMemoryUsage(): Promise<DataModels.MemoryUsageInfo> {
|
||||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||||
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
|
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
|
||||||
@@ -72,27 +59,6 @@ export class NotebookContainerClient {
|
|||||||
|
|
||||||
const { notebookServerEndpoint, authToken } = this.getNotebookServerConfig();
|
const { notebookServerEndpoint, authToken } = this.getNotebookServerConfig();
|
||||||
try {
|
try {
|
||||||
const runMemoryAsync = async () => {
|
|
||||||
return await this._getMemoryAsync(notebookServerEndpoint, authToken);
|
|
||||||
};
|
|
||||||
return await promiseRetry(runMemoryAsync, this.retryOptions);
|
|
||||||
} catch (error) {
|
|
||||||
Logger.logError(getErrorMessage(error), "NotebookContainerClient/getMemoryUsage");
|
|
||||||
if (!this.clearReconnectionAttemptMessage) {
|
|
||||||
this.clearReconnectionAttemptMessage = logConsoleProgress(
|
|
||||||
"Connection lost with Notebook server. Attempting to reconnect..."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.onConnectionLost();
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _getMemoryAsync(
|
|
||||||
notebookServerEndpoint: string,
|
|
||||||
authToken: string
|
|
||||||
): Promise<DataModels.MemoryUsageInfo> {
|
|
||||||
if (this.shouldExecuteMemoryCall()) {
|
|
||||||
const response = await fetch(`${notebookServerEndpoint}api/metrics/memory`, {
|
const response = await fetch(`${notebookServerEndpoint}api/metrics/memory`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -112,36 +78,44 @@ export class NotebookContainerClient {
|
|||||||
freeKB: memoryUsageInfo.free,
|
freeKB: memoryUsageInfo.free,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else if (response.status === HttpStatusCodes.NotFound) {
|
} else if (NotebookUtil.isPhoenixEnabled()) {
|
||||||
throw new AbortError(response.statusText);
|
const connectionStatus: ContainerConnectionInfo = {
|
||||||
|
status: ConnectionStatusType.ReConnect,
|
||||||
|
};
|
||||||
|
useNotebook.getState().resetConatinerConnection(connectionStatus);
|
||||||
|
useNotebook.getState().setIsRefreshed(true);
|
||||||
}
|
}
|
||||||
throw new Error(response.statusText);
|
return undefined;
|
||||||
} else {
|
} catch (error) {
|
||||||
|
Logger.logError(getErrorMessage(error), "NotebookContainerClient/getMemoryUsage");
|
||||||
|
if (!this.clearReconnectionAttemptMessage) {
|
||||||
|
this.clearReconnectionAttemptMessage = logConsoleProgress(
|
||||||
|
"Connection lost with Notebook server. Attempting to reconnect..."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (NotebookUtil.isPhoenixEnabled()) {
|
||||||
|
const connectionStatus: ContainerConnectionInfo = {
|
||||||
|
status: ConnectionStatusType.Failed,
|
||||||
|
};
|
||||||
|
useNotebook.getState().resetConatinerConnection(connectionStatus);
|
||||||
|
useNotebook.getState().setIsRefreshed(true);
|
||||||
|
}
|
||||||
|
this.onConnectionLost();
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private shouldExecuteMemoryCall(): boolean {
|
public async resetWorkspace(): Promise<void> {
|
||||||
return (
|
|
||||||
useNotebook.getState().containerStatus?.status === Constants.ContainerStatusType.Active &&
|
|
||||||
useNotebook.getState().connectionInfo?.status === ConnectionStatusType.Connected
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async resetWorkspace(): Promise<IResponse<IPhoenixConnectionInfoResult>> {
|
|
||||||
this.isResettingWorkspace = true;
|
this.isResettingWorkspace = true;
|
||||||
let response: IResponse<IPhoenixConnectionInfoResult>;
|
|
||||||
try {
|
try {
|
||||||
response = await this._resetWorkspace();
|
await this._resetWorkspace();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Promise.reject(error);
|
Promise.reject(error);
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
this.isResettingWorkspace = false;
|
this.isResettingWorkspace = false;
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _resetWorkspace(): Promise<IResponse<IPhoenixConnectionInfoResult>> {
|
private async _resetWorkspace(): Promise<void> {
|
||||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||||
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
|
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
|
||||||
const error = "No server endpoint detected";
|
const error = "No server endpoint detected";
|
||||||
@@ -149,17 +123,15 @@ export class NotebookContainerClient {
|
|||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { notebookServerEndpoint, authToken } = this.getNotebookServerConfig();
|
||||||
try {
|
try {
|
||||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
await fetch(`${notebookServerEndpoint}/api/shutdown`, {
|
||||||
const provisionData: IProvisionData = {
|
method: "POST",
|
||||||
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
|
headers: { Authorization: authToken },
|
||||||
};
|
});
|
||||||
return await this.phoenixClient.resetContainer(provisionData);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.logError(getErrorMessage(error), "NotebookContainerClient/resetWorkspace");
|
Logger.logError(getErrorMessage(error), "NotebookContainerClient/resetWorkspace");
|
||||||
throw error;
|
await this.recreateNotebookWorkspaceAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,11 +145,22 @@ export class NotebookContainerClient {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private getHeaders(): HeadersInit {
|
private async recreateNotebookWorkspaceAsync(): Promise<void> {
|
||||||
const authorizationHeader = getAuthorizationHeader();
|
const { databaseAccount } = userContext;
|
||||||
return {
|
if (!databaseAccount?.id) {
|
||||||
[authorizationHeader.header]: authorizationHeader.token,
|
throw new Error("DataExplorer not initialized");
|
||||||
[HttpHeaders.contentType]: "application/json",
|
}
|
||||||
};
|
try {
|
||||||
|
await destroy(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name, "default");
|
||||||
|
await createOrUpdate(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
"default"
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
Logger.logError(getErrorMessage(error), "NotebookContainerClient/recreateNotebookWorkspaceAsync");
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -303,8 +303,8 @@ export class NotebookContentClient {
|
|||||||
private getServerConfig(): ServerConfig {
|
private getServerConfig(): ServerConfig {
|
||||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||||
return {
|
return {
|
||||||
endpoint: notebookServerInfo?.notebookServerEndpoint,
|
endpoint: notebookServerInfo.notebookServerEndpoint,
|
||||||
token: notebookServerInfo?.authToken,
|
token: notebookServerInfo.authToken,
|
||||||
crossDomain: true,
|
crossDomain: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
import { ImmutableNotebook } from "@nteract/commutable";
|
import { ImmutableNotebook } from "@nteract/commutable";
|
||||||
import type { IContentProvider } from "@nteract/core";
|
import type { IContentProvider } from "@nteract/core";
|
||||||
import { DataUploaderNotebook } from "Explorer/Notebook/DataUploader/DataUploaderUtils";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { contents } from "rx-jupyter";
|
import { contents } from "rx-jupyter";
|
||||||
import { Areas, HttpStatusCodes } from "../../Common/Constants";
|
import { Areas, HttpStatusCodes } from "../../Common/Constants";
|
||||||
@@ -69,10 +68,6 @@ export default class NotebookManager {
|
|||||||
readonly: true,
|
readonly: true,
|
||||||
content: SchemaAnalyzerNotebook,
|
content: SchemaAnalyzerNotebook,
|
||||||
},
|
},
|
||||||
[DataUploaderNotebook.path]: {
|
|
||||||
readonly: true,
|
|
||||||
content: DataUploaderNotebook,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.gitHubContentProvider = new GitHubContentProvider({
|
this.gitHubContentProvider = new GitHubContentProvider({
|
||||||
|
|||||||
@@ -3,19 +3,14 @@ import { AppState, selectors } from "@nteract/core";
|
|||||||
import domtoimage from "dom-to-image";
|
import domtoimage from "dom-to-image";
|
||||||
import Html2Canvas from "html2canvas";
|
import Html2Canvas from "html2canvas";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||||
import * as StringUtils from "../../Utils/StringUtils";
|
import * as StringUtils from "../../Utils/StringUtils";
|
||||||
import * as InMemoryContentProviderUtils from "../Notebook/NotebookComponent/ContentProviders/InMemoryContentProviderUtils";
|
|
||||||
import { SnapshotFragment } from "./NotebookComponent/types";
|
import { SnapshotFragment } from "./NotebookComponent/types";
|
||||||
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
|
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
|
||||||
|
|
||||||
// Must match rx-jupyter' FileType
|
// Must match rx-jupyter' FileType
|
||||||
export type FileType = "directory" | "file" | "notebook";
|
export type FileType = "directory" | "file" | "notebook";
|
||||||
export enum NotebookContentProviderType {
|
|
||||||
GitHubContentProviderType,
|
|
||||||
InMemoryContentProviderType,
|
|
||||||
JupyterContentProviderType,
|
|
||||||
}
|
|
||||||
// Utilities for notebooks
|
// Utilities for notebooks
|
||||||
export class NotebookUtil {
|
export class NotebookUtil {
|
||||||
public static UntrustedNotebookRunHint = "Please trust notebook first before running any code cells";
|
public static UntrustedNotebookRunHint = "Please trust notebook first before running any code cells";
|
||||||
@@ -132,18 +127,6 @@ export class NotebookUtil {
|
|||||||
return relativePath.split("/").pop();
|
return relativePath.split("/").pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getContentProviderType(path: string): NotebookContentProviderType {
|
|
||||||
if (InMemoryContentProviderUtils.fromContentUri(path)) {
|
|
||||||
return NotebookContentProviderType.InMemoryContentProviderType;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GitHubUtils.fromContentUri(path)) {
|
|
||||||
return NotebookContentProviderType.GitHubContentProviderType;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NotebookContentProviderType.JupyterContentProviderType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static replaceName(path: string, newName: string): string {
|
public static replaceName(path: string, newName: string): string {
|
||||||
const contentInfo = GitHubUtils.fromContentUri(path);
|
const contentInfo = GitHubUtils.fromContentUri(path);
|
||||||
if (contentInfo) {
|
if (contentInfo) {
|
||||||
@@ -346,4 +329,16 @@ export class NotebookUtil {
|
|||||||
link.click();
|
link.click();
|
||||||
document.body.removeChild(link);
|
document.body.removeChild(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static getNotebookBtnTitle(fileName: string): string {
|
||||||
|
if (this.isPhoenixEnabled()) {
|
||||||
|
return `Download to ${fileName}`;
|
||||||
|
} else {
|
||||||
|
return `Download to my notebooks`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static isPhoenixEnabled(): boolean {
|
||||||
|
return userContext.features.notebooksTemporarilyDown === false && userContext.features.phoenix === true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ describe("auto start kernel", () => {
|
|||||||
connectionInfo: {
|
connectionInfo: {
|
||||||
authToken: "autToken",
|
authToken: "autToken",
|
||||||
notebookServerEndpoint: "notebookServerEndpoint",
|
notebookServerEndpoint: "notebookServerEndpoint",
|
||||||
forwardingId: "Id",
|
|
||||||
},
|
},
|
||||||
databaseAccountName: undefined,
|
databaseAccountName: undefined,
|
||||||
defaultExperience: undefined,
|
defaultExperience: undefined,
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
|
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import { PhoenixClient } from "Phoenix/PhoenixClient";
|
|
||||||
import create, { UseStore } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
@@ -9,8 +7,7 @@ import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
|||||||
import * as Logger from "../../Common/Logger";
|
import * as Logger from "../../Common/Logger";
|
||||||
import { configContext } from "../../ConfigContext";
|
import { configContext } from "../../ConfigContext";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { ContainerConnectionInfo, ContainerInfo } from "../../Contracts/DataModels";
|
import { ContainerConnectionInfo } from "../../Contracts/DataModels";
|
||||||
import { useTabs } from "../../hooks/useTabs";
|
|
||||||
import { IPinnedRepo } from "../../Juno/JunoClient";
|
import { IPinnedRepo } from "../../Juno/JunoClient";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
@@ -19,6 +16,7 @@ import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
|
|||||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||||
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
|
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
|
||||||
import NotebookManager from "./NotebookManager";
|
import NotebookManager from "./NotebookManager";
|
||||||
|
import { NotebookUtil } from "./NotebookUtil";
|
||||||
|
|
||||||
interface NotebookState {
|
interface NotebookState {
|
||||||
isNotebookEnabled: boolean;
|
isNotebookEnabled: boolean;
|
||||||
@@ -37,9 +35,6 @@ interface NotebookState {
|
|||||||
notebookFolderName: string;
|
notebookFolderName: string;
|
||||||
isAllocating: boolean;
|
isAllocating: boolean;
|
||||||
isRefreshed: boolean;
|
isRefreshed: boolean;
|
||||||
containerStatus: ContainerInfo;
|
|
||||||
isPhoenixNotebooks: boolean;
|
|
||||||
isPhoenixFeatures: boolean;
|
|
||||||
setIsNotebookEnabled: (isNotebookEnabled: boolean) => void;
|
setIsNotebookEnabled: (isNotebookEnabled: boolean) => void;
|
||||||
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void;
|
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void;
|
||||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void;
|
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void;
|
||||||
@@ -58,12 +53,8 @@ interface NotebookState {
|
|||||||
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]) => void;
|
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]) => void;
|
||||||
setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => void;
|
setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => void;
|
||||||
setIsAllocating: (isAllocating: boolean) => void;
|
setIsAllocating: (isAllocating: boolean) => void;
|
||||||
resetContainerConnection: (connectionStatus: ContainerConnectionInfo) => void;
|
resetConatinerConnection: (connectionStatus: ContainerConnectionInfo) => void;
|
||||||
setIsRefreshed: (isAllocating: boolean) => void;
|
setIsRefreshed: (isAllocating: boolean) => void;
|
||||||
setContainerStatus: (containerStatus: ContainerInfo) => void;
|
|
||||||
getPhoenixStatus: () => Promise<void>;
|
|
||||||
setIsPhoenixNotebooks: (isPhoenixNotebooks: boolean) => void;
|
|
||||||
setIsPhoenixFeatures: (isPhoenixFeatures: boolean) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||||
@@ -72,7 +63,6 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
notebookServerInfo: {
|
notebookServerInfo: {
|
||||||
notebookServerEndpoint: undefined,
|
notebookServerEndpoint: undefined,
|
||||||
authToken: undefined,
|
authToken: undefined,
|
||||||
forwardingId: undefined,
|
|
||||||
},
|
},
|
||||||
sparkClusterConnectionInfo: {
|
sparkClusterConnectionInfo: {
|
||||||
userName: undefined,
|
userName: undefined,
|
||||||
@@ -93,13 +83,6 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
notebookFolderName: undefined,
|
notebookFolderName: undefined,
|
||||||
isAllocating: false,
|
isAllocating: false,
|
||||||
isRefreshed: false,
|
isRefreshed: false,
|
||||||
containerStatus: {
|
|
||||||
status: undefined,
|
|
||||||
durationLeftInMinutes: undefined,
|
|
||||||
notebookServerInfo: undefined,
|
|
||||||
},
|
|
||||||
isPhoenixNotebooks: undefined,
|
|
||||||
isPhoenixFeatures: undefined,
|
|
||||||
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
|
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
|
||||||
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
|
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
|
||||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
||||||
@@ -112,7 +95,6 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
setNotebookBasePath: (notebookBasePath: string) => set({ notebookBasePath }),
|
setNotebookBasePath: (notebookBasePath: string) => set({ notebookBasePath }),
|
||||||
setNotebookFolderName: (notebookFolderName: string) => set({ notebookFolderName }),
|
setNotebookFolderName: (notebookFolderName: string) => set({ notebookFolderName }),
|
||||||
refreshNotebooksEnabledStateForAccount: async (): Promise<void> => {
|
refreshNotebooksEnabledStateForAccount: async (): Promise<void> => {
|
||||||
await get().getPhoenixStatus();
|
|
||||||
const { databaseAccount, authType } = userContext;
|
const { databaseAccount, authType } = userContext;
|
||||||
if (
|
if (
|
||||||
authType === AuthType.EncryptedToken ||
|
authType === AuthType.EncryptedToken ||
|
||||||
@@ -205,7 +187,7 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
||||||
},
|
},
|
||||||
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
|
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
|
||||||
const notebookFolderName = get().isPhoenixNotebooks ? "Temporary Notebooks" : "My Notebooks";
|
const notebookFolderName = NotebookUtil.isPhoenixEnabled() === true ? "Temporary Notebooks" : "My Notebooks";
|
||||||
set({ notebookFolderName });
|
set({ notebookFolderName });
|
||||||
const myNotebooksContentRoot = {
|
const myNotebooksContentRoot = {
|
||||||
name: get().notebookFolderName,
|
name: get().notebookFolderName,
|
||||||
@@ -288,34 +270,13 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
},
|
},
|
||||||
setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => set({ connectionInfo }),
|
setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => set({ connectionInfo }),
|
||||||
setIsAllocating: (isAllocating: boolean) => set({ isAllocating }),
|
setIsAllocating: (isAllocating: boolean) => set({ isAllocating }),
|
||||||
resetContainerConnection: (connectionStatus: ContainerConnectionInfo): void => {
|
resetConatinerConnection: (connectionStatus: ContainerConnectionInfo): void => {
|
||||||
useTabs.getState().closeAllNotebookTabs(true);
|
|
||||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||||
useNotebook.getState().setNotebookServerInfo(undefined);
|
useNotebook.getState().setNotebookServerInfo({
|
||||||
useNotebook.getState().setIsAllocating(false);
|
notebookServerEndpoint: undefined,
|
||||||
useNotebook.getState().setContainerStatus({
|
authToken: undefined,
|
||||||
status: undefined,
|
|
||||||
durationLeftInMinutes: undefined,
|
|
||||||
notebookServerInfo: undefined,
|
|
||||||
});
|
});
|
||||||
|
useNotebook.getState().setIsAllocating(false);
|
||||||
},
|
},
|
||||||
setIsRefreshed: (isRefreshed: boolean) => set({ isRefreshed }),
|
setIsRefreshed: (isRefreshed: boolean) => set({ isRefreshed }),
|
||||||
setContainerStatus: (containerStatus: ContainerInfo) => set({ containerStatus }),
|
|
||||||
getPhoenixStatus: async () => {
|
|
||||||
if (get().isPhoenixNotebooks === undefined || get().isPhoenixFeatures === undefined) {
|
|
||||||
let isPhoenix = false;
|
|
||||||
if (userContext.features.phoenixNotebooks || userContext.features.phoenixFeatures) {
|
|
||||||
const phoenixClient = new PhoenixClient();
|
|
||||||
isPhoenix = isPublicInternetAccessAllowed() && (await phoenixClient.isDbAcountWhitelisted());
|
|
||||||
}
|
|
||||||
|
|
||||||
const isPhoenixNotebooks = userContext.features.phoenixNotebooks && isPhoenix;
|
|
||||||
const isPhoenixFeatures = userContext.features.phoenixFeatures && isPhoenix;
|
|
||||||
|
|
||||||
set({ isPhoenixNotebooks: isPhoenixNotebooks });
|
|
||||||
set({ isPhoenixFeatures: isPhoenixFeatures });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setIsPhoenixNotebooks: (isPhoenixNotebooks: boolean) => set({ isPhoenixNotebooks: isPhoenixNotebooks }),
|
|
||||||
setIsPhoenixFeatures: (isPhoenixFeatures: boolean) => set({ isPhoenixFeatures: isPhoenixFeatures }),
|
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ describe("OpenActions", () => {
|
|||||||
collection.onDocumentDBDocumentsClick = jest.fn();
|
collection.onDocumentDBDocumentsClick = jest.fn();
|
||||||
collection.onMongoDBDocumentsClick = jest.fn();
|
collection.onMongoDBDocumentsClick = jest.fn();
|
||||||
collection.onSchemaAnalyzerClick = jest.fn();
|
collection.onSchemaAnalyzerClick = jest.fn();
|
||||||
collection.onDataUploaderClick = jest.fn();
|
|
||||||
collection.onTableEntitiesClick = jest.fn();
|
collection.onTableEntitiesClick = jest.fn();
|
||||||
collection.onGraphDocumentsClick = jest.fn();
|
collection.onGraphDocumentsClick = jest.fn();
|
||||||
collection.onNewQueryClick = jest.fn();
|
collection.onNewQueryClick = jest.fn();
|
||||||
|
|||||||
@@ -86,14 +86,6 @@ function openCollectionTab(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
action.tabKind === ActionContracts.TabKind.DataUploader ||
|
|
||||||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.DataUploader]
|
|
||||||
) {
|
|
||||||
collection.onDataUploaderClick();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
action.tabKind === ActionContracts.TabKind.TableEntities ||
|
action.tabKind === ActionContracts.TabKind.TableEntities ||
|
||||||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.TableEntities]
|
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.TableEntities]
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ export const OpenFullScreen: React.FunctionComponent = () => {
|
|||||||
<TextField label="Read and Write" readOnly defaultValue={readWriteUrl} />
|
<TextField label="Read and Write" readOnly defaultValue={readWriteUrl} />
|
||||||
<Stack horizontal tokens={{ childrenGap: 10 }}>
|
<Stack horizontal tokens={{ childrenGap: 10 }}>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
ariaLabel={isReadWriteUrlCopy ? "Copied url" : "Copy"}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
copyToClipboard(readWriteUrl);
|
copyToClipboard(readWriteUrl);
|
||||||
setIsReadWriteUrlCopy(true);
|
setIsReadWriteUrlCopy(true);
|
||||||
@@ -44,7 +43,6 @@ export const OpenFullScreen: React.FunctionComponent = () => {
|
|||||||
<TextField label="Read Only" readOnly defaultValue={readUrl} />
|
<TextField label="Read Only" readOnly defaultValue={readUrl} />
|
||||||
<Stack horizontal tokens={{ childrenGap: 10 }}>
|
<Stack horizontal tokens={{ childrenGap: 10 }}>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
ariaLabel={isReadUrlCopy ? "Copied url" : "Copy"}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsReadUrlCopy(true);
|
setIsReadUrlCopy(true);
|
||||||
copyToClipboard(readUrl);
|
copyToClipboard(readUrl);
|
||||||
|
|||||||
@@ -92,7 +92,6 @@ export interface AddCollectionPanelState {
|
|||||||
errorMessage: string;
|
errorMessage: string;
|
||||||
showErrorDetails: boolean;
|
showErrorDetails: boolean;
|
||||||
isExecuting: boolean;
|
isExecuting: boolean;
|
||||||
isThroughputCapExceeded: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AddCollectionPanel extends React.Component<AddCollectionPanelProps, AddCollectionPanelState> {
|
export class AddCollectionPanel extends React.Component<AddCollectionPanelProps, AddCollectionPanelState> {
|
||||||
@@ -123,7 +122,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
errorMessage: "",
|
errorMessage: "",
|
||||||
showErrorDetails: false,
|
showErrorDetails: false,
|
||||||
isExecuting: false,
|
isExecuting: false,
|
||||||
isThroughputCapExceeded: false,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,12 +247,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
showFreeTierExceedThroughputTooltip={this.isFreeTierAccount() && !isFirstResourceCreated}
|
showFreeTierExceedThroughputTooltip={this.isFreeTierAccount() && !isFirstResourceCreated}
|
||||||
isDatabase={true}
|
isDatabase={true}
|
||||||
isSharded={this.state.isSharded}
|
isSharded={this.state.isSharded}
|
||||||
isFreeTier={this.isFreeTierAccount()}
|
|
||||||
setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
|
setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
|
||||||
setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)}
|
setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)}
|
||||||
setIsThroughputCapExceeded={(isThroughputCapExceeded: boolean) =>
|
|
||||||
this.setState({ isThroughputCapExceeded })
|
|
||||||
}
|
|
||||||
onCostAcknowledgeChange={(isAcknowledge: boolean) => (this.isCostAcknowledged = isAcknowledge)}
|
onCostAcknowledgeChange={(isAcknowledge: boolean) => (this.isCostAcknowledged = isAcknowledge)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -280,7 +274,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<span className="mandatoryStar">* </span>
|
<span className="mandatoryStar">* </span>
|
||||||
<Text className="panelTextBold" variant="small">
|
<Text className="panelTextBold" variant="small">
|
||||||
{`${getCollectionName()} id`}
|
{`${getCollectionName()} ${userContext.apiType === "Mongo" ? "name" : "id"}`}
|
||||||
</Text>
|
</Text>
|
||||||
<TooltipHost
|
<TooltipHost
|
||||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||||
@@ -484,12 +478,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
showFreeTierExceedThroughputTooltip={this.isFreeTierAccount() && !isFirstResourceCreated}
|
showFreeTierExceedThroughputTooltip={this.isFreeTierAccount() && !isFirstResourceCreated}
|
||||||
isDatabase={false}
|
isDatabase={false}
|
||||||
isSharded={this.state.isSharded}
|
isSharded={this.state.isSharded}
|
||||||
isFreeTier={this.isFreeTierAccount()}
|
|
||||||
setThroughputValue={(throughput: number) => (this.collectionThroughput = throughput)}
|
setThroughputValue={(throughput: number) => (this.collectionThroughput = throughput)}
|
||||||
setIsAutoscale={(isAutoscale: boolean) => (this.isCollectionAutoscale = isAutoscale)}
|
setIsAutoscale={(isAutoscale: boolean) => (this.isCollectionAutoscale = isAutoscale)}
|
||||||
setIsThroughputCapExceeded={(isThroughputCapExceeded: boolean) =>
|
|
||||||
this.setState({ isThroughputCapExceeded })
|
|
||||||
}
|
|
||||||
onCostAcknowledgeChange={(isAcknowledged: boolean) => {
|
onCostAcknowledgeChange={(isAcknowledged: boolean) => {
|
||||||
this.isCostAcknowledged = isAcknowledged;
|
this.isCostAcknowledged = isAcknowledged;
|
||||||
}}
|
}}
|
||||||
@@ -669,7 +659,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
|
|
||||||
{userContext.apiType === "SQL" && (
|
{userContext.apiType === "SQL" && (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label="My partition key is larger than 101 bytes"
|
label="My partition key is larger than 100 bytes"
|
||||||
checked={this.state.useHashV2}
|
checked={this.state.useHashV2}
|
||||||
styles={{
|
styles={{
|
||||||
text: { fontSize: 12 },
|
text: { fontSize: 12 },
|
||||||
@@ -686,7 +676,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PanelFooterComponent buttonLabel="OK" isButtonDisabled={this.state.isThroughputCapExceeded} />
|
<PanelFooterComponent buttonLabel="OK" />
|
||||||
|
|
||||||
{this.state.isExecuting && <PanelLoadingScreen />}
|
{this.state.isExecuting && <PanelLoadingScreen />}
|
||||||
</form>
|
</form>
|
||||||
@@ -889,6 +879,10 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isServerlessAccount()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
switch (userContext.apiType) {
|
switch (userContext.apiType) {
|
||||||
case "SQL":
|
case "SQL":
|
||||||
case "Mongo":
|
case "Mongo":
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
);
|
);
|
||||||
const [formErrors, setFormErrors] = useState<string>("");
|
const [formErrors, setFormErrors] = useState<string>("");
|
||||||
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
||||||
const [isThroughputCapExceeded, setIsThroughputCapExceeded] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier;
|
const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier;
|
||||||
|
|
||||||
@@ -80,9 +79,7 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||||
};
|
};
|
||||||
TelemetryProcessor.trace(Action.CreateDatabase, ActionModifiers.Open, addDatabasePaneOpenMessage);
|
TelemetryProcessor.trace(Action.CreateDatabase, ActionModifiers.Open, addDatabasePaneOpenMessage);
|
||||||
if (buttonElement) {
|
buttonElement.focus();
|
||||||
buttonElement.focus();
|
|
||||||
}
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onSubmit = () => {
|
const onSubmit = () => {
|
||||||
@@ -146,10 +143,9 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
// TODO add feature flag that disables validation for customers with custom accounts
|
// TODO add feature flag that disables validation for customers with custom accounts
|
||||||
if (isAutoscaleSelected) {
|
if (isAutoscaleSelected) {
|
||||||
if (!AutoPilotUtils.isValidAutoPilotThroughput(throughput)) {
|
if (!AutoPilotUtils.isValidAutoPilotThroughput(throughput)) {
|
||||||
const minAutoPilotThroughput = userContext.features.freetierAutoscaleThroughput
|
setFormErrors(
|
||||||
? AutoPilotUtils.autoPilotThroughput1K
|
`Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput`
|
||||||
: AutoPilotUtils.autoPilotThroughput4K;
|
);
|
||||||
setFormErrors(`Please enter a value greater than ${minAutoPilotThroughput} for autopilot throughput`);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,7 +169,6 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
formError: formErrors,
|
formError: formErrors,
|
||||||
isExecuting,
|
isExecuting,
|
||||||
submitButtonText: "OK",
|
submitButtonText: "OK",
|
||||||
isSubmitButtonDisabled: isThroughputCapExceeded,
|
|
||||||
onSubmit,
|
onSubmit,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -242,10 +237,8 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !useDatabases.getState().isFirstResourceCreated()}
|
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !useDatabases.getState().isFirstResourceCreated()}
|
||||||
isDatabase={true}
|
isDatabase={true}
|
||||||
isSharded={databaseCreateNewShared}
|
isSharded={databaseCreateNewShared}
|
||||||
isFreeTier={isFreeTierAccount}
|
|
||||||
setThroughputValue={(newThroughput: number) => (throughput = newThroughput)}
|
setThroughputValue={(newThroughput: number) => (throughput = newThroughput)}
|
||||||
setIsAutoscale={(isAutoscale: boolean) => (isAutoscaleSelected = isAutoscale)}
|
setIsAutoscale={(isAutoscale: boolean) => (isAutoscaleSelected = isAutoscale)}
|
||||||
setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)}
|
|
||||||
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
|
|||||||
<RightPaneForm
|
<RightPaneForm
|
||||||
formError=""
|
formError=""
|
||||||
isExecuting={false}
|
isExecuting={false}
|
||||||
isSubmitButtonDisabled={false}
|
|
||||||
onSubmit={[Function]}
|
onSubmit={[Function]}
|
||||||
submitButtonText="OK"
|
submitButtonText="OK"
|
||||||
>
|
>
|
||||||
@@ -93,7 +92,6 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
|
|||||||
isSharded={true}
|
isSharded={true}
|
||||||
onCostAcknowledgeChange={[Function]}
|
onCostAcknowledgeChange={[Function]}
|
||||||
setIsAutoscale={[Function]}
|
setIsAutoscale={[Function]}
|
||||||
setIsThroughputCapExceeded={[Function]}
|
|
||||||
setThroughputValue={[Function]}
|
setThroughputValue={[Function]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
|||||||
const [dedicateTableThroughput, setDedicateTableThroughput] = useState<boolean>(false);
|
const [dedicateTableThroughput, setDedicateTableThroughput] = useState<boolean>(false);
|
||||||
const [isExecuting, setIsExecuting] = useState<boolean>();
|
const [isExecuting, setIsExecuting] = useState<boolean>();
|
||||||
const [formError, setFormError] = useState<string>("");
|
const [formError, setFormError] = useState<string>("");
|
||||||
const [isThroughputCapExceeded, setIsThroughputCapExceeded] = useState<boolean>(false);
|
|
||||||
const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier;
|
const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier;
|
||||||
|
|
||||||
const addCollectionPaneOpenMessage = {
|
const addCollectionPaneOpenMessage = {
|
||||||
@@ -150,7 +149,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
|||||||
formError,
|
formError,
|
||||||
isExecuting,
|
isExecuting,
|
||||||
submitButtonText: "OK",
|
submitButtonText: "OK",
|
||||||
isSubmitButtonDisabled: isThroughputCapExceeded,
|
|
||||||
onSubmit,
|
onSubmit,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -262,10 +260,8 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
|||||||
}
|
}
|
||||||
isDatabase
|
isDatabase
|
||||||
isSharded
|
isSharded
|
||||||
isFreeTier={isFreeTierAccount}
|
|
||||||
setThroughputValue={(throughput: number) => (newKeySpaceThroughput = throughput)}
|
setThroughputValue={(throughput: number) => (newKeySpaceThroughput = throughput)}
|
||||||
setIsAutoscale={(isAutoscale: boolean) => (isNewKeySpaceAutoscale = isAutoscale)}
|
setIsAutoscale={(isAutoscale: boolean) => (isNewKeySpaceAutoscale = isAutoscale)}
|
||||||
setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)}
|
|
||||||
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -335,11 +331,9 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
|||||||
<ThroughputInput
|
<ThroughputInput
|
||||||
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !useDatabases.getState().isFirstResourceCreated()}
|
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !useDatabases.getState().isFirstResourceCreated()}
|
||||||
isDatabase={false}
|
isDatabase={false}
|
||||||
isSharded
|
isSharded={false}
|
||||||
isFreeTier={isFreeTierAccount}
|
|
||||||
setThroughputValue={(throughput: number) => (tableThroughput = throughput)}
|
setThroughputValue={(throughput: number) => (tableThroughput = throughput)}
|
||||||
setIsAutoscale={(isAutoscale: boolean) => (isTableAutoscale = isAutoscale)}
|
setIsAutoscale={(isAutoscale: boolean) => (isTableAutoscale = isAutoscale)}
|
||||||
setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)}
|
|
||||||
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils
|
|||||||
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
|
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
|
||||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
import { IPinnedRepo, JunoClient } from "../../../Juno/JunoClient";
|
import { IPinnedRepo, JunoClient } from "../../../Juno/JunoClient";
|
||||||
|
import { userContext } from "../../../UserContext";
|
||||||
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { NotebookContentItem, NotebookContentItemType } from "../../Notebook/NotebookContentItem";
|
import { NotebookContentItem, NotebookContentItemType } from "../../Notebook/NotebookContentItem";
|
||||||
import { useNotebook } from "../../Notebook/useNotebook";
|
import { useNotebook } from "../../Notebook/useNotebook";
|
||||||
|
import { ResourceTreeAdapter } from "../../Tree/ResourceTreeAdapter";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
import { CopyNotebookPaneComponent, CopyNotebookPaneProps } from "./CopyNotebookPaneComponent";
|
import { CopyNotebookPaneComponent, CopyNotebookPaneProps } from "./CopyNotebookPaneComponent";
|
||||||
|
|
||||||
@@ -74,7 +76,7 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
|||||||
selectedLocation.owner,
|
selectedLocation.owner,
|
||||||
selectedLocation.repo
|
selectedLocation.repo
|
||||||
)} - ${selectedLocation.branch}`;
|
)} - ${selectedLocation.branch}`;
|
||||||
} else if (selectedLocation.type === "MyNotebooks" && useNotebook.getState().isPhoenixNotebooks) {
|
} else if (selectedLocation.type === "MyNotebooks" && userContext.features.phoenix) {
|
||||||
destination = useNotebook.getState().notebookFolderName;
|
destination = useNotebook.getState().notebookFolderName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,14 +105,11 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
|||||||
switch (location.type) {
|
switch (location.type) {
|
||||||
case "MyNotebooks":
|
case "MyNotebooks":
|
||||||
parent = {
|
parent = {
|
||||||
name: useNotebook.getState().notebookFolderName,
|
name: ResourceTreeAdapter.MyNotebooksTitle,
|
||||||
path: useNotebook.getState().notebookBasePath,
|
path: useNotebook.getState().notebookBasePath,
|
||||||
type: NotebookContentItemType.Directory,
|
type: NotebookContentItemType.Directory,
|
||||||
};
|
};
|
||||||
isGithubTree = false;
|
isGithubTree = false;
|
||||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
|
||||||
await container.allocateContainer();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "GitHub":
|
case "GitHub":
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
const onSubmit = async (): Promise<void> => {
|
const onSubmit = async (): Promise<void> => {
|
||||||
const collection = useSelectedNode.getState().findSelectedCollection();
|
const collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
if (!collection || inputCollectionName !== collection.id()) {
|
if (!collection || inputCollectionName !== collection.id()) {
|
||||||
const errorMessage = "Input " + collectionName + " id does not match the selected " + collectionName;
|
const errorMessage = "Input " + collectionName + " name does not match the selected " + collectionName;
|
||||||
setFormError(errorMessage);
|
setFormError(errorMessage);
|
||||||
NotificationConsoleUtils.logConsoleError(
|
NotificationConsoleUtils.logConsoleError(
|
||||||
`Error while deleting ${collectionName} ${collection.id()}: ${errorMessage}`
|
`Error while deleting ${collectionName} ${collection.id()}: ${errorMessage}`
|
||||||
|
|||||||
@@ -369,21 +369,18 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
</div>
|
</div>
|
||||||
<PanelFooterComponent
|
<PanelFooterComponent
|
||||||
buttonLabel="OK"
|
buttonLabel="OK"
|
||||||
isButtonDisabled={false}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="panelFooter"
|
className="panelFooter"
|
||||||
>
|
>
|
||||||
<CustomizedPrimaryButton
|
<CustomizedPrimaryButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
text="OK"
|
text="OK"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
text="OK"
|
text="OK"
|
||||||
theme={
|
theme={
|
||||||
@@ -663,7 +660,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
>
|
>
|
||||||
<CustomizedDefaultButton
|
<CustomizedDefaultButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
@@ -945,7 +941,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
>
|
>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
@@ -1228,7 +1223,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
baseClassName="ms-Button"
|
baseClassName="ms-Button"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { IDropdownOption, IImageProps, Image, Stack, Text } from "@fluentui/react";
|
import { IDropdownOption, IImageProps, Image, Stack, Text } from "@fluentui/react";
|
||||||
import { useBoolean } from "@fluentui/react-hooks";
|
import { useBoolean } from "@fluentui/react-hooks";
|
||||||
import React, { FunctionComponent, useRef, useState } from "react";
|
import React, { FunctionComponent, useState } from "react";
|
||||||
import AddPropertyIcon from "../../../../images/Add-property.svg";
|
import AddPropertyIcon from "../../../../images/Add-property.svg";
|
||||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
||||||
@@ -25,16 +25,19 @@ interface UnwrappedExecuteSprocParam {
|
|||||||
export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPaneProps> = ({
|
export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPaneProps> = ({
|
||||||
storedProcedure,
|
storedProcedure,
|
||||||
}: ExecuteSprocParamsPaneProps): JSX.Element => {
|
}: ExecuteSprocParamsPaneProps): JSX.Element => {
|
||||||
const paramKeyValuesRef = useRef<UnwrappedExecuteSprocParam[]>([{ key: "string", text: "" }]);
|
|
||||||
const partitionValueRef = useRef<string>();
|
|
||||||
const partitionKeyRef = useRef<string>("string");
|
|
||||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||||
const [numberOfParams, setNumberOfParams] = useState<number>(1);
|
|
||||||
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
|
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
|
||||||
|
const [paramKeyValues, setParamKeyValues] = useState<UnwrappedExecuteSprocParam[]>([{ key: "string", text: "" }]);
|
||||||
|
const [partitionValue, setPartitionValue] = useState<string>(); // Defaulting to undefined here is important. It is not the same partition key as ""
|
||||||
|
const [selectedKey, setSelectedKey] = React.useState<IDropdownOption>({ key: "string", text: "" });
|
||||||
const [formError, setFormError] = useState<string>("");
|
const [formError, setFormError] = useState<string>("");
|
||||||
|
|
||||||
|
const onPartitionKeyChange = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
|
||||||
|
setSelectedKey(item);
|
||||||
|
};
|
||||||
|
|
||||||
const validateUnwrappedParams = (): boolean => {
|
const validateUnwrappedParams = (): boolean => {
|
||||||
const unwrappedParams: UnwrappedExecuteSprocParam[] = paramKeyValuesRef.current;
|
const unwrappedParams: UnwrappedExecuteSprocParam[] = paramKeyValues;
|
||||||
for (let i = 0; i < unwrappedParams.length; i++) {
|
for (let i = 0; i < unwrappedParams.length; i++) {
|
||||||
const { key: paramType, text: paramValue } = unwrappedParams[i];
|
const { key: paramType, text: paramValue } = unwrappedParams[i];
|
||||||
if (paramType === "custom" && (paramValue === "" || paramValue === undefined)) {
|
if (paramType === "custom" && (paramValue === "" || paramValue === undefined)) {
|
||||||
@@ -50,9 +53,8 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
|
|||||||
};
|
};
|
||||||
|
|
||||||
const submit = (): void => {
|
const submit = (): void => {
|
||||||
const wrappedSprocParams: UnwrappedExecuteSprocParam[] = paramKeyValuesRef.current;
|
const wrappedSprocParams: UnwrappedExecuteSprocParam[] = paramKeyValues;
|
||||||
const partitionValue: string = partitionValueRef.current;
|
const { key: partitionKey } = selectedKey;
|
||||||
const partitionKey: string = partitionKeyRef.current;
|
|
||||||
if (partitionKey === "custom" && (partitionValue === "" || partitionValue === undefined)) {
|
if (partitionKey === "custom" && (partitionValue === "" || partitionValue === undefined)) {
|
||||||
setInvalidParamError(partitionValue);
|
setInvalidParamError(partitionValue);
|
||||||
return;
|
return;
|
||||||
@@ -76,21 +78,37 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
|
|||||||
};
|
};
|
||||||
|
|
||||||
const deleteParamAtIndex = (indexToRemove: number): void => {
|
const deleteParamAtIndex = (indexToRemove: number): void => {
|
||||||
paramKeyValuesRef.current.splice(indexToRemove, 1);
|
const cloneParamKeyValue = [...paramKeyValues];
|
||||||
setNumberOfParams(numberOfParams - 1);
|
cloneParamKeyValue.splice(indexToRemove, 1);
|
||||||
|
setParamKeyValues(cloneParamKeyValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
const addNewParamAtIndex = (indexToAdd: number): void => {
|
const addNewParamAtIndex = (indexToAdd: number): void => {
|
||||||
paramKeyValuesRef.current.splice(indexToAdd, 0, { key: "string", text: "" });
|
const cloneParamKeyValue = [...paramKeyValues];
|
||||||
setNumberOfParams(numberOfParams + 1);
|
cloneParamKeyValue.splice(indexToAdd, 0, { key: "string", text: "" });
|
||||||
|
setParamKeyValues(cloneParamKeyValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const paramValueChange = (value: string, indexOfInput: number): void => {
|
||||||
|
const cloneParamKeyValue = [...paramKeyValues];
|
||||||
|
cloneParamKeyValue[indexOfInput].text = value;
|
||||||
|
setParamKeyValues(cloneParamKeyValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const paramKeyChange = (
|
||||||
|
_event: React.FormEvent<HTMLDivElement>,
|
||||||
|
selectedParam: IDropdownOption,
|
||||||
|
indexOfParam: number
|
||||||
|
): void => {
|
||||||
|
const cloneParamKeyValue = [...paramKeyValues];
|
||||||
|
cloneParamKeyValue[indexOfParam].key = selectedParam.key.toString();
|
||||||
|
setParamKeyValues(cloneParamKeyValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
const addNewParamAtLastIndex = (): void => {
|
const addNewParamAtLastIndex = (): void => {
|
||||||
paramKeyValuesRef.current.push({
|
const cloneParamKeyValue = [...paramKeyValues];
|
||||||
key: "string",
|
cloneParamKeyValue.splice(cloneParamKeyValue.length, 0, { key: "string", text: "" });
|
||||||
text: "",
|
setParamKeyValues(cloneParamKeyValue);
|
||||||
});
|
|
||||||
setNumberOfParams(numberOfParams + 1);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const props: RightPaneFormProps = {
|
const props: RightPaneFormProps = {
|
||||||
@@ -100,52 +118,46 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
|
|||||||
onSubmit: () => submit(),
|
onSubmit: () => submit(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const getInputParameterComponent = (): JSX.Element[] => {
|
|
||||||
const inputParameters: JSX.Element[] = [];
|
|
||||||
for (let i = 0; i < numberOfParams; i++) {
|
|
||||||
const paramKeyValue = paramKeyValuesRef.current[i];
|
|
||||||
inputParameters.push(
|
|
||||||
<InputParameter
|
|
||||||
key={paramKeyValue.text + i}
|
|
||||||
dropdownLabel={i === 0 ? "Key" : ""}
|
|
||||||
inputParameterTitle={i === 0 ? "Enter input parameters (if any)" : ""}
|
|
||||||
inputLabel={i === 0 ? "Param" : ""}
|
|
||||||
isAddRemoveVisible={true}
|
|
||||||
onDeleteParamKeyPress={() => deleteParamAtIndex(i)}
|
|
||||||
onAddNewParamKeyPress={() => addNewParamAtIndex(i + 1)}
|
|
||||||
onParamValueChange={(_event, newInput?: string) => (paramKeyValuesRef.current[i].text = newInput)}
|
|
||||||
onParamKeyChange={(_event, selectedParam: IDropdownOption) =>
|
|
||||||
(paramKeyValuesRef.current[i].key = selectedParam.key.toString())
|
|
||||||
}
|
|
||||||
paramValue={paramKeyValue.text}
|
|
||||||
selectedKey={paramKeyValue.key}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return inputParameters;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RightPaneForm {...props}>
|
<RightPaneForm {...props}>
|
||||||
<div className="panelMainContent">
|
<div className="panelFormWrapper">
|
||||||
<InputParameter
|
<div className="panelMainContent">
|
||||||
dropdownLabel="Key"
|
<InputParameter
|
||||||
inputParameterTitle="Partition key value"
|
dropdownLabel="Key"
|
||||||
inputLabel="Value"
|
inputParameterTitle="Partition key value"
|
||||||
isAddRemoveVisible={false}
|
inputLabel="Value"
|
||||||
onParamValueChange={(_event, newInput?: string) => (partitionValueRef.current = newInput)}
|
isAddRemoveVisible={false}
|
||||||
onParamKeyChange={(_event: React.FormEvent<HTMLDivElement>, item: IDropdownOption) =>
|
onParamValueChange={(_event, newInput?: string) => {
|
||||||
(partitionKeyRef.current = item.key.toString())
|
setPartitionValue(newInput);
|
||||||
}
|
}}
|
||||||
paramValue={partitionValueRef.current}
|
onParamKeyChange={onPartitionKeyChange}
|
||||||
selectedKey={partitionKeyRef.current}
|
paramValue={partitionValue}
|
||||||
/>
|
selectedKey={selectedKey.key}
|
||||||
{getInputParameterComponent()}
|
/>
|
||||||
<Stack horizontal onClick={() => addNewParamAtLastIndex()} tabIndex={0}>
|
{paramKeyValues.map((paramKeyValue, index) => (
|
||||||
<Image {...imageProps} src={AddPropertyIcon} alt="Add param" />
|
<InputParameter
|
||||||
<Text className="addNewParamStyle">Add New Param</Text>
|
key={paramKeyValue && paramKeyValue.text + index}
|
||||||
</Stack>
|
dropdownLabel={!index && "Key"}
|
||||||
|
inputParameterTitle={!index && "Enter input parameters (if any)"}
|
||||||
|
inputLabel={!index && "Param"}
|
||||||
|
isAddRemoveVisible={true}
|
||||||
|
onDeleteParamKeyPress={() => deleteParamAtIndex(index)}
|
||||||
|
onAddNewParamKeyPress={() => addNewParamAtIndex(index + 1)}
|
||||||
|
onParamValueChange={(event, newInput?: string) => {
|
||||||
|
paramValueChange(newInput, index);
|
||||||
|
}}
|
||||||
|
onParamKeyChange={(event: React.FormEvent<HTMLDivElement>, selectedParam: IDropdownOption) => {
|
||||||
|
paramKeyChange(event, selectedParam, index);
|
||||||
|
}}
|
||||||
|
paramValue={paramKeyValue && paramKeyValue.text}
|
||||||
|
selectedKey={paramKeyValue && paramKeyValue.key}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<Stack horizontal onClick={addNewParamAtLastIndex} tabIndex={0}>
|
||||||
|
<Image {...imageProps} src={AddPropertyIcon} alt="Add param" />
|
||||||
|
<Text className="addNewParamStyle">Add New Param</Text>
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</RightPaneForm>
|
</RightPaneForm>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export const InputParameter: FunctionComponent<InputParameterProps> = ({
|
|||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
label={dropdownLabel && dropdownLabel}
|
label={dropdownLabel && dropdownLabel}
|
||||||
defaultSelectedKey={selectedKey}
|
selectedKey={selectedKey}
|
||||||
onChange={onParamKeyChange}
|
onChange={onParamKeyChange}
|
||||||
options={options}
|
options={options}
|
||||||
styles={dropdownStyles}
|
styles={dropdownStyles}
|
||||||
@@ -64,9 +64,8 @@ export const InputParameter: FunctionComponent<InputParameterProps> = ({
|
|||||||
<TextField
|
<TextField
|
||||||
label={inputLabel && inputLabel}
|
label={inputLabel && inputLabel}
|
||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
defaultValue={paramValue}
|
value={paramValue}
|
||||||
onChange={onParamValueChange}
|
onChange={onParamValueChange}
|
||||||
tabIndex={0}
|
|
||||||
/>
|
/>
|
||||||
{isAddRemoveVisible && (
|
{isAddRemoveVisible && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -35,9 +35,6 @@ interface IGitHubReposPanelState {
|
|||||||
}
|
}
|
||||||
export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IGitHubReposPanelState> {
|
export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IGitHubReposPanelState> {
|
||||||
private static readonly PageSize = 30;
|
private static readonly PageSize = 30;
|
||||||
private static readonly MasterBranchName = "master";
|
|
||||||
private static readonly MainBranchName = "main";
|
|
||||||
|
|
||||||
private isAddedRepo = false;
|
private isAddedRepo = false;
|
||||||
private gitHubClient: GitHubClient;
|
private gitHubClient: GitHubClient;
|
||||||
private junoClient: JunoClient;
|
private junoClient: JunoClient;
|
||||||
@@ -119,8 +116,6 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
|
|||||||
if (response.status !== HttpStatusCodes.OK) {
|
if (response.status !== HttpStatusCodes.OK) {
|
||||||
throw new Error(`Received HTTP ${response.status} when saving pinned repos`);
|
throw new Error(`Received HTTP ${response.status} when saving pinned repos`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.explorer.notebookManager?.refreshPinnedRepos();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, "GitHubReposPane/submit", "Failed to save pinned repos");
|
handleError(error, "GitHubReposPane/submit", "Failed to save pinned repos");
|
||||||
}
|
}
|
||||||
@@ -212,14 +207,6 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
|
|||||||
if (response.data) {
|
if (response.data) {
|
||||||
branchesProps.branches = branchesProps.branches.concat(response.data);
|
branchesProps.branches = branchesProps.branches.concat(response.data);
|
||||||
branchesProps.lastPageInfo = response.pageInfo;
|
branchesProps.lastPageInfo = response.pageInfo;
|
||||||
branchesProps.defaultBranchName = branchesProps.branches[0].name;
|
|
||||||
const defaultbranchName = branchesProps.branches.find(
|
|
||||||
(branch) =>
|
|
||||||
branch.name === GitHubReposPanel.MasterBranchName || branch.name === GitHubReposPanel.MainBranchName
|
|
||||||
)?.name;
|
|
||||||
if (defaultbranchName) {
|
|
||||||
branchesProps.defaultBranchName = defaultbranchName;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, "GitHubReposPane/loadMoreBranches", "Failed to fetch branches");
|
handleError(error, "GitHubReposPane/loadMoreBranches", "Failed to fetch branches");
|
||||||
@@ -311,17 +298,6 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
|
|||||||
const existingRepo = this.pinnedReposProps.repos.find((repo) => repo.key === item.key);
|
const existingRepo = this.pinnedReposProps.repos.find((repo) => repo.key === item.key);
|
||||||
if (existingRepo) {
|
if (existingRepo) {
|
||||||
existingRepo.branches = item.branches;
|
existingRepo.branches = item.branches;
|
||||||
this.setState({
|
|
||||||
gitHubReposState: {
|
|
||||||
...this.state.gitHubReposState,
|
|
||||||
reposListProps: {
|
|
||||||
...this.state.gitHubReposState.reposListProps,
|
|
||||||
pinnedReposProps: {
|
|
||||||
repos: this.pinnedReposProps.repos,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
this.pinnedReposProps.repos = [...this.pinnedReposProps.repos, item];
|
this.pinnedReposProps.repos = [...this.pinnedReposProps.repos, item];
|
||||||
}
|
}
|
||||||
@@ -398,7 +374,6 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
|
|||||||
lastPageInfo: undefined,
|
lastPageInfo: undefined,
|
||||||
hasMore: true,
|
hasMore: true,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
defaultBranchName: undefined,
|
|
||||||
loadMore: (): Promise<void> => this.loadMoreBranches(item.repo),
|
loadMore: (): Promise<void> => this.loadMoreBranches(item.repo),
|
||||||
};
|
};
|
||||||
this.loadMoreBranches(item.repo);
|
this.loadMoreBranches(item.repo);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -3,20 +3,12 @@ import React from "react";
|
|||||||
|
|
||||||
export interface PanelFooterProps {
|
export interface PanelFooterProps {
|
||||||
buttonLabel: string;
|
buttonLabel: string;
|
||||||
isButtonDisabled?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PanelFooterComponent: React.FunctionComponent<PanelFooterProps> = ({
|
export const PanelFooterComponent: React.FunctionComponent<PanelFooterProps> = ({
|
||||||
buttonLabel,
|
buttonLabel,
|
||||||
isButtonDisabled,
|
|
||||||
}: PanelFooterProps): JSX.Element => (
|
}: PanelFooterProps): JSX.Element => (
|
||||||
<div className="panelFooter">
|
<div className="panelFooter">
|
||||||
<PrimaryButton
|
<PrimaryButton type="submit" id="sidePanelOkButton" text={buttonLabel} ariaLabel={buttonLabel} />
|
||||||
type="submit"
|
|
||||||
id="sidePanelOkButton"
|
|
||||||
text={buttonLabel}
|
|
||||||
ariaLabel={buttonLabel}
|
|
||||||
disabled={!!isButtonDisabled}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ export interface RightPaneFormProps {
|
|||||||
onSubmit: () => void;
|
onSubmit: () => void;
|
||||||
submitButtonText: string;
|
submitButtonText: string;
|
||||||
isSubmitButtonHidden?: boolean;
|
isSubmitButtonHidden?: boolean;
|
||||||
isSubmitButtonDisabled?: boolean;
|
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,7 +18,6 @@ export const RightPaneForm: FunctionComponent<RightPaneFormProps> = ({
|
|||||||
onSubmit,
|
onSubmit,
|
||||||
submitButtonText,
|
submitButtonText,
|
||||||
isSubmitButtonHidden = false,
|
isSubmitButtonHidden = false,
|
||||||
isSubmitButtonDisabled = false,
|
|
||||||
children,
|
children,
|
||||||
}: RightPaneFormProps) => {
|
}: RightPaneFormProps) => {
|
||||||
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
@@ -32,9 +30,7 @@ export const RightPaneForm: FunctionComponent<RightPaneFormProps> = ({
|
|||||||
<form className="panelFormWrapper" onSubmit={handleOnSubmit}>
|
<form className="panelFormWrapper" onSubmit={handleOnSubmit}>
|
||||||
{formError && <PanelInfoErrorComponent messageType="error" message={formError} showErrorDetails={true} />}
|
{formError && <PanelInfoErrorComponent messageType="error" message={formError} showErrorDetails={true} />}
|
||||||
{children}
|
{children}
|
||||||
{!isSubmitButtonHidden && (
|
{!isSubmitButtonHidden && <PanelFooterComponent buttonLabel={submitButtonText} />}
|
||||||
<PanelFooterComponent buttonLabel={submitButtonText} isButtonDisabled={isSubmitButtonDisabled} />
|
|
||||||
)}
|
|
||||||
</form>
|
</form>
|
||||||
{isExecuting && <PanelLoadingScreen />}
|
{isExecuting && <PanelLoadingScreen />}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -14,21 +14,18 @@ exports[`Right Pane Form should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<PanelFooterComponent
|
<PanelFooterComponent
|
||||||
buttonLabel="Load"
|
buttonLabel="Load"
|
||||||
isButtonDisabled={false}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="panelFooter"
|
className="panelFooter"
|
||||||
>
|
>
|
||||||
<CustomizedPrimaryButton
|
<CustomizedPrimaryButton
|
||||||
ariaLabel="Load"
|
ariaLabel="Load"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
text="Load"
|
text="Load"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
ariaLabel="Load"
|
ariaLabel="Load"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
text="Load"
|
text="Load"
|
||||||
theme={
|
theme={
|
||||||
@@ -308,7 +305,6 @@ exports[`Right Pane Form should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<CustomizedDefaultButton
|
<CustomizedDefaultButton
|
||||||
ariaLabel="Load"
|
ariaLabel="Load"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
@@ -590,7 +586,6 @@ exports[`Right Pane Form should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
ariaLabel="Load"
|
ariaLabel="Load"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
@@ -873,7 +868,6 @@ exports[`Right Pane Form should render Default properly 1`] = `
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
ariaLabel="Load"
|
ariaLabel="Load"
|
||||||
baseClassName="ms-Button"
|
baseClassName="ms-Button"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
|
|||||||
@@ -113,50 +113,20 @@ export const SettingsPane: FunctionComponent = () => {
|
|||||||
const handleOnPageOptionChange = (ev: React.FormEvent<HTMLInputElement>, option: IChoiceGroupOption): void => {
|
const handleOnPageOptionChange = (ev: React.FormEvent<HTMLInputElement>, option: IChoiceGroupOption): void => {
|
||||||
setPageOption(option.key);
|
setPageOption(option.key);
|
||||||
};
|
};
|
||||||
|
|
||||||
const choiceButtonStyles = {
|
|
||||||
root: {
|
|
||||||
clear: "both",
|
|
||||||
},
|
|
||||||
flexContainer: [
|
|
||||||
{
|
|
||||||
selectors: {
|
|
||||||
".ms-ChoiceFieldGroup root-133": {
|
|
||||||
clear: "both",
|
|
||||||
},
|
|
||||||
".ms-ChoiceField-wrapper label": {
|
|
||||||
fontSize: 12,
|
|
||||||
paddingTop: 0,
|
|
||||||
},
|
|
||||||
".ms-ChoiceField": {
|
|
||||||
marginTop: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<RightPaneForm {...genericPaneProps}>
|
<RightPaneForm {...genericPaneProps}>
|
||||||
<div className="paneMainContent">
|
<div className="paneMainContent">
|
||||||
{shouldShowQueryPageOptions && (
|
{shouldShowQueryPageOptions && (
|
||||||
<div className="settingsSection">
|
<div className="settingsSection">
|
||||||
<div className="settingsSectionPart">
|
<div className="settingsSectionPart pageOptionsPart">
|
||||||
<fieldset>
|
<div className="settingsSectionLabel">
|
||||||
<legend id="pageOptions" className="settingsSectionLabel legendLabel">
|
Page options
|
||||||
Page Options
|
|
||||||
</legend>
|
|
||||||
<InfoTooltip>
|
<InfoTooltip>
|
||||||
Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as many
|
Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as many
|
||||||
query results per page.
|
query results per page.
|
||||||
</InfoTooltip>
|
</InfoTooltip>
|
||||||
<ChoiceGroup
|
</div>
|
||||||
ariaLabelledBy="pageOptions"
|
<ChoiceGroup selectedKey={pageOption} options={pageOptionList} onChange={handleOnPageOptionChange} />
|
||||||
selectedKey={pageOption}
|
|
||||||
options={pageOptionList}
|
|
||||||
styles={choiceButtonStyles}
|
|
||||||
onChange={handleOnPageOptionChange}
|
|
||||||
/>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="tabs settingsSectionPart">
|
<div className="tabs settingsSectionPart">
|
||||||
{isCustomPageOptionSelected() && (
|
{isCustomPageOptionSelected() && (
|
||||||
|
|||||||
@@ -14,59 +14,32 @@ exports[`Settings Pane should render Default properly 1`] = `
|
|||||||
className="settingsSection"
|
className="settingsSection"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="settingsSectionPart"
|
className="settingsSectionPart pageOptionsPart"
|
||||||
>
|
>
|
||||||
<fieldset>
|
<div
|
||||||
<legend
|
className="settingsSectionLabel"
|
||||||
className="settingsSectionLabel legendLabel"
|
>
|
||||||
id="pageOptions"
|
Page options
|
||||||
>
|
|
||||||
Page Options
|
|
||||||
</legend>
|
|
||||||
<InfoTooltip>
|
<InfoTooltip>
|
||||||
Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as many query results per page.
|
Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as many query results per page.
|
||||||
</InfoTooltip>
|
</InfoTooltip>
|
||||||
<StyledChoiceGroup
|
</div>
|
||||||
ariaLabelledBy="pageOptions"
|
<StyledChoiceGroup
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
options={
|
options={
|
||||||
Array [
|
Array [
|
||||||
Object {
|
|
||||||
"key": "custom",
|
|
||||||
"text": "Custom",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"key": "unlimited",
|
|
||||||
"text": "Unlimited",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
selectedKey="custom"
|
|
||||||
styles={
|
|
||||||
Object {
|
Object {
|
||||||
"flexContainer": Array [
|
"key": "custom",
|
||||||
Object {
|
"text": "Custom",
|
||||||
"selectors": Object {
|
},
|
||||||
".ms-ChoiceField": Object {
|
Object {
|
||||||
"marginTop": 0,
|
"key": "unlimited",
|
||||||
},
|
"text": "Unlimited",
|
||||||
".ms-ChoiceField-wrapper label": Object {
|
},
|
||||||
"fontSize": 12,
|
]
|
||||||
"paddingTop": 0,
|
}
|
||||||
},
|
selectedKey="custom"
|
||||||
".ms-ChoiceFieldGroup root-133": Object {
|
/>
|
||||||
"clear": "both",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"root": Object {
|
|
||||||
"clear": "both",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="tabs settingsSectionPart"
|
className="tabs settingsSectionPart"
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { PrimaryButton } from "@fluentui/react";
|
||||||
|
import { mount } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
|
import Explorer from "../../Explorer";
|
||||||
|
import { SetupNoteBooksPanel } from "./SetupNotebooksPanel";
|
||||||
|
|
||||||
|
describe("Setup Notebooks Panel", () => {
|
||||||
|
it("should render Default properly", () => {
|
||||||
|
const fakeExplorer = {} as Explorer;
|
||||||
|
const props = {
|
||||||
|
explorer: fakeExplorer,
|
||||||
|
closePanel: (): void => undefined,
|
||||||
|
openNotificationConsole: (): void => undefined,
|
||||||
|
panelTitle: "",
|
||||||
|
panelDescription: "",
|
||||||
|
};
|
||||||
|
const wrapper = mount(<SetupNoteBooksPanel {...props} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render button", () => {
|
||||||
|
const fakeExplorer = {} as Explorer;
|
||||||
|
const props = {
|
||||||
|
explorer: fakeExplorer,
|
||||||
|
closePanel: (): void => undefined,
|
||||||
|
openNotificationConsole: (): void => undefined,
|
||||||
|
panelTitle: "",
|
||||||
|
panelDescription: "",
|
||||||
|
};
|
||||||
|
const wrapper = mount(<SetupNoteBooksPanel {...props} />);
|
||||||
|
const button = wrapper.find("PrimaryButton").first();
|
||||||
|
expect(button).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Button onClick should call onCompleteSetup", () => {
|
||||||
|
const onCompleteSetupClick = jest.fn();
|
||||||
|
const wrapper = mount(<PrimaryButton onClick={onCompleteSetupClick} />);
|
||||||
|
wrapper.find("button").simulate("click");
|
||||||
|
|
||||||
|
expect(onCompleteSetupClick).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Button onKeyPress should call onCompleteSetupKeyPress", () => {
|
||||||
|
const onCompleteSetupKeyPress = jest.fn();
|
||||||
|
const wrapper = mount(<PrimaryButton onKeyPress={onCompleteSetupKeyPress} />);
|
||||||
|
wrapper.find("button").simulate("keypress");
|
||||||
|
|
||||||
|
expect(onCompleteSetupKeyPress).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
121
src/Explorer/Panes/SetupNotebooksPanel/SetupNotebooksPanel.tsx
Normal file
121
src/Explorer/Panes/SetupNotebooksPanel/SetupNotebooksPanel.tsx
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import { PrimaryButton } from "@fluentui/react";
|
||||||
|
import { useBoolean } from "@fluentui/react-hooks";
|
||||||
|
import React, { FunctionComponent, KeyboardEvent, useState } from "react";
|
||||||
|
import { Areas, NormalizedEventKey } from "../../../Common/Constants";
|
||||||
|
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
|
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import { userContext } from "../../../UserContext";
|
||||||
|
import { createOrUpdate } from "../../../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
|
||||||
|
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||||
|
import Explorer from "../../Explorer";
|
||||||
|
import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
|
||||||
|
import { PanelLoadingScreen } from "../PanelLoadingScreen";
|
||||||
|
interface SetupNoteBooksPanelProps {
|
||||||
|
explorer: Explorer;
|
||||||
|
panelTitle: string;
|
||||||
|
panelDescription: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SetupNoteBooksPanel: FunctionComponent<SetupNoteBooksPanelProps> = ({
|
||||||
|
explorer,
|
||||||
|
panelTitle,
|
||||||
|
panelDescription,
|
||||||
|
}: SetupNoteBooksPanelProps): JSX.Element => {
|
||||||
|
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||||
|
|
||||||
|
const description = panelDescription;
|
||||||
|
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
|
||||||
|
const [errorMessage, setErrorMessage] = useState<string>("");
|
||||||
|
const [showErrorDetails, setShowErrorDetails] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const onCompleteSetupClick = async () => {
|
||||||
|
await setupNotebookWorkspace();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCompleteSetupKeyPress = async (event: KeyboardEvent<HTMLButtonElement>) => {
|
||||||
|
if (event.key === " " || event.key === NormalizedEventKey.Enter) {
|
||||||
|
await setupNotebookWorkspace();
|
||||||
|
event.stopPropagation();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setupNotebookWorkspace = async (): Promise<void> => {
|
||||||
|
if (!explorer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const startKey: number = TelemetryProcessor.traceStart(Action.CreateNotebookWorkspace, {
|
||||||
|
dataExplorerArea: Areas.ContextualPane,
|
||||||
|
paneTitle: panelTitle,
|
||||||
|
});
|
||||||
|
|
||||||
|
const clear = NotificationConsoleUtils.logConsoleProgress("Creating a new default notebook workspace");
|
||||||
|
|
||||||
|
try {
|
||||||
|
setLoadingTrue();
|
||||||
|
await createOrUpdate(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
"default"
|
||||||
|
);
|
||||||
|
explorer.refreshExplorer();
|
||||||
|
|
||||||
|
closeSidePanel();
|
||||||
|
|
||||||
|
TelemetryProcessor.traceSuccess(
|
||||||
|
Action.CreateNotebookWorkspace,
|
||||||
|
{
|
||||||
|
dataExplorerArea: Areas.ContextualPane,
|
||||||
|
paneTitle: panelTitle,
|
||||||
|
},
|
||||||
|
startKey
|
||||||
|
);
|
||||||
|
NotificationConsoleUtils.logConsoleInfo("Successfully created a default notebook workspace for the account");
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = getErrorMessage(error);
|
||||||
|
TelemetryProcessor.traceFailure(
|
||||||
|
Action.CreateNotebookWorkspace,
|
||||||
|
{
|
||||||
|
dataExplorerArea: Areas.ContextualPane,
|
||||||
|
paneTitle: panelTitle,
|
||||||
|
error: errorMessage,
|
||||||
|
errorStack: getErrorStack(error),
|
||||||
|
},
|
||||||
|
startKey
|
||||||
|
);
|
||||||
|
setErrorMessage(`Failed to setup a default notebook workspace: ${errorMessage}`);
|
||||||
|
setShowErrorDetails(true);
|
||||||
|
NotificationConsoleUtils.logConsoleError(`Failed to create a default notebook workspace: ${errorMessage}`);
|
||||||
|
} finally {
|
||||||
|
setLoadingFalse();
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className="panelFormWrapper">
|
||||||
|
{errorMessage && (
|
||||||
|
<PanelInfoErrorComponent message={errorMessage} messageType="error" showErrorDetails={showErrorDetails} />
|
||||||
|
)}
|
||||||
|
<div className="panelMainContent">
|
||||||
|
<div className="pkPadding">
|
||||||
|
<div>{description}</div>
|
||||||
|
<PrimaryButton
|
||||||
|
id="completeSetupBtn"
|
||||||
|
className="btncreatecoll1 btnSetupQueries"
|
||||||
|
text="Complete Setup"
|
||||||
|
onClick={onCompleteSetupClick}
|
||||||
|
onKeyPress={onCompleteSetupKeyPress}
|
||||||
|
aria-label="Complete setup"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{isLoading && <PanelLoadingScreen />}
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1262,21 +1262,18 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<PanelFooterComponent
|
<PanelFooterComponent
|
||||||
buttonLabel="OK"
|
buttonLabel="OK"
|
||||||
isButtonDisabled={false}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="panelFooter"
|
className="panelFooter"
|
||||||
>
|
>
|
||||||
<CustomizedPrimaryButton
|
<CustomizedPrimaryButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
text="OK"
|
text="OK"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
text="OK"
|
text="OK"
|
||||||
theme={
|
theme={
|
||||||
@@ -1556,7 +1553,6 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<CustomizedDefaultButton
|
<CustomizedDefaultButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
@@ -1838,7 +1834,6 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
@@ -2121,7 +2116,6 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
baseClassName="ms-Button"
|
baseClassName="ms-Button"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ const {
|
|||||||
Ascii,
|
Ascii,
|
||||||
Bigint,
|
Bigint,
|
||||||
Blob,
|
Blob,
|
||||||
Date: DateType,
|
|
||||||
Decimal,
|
Decimal,
|
||||||
Float,
|
Float,
|
||||||
Int,
|
Int,
|
||||||
@@ -34,61 +33,6 @@ const {
|
|||||||
Inet,
|
Inet,
|
||||||
Smallint,
|
Smallint,
|
||||||
Tinyint,
|
Tinyint,
|
||||||
Timestamp,
|
|
||||||
// List
|
|
||||||
List_Ascii,
|
|
||||||
List_Bigint,
|
|
||||||
List_Blob,
|
|
||||||
List_Boolean,
|
|
||||||
List_Date,
|
|
||||||
List_Decimal,
|
|
||||||
List_Double,
|
|
||||||
List_Float,
|
|
||||||
List_Int,
|
|
||||||
List_Text,
|
|
||||||
List_Timestamp,
|
|
||||||
List_Uuid,
|
|
||||||
List_Varchar,
|
|
||||||
List_Varint,
|
|
||||||
List_Inet,
|
|
||||||
List_Smallint,
|
|
||||||
List_Tinyint,
|
|
||||||
// Map
|
|
||||||
Map_Ascii,
|
|
||||||
Map_Bigint,
|
|
||||||
Map_Blob,
|
|
||||||
Map_Boolean,
|
|
||||||
Map_Date,
|
|
||||||
Map_Decimal,
|
|
||||||
Map_Double,
|
|
||||||
Map_Float,
|
|
||||||
Map_Int,
|
|
||||||
Map_Text,
|
|
||||||
Map_Timestamp,
|
|
||||||
Map_Uuid,
|
|
||||||
Map_Varchar,
|
|
||||||
Map_Varint,
|
|
||||||
Map_Inet,
|
|
||||||
Map_Smallint,
|
|
||||||
Map_Tinyint,
|
|
||||||
// Set
|
|
||||||
Set_Ascii,
|
|
||||||
Set_Bigint,
|
|
||||||
Set_Blob,
|
|
||||||
Set_Boolean,
|
|
||||||
Set_Date,
|
|
||||||
Set_Decimal,
|
|
||||||
Set_Double,
|
|
||||||
Set_Float,
|
|
||||||
Set_Int,
|
|
||||||
Set_Text,
|
|
||||||
Set_Timestamp,
|
|
||||||
Set_Uuid,
|
|
||||||
Set_Varchar,
|
|
||||||
Set_Varint,
|
|
||||||
Set_Inet,
|
|
||||||
Set_Smallint,
|
|
||||||
Set_Tinyint,
|
|
||||||
} = TableConstants.CassandraType;
|
} = TableConstants.CassandraType;
|
||||||
export const cassandraOptions = [
|
export const cassandraOptions = [
|
||||||
{ key: Text, text: Text },
|
{ key: Text, text: Text },
|
||||||
@@ -96,7 +40,6 @@ export const cassandraOptions = [
|
|||||||
{ key: Bigint, text: Bigint },
|
{ key: Bigint, text: Bigint },
|
||||||
{ key: Blob, text: Blob },
|
{ key: Blob, text: Blob },
|
||||||
{ key: Boolean, text: Boolean },
|
{ key: Boolean, text: Boolean },
|
||||||
{ key: DateType, text: DateType },
|
|
||||||
{ key: Decimal, text: Decimal },
|
{ key: Decimal, text: Decimal },
|
||||||
{ key: Double, text: Double },
|
{ key: Double, text: Double },
|
||||||
{ key: Float, text: Float },
|
{ key: Float, text: Float },
|
||||||
@@ -107,61 +50,6 @@ export const cassandraOptions = [
|
|||||||
{ key: Inet, text: Inet },
|
{ key: Inet, text: Inet },
|
||||||
{ key: Smallint, text: Smallint },
|
{ key: Smallint, text: Smallint },
|
||||||
{ key: Tinyint, text: Tinyint },
|
{ key: Tinyint, text: Tinyint },
|
||||||
{ key: Timestamp, text: Timestamp },
|
|
||||||
// List
|
|
||||||
{ key: List_Ascii, text: List_Ascii },
|
|
||||||
{ key: List_Bigint, text: List_Bigint },
|
|
||||||
{ key: List_Blob, text: List_Blob },
|
|
||||||
{ key: List_Boolean, text: List_Boolean },
|
|
||||||
{ key: List_Date, text: List_Date },
|
|
||||||
{ key: List_Decimal, text: List_Decimal },
|
|
||||||
{ key: List_Double, text: List_Double },
|
|
||||||
{ key: List_Float, text: List_Float },
|
|
||||||
{ key: List_Int, text: List_Int },
|
|
||||||
{ key: List_Text, text: List_Text },
|
|
||||||
{ key: List_Timestamp, text: List_Timestamp },
|
|
||||||
{ key: List_Uuid, text: List_Uuid },
|
|
||||||
{ key: List_Varchar, text: List_Varchar },
|
|
||||||
{ key: List_Varint, text: List_Varint },
|
|
||||||
{ key: List_Inet, text: List_Inet },
|
|
||||||
{ key: List_Smallint, text: List_Smallint },
|
|
||||||
{ key: List_Tinyint, text: List_Tinyint },
|
|
||||||
// Map
|
|
||||||
{ key: Map_Ascii, text: Map_Ascii },
|
|
||||||
{ key: Map_Bigint, text: Map_Bigint },
|
|
||||||
{ key: Map_Blob, text: Map_Blob },
|
|
||||||
{ key: Map_Boolean, text: Map_Boolean },
|
|
||||||
{ key: Map_Date, text: Map_Date },
|
|
||||||
{ key: Map_Decimal, text: Map_Decimal },
|
|
||||||
{ key: Map_Double, text: Map_Double },
|
|
||||||
{ key: Map_Float, text: Map_Float },
|
|
||||||
{ key: Map_Int, text: Map_Int },
|
|
||||||
{ key: Map_Text, text: Map_Text },
|
|
||||||
{ key: Map_Timestamp, text: Map_Timestamp },
|
|
||||||
{ key: Map_Uuid, text: Map_Uuid },
|
|
||||||
{ key: Map_Varchar, text: Map_Varchar },
|
|
||||||
{ key: Map_Varint, text: Map_Varint },
|
|
||||||
{ key: Map_Inet, text: Map_Inet },
|
|
||||||
{ key: Map_Smallint, text: Map_Smallint },
|
|
||||||
{ key: Map_Tinyint, text: Map_Tinyint },
|
|
||||||
// Set
|
|
||||||
{ key: Set_Ascii, text: Set_Ascii },
|
|
||||||
{ key: Set_Bigint, text: Set_Bigint },
|
|
||||||
{ key: Set_Blob, text: Set_Blob },
|
|
||||||
{ key: Set_Boolean, text: Set_Boolean },
|
|
||||||
{ key: Set_Date, text: Set_Date },
|
|
||||||
{ key: Set_Decimal, text: Set_Decimal },
|
|
||||||
{ key: Set_Double, text: Set_Double },
|
|
||||||
{ key: Set_Float, text: Set_Float },
|
|
||||||
{ key: Set_Int, text: Set_Int },
|
|
||||||
{ key: Set_Text, text: Set_Text },
|
|
||||||
{ key: Set_Timestamp, text: Set_Timestamp },
|
|
||||||
{ key: Set_Uuid, text: Set_Uuid },
|
|
||||||
{ key: Set_Varchar, text: Set_Varchar },
|
|
||||||
{ key: Set_Varint, text: Set_Varint },
|
|
||||||
{ key: Set_Inet, text: Set_Inet },
|
|
||||||
{ key: Set_Smallint, text: Set_Smallint },
|
|
||||||
{ key: Set_Tinyint, text: Set_Tinyint },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export const imageProps: IImageProps = {
|
export const imageProps: IImageProps = {
|
||||||
|
|||||||
@@ -356,21 +356,18 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<PanelFooterComponent
|
<PanelFooterComponent
|
||||||
buttonLabel="Add Entity"
|
buttonLabel="Add Entity"
|
||||||
isButtonDisabled={false}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="panelFooter"
|
className="panelFooter"
|
||||||
>
|
>
|
||||||
<CustomizedPrimaryButton
|
<CustomizedPrimaryButton
|
||||||
ariaLabel="Add Entity"
|
ariaLabel="Add Entity"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
text="Add Entity"
|
text="Add Entity"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
ariaLabel="Add Entity"
|
ariaLabel="Add Entity"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
text="Add Entity"
|
text="Add Entity"
|
||||||
theme={
|
theme={
|
||||||
@@ -650,7 +647,6 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<CustomizedDefaultButton
|
<CustomizedDefaultButton
|
||||||
ariaLabel="Add Entity"
|
ariaLabel="Add Entity"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
@@ -932,7 +928,6 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
ariaLabel="Add Entity"
|
ariaLabel="Add Entity"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
@@ -1215,7 +1210,6 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
ariaLabel="Add Entity"
|
ariaLabel="Add Entity"
|
||||||
baseClassName="ms-Button"
|
baseClassName="ms-Button"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
|
|||||||
@@ -357,21 +357,18 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<PanelFooterComponent
|
<PanelFooterComponent
|
||||||
buttonLabel="Update"
|
buttonLabel="Update"
|
||||||
isButtonDisabled={false}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="panelFooter"
|
className="panelFooter"
|
||||||
>
|
>
|
||||||
<CustomizedPrimaryButton
|
<CustomizedPrimaryButton
|
||||||
ariaLabel="Update"
|
ariaLabel="Update"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
text="Update"
|
text="Update"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
ariaLabel="Update"
|
ariaLabel="Update"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
text="Update"
|
text="Update"
|
||||||
theme={
|
theme={
|
||||||
@@ -651,7 +648,6 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<CustomizedDefaultButton
|
<CustomizedDefaultButton
|
||||||
ariaLabel="Update"
|
ariaLabel="Update"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
@@ -933,7 +929,6 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
ariaLabel="Update"
|
ariaLabel="Update"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
@@ -1216,7 +1211,6 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
ariaLabel="Update"
|
ariaLabel="Update"
|
||||||
baseClassName="ms-Button"
|
baseClassName="ms-Button"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
|
|||||||
@@ -1041,21 +1041,18 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<PanelFooterComponent
|
<PanelFooterComponent
|
||||||
buttonLabel="OK"
|
buttonLabel="OK"
|
||||||
isButtonDisabled={false}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="panelFooter"
|
className="panelFooter"
|
||||||
>
|
>
|
||||||
<CustomizedPrimaryButton
|
<CustomizedPrimaryButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
text="OK"
|
text="OK"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
text="OK"
|
text="OK"
|
||||||
theme={
|
theme={
|
||||||
@@ -1335,7 +1332,6 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
|||||||
>
|
>
|
||||||
<CustomizedDefaultButton
|
<CustomizedDefaultButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
@@ -1617,7 +1613,6 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
|||||||
>
|
>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
@@ -1900,7 +1895,6 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
baseClassName="ms-Button"
|
baseClassName="ms-Button"
|
||||||
disabled={false}
|
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
primary={true}
|
primary={true}
|
||||||
|
|||||||
@@ -84,7 +84,9 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
const mainItems = this.createMainItems();
|
const mainItems = this.createMainItems();
|
||||||
const commonTaskItems = this.createCommonTaskItems();
|
const commonTaskItems = this.createCommonTaskItems();
|
||||||
let recentItems = this.createRecentItems();
|
let recentItems = this.createRecentItems();
|
||||||
recentItems = recentItems.filter((item) => item.description !== "Notebook");
|
if (userContext.features.notebooksTemporarilyDown) {
|
||||||
|
recentItems = recentItems.filter((item) => item.description !== "Notebook");
|
||||||
|
}
|
||||||
|
|
||||||
const tipsItems = this.createTipsItems();
|
const tipsItems = this.createTipsItems();
|
||||||
const onClearRecent = this.clearMostRecent;
|
const onClearRecent = this.clearMostRecent;
|
||||||
@@ -221,7 +223,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
if (useNotebook.getState().isNotebookEnabled && !userContext.features.notebooksTemporarilyDown) {
|
||||||
heroes.push({
|
heroes.push({
|
||||||
iconSrc: NewNotebookIcon,
|
iconSrc: NewNotebookIcon,
|
||||||
title: "New Notebook",
|
title: "New Notebook",
|
||||||
@@ -305,24 +307,23 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
iconSrc: AddDatabaseIcon,
|
iconSrc: AddDatabaseIcon,
|
||||||
title: "New " + getDatabaseName(),
|
title: "New " + getDatabaseName(),
|
||||||
description: undefined,
|
description: undefined,
|
||||||
onClick: async () => {
|
onClick: () => this.openAddDatabasePanel(),
|
||||||
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
|
||||||
if (throughputCap && throughputCap !== -1) {
|
|
||||||
await useDatabases.getState().loadAllOffers();
|
|
||||||
}
|
|
||||||
useSidePanel
|
|
||||||
.getState()
|
|
||||||
.openSidePanel(
|
|
||||||
"New " + getDatabaseName(),
|
|
||||||
<AddDatabasePanel explorer={this.container} buttonElement={document.activeElement as HTMLElement} />
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private openAddDatabasePanel() {
|
||||||
|
const newDatabaseButton = document.activeElement as HTMLElement;
|
||||||
|
useSidePanel
|
||||||
|
.getState()
|
||||||
|
.openSidePanel(
|
||||||
|
"New " + getDatabaseName(),
|
||||||
|
<AddDatabasePanel explorer={this.container} buttonElement={newDatabaseButton} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private decorateOpenCollectionActivity({ databaseId, collectionId }: MostRecentActivity.OpenCollectionItem) {
|
private decorateOpenCollectionActivity({ databaseId, collectionId }: MostRecentActivity.OpenCollectionItem) {
|
||||||
return {
|
return {
|
||||||
iconSrc: NotebookIcon,
|
iconSrc: NotebookIcon,
|
||||||
|
|||||||
@@ -14,73 +14,17 @@ export const CassandraType = {
|
|||||||
Bigint: "Bigint",
|
Bigint: "Bigint",
|
||||||
Blob: "Blob",
|
Blob: "Blob",
|
||||||
Boolean: "Boolean",
|
Boolean: "Boolean",
|
||||||
Date: "Date",
|
|
||||||
Decimal: "Decimal",
|
Decimal: "Decimal",
|
||||||
Double: "Double",
|
Double: "Double",
|
||||||
Float: "Float",
|
Float: "Float",
|
||||||
Int: "Int",
|
Int: "Int",
|
||||||
Text: "Text",
|
Text: "Text",
|
||||||
Timestamp: "Timestamp",
|
|
||||||
Uuid: "Uuid",
|
Uuid: "Uuid",
|
||||||
Varchar: "Varchar",
|
Varchar: "Varchar",
|
||||||
Varint: "Varint",
|
Varint: "Varint",
|
||||||
Inet: "Inet",
|
Inet: "Inet",
|
||||||
Smallint: "Smallint",
|
Smallint: "Smallint",
|
||||||
Tinyint: "Tinyint",
|
Tinyint: "Tinyint",
|
||||||
|
|
||||||
List_Ascii: "List<Ascii>",
|
|
||||||
List_Bigint: "List<Bigint>",
|
|
||||||
List_Blob: "List<Blob>",
|
|
||||||
List_Boolean: "List<Boolean>",
|
|
||||||
List_Date: "List<Date>",
|
|
||||||
List_Decimal: "List<Decimal>",
|
|
||||||
List_Double: "List<Double>",
|
|
||||||
List_Float: "List<Float>",
|
|
||||||
List_Int: "List<Int>",
|
|
||||||
List_Text: "List<Text>",
|
|
||||||
List_Timestamp: "List<Timestamp>",
|
|
||||||
List_Uuid: "List<Uuid>",
|
|
||||||
List_Varchar: "List<Varchar>",
|
|
||||||
List_Varint: "List<Varint>",
|
|
||||||
List_Inet: "List<Inet>",
|
|
||||||
List_Smallint: "List<Smallint>",
|
|
||||||
List_Tinyint: "List<Tinyint>",
|
|
||||||
|
|
||||||
Map_Ascii: "Map<Ascii>",
|
|
||||||
Map_Bigint: "Map<Bigint>",
|
|
||||||
Map_Blob: "Map<Blob>",
|
|
||||||
Map_Boolean: "Map<Boolean>",
|
|
||||||
Map_Date: "Map<Date>",
|
|
||||||
Map_Decimal: "Map<Decimal>",
|
|
||||||
Map_Double: "Map<Double>",
|
|
||||||
Map_Float: "Map<Float>",
|
|
||||||
Map_Int: "Map<Int>",
|
|
||||||
Map_Text: "Map<Text>",
|
|
||||||
Map_Timestamp: "Map<Timestamp>",
|
|
||||||
Map_Uuid: "Map<Uuid>",
|
|
||||||
Map_Varchar: "Map<Varchar>",
|
|
||||||
Map_Varint: "Map<Varint>",
|
|
||||||
Map_Inet: "Map<Inet>",
|
|
||||||
Map_Smallint: "Map<Smallint>",
|
|
||||||
Map_Tinyint: "Map<Tinyint>",
|
|
||||||
|
|
||||||
Set_Ascii: "Set<Ascii>",
|
|
||||||
Set_Bigint: "Set<Bigint>",
|
|
||||||
Set_Blob: "Set<Blob>",
|
|
||||||
Set_Boolean: "Set<Boolean>",
|
|
||||||
Set_Date: "Set<Date>",
|
|
||||||
Set_Decimal: "Set<Decimal>",
|
|
||||||
Set_Double: "Set<Double>",
|
|
||||||
Set_Float: "Set<Float>",
|
|
||||||
Set_Int: "Set<Int>",
|
|
||||||
Set_Text: "Set<Text>",
|
|
||||||
Set_Timestamp: "Set<Timestamp>",
|
|
||||||
Set_Uuid: "Set<Uuid>",
|
|
||||||
Set_Varchar: "Set<Varchar>",
|
|
||||||
Set_Varint: "Set<Varint>",
|
|
||||||
Set_Inet: "Set<Inet>",
|
|
||||||
Set_Smallint: "Set<Smallint>",
|
|
||||||
Set_Tinyint: "Set<Tinyint>",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ClauseRule = {
|
export const ClauseRule = {
|
||||||
|
|||||||
@@ -431,7 +431,7 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
|||||||
if (newHeaders.length > 0) {
|
if (newHeaders.length > 0) {
|
||||||
// Any new columns found will be added into headers array, which will trigger a re-render of the DataTable.
|
// Any new columns found will be added into headers array, which will trigger a re-render of the DataTable.
|
||||||
// So there is no need to call it here.
|
// So there is no need to call it here.
|
||||||
this.updateHeaders(selectedHeadersUnion, /* notifyColumnChanges */ true);
|
this.updateHeaders(newHeaders, /* notifyColumnChanges */ true);
|
||||||
} else {
|
} else {
|
||||||
if (columnSortOrder) {
|
if (columnSortOrder) {
|
||||||
this.sortColumns(columnSortOrder, oSettings);
|
this.sortColumns(columnSortOrder, oSettings);
|
||||||
|
|||||||
@@ -202,21 +202,14 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
|
|
||||||
let updateQuery = `UPDATE ${collection.databaseId}.${collection.id()}`;
|
let updateQuery = `UPDATE ${collection.databaseId}.${collection.id()}`;
|
||||||
let isPropertyUpdated = false;
|
let isPropertyUpdated = false;
|
||||||
let isFirstPropertyToUpdate = true;
|
|
||||||
for (let property in newEntity) {
|
for (let property in newEntity) {
|
||||||
if (
|
if (
|
||||||
!originalDocument[property] ||
|
!originalDocument[property] ||
|
||||||
newEntity[property]._.toString() !== originalDocument[property]._.toString()
|
newEntity[property]._.toString() !== originalDocument[property]._.toString()
|
||||||
) {
|
) {
|
||||||
let propertyQuerySegment = this.isStringType(newEntity[property].$)
|
updateQuery += this.isStringType(newEntity[property].$)
|
||||||
? `${property} = '${newEntity[property]._}',`
|
? ` SET ${property} = '${newEntity[property]._}',`
|
||||||
: `${property} = ${newEntity[property]._},`;
|
: ` SET ${property} = ${newEntity[property]._},`;
|
||||||
// Only add the "SET" keyword once
|
|
||||||
if (isFirstPropertyToUpdate) {
|
|
||||||
propertyQuerySegment = " SET " + propertyQuerySegment;
|
|
||||||
isFirstPropertyToUpdate = false;
|
|
||||||
}
|
|
||||||
updateQuery += propertyQuerySegment;
|
|
||||||
isPropertyUpdated = true;
|
isPropertyUpdated = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -535,9 +528,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
dataType === TableConstants.CassandraType.Text ||
|
dataType === TableConstants.CassandraType.Text ||
|
||||||
dataType === TableConstants.CassandraType.Inet ||
|
dataType === TableConstants.CassandraType.Inet ||
|
||||||
dataType === TableConstants.CassandraType.Ascii ||
|
dataType === TableConstants.CassandraType.Ascii ||
|
||||||
dataType === TableConstants.CassandraType.Varchar ||
|
dataType === TableConstants.CassandraType.Varchar
|
||||||
dataType === TableConstants.CassandraType.Timestamp ||
|
|
||||||
dataType === TableConstants.CassandraType.Date
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
import * as Constants from "../../Common/Constants";
|
|
||||||
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
|
||||||
import { traceSuccess } from "../../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import { DataUploaderAdapter } from "../Notebook/DataUploader/DataUploaderAdapter";
|
|
||||||
import NotebookTabBase, { NotebookTabBaseOptions } from "./NotebookTabBase";
|
|
||||||
|
|
||||||
export default class DataUploaderTab extends NotebookTabBase {
|
|
||||||
public readonly html = '<div data-bind="react:DataUploaderAdapter" style="height: 100%"></div>';
|
|
||||||
private DataUploaderAdapter: DataUploaderAdapter;
|
|
||||||
|
|
||||||
constructor(options: NotebookTabBaseOptions) {
|
|
||||||
super(options);
|
|
||||||
this.DataUploaderAdapter = new DataUploaderAdapter(
|
|
||||||
{
|
|
||||||
contentRef: undefined,
|
|
||||||
notebookClient: NotebookTabBase.clientManager,
|
|
||||||
},
|
|
||||||
options.collection?.databaseId,
|
|
||||||
options.collection?.id()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onActivate(): void {
|
|
||||||
traceSuccess(
|
|
||||||
Action.Tab,
|
|
||||||
{
|
|
||||||
databaseName: this.collection?.databaseId,
|
|
||||||
collectionName: this.collection?.id,
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
|
||||||
tabTitle: "Upload",
|
|
||||||
},
|
|
||||||
this.onLoadStartKey
|
|
||||||
);
|
|
||||||
|
|
||||||
super.onActivate();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected buildCommandBarOptions(): void {
|
|
||||||
this.updateNavbarWithTabsButtons();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,7 +17,6 @@ export interface NotebookTabBaseOptions extends ViewModels.TabOptions {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Every notebook-based tab inherits from this class. It holds the static reference to a notebook client (singleton)
|
* Every notebook-based tab inherits from this class. It holds the static reference to a notebook client (singleton)
|
||||||
* Re-initiating the constructor when ever a new container got allocated.
|
|
||||||
*/
|
*/
|
||||||
export default class NotebookTabBase extends TabsBase {
|
export default class NotebookTabBase extends TabsBase {
|
||||||
protected static clientManager: NotebookClientV2;
|
protected static clientManager: NotebookClientV2;
|
||||||
@@ -28,15 +27,6 @@ export default class NotebookTabBase extends TabsBase {
|
|||||||
|
|
||||||
this.container = options.container;
|
this.container = options.container;
|
||||||
|
|
||||||
useNotebook.subscribe(
|
|
||||||
() => {
|
|
||||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
|
||||||
if (notebookServerInfo && notebookServerInfo.notebookServerEndpoint) {
|
|
||||||
NotebookTabBase.clientManager = undefined;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(state) => state.notebookServerInfo
|
|
||||||
);
|
|
||||||
if (!NotebookTabBase.clientManager) {
|
if (!NotebookTabBase.clientManager) {
|
||||||
NotebookTabBase.clientManager = new NotebookClientV2({
|
NotebookTabBase.clientManager = new NotebookClientV2({
|
||||||
connectionInfo: useNotebook.getState().notebookServerInfo,
|
connectionInfo: useNotebook.getState().notebookServerInfo,
|
||||||
|
|||||||
@@ -53,16 +53,14 @@ export default class NotebookTabV2 extends NotebookTabBase {
|
|||||||
onUpdateKernelInfo: this.onKernelUpdate,
|
onUpdateKernelInfo: this.onKernelUpdate,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
* Hard cleaning the workspace(Closing tabs connected with old container connection) when new container got allocated.
|
public onCloseTabButtonClick(): Q.Promise<void> {
|
||||||
*/
|
|
||||||
public onCloseTabButtonClick(hardClose = false): Q.Promise<any> {
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
this.notebookComponentAdapter.notebookShutdown();
|
this.notebookComponentAdapter.notebookShutdown();
|
||||||
super.onCloseTabButtonClick();
|
super.onCloseTabButtonClick();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.notebookComponentAdapter.isContentDirty() && hardClose === false) {
|
if (this.notebookComponentAdapter.isContentDirty()) {
|
||||||
useDialog
|
useDialog
|
||||||
.getState()
|
.getState()
|
||||||
.showOkCancelModalDialog(
|
.showOkCancelModalDialog(
|
||||||
@@ -80,7 +78,7 @@ export default class NotebookTabV2 extends NotebookTabBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async reconfigureServiceEndpoints() {
|
public async reconfigureServiceEndpoints(): Promise<void> {
|
||||||
if (!this.notebookComponentAdapter) {
|
if (!this.notebookComponentAdapter) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -138,7 +136,7 @@ export default class NotebookTabV2 extends NotebookTabBase {
|
|||||||
ariaLabel: publishLabel,
|
ariaLabel: publishLabel,
|
||||||
});
|
});
|
||||||
|
|
||||||
let buttons: CommandButtonComponentProps[] = [
|
const buttons: CommandButtonComponentProps[] = [
|
||||||
{
|
{
|
||||||
iconSrc: SaveIcon,
|
iconSrc: SaveIcon,
|
||||||
iconAlt: saveLabel,
|
iconAlt: saveLabel,
|
||||||
@@ -162,7 +160,7 @@ export default class NotebookTabV2 extends NotebookTabBase {
|
|||||||
{
|
{
|
||||||
iconSrc: null,
|
iconSrc: null,
|
||||||
iconAlt: kernelLabel,
|
iconAlt: kernelLabel,
|
||||||
onCommandClick: () => {},
|
onCommandClick: () => undefined,
|
||||||
commandButtonLabel: null,
|
commandButtonLabel: null,
|
||||||
hasPopup: false,
|
hasPopup: false,
|
||||||
disabled: availableKernels.length < 1,
|
disabled: availableKernels.length < 1,
|
||||||
@@ -273,7 +271,7 @@ export default class NotebookTabV2 extends NotebookTabBase {
|
|||||||
{
|
{
|
||||||
iconSrc: null,
|
iconSrc: null,
|
||||||
iconAlt: null,
|
iconAlt: null,
|
||||||
onCommandClick: () => {},
|
onCommandClick: () => undefined,
|
||||||
commandButtonLabel: null,
|
commandButtonLabel: null,
|
||||||
ariaLabel: cellTypeLabel,
|
ariaLabel: cellTypeLabel,
|
||||||
hasPopup: false,
|
hasPopup: false,
|
||||||
@@ -363,7 +361,7 @@ export default class NotebookTabV2 extends NotebookTabBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onKernelUpdate = async () => {
|
private onKernelUpdate = async () => {
|
||||||
await this.configureServiceEndpoints(this.notebookComponentAdapter.getCurrentKernelName()).catch((reason) => {
|
await this.configureServiceEndpoints(this.notebookComponentAdapter.getCurrentKernelName()).catch(() => {
|
||||||
/* Erroring is ok here */
|
/* Erroring is ok here */
|
||||||
});
|
});
|
||||||
this.updateNavbarWithTabsButtons();
|
this.updateNavbarWithTabsButtons();
|
||||||
|
|||||||
@@ -215,13 +215,13 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
{
|
{
|
||||||
metric: "Request Charge",
|
metric: "Request Charge",
|
||||||
value: this.state.requestChargeDisplayText,
|
value: this.state.requestChargeDisplayText,
|
||||||
toolTip: "Request Charge",
|
toolTip: "",
|
||||||
isQueryMetricsEnabled: true,
|
isQueryMetricsEnabled: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
metric: "Showing Results",
|
metric: "Showing Results",
|
||||||
value: this.state.showingDocumentsDisplayText,
|
value: this.state.showingDocumentsDisplayText,
|
||||||
toolTip: "Showing Results",
|
toolTip: "",
|
||||||
isQueryMetricsEnabled: true,
|
isQueryMetricsEnabled: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -364,11 +364,13 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onTabClick(): void {
|
public onTabClick(): void {
|
||||||
if (!this.isCloseClicked) {
|
setTimeout(() => {
|
||||||
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
if (!this.isCloseClicked) {
|
||||||
} else {
|
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||||
this.isCloseClicked = false;
|
} else {
|
||||||
}
|
this.isCloseClicked = false;
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onExecuteQueryClick = async (): Promise<void> => {
|
public onExecuteQueryClick = async (): Promise<void> => {
|
||||||
@@ -873,11 +875,9 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
|
||||||
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
|
||||||
}
|
|
||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
|
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className="tab-pane" id={this.props.tabId} role="tabpanel">
|
<div className="tab-pane" id={this.props.tabId} role="tabpanel">
|
||||||
|
|||||||
@@ -25,11 +25,12 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
|
|||||||
public errors: ko.ObservableArray<ViewModels.QueryError>;
|
public errors: ko.ObservableArray<ViewModels.QueryError>;
|
||||||
public statusMessge: ko.Observable<string>;
|
public statusMessge: ko.Observable<string>;
|
||||||
public statusIcon: ko.Observable<string>;
|
public statusIcon: ko.Observable<string>;
|
||||||
public formFields: ko.ObservableArray<ViewModels.Editable<any>>;
|
public formFields: ko.ObservableArray<ViewModels.Editable<unknown>>;
|
||||||
public formIsValid: ko.Computed<boolean>;
|
public formIsValid: ko.Computed<boolean>;
|
||||||
public formIsDirty: ko.Computed<boolean>;
|
public formIsDirty: ko.Computed<boolean>;
|
||||||
public isNew: ko.Observable<boolean>;
|
public isNew: ko.Observable<boolean>;
|
||||||
// TODO: Remove any. The SDK types for all the script.body are slightly incorrect which makes this REALLY hard to type correct.
|
// TODO: Remove any. The SDK types for all the script.body are slightly incorrect which makes this REALLY hard to type correct.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
public resource: ko.Observable<any>;
|
public resource: ko.Observable<any>;
|
||||||
public isTemplateReady: ko.Observable<boolean>;
|
public isTemplateReady: ko.Observable<boolean>;
|
||||||
protected _partitionKey: DataModels.PartitionKey;
|
protected _partitionKey: DataModels.PartitionKey;
|
||||||
@@ -85,7 +86,6 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
|
|||||||
this.editorState(ViewModels.ScriptEditorState.exisitingDirtyInvalid);
|
this.editorState(ViewModels.ScriptEditorState.exisitingDirtyInvalid);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ViewModels.ScriptEditorState.exisitingDirtyValid:
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -182,7 +182,7 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
|
|||||||
this.editorContent.setBaseline(resource.body);
|
this.editorContent.setBaseline(resource.body);
|
||||||
}
|
}
|
||||||
|
|
||||||
public setBaselines() {
|
public setBaselines(): void {
|
||||||
this._setBaselines();
|
this._setBaselines();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,9 +194,9 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
|
|||||||
}
|
}
|
||||||
|
|
||||||
public abstract onSaveClick: () => void;
|
public abstract onSaveClick: () => void;
|
||||||
public abstract onUpdateClick: () => Promise<any>;
|
public abstract onUpdateClick: () => Promise<void>;
|
||||||
|
|
||||||
public onDiscard = (): Q.Promise<any> => {
|
public onDiscard = (): Q.Promise<void> => {
|
||||||
this.setBaselines();
|
this.setBaselines();
|
||||||
const original = this.editorContent.getEditableOriginalValue();
|
const original = this.editorContent.getEditableOriginalValue();
|
||||||
const editorModel = this.editor() && this.editor().getModel();
|
const editorModel = this.editor() && this.editor().getModel();
|
||||||
@@ -289,7 +289,7 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
|
|||||||
return !!value;
|
return !!value;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async _createBodyEditor() {
|
protected async _createBodyEditor(): Promise<void> {
|
||||||
const id = this.editorId;
|
const id = this.editorId;
|
||||||
const container = document.getElementById(id);
|
const container = document.getElementById(id);
|
||||||
const options = {
|
const options = {
|
||||||
@@ -308,7 +308,7 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
|
|||||||
editorModel.onDidChangeContent(this._onBodyContentChange.bind(this));
|
editorModel.onDidChangeContent(this._onBodyContentChange.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onBodyContentChange(e: monaco.editor.IModelContentChangedEvent) {
|
private _onBodyContentChange() {
|
||||||
const editorModel = this.editor().getModel();
|
const editorModel = this.editor().getModel();
|
||||||
this.editorContent(editorModel.getValue());
|
this.editorContent(editorModel.getValue());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ class NotebookTerminalComponentAdapter implements ReactAdapter {
|
|||||||
public parameters: ko.Computed<boolean>;
|
public parameters: ko.Computed<boolean>;
|
||||||
constructor(
|
constructor(
|
||||||
private getNotebookServerInfo: () => DataModels.NotebookWorkspaceConnectionInfo,
|
private getNotebookServerInfo: () => DataModels.NotebookWorkspaceConnectionInfo,
|
||||||
private getDatabaseAccount: () => DataModels.DatabaseAccount,
|
private getDatabaseAccount: () => DataModels.DatabaseAccount
|
||||||
private getTabId: () => string
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public renderComponent(): JSX.Element {
|
public renderComponent(): JSX.Element {
|
||||||
@@ -34,7 +33,6 @@ class NotebookTerminalComponentAdapter implements ReactAdapter {
|
|||||||
<NotebookTerminalComponent
|
<NotebookTerminalComponent
|
||||||
notebookServerInfo={this.getNotebookServerInfo()}
|
notebookServerInfo={this.getNotebookServerInfo()}
|
||||||
databaseAccount={this.getDatabaseAccount()}
|
databaseAccount={this.getDatabaseAccount()}
|
||||||
tabId={this.getTabId()}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Spinner styles={{ root: { marginTop: 10 } }} size={SpinnerSize.large}></Spinner>
|
<Spinner styles={{ root: { marginTop: 10 } }} size={SpinnerSize.large}></Spinner>
|
||||||
@@ -52,8 +50,7 @@ export default class TerminalTab extends TabsBase {
|
|||||||
this.container = options.container;
|
this.container = options.container;
|
||||||
this.notebookTerminalComponentAdapter = new NotebookTerminalComponentAdapter(
|
this.notebookTerminalComponentAdapter = new NotebookTerminalComponentAdapter(
|
||||||
() => this.getNotebookServerInfo(options),
|
() => this.getNotebookServerInfo(options),
|
||||||
() => userContext?.databaseAccount,
|
() => userContext?.databaseAccount
|
||||||
() => this.tabId
|
|
||||||
);
|
);
|
||||||
this.notebookTerminalComponentAdapter.parameters = ko.computed<boolean>(() => {
|
this.notebookTerminalComponentAdapter.parameters = ko.computed<boolean>(() => {
|
||||||
if (
|
if (
|
||||||
@@ -103,7 +100,6 @@ export default class TerminalTab extends TabsBase {
|
|||||||
return {
|
return {
|
||||||
authToken: info.authToken,
|
authToken: info.authToken,
|
||||||
notebookServerEndpoint: `${info.notebookServerEndpoint.replace(/\/+$/, "")}/${endpointSuffix}`,
|
notebookServerEndpoint: `${info.notebookServerEndpoint.replace(/\/+$/, "")}/${endpointSuffix}`,
|
||||||
forwardingId: info.forwardingId,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Resource, StoredProcedureDefinition, TriggerDefinition, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
import { Resource, StoredProcedureDefinition, TriggerDefinition, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as _ from "underscore";
|
import * as _ from "underscore";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
@@ -529,9 +528,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public onSchemaAnalyzerClick = async () => {
|
public onSchemaAnalyzerClick = async () => {
|
||||||
if (useNotebook.getState().isPhoenixFeatures) {
|
|
||||||
await this.container.allocateContainer();
|
|
||||||
}
|
|
||||||
useSelectedNode.getState().setSelectedNode(this);
|
useSelectedNode.getState().setSelectedNode(this);
|
||||||
this.selectedSubnodeKind(ViewModels.CollectionTabKind.SchemaAnalyzer);
|
this.selectedSubnodeKind(ViewModels.CollectionTabKind.SchemaAnalyzer);
|
||||||
const SchemaAnalyzerTab = await (await import("../Tabs/SchemaAnalyzerTab")).default;
|
const SchemaAnalyzerTab = await (await import("../Tabs/SchemaAnalyzerTab")).default;
|
||||||
@@ -574,56 +570,9 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
public onDataUploaderClick = async () => {
|
|
||||||
if (useNotebook.getState().isPhoenixFeatures) {
|
|
||||||
await this.container.allocateContainer();
|
|
||||||
}
|
|
||||||
useSelectedNode.getState().setSelectedNode(this);
|
|
||||||
this.selectedSubnodeKind(ViewModels.CollectionTabKind.SchemaAnalyzer);
|
|
||||||
const DataUploaderTab = await (await import("../Tabs/DataUploaderTab")).default;
|
|
||||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
|
||||||
description: "Data uploader node",
|
|
||||||
databaseName: this.databaseId,
|
|
||||||
collectionName: this.id(),
|
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const tab of useTabs.getState().openedTabs) {
|
|
||||||
if (
|
|
||||||
tab instanceof DataUploaderTab &&
|
|
||||||
tab.collection?.databaseId === this.databaseId &&
|
|
||||||
tab.collection?.id() === this.id()
|
|
||||||
) {
|
|
||||||
return useTabs.getState().activateTab(tab);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const startKey = TelemetryProcessor.traceStart(Action.Tab, {
|
|
||||||
databaseName: this.databaseId,
|
|
||||||
collectionName: this.id(),
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
|
||||||
tabTitle: "Upload",
|
|
||||||
});
|
|
||||||
this.documentIds([]);
|
|
||||||
useTabs.getState().activateNewTab(
|
|
||||||
new DataUploaderTab({
|
|
||||||
account: userContext.databaseAccount,
|
|
||||||
masterKey: userContext.masterKey || "",
|
|
||||||
container: this.container,
|
|
||||||
tabKind: ViewModels.CollectionTabKind.DataUploader,
|
|
||||||
title: "Upload",
|
|
||||||
tabPath: "",
|
|
||||||
collection: this,
|
|
||||||
node: this,
|
|
||||||
onLoadStartKey: startKey,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
public onSettingsClick = async (): Promise<void> => {
|
public onSettingsClick = async (): Promise<void> => {
|
||||||
useSelectedNode.getState().setSelectedNode(this);
|
useSelectedNode.getState().setSelectedNode(this);
|
||||||
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
await this.loadOffer();
|
||||||
throughputCap && throughputCap !== -1 ? await useDatabases.getState().loadAllOffers() : await this.loadOffer();
|
|
||||||
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Settings);
|
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Settings);
|
||||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||||
description: "Settings node",
|
description: "Settings node",
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export default class Database implements ViewModels.Database {
|
|||||||
this.isOfferRead = false;
|
this.isOfferRead = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public onSettingsClick = async (): Promise<void> => {
|
public onSettingsClick = (): void => {
|
||||||
useSelectedNode.getState().setSelectedNode(this);
|
useSelectedNode.getState().setSelectedNode(this);
|
||||||
this.selectedSubnodeKind(ViewModels.CollectionTabKind.DatabaseSettings);
|
this.selectedSubnodeKind(ViewModels.CollectionTabKind.DatabaseSettings);
|
||||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||||
@@ -66,11 +66,6 @@ export default class Database implements ViewModels.Database {
|
|||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
});
|
});
|
||||||
|
|
||||||
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
|
||||||
if (throughputCap && throughputCap !== -1) {
|
|
||||||
await useDatabases.getState().loadAllOffers();
|
|
||||||
}
|
|
||||||
|
|
||||||
const pendingNotificationsPromise: Promise<DataModels.Notification> = this.getPendingThroughputSplitNotification();
|
const pendingNotificationsPromise: Promise<DataModels.Notification> = this.getPendingThroughputSplitNotification();
|
||||||
const tabKind = ViewModels.CollectionTabKind.DatabaseSettingsV2;
|
const tabKind = ViewModels.CollectionTabKind.DatabaseSettingsV2;
|
||||||
const matchingTabs = useTabs.getState().getTabs(tabKind, (tab) => tab.node?.id() === this.id());
|
const matchingTabs = useTabs.getState().getTabs(tabKind, (tab) => tab.node?.id() === this.id());
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
children: [],
|
children: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!useNotebook.getState().isPhoenixNotebooks) {
|
if (userContext.features.notebooksTemporarilyDown) {
|
||||||
notebooksTree.children.push(buildNotebooksTemporarilyDownTree());
|
notebooksTree.children.push(buildNotebooksTemporarilyDownTree());
|
||||||
} else {
|
} else {
|
||||||
if (galleryContentRoot) {
|
if (galleryContentRoot) {
|
||||||
@@ -130,8 +130,9 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
myNotebooksContentRoot &&
|
myNotebooksContentRoot &&
|
||||||
useNotebook.getState().isPhoenixNotebooks &&
|
((NotebookUtil.isPhoenixEnabled() &&
|
||||||
useNotebook.getState().connectionInfo.status === ConnectionStatusType.Connected
|
useNotebook.getState().connectionInfo.status === ConnectionStatusType.Connected) ||
|
||||||
|
userContext.features.phoenix === false)
|
||||||
) {
|
) {
|
||||||
notebooksTree.children.push(buildMyNotebooksTree());
|
notebooksTree.children.push(buildMyNotebooksTree());
|
||||||
}
|
}
|
||||||
@@ -165,7 +166,15 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
const myNotebooksTree: TreeNode = buildNotebookDirectoryNode(
|
const myNotebooksTree: TreeNode = buildNotebookDirectoryNode(
|
||||||
myNotebooksContentRoot,
|
myNotebooksContentRoot,
|
||||||
(item: NotebookContentItem) => {
|
(item: NotebookContentItem) => {
|
||||||
container.openNotebook(item);
|
container.openNotebook(item).then((hasOpened) => {
|
||||||
|
if (
|
||||||
|
hasOpened &&
|
||||||
|
userContext.features.notebooksTemporarilyDown === false &&
|
||||||
|
userContext.features.phoenix === false
|
||||||
|
) {
|
||||||
|
mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -180,7 +189,15 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
const gitHubNotebooksTree: TreeNode = buildNotebookDirectoryNode(
|
const gitHubNotebooksTree: TreeNode = buildNotebookDirectoryNode(
|
||||||
gitHubNotebooksContentRoot,
|
gitHubNotebooksContentRoot,
|
||||||
(item: NotebookContentItem) => {
|
(item: NotebookContentItem) => {
|
||||||
container.openNotebook(item);
|
container.openNotebook(item).then((hasOpened) => {
|
||||||
|
if (
|
||||||
|
hasOpened &&
|
||||||
|
userContext.features.notebooksTemporarilyDown === false &&
|
||||||
|
userContext.features.phoenix === false
|
||||||
|
) {
|
||||||
|
mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item);
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
@@ -380,11 +397,6 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
//disallow renaming of temporary notebook workspace
|
|
||||||
if (item?.path === useNotebook.getState().notebookBasePath) {
|
|
||||||
items = items.filter((item) => item.label !== "Rename");
|
|
||||||
}
|
|
||||||
|
|
||||||
// For GitHub paths remove "Delete", "Rename", "New Directory", "Upload File"
|
// For GitHub paths remove "Delete", "Rename", "New Directory", "Upload File"
|
||||||
if (GitHubUtils.fromContentUri(item.path)) {
|
if (GitHubUtils.fromContentUri(item.path)) {
|
||||||
items = items.filter(
|
items = items.filter(
|
||||||
@@ -516,7 +528,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
isNotebookEnabled &&
|
isNotebookEnabled &&
|
||||||
userContext.apiType === "Mongo" &&
|
userContext.apiType === "Mongo" &&
|
||||||
isPublicInternetAccessAllowed() &&
|
isPublicInternetAccessAllowed() &&
|
||||||
useNotebook.getState().isPhoenixFeatures
|
!userContext.features.notebooksTemporarilyDown
|
||||||
) {
|
) {
|
||||||
children.push({
|
children.push({
|
||||||
label: "Schema (Preview)",
|
label: "Schema (Preview)",
|
||||||
@@ -526,15 +538,6 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
.getState()
|
.getState()
|
||||||
.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.SchemaAnalyzer]),
|
.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.SchemaAnalyzer]),
|
||||||
});
|
});
|
||||||
|
|
||||||
children.push({
|
|
||||||
label: "Data Upload (Preview)",
|
|
||||||
onClick: collection.onDataUploaderClick.bind(collection),
|
|
||||||
isSelected: () =>
|
|
||||||
useSelectedNode
|
|
||||||
.getState()
|
|
||||||
.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.DataUploader]),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userContext.apiType !== "Cassandra" || !isServerlessAccount()) {
|
if (userContext.apiType !== "Cassandra" || !isServerlessAccount()) {
|
||||||
|
|||||||
@@ -283,15 +283,6 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
.getState()
|
.getState()
|
||||||
.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.SchemaAnalyzer]),
|
.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.SchemaAnalyzer]),
|
||||||
});
|
});
|
||||||
|
|
||||||
children.push({
|
|
||||||
label: "Data Upload (Preview)",
|
|
||||||
onClick: collection.onDataUploaderClick.bind(collection),
|
|
||||||
isSelected: () =>
|
|
||||||
useSelectedNode
|
|
||||||
.getState()
|
|
||||||
.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.DataUploader]),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userContext.apiType !== "Cassandra" || !isServerlessAccount()) {
|
if (userContext.apiType !== "Cassandra" || !isServerlessAccount()) {
|
||||||
@@ -817,11 +808,6 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
//disallow renaming of temporary notebook workspace
|
|
||||||
if (item?.path === useNotebook.getState().notebookBasePath) {
|
|
||||||
items = items.filter((item) => item.label !== "Rename");
|
|
||||||
}
|
|
||||||
|
|
||||||
// For GitHub paths remove "Delete", "Rename", "New Directory", "Upload File"
|
// For GitHub paths remove "Delete", "Rename", "New Directory", "Upload File"
|
||||||
if (GitHubUtils.fromContentUri(item.path)) {
|
if (GitHubUtils.fromContentUri(item.path)) {
|
||||||
items = items.filter(
|
items = items.filter(
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ interface DatabasesState {
|
|||||||
findCollection: (databaseId: string, collectionId: string) => ViewModels.Collection;
|
findCollection: (databaseId: string, collectionId: string) => ViewModels.Collection;
|
||||||
isLastCollection: () => boolean;
|
isLastCollection: () => boolean;
|
||||||
loadDatabaseOffers: () => Promise<void>;
|
loadDatabaseOffers: () => Promise<void>;
|
||||||
loadAllOffers: () => Promise<void>;
|
|
||||||
isFirstResourceCreated: () => boolean;
|
isFirstResourceCreated: () => boolean;
|
||||||
findSelectedDatabase: () => ViewModels.Database;
|
findSelectedDatabase: () => ViewModels.Database;
|
||||||
validateDatabaseId: (id: string) => boolean;
|
validateDatabaseId: (id: string) => boolean;
|
||||||
@@ -98,19 +97,6 @@ export const useDatabases: UseStore<DatabasesState> = create((set, get) => ({
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
loadAllOffers: async () => {
|
|
||||||
await Promise.all(
|
|
||||||
get().databases?.map(async (database: ViewModels.Database) => {
|
|
||||||
await database.loadOffer();
|
|
||||||
await database.loadCollections();
|
|
||||||
await Promise.all(
|
|
||||||
(database.collections() || []).map(async (collection: ViewModels.Collection) => {
|
|
||||||
await collection.loadOffer();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
isFirstResourceCreated: () => {
|
isFirstResourceCreated: () => {
|
||||||
const databases = get().databases;
|
const databases = get().databases;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { ConnectionStatusType } from "Common/Constants";
|
|
||||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
|
||||||
import create, { UseStore } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import { useTabs } from "../hooks/useTabs";
|
import { useTabs } from "../hooks/useTabs";
|
||||||
@@ -14,7 +12,6 @@ export interface SelectedNodeState {
|
|||||||
collectionId?: string,
|
collectionId?: string,
|
||||||
subnodeKinds?: ViewModels.CollectionTabKind[]
|
subnodeKinds?: ViewModels.CollectionTabKind[]
|
||||||
) => boolean;
|
) => boolean;
|
||||||
isConnectedToContainer: () => boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSelectedNode: UseStore<SelectedNodeState> = create((set, get) => ({
|
export const useSelectedNode: UseStore<SelectedNodeState> = create((set, get) => ({
|
||||||
@@ -62,7 +59,4 @@ export const useSelectedNode: UseStore<SelectedNodeState> = create((set, get) =>
|
|||||||
subnodeKinds.includes(selectedSubnodeKind)
|
subnodeKinds.includes(selectedSubnodeKind)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
isConnectedToContainer: (): boolean => {
|
|
||||||
return useNotebook.getState().connectionInfo?.status === ConnectionStatusType.Connected;
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import postRobot from "post-robot";
|
import postRobot from "post-robot";
|
||||||
import { GetGithubClientId } from "Utils/GitHubUtils";
|
|
||||||
import { HttpStatusCodes } from "../Common/Constants";
|
import { HttpStatusCodes } from "../Common/Constants";
|
||||||
import { handleError } from "../Common/ErrorHandlingUtils";
|
import { handleError } from "../Common/ErrorHandlingUtils";
|
||||||
|
import { configContext } from "../ConfigContext";
|
||||||
import { AuthorizeAccessComponent } from "../Explorer/Controls/GitHub/AuthorizeAccessComponent";
|
import { AuthorizeAccessComponent } from "../Explorer/Controls/GitHub/AuthorizeAccessComponent";
|
||||||
import { JunoClient } from "../Juno/JunoClient";
|
import { JunoClient } from "../Juno/JunoClient";
|
||||||
import { logConsoleInfo } from "../Utils/NotificationConsoleUtils";
|
import { logConsoleInfo } from "../Utils/NotificationConsoleUtils";
|
||||||
@@ -55,7 +55,7 @@ export class GitHubOAuthService {
|
|||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
scope,
|
scope,
|
||||||
client_id: GetGithubClientId(),
|
client_id: configContext.GITHUB_CLIENT_ID,
|
||||||
redirect_uri: new URL("./connectToGitHub.html", window.location.href).href,
|
redirect_uri: new URL("./connectToGitHub.html", window.location.href).href,
|
||||||
state: this.resetState(),
|
state: this.resetState(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import { allowedJunoOrigins, validateEndpoint } from "Utils/EndpointValidation";
|
|
||||||
import { GetGithubClientId } from "Utils/GitHubUtils";
|
|
||||||
import { HttpHeaders, HttpStatusCodes } from "../Common/Constants";
|
import { HttpHeaders, HttpStatusCodes } from "../Common/Constants";
|
||||||
import { configContext } from "../ConfigContext";
|
import { configContext } from "../ConfigContext";
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
@@ -485,7 +483,7 @@ export class JunoClient {
|
|||||||
// public for tests
|
// public for tests
|
||||||
public static getJunoEndpoint(): string {
|
public static getJunoEndpoint(): string {
|
||||||
const junoEndpoint = userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT;
|
const junoEndpoint = userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT;
|
||||||
if (!validateEndpoint(junoEndpoint, allowedJunoOrigins)) {
|
if (configContext.allowedJunoOrigins.indexOf(new URL(junoEndpoint).origin) === -1) {
|
||||||
const error = `${junoEndpoint} not allowed as juno endpoint`;
|
const error = `${junoEndpoint} not allowed as juno endpoint`;
|
||||||
console.error(error);
|
console.error(error);
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
@@ -524,7 +522,7 @@ export class JunoClient {
|
|||||||
|
|
||||||
private static getGitHubClientParams(): URLSearchParams {
|
private static getGitHubClientParams(): URLSearchParams {
|
||||||
const githubParams = new URLSearchParams({
|
const githubParams = new URLSearchParams({
|
||||||
client_id: GetGithubClientId(),
|
client_id: configContext.GITHUB_CLIENT_ID,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (configContext.GITHUB_CLIENT_SECRET) {
|
if (configContext.GITHUB_CLIENT_SECRET) {
|
||||||
|
|||||||
@@ -1,57 +1,33 @@
|
|||||||
import promiseRetry, { AbortError } from "p-retry";
|
import { HttpHeaders, HttpStatusCodes } from "../Common/Constants";
|
||||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
|
||||||
import { allowedJunoOrigins, validateEndpoint } from "Utils/EndpointValidation";
|
|
||||||
import {
|
|
||||||
Areas,
|
|
||||||
ConnectionStatusType,
|
|
||||||
ContainerStatusType,
|
|
||||||
HttpHeaders,
|
|
||||||
HttpStatusCodes,
|
|
||||||
Notebook,
|
|
||||||
} from "../Common/Constants";
|
|
||||||
import { getErrorMessage } from "../Common/ErrorHandlingUtils";
|
|
||||||
import * as Logger from "../Common/Logger";
|
|
||||||
import { configContext } from "../ConfigContext";
|
import { configContext } from "../ConfigContext";
|
||||||
import {
|
|
||||||
ContainerConnectionInfo,
|
|
||||||
ContainerInfo,
|
|
||||||
IContainerData,
|
|
||||||
IPhoenixConnectionInfoResult,
|
|
||||||
IProvisionData,
|
|
||||||
IResponse,
|
|
||||||
} from "../Contracts/DataModels";
|
|
||||||
import { useNotebook } from "../Explorer/Notebook/useNotebook";
|
|
||||||
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||||
|
|
||||||
|
export interface IPhoenixResponse<T> {
|
||||||
|
status: number;
|
||||||
|
data: T;
|
||||||
|
}
|
||||||
|
export interface IPhoenixConnectionInfoResult {
|
||||||
|
readonly notebookAuthToken?: string;
|
||||||
|
readonly notebookServerUrl?: string;
|
||||||
|
}
|
||||||
|
export interface IProvosionData {
|
||||||
|
cosmosEndpoint: string;
|
||||||
|
dbAccountName: string;
|
||||||
|
aadToken: string;
|
||||||
|
resourceGroup: string;
|
||||||
|
subscriptionId: string;
|
||||||
|
}
|
||||||
export class PhoenixClient {
|
export class PhoenixClient {
|
||||||
private containerHealthHandler: NodeJS.Timeout;
|
public async containerConnectionInfo(
|
||||||
private retryOptions: promiseRetry.Options = {
|
provisionData: IProvosionData
|
||||||
retries: Notebook.retryAttempts,
|
): Promise<IPhoenixResponse<IPhoenixConnectionInfoResult>> {
|
||||||
maxTimeout: Notebook.retryAttemptDelayMs,
|
|
||||||
minTimeout: Notebook.retryAttemptDelayMs,
|
|
||||||
};
|
|
||||||
|
|
||||||
public async allocateContainer(provisionData: IProvisionData): Promise<IResponse<IPhoenixConnectionInfoResult>> {
|
|
||||||
return this.executeContainerAssignmentOperation(provisionData, "allocate");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async resetContainer(provisionData: IProvisionData): Promise<IResponse<IPhoenixConnectionInfoResult>> {
|
|
||||||
return this.executeContainerAssignmentOperation(provisionData, "reset");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async executeContainerAssignmentOperation(
|
|
||||||
provisionData: IProvisionData,
|
|
||||||
operation: string
|
|
||||||
): Promise<IResponse<IPhoenixConnectionInfoResult>> {
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${this.getPhoenixControlPlanePathPrefix()}/containerconnections`, {
|
const response = await window.fetch(`${this.getPhoenixContainerPoolingEndPoint()}/allocate`, {
|
||||||
method: operation === "allocate" ? "POST" : "PATCH",
|
method: "POST",
|
||||||
headers: PhoenixClient.getHeaders(),
|
headers: PhoenixClient.getHeaders(),
|
||||||
body: JSON.stringify(provisionData),
|
body: JSON.stringify(provisionData),
|
||||||
});
|
});
|
||||||
|
|
||||||
let data: IPhoenixConnectionInfoResult;
|
let data: IPhoenixConnectionInfoResult;
|
||||||
if (response.status === HttpStatusCodes.OK) {
|
if (response.status === HttpStatusCodes.OK) {
|
||||||
data = await response.json();
|
data = await response.json();
|
||||||
@@ -66,96 +42,10 @@ export class PhoenixClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async initiateContainerHeartBeat(containerData: IContainerData) {
|
|
||||||
if (this.containerHealthHandler) {
|
|
||||||
clearTimeout(this.containerHealthHandler);
|
|
||||||
}
|
|
||||||
await this.getContainerHealth(Notebook.containerStatusHeartbeatDelayMs, containerData);
|
|
||||||
}
|
|
||||||
|
|
||||||
private scheduleContainerHeartbeat(delayMs: number, containerData: IContainerData): void {
|
|
||||||
this.containerHealthHandler = setTimeout(async () => {
|
|
||||||
await this.getContainerHealth(delayMs, containerData);
|
|
||||||
}, delayMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getContainerStatusAsync(containerData: IContainerData): Promise<ContainerInfo> {
|
|
||||||
try {
|
|
||||||
const runContainerStatusAsync = async () => {
|
|
||||||
const response = await window.fetch(
|
|
||||||
`${this.getPhoenixControlPlanePathPrefix()}/${containerData.forwardingId}`,
|
|
||||||
{
|
|
||||||
method: "GET",
|
|
||||||
headers: PhoenixClient.getHeaders(),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (response.status === HttpStatusCodes.OK) {
|
|
||||||
const containerStatus = await response.json();
|
|
||||||
return {
|
|
||||||
durationLeftInMinutes: containerStatus?.durationLeftInMinutes,
|
|
||||||
notebookServerInfo: containerStatus?.notebookServerInfo,
|
|
||||||
status: ContainerStatusType.Active,
|
|
||||||
};
|
|
||||||
} else if (response.status === HttpStatusCodes.NotFound) {
|
|
||||||
const error = "Disconnected from compute workspace";
|
|
||||||
Logger.logError(error, "");
|
|
||||||
const connectionStatus: ContainerConnectionInfo = {
|
|
||||||
status: ConnectionStatusType.Reconnect,
|
|
||||||
};
|
|
||||||
TelemetryProcessor.traceMark(Action.PhoenixHeartBeat, {
|
|
||||||
dataExplorerArea: Areas.Notebook,
|
|
||||||
message: getErrorMessage(error),
|
|
||||||
});
|
|
||||||
useNotebook.getState().resetContainerConnection(connectionStatus);
|
|
||||||
useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed);
|
|
||||||
throw new AbortError(response.statusText);
|
|
||||||
}
|
|
||||||
throw new Error(response.statusText);
|
|
||||||
};
|
|
||||||
return await promiseRetry(runContainerStatusAsync, this.retryOptions);
|
|
||||||
} catch (error) {
|
|
||||||
TelemetryProcessor.traceFailure(Action.PhoenixHeartBeat, {
|
|
||||||
dataExplorerArea: Areas.Notebook,
|
|
||||||
});
|
|
||||||
Logger.logError(getErrorMessage(error), "");
|
|
||||||
const connectionStatus: ContainerConnectionInfo = {
|
|
||||||
status: ConnectionStatusType.Failed,
|
|
||||||
};
|
|
||||||
useNotebook.getState().resetContainerConnection(connectionStatus);
|
|
||||||
useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed);
|
|
||||||
return {
|
|
||||||
durationLeftInMinutes: undefined,
|
|
||||||
notebookServerInfo: undefined,
|
|
||||||
status: ContainerStatusType.Disconnected,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getContainerHealth(delayMs: number, containerData: IContainerData) {
|
|
||||||
const containerInfo = await this.getContainerStatusAsync(containerData);
|
|
||||||
useNotebook.getState().setContainerStatus(containerInfo);
|
|
||||||
if (useNotebook.getState().containerStatus?.status === ContainerStatusType.Active) {
|
|
||||||
this.scheduleContainerHeartbeat(delayMs, containerData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async isDbAcountWhitelisted(): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
const response = await window.fetch(`${this.getPhoenixControlPlanePathPrefix()}`, {
|
|
||||||
method: "GET",
|
|
||||||
headers: PhoenixClient.getHeaders(),
|
|
||||||
});
|
|
||||||
return response.status === HttpStatusCodes.OK;
|
|
||||||
} catch (error) {
|
|
||||||
Logger.logError(getErrorMessage(error), "PhoenixClient/IsDbAcountWhitelisted");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getPhoenixEndpoint(): string {
|
public static getPhoenixEndpoint(): string {
|
||||||
const phoenixEndpoint =
|
const phoenixEndpoint =
|
||||||
userContext.features.phoenixEndpoint ?? userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT;
|
userContext.features.phoenixEndpoint ?? userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT;
|
||||||
if (!validateEndpoint(phoenixEndpoint, allowedJunoOrigins)) {
|
if (configContext.allowedJunoOrigins.indexOf(new URL(phoenixEndpoint).origin) === -1) {
|
||||||
const error = `${phoenixEndpoint} not allowed as juno endpoint`;
|
const error = `${phoenixEndpoint} not allowed as juno endpoint`;
|
||||||
console.error(error);
|
console.error(error);
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
@@ -164,12 +54,9 @@ export class PhoenixClient {
|
|||||||
return phoenixEndpoint;
|
return phoenixEndpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPhoenixControlPlanePathPrefix(): string {
|
public getPhoenixContainerPoolingEndPoint(): string {
|
||||||
return `${PhoenixClient.getPhoenixEndpoint()}/api/controlplane/toolscontainer/cosmosaccounts${
|
return `${PhoenixClient.getPhoenixEndpoint()}/api/controlplane/toolscontainer`;
|
||||||
userContext.databaseAccount.id
|
|
||||||
}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static getHeaders(): HeadersInit {
|
private static getHeaders(): HeadersInit {
|
||||||
const authorizationHeader = getAuthorizationHeader();
|
const authorizationHeader = getAuthorizationHeader();
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
export type Features = {
|
export type Features = {
|
||||||
// set only via feature flags
|
|
||||||
readonly canExceedMaximumValue: boolean;
|
readonly canExceedMaximumValue: boolean;
|
||||||
readonly cosmosdb: boolean;
|
readonly cosmosdb: boolean;
|
||||||
readonly enableChangeFeedPolicy: boolean;
|
readonly enableChangeFeedPolicy: boolean;
|
||||||
@@ -9,6 +8,10 @@ export type Features = {
|
|||||||
readonly enableReactPane: boolean;
|
readonly enableReactPane: boolean;
|
||||||
readonly enableRightPanelV2: boolean;
|
readonly enableRightPanelV2: boolean;
|
||||||
readonly enableSchema: boolean;
|
readonly enableSchema: boolean;
|
||||||
|
autoscaleDefault: boolean;
|
||||||
|
partitionKeyDefault: boolean;
|
||||||
|
partitionKeyDefault2: boolean;
|
||||||
|
phoenix: boolean;
|
||||||
readonly enableSDKoperations: boolean;
|
readonly enableSDKoperations: boolean;
|
||||||
readonly enableSpark: boolean;
|
readonly enableSpark: boolean;
|
||||||
readonly enableTtl: boolean;
|
readonly enableTtl: boolean;
|
||||||
@@ -18,6 +21,7 @@ export type Features = {
|
|||||||
readonly hostedDataExplorer: boolean;
|
readonly hostedDataExplorer: boolean;
|
||||||
readonly junoEndpoint?: string;
|
readonly junoEndpoint?: string;
|
||||||
readonly phoenixEndpoint?: string;
|
readonly phoenixEndpoint?: string;
|
||||||
|
readonly livyEndpoint?: string;
|
||||||
readonly notebookBasePath?: string;
|
readonly notebookBasePath?: string;
|
||||||
readonly notebookServerToken?: string;
|
readonly notebookServerToken?: string;
|
||||||
readonly notebookServerUrl?: string;
|
readonly notebookServerUrl?: string;
|
||||||
@@ -28,23 +32,12 @@ export type Features = {
|
|||||||
readonly ttl90Days: boolean;
|
readonly ttl90Days: boolean;
|
||||||
readonly mongoProxyEndpoint?: string;
|
readonly mongoProxyEndpoint?: string;
|
||||||
readonly mongoProxyAPIs?: string;
|
readonly mongoProxyAPIs?: string;
|
||||||
readonly enableThroughputCap: boolean;
|
readonly notebooksTemporarilyDown: boolean;
|
||||||
|
|
||||||
// can be set via both flight and feature flag
|
|
||||||
autoscaleDefault: boolean;
|
|
||||||
partitionKeyDefault: boolean;
|
|
||||||
partitionKeyDefault2: boolean;
|
|
||||||
phoenixNotebooks?: boolean;
|
|
||||||
phoenixFeatures?: boolean;
|
|
||||||
notebooksDownBanner: boolean;
|
|
||||||
freetierAutoscaleThroughput: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function extractFeatures(given = new URLSearchParams(window.location.search)): Features {
|
export function extractFeatures(given = new URLSearchParams(window.location.search)): Features {
|
||||||
const downcased = new URLSearchParams();
|
const downcased = new URLSearchParams();
|
||||||
const set = (value: string, key: string) => {
|
const set = (value: string, key: string) => downcased.set(key.toLowerCase(), value);
|
||||||
downcased.set(key.toLowerCase(), value);
|
|
||||||
};
|
|
||||||
const get = (key: string, defaultValue?: string) =>
|
const get = (key: string, defaultValue?: string) =>
|
||||||
downcased.get("feature." + key) ?? downcased.get(key) ?? defaultValue;
|
downcased.get("feature." + key) ?? downcased.get(key) ?? defaultValue;
|
||||||
|
|
||||||
@@ -77,6 +70,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
|
|||||||
mongoProxyAPIs: get("mongoproxyapis"),
|
mongoProxyAPIs: get("mongoproxyapis"),
|
||||||
junoEndpoint: get("junoendpoint"),
|
junoEndpoint: get("junoendpoint"),
|
||||||
phoenixEndpoint: get("phoenixendpoint"),
|
phoenixEndpoint: get("phoenixendpoint"),
|
||||||
|
livyEndpoint: get("livyendpoint"),
|
||||||
notebookBasePath: get("notebookbasepath"),
|
notebookBasePath: get("notebookbasepath"),
|
||||||
notebookServerToken: get("notebookservertoken"),
|
notebookServerToken: get("notebookservertoken"),
|
||||||
notebookServerUrl: get("notebookserverurl"),
|
notebookServerUrl: get("notebookserverurl"),
|
||||||
@@ -88,9 +82,8 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
|
|||||||
autoscaleDefault: "true" === get("autoscaledefault"),
|
autoscaleDefault: "true" === get("autoscaledefault"),
|
||||||
partitionKeyDefault: "true" === get("partitionkeytest"),
|
partitionKeyDefault: "true" === get("partitionkeytest"),
|
||||||
partitionKeyDefault2: "true" === get("pkpartitionkeytest"),
|
partitionKeyDefault2: "true" === get("pkpartitionkeytest"),
|
||||||
notebooksDownBanner: "true" === get("notebooksDownBanner"),
|
notebooksTemporarilyDown: "true" === get("notebookstemporarilydown", "true"),
|
||||||
enableThroughputCap: "true" === get("enablethroughputcap"),
|
phoenix: "true" === get("phoenix"),
|
||||||
freetierAutoscaleThroughput: "true" === get("freetierautoscalethroughput"),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { RefreshResult } from "../SelfServeTypes";
|
|||||||
import SqlX from "./SqlX";
|
import SqlX from "./SqlX";
|
||||||
import {
|
import {
|
||||||
FetchPricesResponse,
|
FetchPricesResponse,
|
||||||
PriceMapAndCurrencyCode,
|
|
||||||
RegionsResponse,
|
RegionsResponse,
|
||||||
SqlxServiceResource,
|
SqlxServiceResource,
|
||||||
UpdateDedicatedGatewayRequestParameters,
|
UpdateDedicatedGatewayRequestParameters,
|
||||||
@@ -179,18 +178,18 @@ const getFetchPricesPathForRegion = (subscriptionId: string): string => {
|
|||||||
return `/subscriptions/${subscriptionId}/providers/Microsoft.CostManagement/fetchPrices`;
|
return `/subscriptions/${subscriptionId}/providers/Microsoft.CostManagement/fetchPrices`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getPriceMapAndCurrencyCode = async (regions: Array<string>): Promise<PriceMapAndCurrencyCode> => {
|
export const getPriceMap = async (regions: Array<string>): Promise<Map<string, Map<string, number>>> => {
|
||||||
const telemetryData = {
|
const telemetryData = {
|
||||||
feature: "Calculate approximate cost",
|
feature: "Calculate approximate cost",
|
||||||
function: "getPriceMapAndCurrencyCode",
|
function: "getPriceMap",
|
||||||
description: "fetch prices API call",
|
description: "fetch prices API call",
|
||||||
selfServeClassName: SqlX.name,
|
selfServeClassName: SqlX.name,
|
||||||
};
|
};
|
||||||
const getPriceMapAndCurrencyCodeTimestamp = selfServeTraceStart(telemetryData);
|
const getPriceMapTimestamp = selfServeTraceStart(telemetryData);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const priceMap = new Map<string, Map<string, number>>();
|
const priceMap = new Map<string, Map<string, number>>();
|
||||||
let currencyCode;
|
|
||||||
for (const region of regions) {
|
for (const region of regions) {
|
||||||
const regionPriceMap = new Map<string, number>();
|
const regionPriceMap = new Map<string, number>();
|
||||||
|
|
||||||
@@ -208,21 +207,17 @@ export const getPriceMapAndCurrencyCode = async (regions: Array<string>): Promis
|
|||||||
});
|
});
|
||||||
|
|
||||||
for (const item of response.result.Items) {
|
for (const item of response.result.Items) {
|
||||||
if (currencyCode === undefined) {
|
|
||||||
currencyCode = item.currencyCode;
|
|
||||||
} else if (item.currencyCode !== currencyCode) {
|
|
||||||
throw Error("Currency Code Mismatch: Currency code not same for all regions / skus.");
|
|
||||||
}
|
|
||||||
regionPriceMap.set(item.skuName, item.retailPrice);
|
regionPriceMap.set(item.skuName, item.retailPrice);
|
||||||
}
|
}
|
||||||
priceMap.set(region, regionPriceMap);
|
priceMap.set(region, regionPriceMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
selfServeTraceSuccess(telemetryData, getPriceMapAndCurrencyCodeTimestamp);
|
selfServeTraceSuccess(telemetryData, getPriceMapTimestamp);
|
||||||
return { priceMap: priceMap, currencyCode: currencyCode };
|
return priceMap;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const failureTelemetry = { err, selfServeClassName: SqlX.name };
|
const failureTelemetry = { err, selfServeClassName: SqlX.name };
|
||||||
selfServeTraceFailure(failureTelemetry, getPriceMapAndCurrencyCodeTimestamp);
|
selfServeTraceFailure(failureTelemetry, getPriceMapTimestamp);
|
||||||
return { priceMap: undefined, currencyCode: undefined };
|
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { BladeType, generateBladeLink } from "../SelfServeUtils";
|
|||||||
import {
|
import {
|
||||||
deleteDedicatedGatewayResource,
|
deleteDedicatedGatewayResource,
|
||||||
getCurrentProvisioningState,
|
getCurrentProvisioningState,
|
||||||
getPriceMapAndCurrencyCode,
|
getPriceMap,
|
||||||
getRegions,
|
getRegions,
|
||||||
refreshDedicatedGatewayProvisioning,
|
refreshDedicatedGatewayProvisioning,
|
||||||
updateDedicatedGatewayResource,
|
updateDedicatedGatewayResource,
|
||||||
@@ -207,7 +207,6 @@ const ApproximateCostDropDownInfo: Info = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let priceMap: Map<string, Map<string, number>>;
|
let priceMap: Map<string, Map<string, number>>;
|
||||||
let currencyCode: string;
|
|
||||||
let regions: Array<string>;
|
let regions: Array<string>;
|
||||||
|
|
||||||
const calculateCost = (skuName: string, instanceCount: number): Description => {
|
const calculateCost = (skuName: string, instanceCount: number): Description => {
|
||||||
@@ -238,7 +237,7 @@ const calculateCost = (skuName: string, instanceCount: number): Description => {
|
|||||||
|
|
||||||
selfServeTraceSuccess(telemetryData, calculateCostTimestamp);
|
selfServeTraceSuccess(telemetryData, calculateCostTimestamp);
|
||||||
return {
|
return {
|
||||||
textTKey: `${costPerHour} ${currencyCode}`,
|
textTKey: `${costPerHour} USD`,
|
||||||
type: DescriptionType.Text,
|
type: DescriptionType.Text,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -347,9 +346,7 @@ export default class SqlX extends SelfServeBaseClass {
|
|||||||
});
|
});
|
||||||
|
|
||||||
regions = await getRegions();
|
regions = await getRegions();
|
||||||
const priceMapAndCurrencyCode = await getPriceMapAndCurrencyCode(regions);
|
priceMap = await getPriceMap(regions);
|
||||||
priceMap = priceMapAndCurrencyCode.priceMap;
|
|
||||||
currencyCode = priceMapAndCurrencyCode.currencyCode;
|
|
||||||
|
|
||||||
const response = await getCurrentProvisioningState();
|
const response = await getCurrentProvisioningState();
|
||||||
if (response.status && response.status !== "Deleting") {
|
if (response.status && response.status !== "Deleting") {
|
||||||
|
|||||||
@@ -36,15 +36,9 @@ export type FetchPricesResponse = {
|
|||||||
Count: number;
|
Count: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PriceMapAndCurrencyCode = {
|
|
||||||
priceMap: Map<string, Map<string, number>>;
|
|
||||||
currencyCode: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PriceItem = {
|
export type PriceItem = {
|
||||||
retailPrice: number;
|
retailPrice: number;
|
||||||
skuName: string;
|
skuName: string;
|
||||||
currencyCode: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RegionsResponse = {
|
export type RegionsResponse = {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user