mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-23 10:51:30 +00:00
Compare commits
2 Commits
fix_a11y_D
...
add-eslint
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27d129e689 | ||
|
|
fb9f97b43a |
@@ -81,9 +81,17 @@ src/Explorer/Tables/DataTable/DataTableBindingManager.ts
|
||||
src/Explorer/Tables/DataTable/DataTableBuilder.ts
|
||||
src/Explorer/Tables/DataTable/DataTableContextMenu.ts
|
||||
src/Explorer/Tables/DataTable/DataTableOperationManager.ts
|
||||
src/Explorer/Tables/DataTable/DataTableOperations.ts
|
||||
src/Explorer/Tables/DataTable/DataTableViewModel.ts
|
||||
src/Explorer/Tables/DataTable/TableCommands.ts
|
||||
src/Explorer/Tables/DataTable/TableEntityCache.ts
|
||||
src/Explorer/Tables/DataTable/TableEntityListViewModel.ts
|
||||
src/Explorer/Tables/Entities.ts
|
||||
src/Explorer/Tables/QueryBuilder/ClauseGroup.ts
|
||||
src/Explorer/Tables/QueryBuilder/ClauseGroupViewModel.ts
|
||||
src/Explorer/Tables/QueryBuilder/CustomTimestampHelper.ts
|
||||
src/Explorer/Tables/QueryBuilder/QueryBuilderViewModel.ts
|
||||
src/Explorer/Tables/QueryBuilder/QueryClauseViewModel.ts
|
||||
src/Explorer/Tables/TableDataClient.ts
|
||||
src/Explorer/Tables/TableEntityProcessor.ts
|
||||
src/Explorer/Tables/Utilities.ts
|
||||
@@ -107,10 +115,15 @@ src/Explorer/Tree/ObjectId.ts
|
||||
src/Explorer/Tree/ResourceTokenCollection.ts
|
||||
src/Explorer/Tree/StoredProcedure.ts
|
||||
src/Explorer/Tree/TreeComponents.ts
|
||||
src/Explorer/Tree/Trigger.ts
|
||||
src/Explorer/WaitsForTemplateViewModel.ts
|
||||
src/GitHub/GitHubClient.test.ts
|
||||
src/GitHub/GitHubClient.ts
|
||||
src/GitHub/GitHubConnector.ts
|
||||
src/GitHub/GitHubOAuthService.ts
|
||||
src/Index.ts
|
||||
src/Juno/JunoClient.test.ts
|
||||
src/Juno/JunoClient.ts
|
||||
src/Platform/Hosted/Authorization.ts
|
||||
src/ReactDevTools.ts
|
||||
src/Shared/Constants.ts
|
||||
@@ -130,13 +143,20 @@ src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx
|
||||
src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx
|
||||
src/Explorer/Controls/TreeComponent/TreeComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/GraphVizComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/LeftPaneComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/MiddlePaneComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.test.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx
|
||||
src/Explorer/Menus/CommandBar/CommandBarUtil.tsx
|
||||
src/Explorer/Notebook/NotebookComponent/NotebookComponentAdapter.tsx
|
||||
src/Explorer/Notebook/NotebookComponent/NotebookComponentBootstrapper.tsx
|
||||
src/Explorer/Notebook/NotebookComponent/VirtualCommandBarComponent.tsx
|
||||
src/Explorer/Notebook/NotebookComponent/contents/index.tsx
|
||||
src/Explorer/Notebook/NotebookRenderer/NotebookReadOnlyRenderer.tsx
|
||||
src/Explorer/Notebook/NotebookRenderer/NotebookRenderer.tsx
|
||||
src/Explorer/Notebook/NotebookRenderer/decorators/draggable/index.tsx
|
||||
src/Explorer/Notebook/NotebookRenderer/decorators/hijack-scroll/index.tsx
|
||||
|
||||
@@ -3,8 +3,8 @@ module.exports = {
|
||||
browser: true,
|
||||
es6: true,
|
||||
},
|
||||
plugins: ["@typescript-eslint", "no-null", "prefer-arrow", "react-hooks"],
|
||||
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
||||
plugins: ["@typescript-eslint", "no-null", "prefer-arrow", "react-hooks", "jsx-a11y"],
|
||||
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:jsx-a11y/recommended"],
|
||||
globals: {
|
||||
Atomics: "readonly",
|
||||
SharedArrayBuffer: "readonly",
|
||||
@@ -39,6 +39,7 @@ module.exports = {
|
||||
"@typescript-eslint/switch-exhaustiveness-check": "error",
|
||||
"@typescript-eslint/no-unused-vars": "error",
|
||||
"@typescript-eslint/no-extraneous-class": "error",
|
||||
"no-null/no-null": "error",
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }],
|
||||
eqeqeq: "error",
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -22,6 +22,5 @@
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true,
|
||||
"source.organizeImports": true
|
||||
},
|
||||
"typescript.preferences.importModuleSpecifier": "non-relative"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
{
|
||||
"JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com",
|
||||
"isTerminalEnabled" : true
|
||||
"JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com"
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
{
|
||||
"JUNO_ENDPOINT": "https://tools.cosmos.azure.com",
|
||||
"isTerminalEnabled" : false
|
||||
"JUNO_ENDPOINT": "https://tools.cosmos.azure.com"
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ module.exports = {
|
||||
global: {
|
||||
branches: 25,
|
||||
functions: 25,
|
||||
lines: 29,
|
||||
statements: 29,
|
||||
lines: 29.5,
|
||||
statements: 29.5,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -129,8 +129,6 @@ module.exports = {
|
||||
// The test environment that will be used for testing
|
||||
// testEnvironment: "jest-environment-jsdom",
|
||||
|
||||
modulePaths: ["node_modules", "<rootDir>/src"],
|
||||
|
||||
// Options that will be passed to the testEnvironment
|
||||
// testEnvironmentOptions: {},
|
||||
|
||||
|
||||
@@ -2077,7 +2077,7 @@ a:link {
|
||||
.resourceTreeAndTabs {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
overflow-x: clip;
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -2245,7 +2245,7 @@ a:link {
|
||||
}
|
||||
|
||||
.refreshColHeader {
|
||||
padding: 3px 6px 10px 0px !important;
|
||||
padding: 3px 6px 6px 6px;
|
||||
}
|
||||
|
||||
.refreshColHeader:hover {
|
||||
@@ -2869,39 +2869,31 @@ a:link {
|
||||
}
|
||||
}
|
||||
|
||||
.settingsSection {
|
||||
border-bottom: 1px solid @BaseMedium;
|
||||
margin-right: 24px;
|
||||
padding: @MediumSpace 0px;
|
||||
settings-pane {
|
||||
.settingsSection {
|
||||
border-bottom: 1px solid @BaseMedium;
|
||||
margin-right: 24px;
|
||||
padding: @MediumSpace 0px;
|
||||
|
||||
&:first-child {
|
||||
padding-top: 0px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
&:first-child {
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.settingsSectionPart {
|
||||
padding-left: 8px;
|
||||
}
|
||||
.settingsSectionPart {
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.settingsSectionLabel {
|
||||
margin-bottom: @DefaultSpace;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.settingsSectionLabel {
|
||||
margin-bottom: @DefaultSpace;
|
||||
}
|
||||
|
||||
.pageOptionsPart {
|
||||
padding-bottom: @MediumSpace;
|
||||
}
|
||||
|
||||
.legendLabel {
|
||||
border-bottom: 0px;
|
||||
width: auto;
|
||||
font-size: @mediumFontSize;
|
||||
display: inline !important;
|
||||
float: left;
|
||||
.pageOptionsPart {
|
||||
padding-bottom: @MediumSpace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3087,3 +3079,6 @@ a:link {
|
||||
background: white;
|
||||
height: 100%;
|
||||
}
|
||||
.moreOption {
|
||||
color: #337ab7;
|
||||
}
|
||||
29757
package-lock.json
generated
29757
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -61,6 +61,7 @@
|
||||
"dom-to-image": "2.6.0",
|
||||
"dotenv": "8.2.0",
|
||||
"eslint-plugin-jest": "23.13.2",
|
||||
"eslint-plugin-jsx-a11y": "6.4.1",
|
||||
"eslint-plugin-react": "7.20.0",
|
||||
"hasher": "1.2.0",
|
||||
"html2canvas": "1.0.0-rc.5",
|
||||
|
||||
@@ -31,17 +31,15 @@ export const CollapsedResourceTree: FunctionComponent<CollapsedResourceTreeProps
|
||||
<div id="mini" className={!isLeftPaneExpanded ? "mini toggle-mini" : "hiddenMain"}>
|
||||
<div className="main-nav nav">
|
||||
<ul className="nav">
|
||||
<li
|
||||
className="resourceTreeCollapse"
|
||||
id="collapseToggleLeftPaneButton"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label="Expand Tree"
|
||||
onClick={toggleLeftPaneExpanded}
|
||||
onKeyPress={onKeyPressToggleLeftPaneExpanded}
|
||||
ref={focusButton}
|
||||
>
|
||||
<span className="leftarrowCollapsed">
|
||||
<li className="resourceTreeCollapse" id="collapseToggleLeftPaneButton" aria-label="Expand Tree">
|
||||
<span
|
||||
className="leftarrowCollapsed"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={toggleLeftPaneExpanded}
|
||||
onKeyPress={onKeyPressToggleLeftPaneExpanded}
|
||||
ref={focusButton}
|
||||
>
|
||||
<img className="arrowCollapsed" src={arrowLeftImg} alt="Expand" />
|
||||
</span>
|
||||
<span className="collectionCollapsed">
|
||||
|
||||
@@ -96,9 +96,7 @@ export class Flights {
|
||||
public static readonly AutoscaleTest = "autoscaletest";
|
||||
public static readonly PartitionKeyTest = "partitionkeytest";
|
||||
public static readonly PKPartitionKeyTest = "pkpartitionkeytest";
|
||||
public static readonly PhoenixNotebooks = "phoenixnotebooks";
|
||||
public static readonly PhoenixFeatures = "phoenixfeatures";
|
||||
public static readonly NotebooksDownBanner = "notebooksdownbanner";
|
||||
public static readonly Phoenix = "phoenix";
|
||||
}
|
||||
|
||||
export class AfecFeatures {
|
||||
@@ -341,16 +339,9 @@ export enum ConflictOperationType {
|
||||
}
|
||||
|
||||
export enum ConnectionStatusType {
|
||||
Connect = "Connect",
|
||||
Connecting = "Connecting",
|
||||
Connected = "Connected",
|
||||
Failed = "Connection Failed",
|
||||
Reconnect = "Reconnect",
|
||||
}
|
||||
|
||||
export enum ContainerStatusType {
|
||||
Active = "Active",
|
||||
Disconnected = "Disconnected",
|
||||
}
|
||||
|
||||
export const EmulatorMasterKey =
|
||||
@@ -362,37 +353,15 @@ export const StyleConstants = require("less-vars-loader!../../less/Common/Consta
|
||||
|
||||
export class Notebook {
|
||||
public static readonly defaultBasePath = "./notebooks";
|
||||
public static readonly heartbeatDelayMs = 60000;
|
||||
public static readonly containerStatusHeartbeatDelayMs = 30000;
|
||||
public static readonly heartbeatDelayMs = 5000;
|
||||
public static readonly kernelRestartInitialDelayMs = 1000;
|
||||
public static readonly kernelRestartMaxDelayMs = 20000;
|
||||
public static readonly autoSaveIntervalMs = 300000;
|
||||
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 autoSaveIntervalMs = 120000;
|
||||
public static readonly temporarilyDownMsg = "Notebooks is currently not available. We are working on it.";
|
||||
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.";
|
||||
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.";
|
||||
public static saveNotebookModalTitle = "Save notebook in temporary workspace";
|
||||
public static saveNotebookModalContent =
|
||||
"This notebook will be saved in the temporary workspace and will be removed when the session expires.";
|
||||
public static newNotebookModalTitle = "Create notebook in temporary workspace";
|
||||
public static newNotebookUploadModalTitle = "Upload notebook to temporary workspace";
|
||||
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.";
|
||||
public static newNotebookModalContent2 =
|
||||
"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 galleryNotebookDownloadContent1 =
|
||||
"To download, run, and make changes to this sample notebook, a temporary workspace will be created. When the session expires, any notebooks in the workspace will be removed.";
|
||||
public static galleryNotebookDownloadContent2 =
|
||||
"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 cosmosNotebookHomePageUrl = "https://aka.ms/cosmos-notebooks-limits";
|
||||
public static cosmosNotebookGitDocumentationUrl = "https://aka.ms/cosmos-notebooks-github";
|
||||
public static learnMore = "Learn more.";
|
||||
}
|
||||
|
||||
export class SparkLibrary {
|
||||
@@ -413,11 +382,3 @@ export class TerminalQueryParams {
|
||||
public static readonly SubscriptionId = "subscriptionId";
|
||||
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 { RequestInfo, setAuthorizationTokenHeaderUsingMasterKey } from "@azure/cosmos";
|
||||
import { CosmosHeaders } from "@azure/cosmos/dist-esm";
|
||||
import { configContext, Platform } from "../ConfigContext";
|
||||
import { userContext } from "../UserContext";
|
||||
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;
|
||||
|
||||
export function client(): Cosmos.CosmosClient {
|
||||
if (_client) return _client;
|
||||
|
||||
let _defaultHeaders: CosmosHeaders = {};
|
||||
_defaultHeaders["x-ms-cosmos-sdk-supported-capabilities"] =
|
||||
SDKSupportedCapabilities.None | SDKSupportedCapabilities.PartitionMerge;
|
||||
|
||||
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
|
||||
key: userContext.masterKey,
|
||||
@@ -101,7 +89,6 @@ export function client(): Cosmos.CosmosClient {
|
||||
enableEndpointDiscovery: false,
|
||||
},
|
||||
userAgentSuffix: "Azure Portal",
|
||||
defaultHeaders: _defaultHeaders,
|
||||
};
|
||||
|
||||
if (configContext.PROXY_PATH !== undefined) {
|
||||
|
||||
@@ -40,7 +40,6 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
|
||||
<TextField
|
||||
label={entityValueLabel && entityValueLabel}
|
||||
id="entityTimeId"
|
||||
autoFocus
|
||||
type="time"
|
||||
value={entityTimeValue}
|
||||
onChange={onEntityTimeValueChange}
|
||||
@@ -55,7 +54,6 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
|
||||
label={entityValueLabel && entityValueLabel}
|
||||
className="addEntityTextField"
|
||||
id="entityValueId"
|
||||
autoFocus
|
||||
disabled={isEntityValueDisable}
|
||||
type={entityValueType}
|
||||
placeholder={entityValuePlaceholder}
|
||||
|
||||
@@ -96,7 +96,6 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
|
||||
<TextField
|
||||
label={entityPropertyLabel && entityPropertyLabel}
|
||||
id="entityPropertyId"
|
||||
autoFocus
|
||||
disabled={isPropertyTypeDisable}
|
||||
placeholder={entityPropertyPlaceHolder}
|
||||
value={entityProperty}
|
||||
|
||||
@@ -66,9 +66,15 @@ export const Upload: FunctionComponent<UploadProps> = ({
|
||||
onChange={onUpload}
|
||||
role="button"
|
||||
/>
|
||||
<a href="#" id="fileImportLinkNotebook" onClick={onImportLinkClick} onKeyPress={onImportLinkKeyPress}>
|
||||
<span
|
||||
id="fileImportLinkNotebook"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={onImportLinkClick}
|
||||
onKeyPress={onImportLinkKeyPress}
|
||||
>
|
||||
<Image className="fileImportImg" src={FolderIcon} alt={title} title={title} />
|
||||
</a>
|
||||
</span>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { JunoEndpoints } from "Common/Constants";
|
||||
|
||||
export enum Platform {
|
||||
Portal = "Portal",
|
||||
Hosted = "Hosted",
|
||||
@@ -25,9 +23,7 @@ export interface ConfigContext {
|
||||
PROXY_PATH?: string;
|
||||
JUNO_ENDPOINT: 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.
|
||||
isTerminalEnabled: boolean;
|
||||
hostedExplorerURL: string;
|
||||
armAPIVersion?: string;
|
||||
allowedJunoOrigins: string[];
|
||||
@@ -56,17 +52,14 @@ let configContext: Readonly<ConfigContext> = {
|
||||
GRAPH_API_VERSION: "1.6",
|
||||
ARCADIA_ENDPOINT: "https://workspaceartifacts.projectarcadia.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_TEST_ENV_CLIENT_ID: "b63fc8cbf87fd3c6e2eb", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1777772
|
||||
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/settings/applications/1189306
|
||||
JUNO_ENDPOINT: "https://tools.cosmos.azure.com",
|
||||
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
|
||||
isTerminalEnabled: false,
|
||||
allowedJunoOrigins: [
|
||||
JunoEndpoints.Test,
|
||||
JunoEndpoints.Test2,
|
||||
JunoEndpoints.Test3,
|
||||
JunoEndpoints.Prod,
|
||||
JunoEndpoints.Stage,
|
||||
"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",
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ConnectionStatusType, ContainerStatusType } from "../Common/Constants";
|
||||
import { ConnectionStatusType } from "../Common/Constants";
|
||||
|
||||
export interface DatabaseAccount {
|
||||
id: string;
|
||||
@@ -26,8 +26,6 @@ export interface DatabaseAccountExtendedProperties {
|
||||
isVirtualNetworkFilterEnabled?: boolean;
|
||||
ipRules?: IpRule[];
|
||||
privateEndpointConnections?: unknown[];
|
||||
capacity?: { totalThroughputLimit: number };
|
||||
locations?: DatabaseAccountResponseLocation[];
|
||||
}
|
||||
|
||||
export interface DatabaseAccountResponseLocation {
|
||||
@@ -428,32 +426,6 @@ export interface OperationStatus {
|
||||
export interface NotebookWorkspaceConnectionInfo {
|
||||
authToken: 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 {
|
||||
|
||||
@@ -33,7 +33,6 @@ export enum MessageTypes {
|
||||
CreateWorkspace,
|
||||
CreateSparkPool,
|
||||
RefreshDatabaseAccount,
|
||||
CloseTab,
|
||||
}
|
||||
|
||||
export { Versions, ActionContracts, Diagnostics };
|
||||
|
||||
@@ -83,6 +83,7 @@ export const createCollectionContextMenuButton = (
|
||||
|
||||
items.push({
|
||||
iconSrc: HostedTerminalIcon,
|
||||
isDisabled: useNotebook.getState().isShellEnabled && userContext.features.notebooksTemporarilyDown,
|
||||
onClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||
if (useNotebook.getState().isShellEnabled) {
|
||||
|
||||
@@ -55,7 +55,13 @@ export class AccordionItemComponent extends React.Component<AccordionItemCompone
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<div className="accordionItemContainer">
|
||||
<div className="accordionItemHeader" onClick={this.onHeaderClick} onKeyPress={this.onHeaderKeyPress}>
|
||||
<div
|
||||
className="accordionItemHeader"
|
||||
onClick={this.onHeaderClick}
|
||||
onKeyPress={this.onHeaderKeyPress}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
{this.renderCollapseExpandIcon()}
|
||||
{this.props.title}
|
||||
</div>
|
||||
@@ -74,8 +80,6 @@ export class AccordionItemComponent extends React.Component<AccordionItemCompone
|
||||
className="expandCollapseIcon"
|
||||
src={this.state.isExpanded ? TriangleDownIcon : TriangleRightIcon}
|
||||
alt="Hide"
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
|
||||
onClick={this.toggleCollapsed}
|
||||
onKeyPress={this.onKeyPress}
|
||||
tabIndex={0}
|
||||
aria-name="Advanced"
|
||||
aria-label="Advanced"
|
||||
role="button"
|
||||
aria-expanded={this.state.isExpanded}
|
||||
>
|
||||
|
||||
@@ -4,7 +4,7 @@ exports[`CollapsibleSectionComponent renders 1`] = `
|
||||
<Fragment>
|
||||
<Stack
|
||||
aria-expanded={true}
|
||||
aria-name="Advanced"
|
||||
aria-label="Advanced"
|
||||
className="collapsibleSection"
|
||||
horizontal={true}
|
||||
onClick={[Function]}
|
||||
|
||||
@@ -188,12 +188,13 @@ export class CommandButtonComponent extends React.Component<CommandButtonCompone
|
||||
ref={(ref: HTMLElement) => {
|
||||
this.expandButtonElt = ref;
|
||||
}}
|
||||
role="button"
|
||||
onKeyDown={(e: React.KeyboardEvent<HTMLDivElement>) => this.onLauncherKeyDown(e)}
|
||||
>
|
||||
<div className="commandDropdownLauncher">
|
||||
<span className="partialSplitter" />
|
||||
<span className="expandDropdown">
|
||||
<img src={CollapseChevronDownIcon} />
|
||||
<img src={CollapseChevronDownIcon} alt="Collapse down icon" />
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -30,7 +30,6 @@ export interface DialogState {
|
||||
onOk: () => void,
|
||||
cancelLabel: string,
|
||||
onCancel: () => void,
|
||||
contentHtml?: JSX.Element,
|
||||
choiceGroupProps?: IChoiceGroupProps,
|
||||
textFieldProps?: TextFieldProps,
|
||||
primaryButtonDisabled?: boolean
|
||||
@@ -59,7 +58,6 @@ export const useDialog: UseStore<DialogState> = create((set, get) => ({
|
||||
onOk: () => void,
|
||||
cancelLabel: string,
|
||||
onCancel: () => void,
|
||||
contentHtml?: JSX.Element,
|
||||
choiceGroupProps?: IChoiceGroupProps,
|
||||
textFieldProps?: TextFieldProps,
|
||||
primaryButtonDisabled?: boolean
|
||||
@@ -78,7 +76,6 @@ export const useDialog: UseStore<DialogState> = create((set, get) => ({
|
||||
get().closeDialog();
|
||||
onCancel && onCancel();
|
||||
},
|
||||
contentHtml,
|
||||
choiceGroupProps,
|
||||
textFieldProps,
|
||||
primaryButtonDisabled,
|
||||
@@ -127,7 +124,6 @@ export interface DialogProps {
|
||||
type?: DialogType;
|
||||
showCloseButton?: boolean;
|
||||
onDismiss?: () => void;
|
||||
contentHtml?: JSX.Element;
|
||||
}
|
||||
|
||||
const DIALOG_MIN_WIDTH = "400px";
|
||||
@@ -154,7 +150,6 @@ export const Dialog: FC = () => {
|
||||
type,
|
||||
showCloseButton,
|
||||
onDismiss,
|
||||
contentHtml,
|
||||
} = props || {};
|
||||
|
||||
const dialogProps: IDialogProps = {
|
||||
@@ -196,7 +191,6 @@ export const Dialog: FC = () => {
|
||||
{linkProps.linkText} <FontIcon iconName="NavigateExternalInline" />
|
||||
</Link>
|
||||
)}
|
||||
{contentHtml}
|
||||
{progressIndicatorProps && <ProgressIndicator {...progressIndicatorProps} />}
|
||||
<DialogFooter>
|
||||
<PrimaryButton {...primaryButtonProps} />
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { DefaultButton, IButtonProps, ITextFieldProps, TextField } from "@fluentui/react";
|
||||
import * as React from "react";
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import * as UrlUtility from "../../../Common/UrlUtility";
|
||||
import { IGitHubRepo } from "../../../GitHub/GitHubClient";
|
||||
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 { 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 {
|
||||
container: Explorer;
|
||||
@@ -27,6 +27,7 @@ export class AddRepoComponent extends React.Component<AddRepoComponentProps, Add
|
||||
private static readonly ButtonText = "Add";
|
||||
private static readonly TextFieldPlaceholder = "https://github.com/owner/repo/tree/branch";
|
||||
private static readonly TextFieldErrorMessage = "Invalid url";
|
||||
private static readonly DefaultBranchName = "master";
|
||||
|
||||
constructor(props: AddRepoComponentProps) {
|
||||
super(props);
|
||||
@@ -77,7 +78,7 @@ export class AddRepoComponent extends React.Component<AddRepoComponentProps, Add
|
||||
});
|
||||
let enteredUrl = this.state.textFieldValue;
|
||||
if (enteredUrl.indexOf("/tree/") === -1) {
|
||||
enteredUrl = UrlUtility.createUri(enteredUrl, `tree/`);
|
||||
enteredUrl = UrlUtility.createUri(enteredUrl, `tree/${AddRepoComponent.DefaultBranchName}`);
|
||||
}
|
||||
|
||||
const repoInfo = GitHubUtils.fromRepoUri(enteredUrl);
|
||||
@@ -92,7 +93,11 @@ export class AddRepoComponent extends React.Component<AddRepoComponentProps, Add
|
||||
const item: RepoListItem = {
|
||||
key: GitHubUtils.toRepoFullName(repo.owner, repo.name),
|
||||
repo,
|
||||
branches: repoInfo.branch ? [{ name: repoInfo.branch }] : [],
|
||||
branches: [
|
||||
{
|
||||
name: repoInfo.branch,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
TelemetryProcessor.traceSuccess(
|
||||
|
||||
@@ -24,11 +24,11 @@ import { RepoListItem } from "./GitHubReposComponent";
|
||||
import {
|
||||
BranchesDropdownCheckboxStyles,
|
||||
BranchesDropdownOptionContainerStyle,
|
||||
BranchesDropdownStyles,
|
||||
BranchesDropdownWidth,
|
||||
ReposListBranchesColumnWidth,
|
||||
ReposListCheckboxStyles,
|
||||
ReposListRepoColumnMinWidth,
|
||||
ReposListBranchesColumnWidth,
|
||||
BranchesDropdownWidth,
|
||||
BranchesDropdownStyles,
|
||||
} from "./GitHubStyleConstants";
|
||||
|
||||
export interface ReposListComponentProps {
|
||||
@@ -44,7 +44,6 @@ export interface BranchesProps {
|
||||
lastPageInfo?: IGitHubPageInfo;
|
||||
hasMore: boolean;
|
||||
isLoading: boolean;
|
||||
defaultBranchName: string;
|
||||
loadMore: () => void;
|
||||
}
|
||||
|
||||
@@ -65,7 +64,7 @@ export class ReposListComponent extends React.Component<ReposListComponentProps>
|
||||
private static readonly BranchesColumnName = "Branches";
|
||||
private static readonly LoadingText = "Loading...";
|
||||
private static readonly LoadMoreText = "Load more";
|
||||
private static readonly DefaultBranchNames = "master/main";
|
||||
private static readonly DefaultBranchName = "master";
|
||||
private static readonly FooterIndex = -1;
|
||||
|
||||
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)];
|
||||
if (item.branches.length === 0 && branchesProps.defaultBranchName) {
|
||||
item.branches = [{ name: branchesProps.defaultBranchName }];
|
||||
}
|
||||
|
||||
const options: IDropdownOption[] = branchesProps.branches.map((branch) => ({
|
||||
key: branch.name,
|
||||
text: branch.name,
|
||||
@@ -203,7 +198,7 @@ export class ReposListComponent extends React.Component<ReposListComponentProps>
|
||||
const dropdownProps: IDropdownProps = {
|
||||
styles: BranchesDropdownStyles,
|
||||
options: [],
|
||||
placeholder: ReposListComponent.DefaultBranchNames,
|
||||
placeholder: ReposListComponent.DefaultBranchName,
|
||||
disabled: true,
|
||||
};
|
||||
|
||||
@@ -277,7 +272,7 @@ export class ReposListComponent extends React.Component<ReposListComponentProps>
|
||||
styles: ReposListCheckboxStyles,
|
||||
onChange: () => {
|
||||
const repoListItem = { ...item };
|
||||
repoListItem.branches = [];
|
||||
repoListItem.branches = [{ name: ReposListComponent.DefaultBranchName }];
|
||||
this.props.pinRepo(repoListItem);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -35,19 +35,16 @@ const testCassandraAccount: DataModels.DatabaseAccount = {
|
||||
const testNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||
authToken: "authToken",
|
||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com",
|
||||
forwardingId: "Id",
|
||||
};
|
||||
|
||||
const testMongoNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||
authToken: "authToken",
|
||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo",
|
||||
forwardingId: "Id",
|
||||
};
|
||||
|
||||
const testCassandraNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||
authToken: "authToken",
|
||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/cassandra",
|
||||
forwardingId: "Id",
|
||||
};
|
||||
|
||||
describe("NotebookTerminalComponent", () => {
|
||||
@@ -55,7 +52,6 @@ describe("NotebookTerminalComponent", () => {
|
||||
const props: NotebookTerminalComponentProps = {
|
||||
databaseAccount: testAccount,
|
||||
notebookServerInfo: testNotebookServerInfo,
|
||||
tabId: undefined,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||
@@ -66,7 +62,6 @@ describe("NotebookTerminalComponent", () => {
|
||||
const props: NotebookTerminalComponentProps = {
|
||||
databaseAccount: testMongo32Account,
|
||||
notebookServerInfo: testMongoNotebookServerInfo,
|
||||
tabId: undefined,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||
@@ -77,7 +72,6 @@ describe("NotebookTerminalComponent", () => {
|
||||
const props: NotebookTerminalComponentProps = {
|
||||
databaseAccount: testMongo36Account,
|
||||
notebookServerInfo: testMongoNotebookServerInfo,
|
||||
tabId: undefined,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||
@@ -88,7 +82,6 @@ describe("NotebookTerminalComponent", () => {
|
||||
const props: NotebookTerminalComponentProps = {
|
||||
databaseAccount: testCassandraAccount,
|
||||
notebookServerInfo: testCassandraNotebookServerInfo,
|
||||
tabId: undefined,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||
|
||||
@@ -12,7 +12,6 @@ import * as StringUtils from "../../../Utils/StringUtils";
|
||||
export interface NotebookTerminalComponentProps {
|
||||
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
||||
databaseAccount: DataModels.DatabaseAccount;
|
||||
tabId: string;
|
||||
}
|
||||
|
||||
export class NotebookTerminalComponent extends React.Component<NotebookTerminalComponentProps> {
|
||||
@@ -56,7 +55,6 @@ export class NotebookTerminalComponent extends React.Component<NotebookTerminalC
|
||||
apiType: userContext.apiType,
|
||||
authType: userContext.authType,
|
||||
databaseAccount: userContext.databaseAccount,
|
||||
tabId: this.props.tabId,
|
||||
};
|
||||
|
||||
postRobot.send(this.terminalWindow, "props", props, {
|
||||
|
||||
@@ -17,7 +17,6 @@ import Explorer from "../../Explorer";
|
||||
import { NotebookClientV2 } from "../../Notebook/NotebookClientV2";
|
||||
import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper";
|
||||
import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer";
|
||||
import { useNotebook } from "../../Notebook/useNotebook";
|
||||
import { Dialog, TextFieldProps, useDialog } from "../Dialog";
|
||||
import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
|
||||
import "./NotebookViewerComponent.less";
|
||||
@@ -52,7 +51,7 @@ export class NotebookViewerComponent
|
||||
super(props);
|
||||
|
||||
this.clientManager = new NotebookClientV2({
|
||||
connectionInfo: { authToken: undefined, notebookServerEndpoint: undefined, forwardingId: undefined },
|
||||
connectionInfo: { authToken: undefined, notebookServerEndpoint: undefined },
|
||||
databaseAccountName: undefined,
|
||||
defaultExperience: "NotebookViewer",
|
||||
isReadOnly: true,
|
||||
@@ -147,7 +146,7 @@ export class NotebookViewerComponent
|
||||
<NotebookMetadataComponent
|
||||
data={this.state.galleryItem}
|
||||
isFavorite={this.state.isFavorite}
|
||||
downloadButtonText={this.props.container && `Download to ${useNotebook.getState().notebookFolderName}`}
|
||||
downloadButtonText={this.props.container && "Download to my notebooks"}
|
||||
onTagClick={this.props.onTagClick}
|
||||
onFavoriteClick={this.favoriteItem}
|
||||
onUnfavoriteClick={this.unfavoriteItem}
|
||||
|
||||
@@ -150,7 +150,7 @@ export class QueriesGridComponent extends React.Component<QueriesGridComponentPr
|
||||
To write a new query, open a new query tab and enter the desired query. Once ready to save, click on Save
|
||||
Query and follow the prompt in order to save the query.
|
||||
</div>
|
||||
<img {...bannerProps} />
|
||||
<img {...bannerProps} alt="Save query helper banner" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { IPivotItemProps, IPivotProps, Pivot, PivotItem } from "@fluentui/react";
|
||||
import { useDatabases } from "Explorer/useDatabases";
|
||||
import * as React from "react";
|
||||
import DiscardIcon from "../../../../images/discard.svg";
|
||||
import SaveIcon from "../../../../images/save-cosmos.svg";
|
||||
@@ -72,7 +71,6 @@ export interface SettingsComponentState {
|
||||
wasAutopilotOriginallySet: boolean;
|
||||
isScaleSaveable: boolean;
|
||||
isScaleDiscardable: boolean;
|
||||
throughputError: string;
|
||||
|
||||
timeToLive: TtlType;
|
||||
timeToLiveBaseline: TtlType;
|
||||
@@ -126,7 +124,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
private changeFeedPolicyVisible: boolean;
|
||||
private isFixedContainer: boolean;
|
||||
private shouldShowIndexingPolicyEditor: boolean;
|
||||
private totalThroughputUsed: number;
|
||||
public mongoDBCollectionResource: MongoDBCollectionResource;
|
||||
|
||||
constructor(props: SettingsComponentProps) {
|
||||
@@ -158,7 +155,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
wasAutopilotOriginallySet: false,
|
||||
isScaleSaveable: false,
|
||||
isScaleDiscardable: false,
|
||||
throughputError: undefined,
|
||||
|
||||
timeToLive: undefined,
|
||||
timeToLiveBaseline: undefined,
|
||||
@@ -212,11 +208,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
||||
if (throughputCap && throughputCap !== -1) {
|
||||
this.calculateTotalThroughputUsed();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
@@ -263,10 +254,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.state.throughputError) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
this.state.isScaleSaveable ||
|
||||
this.state.isSubSettingsSaveable ||
|
||||
@@ -494,26 +481,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
private onMongoIndexingPolicyDiscardableChange = (isMongoIndexingPolicyDiscardable: boolean): void =>
|
||||
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 => {
|
||||
if (this.isAnalyticalStorageEnabled) {
|
||||
if (this.state.analyticalStorageTtlSelection === TtlType.On) {
|
||||
@@ -676,31 +643,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
return buttons;
|
||||
};
|
||||
|
||||
private onMaxAutoPilotThroughputChange = (newThroughput: number): void => {
|
||||
let throughputError = "";
|
||||
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 onMaxAutoPilotThroughputChange = (newThroughput: number): void =>
|
||||
this.setState({ autoPilotThroughput: newThroughput });
|
||||
|
||||
private onThroughputChange = (newThroughput: number): void => {
|
||||
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 onThroughputChange = (newThroughput: number): void => this.setState({ throughput: newThroughput });
|
||||
|
||||
private onAutoPilotSelected = (isAutoPilotSelected: boolean): void =>
|
||||
this.setState({ isAutoPilotSelected: isAutoPilotSelected });
|
||||
@@ -947,7 +893,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
onScaleSaveableChange: this.onScaleSaveableChange,
|
||||
onScaleDiscardableChange: this.onScaleDiscardableChange,
|
||||
initialNotification: this.props.settingsTab.pendingNotification(),
|
||||
throughputError: this.state.throughputError,
|
||||
};
|
||||
|
||||
if (!this.isCollectionSettingsTab) {
|
||||
|
||||
@@ -122,7 +122,7 @@ export class IndexingPolicyComponent extends React.Component<
|
||||
{isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && (
|
||||
<MessageBar messageBarType={MessageBarType.warning}>{indexingPolicynUnsavedWarningMessage}</MessageBar>
|
||||
)}
|
||||
<div className="settingsV2IndexingPolicyEditor" tabIndex={0} ref={this.indexingPolicyDiv}></div>
|
||||
<div className="settingsV2IndexingPolicyEditor" ref={this.indexingPolicyDiv}></div>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ export interface ScaleComponentProps {
|
||||
onScaleSaveableChange: (isScaleSaveable: boolean) => void;
|
||||
onScaleDiscardableChange: (isScaleDiscardable: boolean) => void;
|
||||
initialNotification: DataModels.Notification;
|
||||
throughputError?: string;
|
||||
}
|
||||
|
||||
export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
||||
@@ -190,7 +189,6 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
||||
onScaleDiscardableChange={this.props.onScaleDiscardableChange}
|
||||
getThroughputWarningMessage={this.getThroughputWarningMessage}
|
||||
usageSizeInKB={this.props.collection?.usageSizeInKB()}
|
||||
throughputError={this.props.throughputError}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -75,7 +75,6 @@ export interface ThroughputInputAutoPilotV3Props {
|
||||
onScaleDiscardableChange: (isScaleDiscardable: boolean) => void;
|
||||
getThroughputWarningMessage: () => JSX.Element;
|
||||
usageSizeInKB: number;
|
||||
throughputError?: string;
|
||||
}
|
||||
|
||||
interface ThroughputInputAutoPilotV3State {
|
||||
@@ -541,7 +540,6 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
value={this.overrideWithProvisionedThroughputSettings() ? "" : this.props.maxAutoPilotThroughput?.toString()}
|
||||
onChange={this.onAutoPilotThroughputChange}
|
||||
min={minAutoPilotThroughput}
|
||||
errorMessage={this.props.throughputError}
|
||||
/>
|
||||
{!this.overrideWithProvisionedThroughputSettings() && this.getAutoPilotUsageCost()}
|
||||
{this.minRUperGBSurvey()}
|
||||
@@ -581,7 +579,6 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
}
|
||||
onChange={this.onThroughputChange}
|
||||
min={this.props.minimum}
|
||||
errorMessage={this.props.throughputError}
|
||||
/>
|
||||
{this.state.exceedFreeTierThroughput && (
|
||||
<MessageBar
|
||||
|
||||
@@ -13,7 +13,6 @@ exports[`IndexingPolicyComponent renders 1`] = `
|
||||
/>
|
||||
<div
|
||||
className="settingsV2IndexingPolicyEditor"
|
||||
tabIndex={0}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
@@ -34,13 +34,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"isTabsContentExpanded": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"phoenixClient": PhoenixClient {
|
||||
"retryOptions": Object {
|
||||
"maxTimeout": 5000,
|
||||
"minTimeout": 5000,
|
||||
"retries": 3,
|
||||
},
|
||||
},
|
||||
"phoenixClient": PhoenixClient {},
|
||||
"provideFeedbackEmail": [Function],
|
||||
"queriesClient": QueriesClient {
|
||||
"container": [Circular],
|
||||
@@ -108,13 +102,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"isTabsContentExpanded": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"phoenixClient": PhoenixClient {
|
||||
"retryOptions": Object {
|
||||
"maxTimeout": 5000,
|
||||
"minTimeout": 5000,
|
||||
"retries": 3,
|
||||
},
|
||||
},
|
||||
"phoenixClient": PhoenixClient {},
|
||||
"provideFeedbackEmail": [Function],
|
||||
"queriesClient": QueriesClient {
|
||||
"container": [Circular],
|
||||
|
||||
@@ -7,7 +7,6 @@ const props = {
|
||||
isSharded: true,
|
||||
setThroughputValue: () => jest.fn(),
|
||||
setIsAutoscale: () => jest.fn(),
|
||||
setIsThroughputCapExceeded: () => jest.fn(),
|
||||
onCostAcknowledgeChange: () => jest.fn(),
|
||||
};
|
||||
describe("ThroughputInput Pane", () => {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Checkbox, DirectionalHint, Link, Stack, Text, TextField, TooltipHost } from "@fluentui/react";
|
||||
import { useDatabases } from "Explorer/useDatabases";
|
||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||
import React, { FunctionComponent, useState } from "react";
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
|
||||
import * as SharedConstants from "../../../Shared/Constants";
|
||||
@@ -17,7 +16,6 @@ export interface ThroughputInputProps {
|
||||
showFreeTierExceedThroughputTooltip: boolean;
|
||||
setThroughputValue: (throughput: number) => void;
|
||||
setIsAutoscale: (isAutoscale: boolean) => void;
|
||||
setIsThroughputCapExceeded: (isThroughputCapExceeded: boolean) => void;
|
||||
onCostAcknowledgeChange: (isAcknowledged: boolean) => void;
|
||||
}
|
||||
|
||||
@@ -26,7 +24,6 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||
showFreeTierExceedThroughputTooltip,
|
||||
setThroughputValue,
|
||||
setIsAutoscale,
|
||||
setIsThroughputCapExceeded,
|
||||
isSharded,
|
||||
onCostAcknowledgeChange,
|
||||
}: ThroughputInputProps) => {
|
||||
@@ -34,60 +31,10 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||
const [throughput, setThroughput] = useState<number>(AutoPilotUtils.minAutoPilotThroughput);
|
||||
const [isCostAcknowledged, setIsCostAcknowledged] = useState<boolean>(false);
|
||||
const [throughputError, setThroughputError] = useState<string>("");
|
||||
const [totalThroughputUsed, setTotalThroughputUsed] = useState<number>(0);
|
||||
|
||||
setIsAutoscale(isAutoscaleSelected);
|
||||
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 => {
|
||||
let throughputHeaderText: string;
|
||||
if (isAutoscaleSelected) {
|
||||
@@ -113,17 +60,11 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||
const newThroughput = parseInt(newInput);
|
||||
setThroughput(newThroughput);
|
||||
setThroughputValue(newThroughput);
|
||||
|
||||
if (!isSharded && newThroughput > 10000) {
|
||||
setThroughputError("Unsharded collections support up to 10,000 RUs");
|
||||
return;
|
||||
} else {
|
||||
setThroughputError("");
|
||||
}
|
||||
|
||||
if (!checkThroughputCap(newThroughput)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setThroughputError("");
|
||||
};
|
||||
|
||||
const getAutoScaleTooltip = (): string => {
|
||||
@@ -155,13 +96,11 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||
setIsAutoScaleSelected(true);
|
||||
setThroughputValue(AutoPilotUtils.minAutoPilotThroughput);
|
||||
setIsAutoscale(true);
|
||||
checkThroughputCap(AutoPilotUtils.minAutoPilotThroughput);
|
||||
} else {
|
||||
setThroughput(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
|
||||
setIsAutoScaleSelected(false);
|
||||
setThroughputValue(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
|
||||
setIsAutoscale(false);
|
||||
checkThroughputCap(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -179,10 +118,8 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||
<input
|
||||
className="throughputInputRadioBtn"
|
||||
aria-label="Autoscale mode"
|
||||
aria-required={true}
|
||||
checked={isAutoscaleSelected}
|
||||
type="radio"
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
onChange={(e) => handleOnChangeMode(e, "Autoscale")}
|
||||
/>
|
||||
@@ -193,8 +130,6 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||
aria-label="Manual mode"
|
||||
checked={!isAutoscaleSelected}
|
||||
type="radio"
|
||||
aria-required={true}
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
onChange={(e) => handleOnChangeMode(e, "Manual")}
|
||||
/>
|
||||
|
||||
@@ -6,7 +6,6 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
isSharded={true}
|
||||
onCostAcknowledgeChange={[Function]}
|
||||
setIsAutoscale={[Function]}
|
||||
setIsThroughputCapExceeded={[Function]}
|
||||
setThroughputValue={[Function]}
|
||||
showFreeTierExceedThroughputTooltip={true}
|
||||
>
|
||||
@@ -655,12 +654,10 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
>
|
||||
<input
|
||||
aria-label="Autoscale mode"
|
||||
aria-required={true}
|
||||
checked={true}
|
||||
className="throughputInputRadioBtn"
|
||||
key=".0:$.0"
|
||||
onChange={[Function]}
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
type="radio"
|
||||
/>
|
||||
@@ -672,12 +669,10 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
</span>
|
||||
<input
|
||||
aria-label="Manual mode"
|
||||
aria-required={true}
|
||||
checked={false}
|
||||
className="throughputInputRadioBtn"
|
||||
key=".0:$.2"
|
||||
onChange={[Function]}
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
type="radio"
|
||||
/>
|
||||
|
||||
@@ -1,25 +1,16 @@
|
||||
import { Link } from "@fluentui/react/lib/Link";
|
||||
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
|
||||
import * as ko from "knockout";
|
||||
import React from "react";
|
||||
import _ from "underscore";
|
||||
import shallow from "zustand/shallow";
|
||||
import { AuthType } from "../AuthType";
|
||||
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
|
||||
import * as Constants from "../Common/Constants";
|
||||
import { Areas, ConnectionStatusType, HttpStatusCodes, Notebook } from "../Common/Constants";
|
||||
import { readCollection } from "../Common/dataAccess/readCollection";
|
||||
import { readDatabases } from "../Common/dataAccess/readDatabases";
|
||||
import { isPublicInternetAccessAllowed } from "../Common/DatabaseAccountUtility";
|
||||
import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHandlingUtils";
|
||||
import * as Logger from "../Common/Logger";
|
||||
import { QueriesClient } from "../Common/QueriesClient";
|
||||
import * as DataModels from "../Contracts/DataModels";
|
||||
import {
|
||||
ContainerConnectionInfo,
|
||||
IPhoenixConnectionInfoResult,
|
||||
IProvisionData,
|
||||
IResponse,
|
||||
} from "../Contracts/DataModels";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
|
||||
import { useSidePanel } from "../hooks/useSidePanel";
|
||||
@@ -35,6 +26,7 @@ import { update } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
|
||||
import {
|
||||
get as getWorkspace,
|
||||
listByDatabaseAccount,
|
||||
listConnectionInfo,
|
||||
start,
|
||||
} from "../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
|
||||
import { stringToBlob } from "../Utils/BlobUtils";
|
||||
@@ -170,9 +162,24 @@ export default class Explorer {
|
||||
);
|
||||
|
||||
useNotebook.subscribe(
|
||||
async () => this.initiateAndRefreshNotebookList(),
|
||||
(state) => [state.isNotebookEnabled, state.isRefreshed],
|
||||
shallow
|
||||
async () => {
|
||||
if (!this.notebookManager) {
|
||||
const NotebookManager = await (
|
||||
await import(/* webpackChunkName: "NotebookManager" */ "./Notebook/NotebookManager")
|
||||
).default;
|
||||
this.notebookManager = new NotebookManager();
|
||||
this.notebookManager.initialize({
|
||||
container: this,
|
||||
resourceTree: this.resourceTree,
|
||||
refreshCommandBarButtons: () => this.refreshCommandBarButtons(),
|
||||
refreshNotebookList: () => this.refreshNotebookList(),
|
||||
});
|
||||
}
|
||||
|
||||
this.refreshCommandBarButtons();
|
||||
this.refreshNotebookList();
|
||||
},
|
||||
(state) => state.isNotebookEnabled
|
||||
);
|
||||
|
||||
this.resourceTree = new ResourceTreeAdapter(this);
|
||||
@@ -182,7 +189,6 @@ export default class Explorer {
|
||||
useNotebook.getState().setNotebookServerInfo({
|
||||
notebookServerEndpoint: userContext.features.notebookServerUrl,
|
||||
authToken: userContext.features.notebookServerToken,
|
||||
forwardingId: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -206,23 +212,6 @@ export default class Explorer {
|
||||
this.refreshExplorer();
|
||||
}
|
||||
|
||||
public async initiateAndRefreshNotebookList(): Promise<void> {
|
||||
if (!this.notebookManager) {
|
||||
const NotebookManager = (await import(/* webpackChunkName: "NotebookManager" */ "./Notebook/NotebookManager"))
|
||||
.default;
|
||||
this.notebookManager = new NotebookManager();
|
||||
this.notebookManager.initialize({
|
||||
container: this,
|
||||
resourceTree: this.resourceTree,
|
||||
refreshCommandBarButtons: () => this.refreshCommandBarButtons(),
|
||||
refreshNotebookList: () => this.refreshNotebookList(),
|
||||
});
|
||||
}
|
||||
|
||||
this.refreshCommandBarButtons();
|
||||
this.refreshNotebookList();
|
||||
}
|
||||
|
||||
public openEnableSynapseLinkDialog(): void {
|
||||
const addSynapseLinkDialogProps: DialogProps = {
|
||||
linkProps: {
|
||||
@@ -356,79 +345,42 @@ export default class Explorer {
|
||||
return;
|
||||
}
|
||||
this._isInitializingNotebooks = true;
|
||||
this.refreshNotebookList();
|
||||
this._isInitializingNotebooks = false;
|
||||
}
|
||||
|
||||
public async allocateContainer(): Promise<void> {
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
const isAllocating = useNotebook.getState().isAllocating;
|
||||
if (
|
||||
isAllocating === false &&
|
||||
(notebookServerInfo === undefined ||
|
||||
(notebookServerInfo && notebookServerInfo.notebookServerEndpoint === undefined))
|
||||
) {
|
||||
const provisionData: IProvisionData = {
|
||||
if (userContext.features.phoenix) {
|
||||
const provisionData = {
|
||||
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
|
||||
resourceId: userContext.databaseAccount.id,
|
||||
dbAccountName: userContext.databaseAccount.name,
|
||||
aadToken: userContext.authorizationToken,
|
||||
resourceGroup: userContext.resourceGroup,
|
||||
subscriptionId: userContext.subscriptionId,
|
||||
};
|
||||
const connectionStatus: ContainerConnectionInfo = {
|
||||
status: ConnectionStatusType.Connecting,
|
||||
};
|
||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||
try {
|
||||
TelemetryProcessor.traceStart(Action.PhoenixConnection, {
|
||||
dataExplorerArea: Areas.Notebook,
|
||||
const connectionInfo = await this.phoenixClient.containerConnectionInfo(provisionData);
|
||||
if (connectionInfo.data && connectionInfo.data.notebookServerUrl) {
|
||||
useNotebook.getState().setNotebookServerInfo({
|
||||
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.data.notebookServerUrl,
|
||||
authToken: userContext.features.notebookServerToken || connectionInfo.data.notebookAuthToken,
|
||||
});
|
||||
useNotebook.getState().setIsAllocating(true);
|
||||
const connectionInfo = await this.phoenixClient.allocateContainer(provisionData);
|
||||
if (connectionInfo.status !== HttpStatusCodes.OK) {
|
||||
throw new Error(`Received status code: ${connectionInfo?.status}`);
|
||||
}
|
||||
if (!connectionInfo?.data?.notebookServerUrl) {
|
||||
throw new Error(`NotebookServerUrl is invalid!`);
|
||||
}
|
||||
await this.setNotebookInfo(connectionInfo, connectionStatus);
|
||||
TelemetryProcessor.traceSuccess(Action.PhoenixConnection, {
|
||||
dataExplorerArea: Areas.Notebook,
|
||||
});
|
||||
} catch (error) {
|
||||
TelemetryProcessor.traceFailure(Action.PhoenixConnection, {
|
||||
dataExplorerArea: Areas.Notebook,
|
||||
error: getErrorMessage(error),
|
||||
errorStack: getErrorStack(error),
|
||||
});
|
||||
connectionStatus.status = ConnectionStatusType.Failed;
|
||||
useNotebook.getState().resetContainerConnection(connectionStatus);
|
||||
throw error;
|
||||
} finally {
|
||||
useNotebook.getState().setIsAllocating(false);
|
||||
this.refreshCommandBarButtons();
|
||||
this.refreshNotebookList();
|
||||
this._isInitializingNotebooks = false;
|
||||
}
|
||||
} else {
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async setNotebookInfo(
|
||||
connectionInfo: IResponse<IPhoenixConnectionInfoResult>,
|
||||
connectionStatus: DataModels.ContainerConnectionInfo
|
||||
) {
|
||||
const containerData = {
|
||||
forwardingId: connectionInfo.data.forwardingId,
|
||||
dbAccountName: userContext.databaseAccount.name,
|
||||
};
|
||||
await this.phoenixClient.initiateContainerHeartBeat(containerData);
|
||||
useNotebook.getState().initializeNotebooksTree(this.notebookManager);
|
||||
|
||||
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,
|
||||
forwardingId: connectionInfo.data.forwardingId,
|
||||
});
|
||||
this.notebookManager?.notebookClient
|
||||
.getMemoryUsage()
|
||||
.then((memoryUsageInfo) => useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo));
|
||||
this.refreshNotebookList();
|
||||
|
||||
this._isInitializingNotebooks = false;
|
||||
}
|
||||
|
||||
public resetNotebookWorkspace(): void {
|
||||
@@ -439,14 +391,11 @@ export default class Explorer {
|
||||
);
|
||||
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 = {
|
||||
isModal: true,
|
||||
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",
|
||||
secondaryButtonText: "Cancel",
|
||||
onPrimaryButtonClick: this._resetNotebookWorkspace,
|
||||
@@ -504,54 +453,16 @@ export default class Explorer {
|
||||
private _resetNotebookWorkspace = async () => {
|
||||
useDialog.getState().closeDialog();
|
||||
const clearInProgressMessage = logConsoleProgress("Resetting notebook workspace");
|
||||
let connectionStatus: ContainerConnectionInfo;
|
||||
try {
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
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);
|
||||
}
|
||||
await this.notebookManager?.notebookClient.resetWorkspace();
|
||||
logConsoleInfo("Successfully reset notebook workspace");
|
||||
TelemetryProcessor.traceSuccess(Action.PhoenixResetWorkspace, {
|
||||
dataExplorerArea: Areas.Notebook,
|
||||
});
|
||||
TelemetryProcessor.traceSuccess(Action.ResetNotebookWorkspace);
|
||||
} catch (error) {
|
||||
logConsoleError(`Failed to reset notebook workspace: ${error}`);
|
||||
TelemetryProcessor.traceFailure(Action.PhoenixResetWorkspace, {
|
||||
dataExplorerArea: Areas.Notebook,
|
||||
TelemetryProcessor.traceFailure(Action.ResetNotebookWorkspace, {
|
||||
error: getErrorMessage(error),
|
||||
errorStack: getErrorStack(error),
|
||||
});
|
||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
||||
connectionStatus = {
|
||||
status: ConnectionStatusType.Failed,
|
||||
};
|
||||
useNotebook.getState().resetContainerConnection(connectionStatus);
|
||||
useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed);
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
clearInProgressMessage();
|
||||
@@ -743,9 +654,6 @@ export default class Explorer {
|
||||
if (!notebookContentItem || !notebookContentItem.path) {
|
||||
throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`);
|
||||
}
|
||||
if (notebookContentItem.type === NotebookContentItemType.Notebook && useNotebook.getState().isPhoenixNotebooks) {
|
||||
await this.allocateContainer();
|
||||
}
|
||||
|
||||
const notebookTabs = useTabs
|
||||
.getState()
|
||||
@@ -961,54 +869,15 @@ export default class Explorer {
|
||||
/**
|
||||
* 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) {
|
||||
const error = "Attempt to create new notebook, but notebook is not enabled";
|
||||
handleError(error, "Explorer/onNewNotebookClicked");
|
||||
throw new Error(error);
|
||||
}
|
||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
||||
if (isGithubTree) {
|
||||
await this.allocateContainer();
|
||||
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
||||
this.createNewNoteBook(parent, isGithubTree);
|
||||
} else {
|
||||
useDialog.getState().showOkCancelModalDialog(
|
||||
Notebook.newNotebookModalTitle,
|
||||
undefined,
|
||||
"Create",
|
||||
async () => {
|
||||
await this.allocateContainer();
|
||||
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
||||
this.createNewNoteBook(parent, isGithubTree);
|
||||
},
|
||||
"Cancel",
|
||||
undefined,
|
||||
this.getNewNoteWarningText()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
||||
this.createNewNoteBook(parent, isGithubTree);
|
||||
}
|
||||
}
|
||||
|
||||
private getNewNoteWarningText(): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<p>{Notebook.newNotebookModalContent1}</p>
|
||||
<br />
|
||||
<p>
|
||||
{Notebook.newNotebookModalContent2}
|
||||
<Link href={Notebook.cosmosNotebookHomePageUrl} target="_blank">
|
||||
{Notebook.learnMore}
|
||||
</Link>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
||||
|
||||
private createNewNoteBook(parent?: NotebookContentItem, isGithubTree?: boolean): void {
|
||||
const clearInProgressMessage = logConsoleProgress(`Creating new notebook in ${parent.path}`);
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.CreateNewNotebook, {
|
||||
dataExplorerArea: Constants.Areas.Notebook,
|
||||
@@ -1055,26 +924,7 @@ export default class Explorer {
|
||||
await this.notebookManager?.notebookContentClient.updateItemChildrenInPlace(item);
|
||||
}
|
||||
|
||||
public async openNotebookTerminal(kind: ViewModels.TerminalKind): Promise<void> {
|
||||
if (useNotebook.getState().isPhoenixFeatures) {
|
||||
await this.allocateContainer();
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
if (notebookServerInfo && notebookServerInfo.notebookServerEndpoint !== undefined) {
|
||||
this.connectToNotebookTerminal(kind);
|
||||
} else {
|
||||
useDialog
|
||||
.getState()
|
||||
.showOkModalDialog(
|
||||
"Failed to connect",
|
||||
"Failed to connect to temporary workspace. This could happen because of network issues. Please refresh the page and try again."
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.connectToNotebookTerminal(kind);
|
||||
}
|
||||
}
|
||||
|
||||
private connectToNotebookTerminal(kind: ViewModels.TerminalKind): void {
|
||||
public openNotebookTerminal(kind: ViewModels.TerminalKind): void {
|
||||
let title: string;
|
||||
|
||||
switch (kind) {
|
||||
@@ -1096,7 +946,7 @@ export default class Explorer {
|
||||
|
||||
const terminalTabs: TerminalTab[] = useTabs
|
||||
.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;
|
||||
if (terminalTabs.length > 0) {
|
||||
@@ -1125,7 +975,7 @@ export default class Explorer {
|
||||
notebookUrl?: string,
|
||||
galleryItem?: IGalleryItem,
|
||||
isFavorite?: boolean
|
||||
): Promise<void> {
|
||||
) {
|
||||
const title = "Gallery";
|
||||
const GalleryTab = await (await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab")).default;
|
||||
const galleryTab = useTabs
|
||||
@@ -1168,10 +1018,7 @@ export default class Explorer {
|
||||
<CassandraAddCollectionPane explorer={this} cassandraApiClient={new CassandraAPIDataClient()} />
|
||||
);
|
||||
} else {
|
||||
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
||||
throughputCap && throughputCap !== -1
|
||||
? await useDatabases.getState().loadAllOffers()
|
||||
: await useDatabases.getState().loadDatabaseOffers();
|
||||
await useDatabases.getState().loadDatabaseOffers();
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel("New " + getCollectionName(), <AddCollectionPanel explorer={this} databaseId={databaseId} />);
|
||||
@@ -1197,9 +1044,10 @@ export default class Explorer {
|
||||
}
|
||||
|
||||
public async handleOpenFileAction(path: string): Promise<void> {
|
||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
||||
await this.allocateContainer();
|
||||
} else if (!(await this._containsDefaultNotebookWorkspace(userContext.databaseAccount))) {
|
||||
if (
|
||||
userContext.features.phoenix === false &&
|
||||
!(await this._containsDefaultNotebookWorkspace(userContext.databaseAccount))
|
||||
) {
|
||||
this._openSetupNotebooksPaneForQuickstart();
|
||||
}
|
||||
|
||||
@@ -1231,27 +1079,7 @@ export default class Explorer {
|
||||
}
|
||||
|
||||
public openUploadFilePanel(parent?: NotebookContentItem): void {
|
||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
||||
useDialog.getState().showOkCancelModalDialog(
|
||||
Notebook.newNotebookUploadModalTitle,
|
||||
undefined,
|
||||
"Upload",
|
||||
async () => {
|
||||
await this.allocateContainer();
|
||||
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
||||
this.uploadFilePanel(parent);
|
||||
},
|
||||
"Cancel",
|
||||
undefined,
|
||||
this.getNewNoteWarningText()
|
||||
);
|
||||
} else {
|
||||
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
||||
this.uploadFilePanel(parent);
|
||||
}
|
||||
}
|
||||
|
||||
private uploadFilePanel(parent?: NotebookContentItem): void {
|
||||
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
@@ -1260,44 +1088,33 @@ export default class Explorer {
|
||||
);
|
||||
}
|
||||
|
||||
public getDownloadModalConent(fileName: string): JSX.Element {
|
||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
||||
return (
|
||||
<>
|
||||
<p>{Notebook.galleryNotebookDownloadContent1}</p>
|
||||
<br />
|
||||
<p>
|
||||
{Notebook.galleryNotebookDownloadContent2}
|
||||
<Link href={Notebook.cosmosNotebookGitDocumentationUrl} target="_blank">
|
||||
{Notebook.learnMore}
|
||||
</Link>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return <p> Download {fileName} from gallery as a copy to your notebooks to run and/or edit the notebook. </p>;
|
||||
}
|
||||
|
||||
public async refreshExplorer(): Promise<void> {
|
||||
userContext.authType === AuthType.ResourceToken
|
||||
? this.refreshDatabaseForResourceToken()
|
||||
: this.refreshAllDatabases();
|
||||
await useNotebook.getState().refreshNotebooksEnabledStateForAccount();
|
||||
|
||||
// TODO: remove reference to isNotebookEnabled and isNotebooksEnabledForAccount
|
||||
const isNotebookEnabled = userContext.features.notebooksDownBanner || useNotebook.getState().isPhoenixNotebooks;
|
||||
let isNotebookEnabled = true;
|
||||
if (!userContext.features.phoenix) {
|
||||
isNotebookEnabled =
|
||||
userContext.authType !== AuthType.ResourceToken &&
|
||||
((await this._containsDefaultNotebookWorkspace(userContext.databaseAccount)) ||
|
||||
userContext.features.enableNotebooks);
|
||||
}
|
||||
useNotebook.getState().setIsNotebookEnabled(isNotebookEnabled);
|
||||
useNotebook
|
||||
.getState()
|
||||
.setIsShellEnabled(useNotebook.getState().isPhoenixFeatures && isPublicInternetAccessAllowed());
|
||||
useNotebook.getState().setIsShellEnabled(isNotebookEnabled && isPublicInternetAccessAllowed());
|
||||
|
||||
TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, {
|
||||
isNotebookEnabled,
|
||||
dataExplorerArea: Constants.Areas.Notebook,
|
||||
});
|
||||
|
||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
||||
await this.initNotebooks(userContext.databaseAccount);
|
||||
if (!userContext.features.notebooksTemporarilyDown) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,8 +214,14 @@ export class EditorNeighborsComponent extends React.Component<EditorNeighborsCom
|
||||
/>
|
||||
</td>
|
||||
<td className="actionCol">
|
||||
<span className="rightPaneTrashIcon rightPaneBtns">
|
||||
<img src={DeleteIcon} alt="Delete" onClick={() => this.removeAddedEdgeToNeighbor(index)} />
|
||||
<span
|
||||
className="rightPaneTrashIcon rightPaneBtns"
|
||||
role="button"
|
||||
onKeyPress={() => this.removeAddedEdgeToNeighbor(index)}
|
||||
onClick={() => this.removeAddedEdgeToNeighbor(index)}
|
||||
tabIndex={0}
|
||||
>
|
||||
<img src={DeleteIcon} alt="Delete" />
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -123,7 +123,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
|
||||
<select
|
||||
className="typeSelect"
|
||||
value={singleValue.type}
|
||||
onChange={(e) => {
|
||||
onBlur={(e) => {
|
||||
singleValue.type = e.target.value as ViewModels.InputPropertyValueTypeString;
|
||||
if (singleValue.type === "null") {
|
||||
singleValue.value = undefined;
|
||||
@@ -217,7 +217,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
|
||||
<select
|
||||
className="typeSelect"
|
||||
value={firstValue.type}
|
||||
onChange={(e) => {
|
||||
onBlur={(e) => {
|
||||
firstValue.type = e.target.value as ViewModels.InputPropertyValueTypeString;
|
||||
this.props.onUpdateProperties(this.props.editedProperties);
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||
import * as Q from "q";
|
||||
import * as React from "react";
|
||||
@@ -296,6 +294,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
this.setGremlinParams();
|
||||
}
|
||||
|
||||
const selectedNode = this.state.highlightedNode;
|
||||
|
||||
props.onGraphAccessorCreated({
|
||||
applyFilter: this.submitQuery.bind(this),
|
||||
addVertex: this.addVertex.bind(this),
|
||||
@@ -303,7 +303,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
});
|
||||
} // constructor
|
||||
|
||||
public shareIGraphConfig(igraphConfig: IGraphConfig): void {
|
||||
public shareIGraphConfig(igraphConfig: IGraphConfig) {
|
||||
this.setState({
|
||||
igraphConfig: { ...igraphConfig },
|
||||
});
|
||||
@@ -330,10 +330,10 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
const partitionKeyProperty = this.props.collectionPartitionKeyProperty;
|
||||
|
||||
// aggregate all the properties, remove dropped ones
|
||||
const finalProperties = editedProperties.existingProperties.concat(editedProperties.addedProperties);
|
||||
let finalProperties = editedProperties.existingProperties.concat(editedProperties.addedProperties);
|
||||
|
||||
// Compose the query
|
||||
const pkId = editedProperties.pkId;
|
||||
let pkId = editedProperties.pkId;
|
||||
let updateQueryFragment = "";
|
||||
|
||||
finalProperties.forEach((p) => {
|
||||
@@ -422,7 +422,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
* Called from ko binding
|
||||
* @param id
|
||||
*/
|
||||
public selectNode(id: string): void {
|
||||
public selectNode(id: string) {
|
||||
if (!this.d3ForceGraph) {
|
||||
console.warn("Attempting to select node, but d3ForceGraph not initialized, yet.");
|
||||
return;
|
||||
@@ -431,7 +431,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
this.d3ForceGraph.selectNode(id);
|
||||
}
|
||||
|
||||
public deleteHighlightedNode(): void {
|
||||
public deleteHighlightedNode() {
|
||||
if (!this.state.highlightedNode) {
|
||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, "No highlighted node to remove.");
|
||||
return;
|
||||
@@ -467,23 +467,23 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
* Is of type: {e: GremlinEdge, v: GremlinVertex}[]
|
||||
* @param data
|
||||
*/
|
||||
public static isEdgeVertexPairArray(data: any): boolean {
|
||||
public static isEdgeVertexPairArray(data: any) {
|
||||
if (!(data instanceof Array)) {
|
||||
GraphExplorer.reportToConsole(ConsoleDataType.Info, "Query result not an array", data);
|
||||
return false;
|
||||
}
|
||||
|
||||
const pairs: any[] = data;
|
||||
let pairs: any[] = data;
|
||||
for (let i = 0; i < pairs.length; i++) {
|
||||
const item = pairs[i];
|
||||
if (
|
||||
!Object.prototype.hasOwnProperty.call(item, "e") ||
|
||||
!Object.prototype.hasOwnProperty.call(item, "v") ||
|
||||
!Object.prototype.hasOwnProperty.call(item["e"], "id") ||
|
||||
!Object.prototype.hasOwnProperty.call(item["e"], "type") ||
|
||||
!item.hasOwnProperty("e") ||
|
||||
!item.hasOwnProperty("v") ||
|
||||
!item["e"].hasOwnProperty("id") ||
|
||||
!item["e"].hasOwnProperty("type") ||
|
||||
item["e"].type !== "edge" ||
|
||||
!Object.prototype.hasOwnProperty.call(item["v"], "id") ||
|
||||
!Object.prototype.hasOwnProperty.call(item["e"], "type") ||
|
||||
!item["v"].hasOwnProperty("id") ||
|
||||
!item["v"].hasOwnProperty("type") ||
|
||||
item["v"].type !== "vertex"
|
||||
) {
|
||||
return false;
|
||||
@@ -514,7 +514,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
// Try hitting cache first
|
||||
const cache = outE ? this.outECache : this.inECache;
|
||||
const pairs = cache.retrieve(vertex.id, startIndex, pageSize);
|
||||
if (pairs !== null && pairs.length === pageSize) {
|
||||
if (pairs != null && pairs.length === pageSize) {
|
||||
const msg = `Retrieved ${pairs.length} ${outE ? "outE" : "inE"} edges from cache for vertex id: ${vertex.id}`;
|
||||
GraphExplorer.reportToConsole(ConsoleDataType.Info, msg);
|
||||
return Q.resolve(pairs);
|
||||
@@ -588,6 +588,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
vertex._outEAllLoaded &&
|
||||
vertex._inEAllLoaded
|
||||
) {
|
||||
console.info("No more edges to load for vertex " + vertex.id);
|
||||
updateGraphData();
|
||||
return Q.resolve(graphData);
|
||||
}
|
||||
@@ -667,7 +668,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
}
|
||||
);
|
||||
|
||||
return promise.then(() => {
|
||||
return promise.then((nbPairsFetched: number) => {
|
||||
if (offsetIndex >= GraphExplorer.LOAD_PAGE_SIZE || !vertex._outEAllLoaded || !vertex._inEAllLoaded) {
|
||||
vertex._pagination = {
|
||||
total:
|
||||
@@ -753,7 +754,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
* Create a new edge in docdb and update graph
|
||||
* @param e
|
||||
*/
|
||||
public createNewEdge(e: GraphNewEdgeData): Q.Promise<unknown> {
|
||||
public createNewEdge(e: GraphNewEdgeData): Q.Promise<any> {
|
||||
const q = `g.V('${GraphUtil.escapeSingleQuotes(e.inputOutV)}').addE('${GraphUtil.escapeSingleQuotes(
|
||||
e.label
|
||||
)}').To(g.V('${GraphUtil.escapeSingleQuotes(e.inputInV)}'))`;
|
||||
@@ -771,8 +772,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
return;
|
||||
}
|
||||
|
||||
const edge = edges[0];
|
||||
const graphData = this.originalGraphData;
|
||||
let edge = edges[0];
|
||||
let graphData = this.originalGraphData;
|
||||
graphData.addEdge(edge);
|
||||
|
||||
// Allow loadNeighbors to load list new edge
|
||||
@@ -799,10 +800,10 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
* Manually update in-memory graph.
|
||||
* @param edgeId
|
||||
*/
|
||||
public removeEdge(edgeId: string): Q.Promise<unknown> {
|
||||
public removeEdge(edgeId: string): Q.Promise<any> {
|
||||
return this.submitToBackend(`g.E('${GraphUtil.escapeSingleQuotes(edgeId)}').drop()`).then(
|
||||
() => {
|
||||
const graphData = this.originalGraphData;
|
||||
let graphData = this.originalGraphData;
|
||||
graphData.removeEdge(edgeId, false);
|
||||
this.updateGraphData(graphData, this.state.igraphConfig);
|
||||
},
|
||||
@@ -825,14 +826,10 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
return false;
|
||||
}
|
||||
|
||||
const vertices: any[] = data;
|
||||
let vertices: any[] = data;
|
||||
if (vertices.length > 0) {
|
||||
const v0 = vertices[0];
|
||||
if (
|
||||
!Object.prototype.hasOwnProperty.call(v0, "id") ||
|
||||
!Object.prototype.hasOwnProperty.call(v0, "type") ||
|
||||
v0.type !== "vertex"
|
||||
) {
|
||||
let v0 = vertices[0];
|
||||
if (!v0.hasOwnProperty("id") || !v0.hasOwnProperty("type") || v0.type !== "vertex") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -840,7 +837,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
}
|
||||
|
||||
public processGremlinQueryResults(result: GremlinClient.GremlinRequestResult): void {
|
||||
const data = result.data as GraphData.GremlinVertex[];
|
||||
const data = result.data as any;
|
||||
this.setFilterQueryStatus(FilterQueryStatus.GraphEmptyResult);
|
||||
|
||||
if (data === null) {
|
||||
@@ -930,13 +927,13 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
throw { title: err };
|
||||
}
|
||||
|
||||
if (vertices === null || vertices.length < 1) {
|
||||
if (vertices == null || vertices.length < 1) {
|
||||
const err = "Failed to create vertex (no vertex in response)";
|
||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, err, vertices);
|
||||
throw { title: err };
|
||||
}
|
||||
|
||||
const vertex = vertices[0];
|
||||
let vertex = vertices[0];
|
||||
const graphData = this.originalGraphData;
|
||||
graphData.addVertex(vertex);
|
||||
this.updateGraphData(graphData, this.state.igraphConfig);
|
||||
@@ -1025,7 +1022,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
this.gremlinClient.destroy();
|
||||
}
|
||||
public componentDidMount(): void {
|
||||
if (this.props.onLoadStartKey !== null && this.props.onLoadStartKey !== undefined) {
|
||||
if (this.props.onLoadStartKey != null && this.props.onLoadStartKey != undefined) {
|
||||
TelemetryProcessor.traceSuccess(
|
||||
Action.Tab,
|
||||
{
|
||||
@@ -1085,7 +1082,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
public static reportToConsole(type: ConsoleDataType.Info, msg: string, ...errorData: any[]): void;
|
||||
public static reportToConsole(type: ConsoleDataType.Error, msg: string, ...errorData: any[]): void;
|
||||
public static reportToConsole(type: ConsoleDataType, msg: string, ...errorData: any[]): void | (() => void) {
|
||||
let errorDataStr = "";
|
||||
let errorDataStr: string = "";
|
||||
if (errorData && errorData.length > 0) {
|
||||
console.error(msg, errorData);
|
||||
errorDataStr = ": " + JSON.stringify(errorData);
|
||||
@@ -1164,15 +1161,12 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
)}"`
|
||||
).then(
|
||||
(documents: DataModels.DocumentId[]) => {
|
||||
$.each(
|
||||
documents,
|
||||
(index: number, doc: { _graph_icon_property_value: string; icon: string; format: string }) => {
|
||||
newIconsMap[doc["_graph_icon_property_value"]] = {
|
||||
data: doc["icon"],
|
||||
format: doc["format"],
|
||||
};
|
||||
}
|
||||
);
|
||||
$.each(documents, (index: number, doc: any) => {
|
||||
newIconsMap[doc["_graph_icon_property_value"]] = {
|
||||
data: doc["icon"],
|
||||
format: doc["format"],
|
||||
};
|
||||
});
|
||||
|
||||
// Update graph configuration
|
||||
this.setState({
|
||||
@@ -1229,8 +1223,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
const key = this.state.igraphConfig.nodeCaption;
|
||||
return $.map(
|
||||
this.state.rootMap,
|
||||
(value: any): LeftPane.CaptionId => {
|
||||
const result = GraphData.GraphData.getNodePropValue(value, key);
|
||||
(value: any, index: number): LeftPane.CaptionId => {
|
||||
let result = GraphData.GraphData.getNodePropValue(value, key);
|
||||
return {
|
||||
caption: result !== undefined ? result : value.id,
|
||||
id: value.id,
|
||||
@@ -1243,7 +1237,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
* Selecting a root node means
|
||||
* @param node
|
||||
*/
|
||||
private selectRootNode(id: string): Q.Promise<unknown> {
|
||||
private selectRootNode(id: string): Q.Promise<any> {
|
||||
if (!this.d3ForceGraph) {
|
||||
console.warn("Attempting to reset zoom, but d3ForceGraph not initialized, yet.");
|
||||
} else {
|
||||
@@ -1288,7 +1282,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
this.collectNodeProperties(this.originalGraphData.vertices);
|
||||
this.updatePropertiesPane(id);
|
||||
},
|
||||
(reason: string) => {
|
||||
(reason: any) => {
|
||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, `Failed to select root node. Reason:${reason}`);
|
||||
}
|
||||
);
|
||||
@@ -1355,10 +1349,10 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
private getPkIdFromVertex(v: GraphData.GremlinVertex): string {
|
||||
if (
|
||||
this.props.collectionPartitionKeyProperty &&
|
||||
Object.prototype.hasOwnProperty.call(v, "properties") &&
|
||||
Object.prototype.hasOwnProperty.call(v.properties, this.props.collectionPartitionKeyProperty) &&
|
||||
v.hasOwnProperty("properties") &&
|
||||
v.properties.hasOwnProperty(this.props.collectionPartitionKeyProperty) &&
|
||||
v.properties[this.props.collectionPartitionKeyProperty].length > 0 &&
|
||||
Object.prototype.hasOwnProperty.call(v.properties[this.props.collectionPartitionKeyProperty][0], "value")
|
||||
v.properties[this.props.collectionPartitionKeyProperty][0].hasOwnProperty("value")
|
||||
) {
|
||||
const pk = v.properties[this.props.collectionPartitionKeyProperty][0].value;
|
||||
return GraphExplorer.generatePkIdPair(pk, v.id);
|
||||
@@ -1376,8 +1370,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
private getPkIdFromNodeData(v: GraphHighlightedNodeData): string {
|
||||
if (
|
||||
this.props.collectionPartitionKeyProperty &&
|
||||
Object.prototype.hasOwnProperty.call(v, "properties") &&
|
||||
Object.prototype.hasOwnProperty.call(v.properties, this.props.collectionPartitionKeyProperty)
|
||||
v.hasOwnProperty("properties") &&
|
||||
v.properties.hasOwnProperty(this.props.collectionPartitionKeyProperty)
|
||||
) {
|
||||
const pk = v.properties[this.props.collectionPartitionKeyProperty];
|
||||
return GraphExplorer.generatePkIdPair(pk[0] as PartitionKeyValueType, v.id);
|
||||
@@ -1394,14 +1388,14 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
* @return id
|
||||
*/
|
||||
public static getPkIdFromDocumentId(d: DataModels.DocumentId, collectionPartitionKeyProperty: string): string {
|
||||
const { id } = d;
|
||||
let { id } = d;
|
||||
if (typeof id !== "string") {
|
||||
const error = `Vertex id is not a string: ${JSON.stringify(id)}.`;
|
||||
logConsoleError(error);
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
if (collectionPartitionKeyProperty && Object.prototype.hasOwnProperty.call(d, collectionPartitionKeyProperty)) {
|
||||
if (collectionPartitionKeyProperty && d.hasOwnProperty(collectionPartitionKeyProperty)) {
|
||||
let pk = (d as any)[collectionPartitionKeyProperty];
|
||||
if (typeof pk !== "string" && typeof pk !== "number" && typeof pk !== "boolean") {
|
||||
if (Array.isArray(pk) && pk.length > 0) {
|
||||
@@ -1431,7 +1425,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
}"] AS p FROM c WHERE NOT IS_DEFINED(c._isEdge)`;
|
||||
return this.executeNonPagedDocDbQuery(q).then(
|
||||
(documents: DataModels.DocumentId[]) => {
|
||||
const possibleVertices = [] as PossibleVertex[];
|
||||
let possibleVertices = [] as PossibleVertex[];
|
||||
$.each(documents, (index: number, item: any) => {
|
||||
if (highlightedNodeId && item.id === highlightedNodeId) {
|
||||
// Exclude highlighed node in the list
|
||||
@@ -1445,7 +1439,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
caption: item.p,
|
||||
});
|
||||
} else {
|
||||
if (Object.prototype.hasOwnProperty.call(item, "p")) {
|
||||
if (item.hasOwnProperty("p")) {
|
||||
possibleVertices.push({
|
||||
value: item.id,
|
||||
caption: item.p[0]["_value"],
|
||||
@@ -1468,17 +1462,17 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
* @param addedEdges
|
||||
* @return promise when done
|
||||
*/
|
||||
private editGraphEdges(editedEdges: EditedEdges): Q.Promise<unknown> {
|
||||
const promises = [];
|
||||
private editGraphEdges(editedEdges: EditedEdges): Q.Promise<any> {
|
||||
let promises = [];
|
||||
// Drop edges
|
||||
for (let i = 0; i < editedEdges.droppedIds.length; i++) {
|
||||
const id = editedEdges.droppedIds[i];
|
||||
let id = editedEdges.droppedIds[i];
|
||||
promises.push(this.removeEdge(id));
|
||||
}
|
||||
|
||||
// Add edges
|
||||
for (let i = 0; i < editedEdges.addedEdges.length; i++) {
|
||||
const e = editedEdges.addedEdges[i];
|
||||
let e = editedEdges.addedEdges[i];
|
||||
promises.push(
|
||||
this.createNewEdge(e).then(() => {
|
||||
// Reload neighbors in case we linked to a vertex that isn't loaded in the graph
|
||||
@@ -1531,9 +1525,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
/**
|
||||
* For unit testing purposes
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public onGraphUpdated(_timestamp: number): void {}
|
||||
public onGraphUpdated(timestamp: number): void {}
|
||||
|
||||
/**
|
||||
* Get node properties for styling purposes. Result is the union of all properties of all nodes.
|
||||
@@ -1541,17 +1533,17 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
private collectNodeProperties(vertices: GraphData.GremlinVertex[]) {
|
||||
const props = {} as any; // Hashset
|
||||
$.each(vertices, (index: number, item: GraphData.GremlinVertex) => {
|
||||
for (const p in item) {
|
||||
for (var p in item) {
|
||||
// DocDB: Exclude type because it's always 'vertex'
|
||||
if (p !== "type" && typeof (item as any)[p] === "string") {
|
||||
props[p] = true;
|
||||
}
|
||||
}
|
||||
// Inspect properties
|
||||
if (Object.prototype.hasOwnProperty.call(item, "properties")) {
|
||||
if (item.hasOwnProperty("properties")) {
|
||||
// TODO This is DocDB-graph specific
|
||||
// Assume each property value is [{value:... }]
|
||||
for (const f in item.properties) {
|
||||
for (var f in item.properties) {
|
||||
props[f] = true;
|
||||
}
|
||||
}
|
||||
@@ -1578,21 +1570,21 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
return;
|
||||
}
|
||||
|
||||
const data = this.originalGraphData.getVertexById(id);
|
||||
let data = this.originalGraphData.getVertexById(id);
|
||||
|
||||
// A bit of translation to make it easier to display
|
||||
const props: { [id: string]: ViewModels.GremlinPropertyValueType[] } = {};
|
||||
for (const p in data.properties) {
|
||||
let props: { [id: string]: ViewModels.GremlinPropertyValueType[] } = {};
|
||||
for (let p in data.properties) {
|
||||
props[p] = data.properties[p].map((gremlinProperty) => gremlinProperty.value);
|
||||
}
|
||||
|
||||
// update neighbors
|
||||
const sources: NeighborVertexBasicInfo[] = [];
|
||||
const targets: NeighborVertexBasicInfo[] = [];
|
||||
let sources: NeighborVertexBasicInfo[] = [];
|
||||
let targets: NeighborVertexBasicInfo[] = [];
|
||||
this.props.onResetDefaultGraphConfigValues();
|
||||
const nodeCaption = this.state.igraphConfigUiData.nodeCaptionChoice;
|
||||
let nodeCaption = this.state.igraphConfigUiData.nodeCaptionChoice;
|
||||
this.updateSelectedNodeNeighbors(data.id, nodeCaption, sources, targets);
|
||||
const sData: GraphHighlightedNodeData = {
|
||||
let sData: GraphHighlightedNodeData = {
|
||||
id: data.id,
|
||||
label: data.label,
|
||||
properties: props,
|
||||
@@ -1619,16 +1611,16 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
targets: NeighborVertexBasicInfo[]
|
||||
): void {
|
||||
// update neighbors
|
||||
const gd = this.originalGraphData;
|
||||
const v = gd.getVertexById(id);
|
||||
let gd = this.originalGraphData;
|
||||
let v = gd.getVertexById(id);
|
||||
|
||||
// Clear the array while keeping the references
|
||||
sources.length = 0;
|
||||
targets.length = 0;
|
||||
|
||||
const possibleEdgeLabels = {} as any; // Collect all edge labels in a hashset
|
||||
let possibleEdgeLabels = {} as any; // Collect all edge labels in a hashset
|
||||
|
||||
for (const p in v.inE) {
|
||||
for (let p in v.inE) {
|
||||
possibleEdgeLabels[p] = true;
|
||||
const edges = v.inE[p];
|
||||
$.each(edges, (index: number, edge: GraphData.GremlinShortInEdge) => {
|
||||
@@ -1637,7 +1629,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
// If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet
|
||||
return;
|
||||
}
|
||||
const caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
|
||||
let caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
|
||||
sources.push({
|
||||
name: caption,
|
||||
id: neighborId,
|
||||
@@ -1647,7 +1639,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
});
|
||||
}
|
||||
|
||||
for (const p in v.outE) {
|
||||
for (let p in v.outE) {
|
||||
possibleEdgeLabels[p] = true;
|
||||
const edges = v.outE[p];
|
||||
$.each(edges, (index: number, edge: GraphData.GremlinShortOutEdge) => {
|
||||
@@ -1656,7 +1648,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
// If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet
|
||||
return;
|
||||
}
|
||||
const caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
|
||||
let caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
|
||||
targets.push({
|
||||
name: caption,
|
||||
id: neighborId,
|
||||
@@ -1668,7 +1660,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
|
||||
this.setState({
|
||||
possibleEdgeLabels: Object.keys(possibleEdgeLabels).map(
|
||||
(value: string): InputTypeaheadComponent.Item => {
|
||||
(value: string, index: number, array: string[]): InputTypeaheadComponent.Item => {
|
||||
return { caption: value, value: value };
|
||||
}
|
||||
),
|
||||
@@ -1689,20 +1681,20 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedVertex = vertices[0];
|
||||
let updatedVertex = vertices[0];
|
||||
if (this.originalGraphData.hasVertexId(updatedVertex.id)) {
|
||||
const currentVertex = this.originalGraphData.getVertexById(updatedVertex.id);
|
||||
let currentVertex = this.originalGraphData.getVertexById(updatedVertex.id);
|
||||
// Copy updated properties
|
||||
if (Object.prototype.hasOwnProperty.call(currentVertex, "properties")) {
|
||||
if (currentVertex.hasOwnProperty("properties")) {
|
||||
delete currentVertex["properties"];
|
||||
}
|
||||
for (const p in updatedVertex) {
|
||||
for (var p in updatedVertex) {
|
||||
(currentVertex as any)[p] = updatedVertex[p];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO This kind of assumes saveVertexProperty is done from property panes.
|
||||
const hn = this.state.highlightedNode;
|
||||
let hn = this.state.highlightedNode;
|
||||
if (hn && hn.id === updatedVertex.id) {
|
||||
this.updatePropertiesPane(hn.id);
|
||||
}
|
||||
@@ -1716,7 +1708,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
igraphConfig?: IGraphConfig
|
||||
) {
|
||||
this.originalGraphData = graphData;
|
||||
const gd = JSON.parse(JSON.stringify(this.originalGraphData));
|
||||
let gd = JSON.parse(JSON.stringify(this.originalGraphData));
|
||||
if (!this.d3ForceGraph) {
|
||||
console.warn("Attempting to update graph, but d3ForceGraph not initialized, yet.");
|
||||
return;
|
||||
@@ -1881,7 +1873,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
|
||||
promise
|
||||
.then((result: GremlinClient.GremlinRequestResult) => this.processGremlinQueryResults(result))
|
||||
.catch((error: Error) => {
|
||||
.catch((error: any) => {
|
||||
const errorMsg = `Failed to process query result: ${getErrorMessage(error)}`;
|
||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
|
||||
this.setState({
|
||||
|
||||
@@ -58,7 +58,7 @@ export class LeftPaneComponent extends React.Component<LeftPaneComponentProps> {
|
||||
className={className}
|
||||
as="tr"
|
||||
aria-label={node.caption}
|
||||
onActivated={() => this.props.onRootNodeSelected(node.id)}
|
||||
onActivated={(e) => this.props.onRootNodeSelected(node.id)}
|
||||
key={node.id}
|
||||
>
|
||||
<td className="resultItem">
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from "react";
|
||||
import { mount, ReactWrapper } from "enzyme";
|
||||
import * as Q from "q";
|
||||
import React from "react";
|
||||
import { GraphHighlightedNodeData, PossibleVertex } from "./GraphExplorer";
|
||||
import { Mode, NodePropertiesComponent, NodePropertiesComponentProps } from "./NodePropertiesComponent";
|
||||
import { NodePropertiesComponent, NodePropertiesComponentProps, Mode } from "./NodePropertiesComponent";
|
||||
import { GraphHighlightedNodeData, EditedProperties, EditedEdges, PossibleVertex } from "./GraphExplorer";
|
||||
|
||||
describe("Property pane", () => {
|
||||
const title = "My Title";
|
||||
@@ -37,18 +37,17 @@ describe("Property pane", () => {
|
||||
return {
|
||||
expandedTitle: title,
|
||||
isCollapsed: false,
|
||||
onCollapsedChanged: jest.fn(),
|
||||
onCollapsedChanged: (newValue: boolean): void => {},
|
||||
node: highlightedNode,
|
||||
getPkIdFromNodeData: (): string => undefined,
|
||||
collectionPartitionKeyProperty: undefined,
|
||||
updateVertexProperties: (): Q.Promise<void> => Q.resolve(),
|
||||
selectNode: jest.fn(),
|
||||
updatePossibleVertices: (): Q.Promise<PossibleVertex[]> => Q.resolve(undefined),
|
||||
possibleEdgeLabels: undefined,
|
||||
//eslint-disable-next-line
|
||||
editGraphEdges: (): Q.Promise<any> => Q.resolve(),
|
||||
deleteHighlightedNode: jest.fn(),
|
||||
onModeChanged: jest.fn(),
|
||||
getPkIdFromNodeData: (v: GraphHighlightedNodeData): string => null,
|
||||
collectionPartitionKeyProperty: null,
|
||||
updateVertexProperties: (editedProperties: EditedProperties): Q.Promise<void> => Q.resolve(),
|
||||
selectNode: (id: string): void => {},
|
||||
updatePossibleVertices: (): Q.Promise<PossibleVertex[]> => Q.resolve(null),
|
||||
possibleEdgeLabels: null,
|
||||
editGraphEdges: (editedEdges: EditedEdges): Q.Promise<any> => Q.resolve(),
|
||||
deleteHighlightedNode: (): void => {},
|
||||
onModeChanged: (newMode: Mode): void => {},
|
||||
viewMode: Mode.READONLY_PROP,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -72,7 +72,7 @@ export class NodePropertiesComponent extends React.Component<
|
||||
super(props);
|
||||
this.state = {
|
||||
editedProperties: {
|
||||
pkId: undefined,
|
||||
pkId: null,
|
||||
readOnlyProperties: [],
|
||||
existingProperties: [],
|
||||
addedProperties: [],
|
||||
@@ -98,12 +98,15 @@ export class NodePropertiesComponent extends React.Component<
|
||||
};
|
||||
}
|
||||
|
||||
public static getDerivedStateFromProps(props: NodePropertiesComponentProps): Partial<NodePropertiesComponentState> {
|
||||
public static getDerivedStateFromProps(
|
||||
props: NodePropertiesComponentProps,
|
||||
state: NodePropertiesComponentState
|
||||
): Partial<NodePropertiesComponentState> {
|
||||
if (props.viewMode !== Mode.READONLY_PROP) {
|
||||
return { isDeleteConfirm: false };
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
@@ -135,10 +138,10 @@ export class NodePropertiesComponent extends React.Component<
|
||||
* @param value
|
||||
*/
|
||||
private static getTypeOption(value: any): ViewModels.InputPropertyValueTypeString {
|
||||
if (value === undefined) {
|
||||
if (value == null) {
|
||||
return "null";
|
||||
}
|
||||
const type = typeof value;
|
||||
let type = typeof value;
|
||||
switch (type) {
|
||||
case "number":
|
||||
case "boolean":
|
||||
@@ -169,9 +172,10 @@ export class NodePropertiesComponent extends React.Component<
|
||||
];
|
||||
|
||||
const existingProps: ViewModels.InputProperty[] = [];
|
||||
|
||||
if (this.props.node.hasOwnProperty("properties")) {
|
||||
const hProps = this.props.node["properties"];
|
||||
for (const p in hProps) {
|
||||
for (let p in hProps) {
|
||||
const propValues = hProps[p];
|
||||
(p === partitionKeyProperty ? readOnlyProps : existingProps).push({
|
||||
key: p,
|
||||
@@ -433,7 +437,7 @@ export class NodePropertiesComponent extends React.Component<
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ exports[`<EditorNodePropertiesComponent /> renders component 1`] = `
|
||||
<td>
|
||||
<select
|
||||
className="typeSelect"
|
||||
onChange={[Function]}
|
||||
onBlur={[Function]}
|
||||
required={true}
|
||||
value="string"
|
||||
>
|
||||
@@ -282,7 +282,7 @@ exports[`<EditorNodePropertiesComponent /> renders proper unicode 1`] = `
|
||||
<td>
|
||||
<select
|
||||
className="typeSelect"
|
||||
onChange={[Function]}
|
||||
onBlur={[Function]}
|
||||
required={true}
|
||||
value="string"
|
||||
>
|
||||
@@ -344,7 +344,7 @@ exports[`<EditorNodePropertiesComponent /> renders proper unicode 1`] = `
|
||||
<td>
|
||||
<select
|
||||
className="typeSelect"
|
||||
onChange={[Function]}
|
||||
onBlur={[Function]}
|
||||
required={true}
|
||||
value="string"
|
||||
>
|
||||
|
||||
@@ -132,7 +132,6 @@ export const NewVertexComponent: FunctionComponent<INewVertexComponentProps> = (
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
onLabelChange(event);
|
||||
}}
|
||||
autoFocus
|
||||
/>
|
||||
<div className="actionCol"></div>
|
||||
</div>
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
* and update any knockout observables passed from the parent.
|
||||
*/
|
||||
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
|
||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
||||
import * as React from "react";
|
||||
import create, { UseStore } from "zustand";
|
||||
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 Explorer from "../../Explorer";
|
||||
import { useSelectedNode } from "../../useSelectedNode";
|
||||
@@ -53,8 +55,16 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
||||
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
|
||||
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
|
||||
|
||||
if (useNotebook.getState().isPhoenixNotebooks || useNotebook.getState().isPhoenixFeatures) {
|
||||
uiFabricControlButtons.unshift(CommandBarUtil.createConnectionStatus(container, "connectionStatus"));
|
||||
if (
|
||||
userContext.features.notebooksTemporarilyDown === false &&
|
||||
userContext.features.phoenix === true &&
|
||||
useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2
|
||||
) {
|
||||
uiFabricControlButtons.unshift(CommandBarUtil.createConnectionStatus("connectionStatus"));
|
||||
}
|
||||
|
||||
if (useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2) {
|
||||
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker"));
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -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 enableAzureSynapseLinkBtn = buttons.find(
|
||||
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel
|
||||
);
|
||||
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", () => {
|
||||
|
||||
@@ -25,6 +25,7 @@ import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import { JunoClient } from "../../../Juno/JunoClient";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import { getCollectionName, getDatabaseName } from "../../../Utils/APITypeUtils";
|
||||
import { isServerlessAccount } from "../../../Utils/CapabilityUtils";
|
||||
import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils";
|
||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
import Explorer from "../../Explorer";
|
||||
@@ -77,10 +78,9 @@ export function createStaticCommandBarButtons(
|
||||
if (container.notebookManager?.gitHubOAuthService) {
|
||||
notebookButtons.push(createManageGitHubAccountButton(container));
|
||||
}
|
||||
if (useNotebook.getState().isPhoenixFeatures && configContext.isTerminalEnabled) {
|
||||
notebookButtons.push(createOpenTerminalButton(container));
|
||||
}
|
||||
if (useNotebook.getState().isPhoenixNotebooks && selectedNodeState.isConnectedToContainer()) {
|
||||
|
||||
notebookButtons.push(createOpenTerminalButton(container));
|
||||
if (userContext.features.phoenix === false) {
|
||||
notebookButtons.push(createNotebookWorkspaceResetButton(container));
|
||||
}
|
||||
if (
|
||||
@@ -98,21 +98,19 @@ export function createStaticCommandBarButtons(
|
||||
}
|
||||
|
||||
notebookButtons.forEach((btn) => {
|
||||
if (btn.commandButtonLabel.indexOf("Cassandra") !== -1) {
|
||||
if (!useNotebook.getState().isPhoenixFeatures) {
|
||||
if (userContext.features.notebooksTemporarilyDown) {
|
||||
if (btn.commandButtonLabel.indexOf("Cassandra") !== -1) {
|
||||
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.cassandraShellTemporarilyDownMsg);
|
||||
}
|
||||
} else if (btn.commandButtonLabel.indexOf("Mongo") !== -1) {
|
||||
if (!useNotebook.getState().isPhoenixFeatures) {
|
||||
} else if (btn.commandButtonLabel.indexOf("Mongo") !== -1) {
|
||||
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.mongoShellTemporarilyDownMsg);
|
||||
} else {
|
||||
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.temporarilyDownMsg);
|
||||
}
|
||||
} else if (!useNotebook.getState().isPhoenixNotebooks) {
|
||||
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.temporarilyDownMsg);
|
||||
}
|
||||
buttons.push(btn);
|
||||
});
|
||||
} else {
|
||||
if (!isRunningOnNationalCloud() && useNotebook.getState().isPhoenixNotebooks) {
|
||||
if (!isRunningOnNationalCloud() && !userContext.features.notebooksTemporarilyDown) {
|
||||
buttons.push(createDivider());
|
||||
buttons.push(createEnableNotebooksButton(container));
|
||||
}
|
||||
@@ -170,7 +168,9 @@ export function createContextCommandBarButtons(
|
||||
onCommandClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||
if (useNotebook.getState().isShellEnabled) {
|
||||
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
||||
if (!userContext.features.notebooksTemporarilyDown) {
|
||||
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
||||
}
|
||||
} else {
|
||||
selectedCollection && selectedCollection.onNewMongoShellClick();
|
||||
}
|
||||
@@ -178,6 +178,13 @@ export function createContextCommandBarButtons(
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
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);
|
||||
}
|
||||
@@ -273,6 +280,10 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (isServerlessAccount()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (userContext?.databaseAccount?.properties?.enableAnalyticalStorage) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -299,13 +310,8 @@ function createNewDatabase(container: Explorer): CommandButtonComponentProps {
|
||||
return {
|
||||
iconSrc: AddDatabaseIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: async () => {
|
||||
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
||||
if (throughputCap && throughputCap !== -1) {
|
||||
await useDatabases.getState().loadAllOffers();
|
||||
}
|
||||
useSidePanel.getState().openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={container} />);
|
||||
},
|
||||
onCommandClick: () =>
|
||||
useSidePanel.getState().openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={container} />),
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: true,
|
||||
@@ -590,7 +596,7 @@ function createManageGitHubAccountButton(container: Explorer): CommandButtonComp
|
||||
return {
|
||||
iconSrc: GitHubIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () => {
|
||||
onCommandClick: () =>
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
@@ -600,8 +606,7 @@ function createManageGitHubAccountButton(container: Explorer): CommandButtonComp
|
||||
gitHubClientProp={container.notebookManager.gitHubClient}
|
||||
junoClientProp={junoClient}
|
||||
/>
|
||||
);
|
||||
},
|
||||
),
|
||||
commandButtonLabel: label,
|
||||
hasPopup: false,
|
||||
disabled: false,
|
||||
|
||||
@@ -13,7 +13,6 @@ import { StyleConstants } from "../../../Common/Constants";
|
||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
import Explorer from "../../Explorer";
|
||||
import { ConnectionStatus } from "./ConnectionStatusComponent";
|
||||
import { MemoryTracker } from "./MemoryTrackerComponent";
|
||||
|
||||
@@ -204,9 +203,9 @@ export const createMemoryTracker = (key: string): ICommandBarItemProps => {
|
||||
};
|
||||
};
|
||||
|
||||
export const createConnectionStatus = (container: Explorer, key: string): ICommandBarItemProps => {
|
||||
export const createConnectionStatus = (key: string): ICommandBarItemProps => {
|
||||
return {
|
||||
key,
|
||||
onRender: () => <ConnectionStatus container={container} />,
|
||||
onRender: () => <ConnectionStatus />,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -3,182 +3,77 @@
|
||||
.connectionStatusContainer {
|
||||
cursor: default;
|
||||
align-items: center;
|
||||
margin: 0 9px;
|
||||
border: 1px;
|
||||
min-height: 44px;
|
||||
|
||||
> span {
|
||||
padding-right: 12px;
|
||||
font-size: 12px;
|
||||
font-size: 13px;
|
||||
font-family: @DataExplorerFont;
|
||||
color: @DefaultFontColor;
|
||||
}
|
||||
&:focus{
|
||||
outline: 0px;
|
||||
}
|
||||
}
|
||||
.commandReactBtn {
|
||||
&:hover {
|
||||
background-color: rgb(238, 247, 255);
|
||||
color: rgb(32, 31, 30);
|
||||
cursor: pointer;
|
||||
}
|
||||
&:focus{
|
||||
outline: 1px dashed #605e5c;
|
||||
}
|
||||
.connectionStatusFailed{
|
||||
color: #bd1919;
|
||||
}
|
||||
.connectedReactBtn {
|
||||
&:hover {
|
||||
background-color: rgb(238, 247, 255);
|
||||
color: rgb(32, 31, 30);
|
||||
cursor: pointer;
|
||||
}
|
||||
&:focus{
|
||||
outline: 0px;
|
||||
}
|
||||
}
|
||||
.connectIcon{
|
||||
margin: 0px 4px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
color: rgb(0, 120, 212);
|
||||
}
|
||||
.status {
|
||||
.ring-container {
|
||||
position: relative;
|
||||
display: block;
|
||||
margin-right: 8px;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
font-size: 9px!important;
|
||||
padding: 0px!important;
|
||||
border-radius: 0.5em;
|
||||
}
|
||||
|
||||
.status::before,
|
||||
.status::after {
|
||||
}
|
||||
|
||||
.ringringGreen {
|
||||
border: 3px solid green;
|
||||
border-radius: 30px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
position: absolute;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.status::before {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
background-color: rgba(#fff, 0.1);
|
||||
border-radius: 100%;
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0) scale(0);
|
||||
}
|
||||
|
||||
.connected{
|
||||
background-color: green;
|
||||
box-shadow:
|
||||
0 0 0 0em rgba(green, 0),
|
||||
0em 0.05em 0.1em rgba(#000000, 0.2);
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
}
|
||||
.connecting{
|
||||
background-color:#ffbf00;
|
||||
box-shadow:
|
||||
0 0 0 0em rgba(#ffbf00, 0),
|
||||
0em 0.05em 0.1em rgba(#000000, 0.2);
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
}
|
||||
.failed{
|
||||
background-color:#bd1919;
|
||||
box-shadow:
|
||||
0 0 0 0em rgba(#bd1919, 0),
|
||||
0em 0.05em 0.1em rgba(#000000, 0.2);
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
}
|
||||
|
||||
.status.connecting.is-animating {
|
||||
animation: status-outer-connecting 3000ms infinite;
|
||||
}
|
||||
.status.failed.is-animating {
|
||||
animation: status-outer-failed 3000ms infinite;
|
||||
}
|
||||
.status.connected.is-animating {
|
||||
animation: status-outer-connected 3000ms infinite;
|
||||
}
|
||||
@keyframes status-outer-connected {
|
||||
|
||||
0% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em #008000, 0em 0.05em 0.1em rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
20% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0.6), 0em 0.05em 0.1em rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
40% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0.5), 0em 0.05em 0.1em rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
60% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0.3), 0em 0.05em 0.1em rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
80% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0.5em rgba(0, 128, 0, 0.1), 0em 0.05em 0.1em rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
85% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0), 0em 0.05em 0.1em rgba(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
@keyframes status-outer-failed {
|
||||
|
||||
0% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em #bd1919, 0em 0.05em 0.1em rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
20% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em #c52d2d, 0em 0.05em 0.1em rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
40% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em #b47b7b, 0em 0.05em 0.1em rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
60% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0.3), 0em 0.05em 0.1em rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
80% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0.5em rgba(0, 128, 0, 0.1), 0em 0.05em 0.1em rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
85% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0), 0em 0.05em 0.1em rgba(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
@keyframes status-outer-connecting {
|
||||
|
||||
0% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em #ffbf00, 0em 0.05em 0.1em rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
20% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em #f0dfad, 0em 0.05em 0.1em rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
40% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em rgba(198, 243, 198, 0.5), 0em 0.05em 0.1em rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
60% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em rgba(213, 241, 213, 0.3), 0em 0.05em 0.1em rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
80% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0.5em rgba(0, 128, 0, 0.1), 0em 0.05em 0.1em rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
85% {
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0), 0em 0.05em 0.1em rgba(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
margin: .4285em 0em 0em 0.07477em;
|
||||
animation: pulsate 3s ease-out;
|
||||
animation-iteration-count: infinite;
|
||||
opacity: 0.0
|
||||
}
|
||||
.ringringYellow{
|
||||
border: 3px solid #ffbf00;
|
||||
border-radius: 30px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
position: absolute;
|
||||
margin: .4285em 0em 0em 0.07477em;
|
||||
animation: pulsate 3s ease-out;
|
||||
animation-iteration-count: infinite;
|
||||
opacity: 0.0
|
||||
}
|
||||
.ringringRed{
|
||||
border: 3px solid #bd1919;
|
||||
border-radius: 30px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
position: absolute;
|
||||
margin: .4285em 0em 0em 0.07477em;
|
||||
animation: pulsate 3s ease-out;
|
||||
animation-iteration-count: infinite;
|
||||
opacity: 0.0
|
||||
}
|
||||
@keyframes pulsate {
|
||||
0% {-webkit-transform: scale(0.1, 0.1); opacity: 0.0;}
|
||||
15% {opacity: 0.8;}
|
||||
25% {opacity: 0.6;}
|
||||
45% {opacity: 0.4;}
|
||||
70% {opacity: 0.3;}
|
||||
100% {-webkit-transform: scale(.7, .7); opacity: 0.1;}
|
||||
}
|
||||
.locationGreenDot{
|
||||
font-size: 20px;
|
||||
margin-right: 0.07em;
|
||||
color: green;
|
||||
}
|
||||
.locationYellowDot{
|
||||
font-size: 20px;
|
||||
margin-right: 0.07em;
|
||||
color: #ffbf00;
|
||||
}
|
||||
.locationRedDot{
|
||||
font-size: 20px;
|
||||
margin-right: 0.07em;
|
||||
color: #bd1919;
|
||||
}
|
||||
@@ -1,54 +1,17 @@
|
||||
import {
|
||||
FocusTrapCallout,
|
||||
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 { Icon, ProgressIndicator, Stack, TooltipHost } from "@fluentui/react";
|
||||
import * as React from "react";
|
||||
import "../../../../less/hostedexplorer.less";
|
||||
import { ConnectionStatusType, ContainerStatusType, Notebook } from "../../../Common/Constants";
|
||||
import Explorer from "../../Explorer";
|
||||
import { ConnectionStatusType } from "../../../Common/Constants";
|
||||
import { useNotebook } from "../../Notebook/useNotebook";
|
||||
import "../CommandBar/ConnectionStatusComponent.less";
|
||||
interface Props {
|
||||
container: Explorer;
|
||||
}
|
||||
export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Element => {
|
||||
const connectionInfo = useNotebook((state) => state.connectionInfo);
|
||||
|
||||
export const ConnectionStatus: React.FC = (): JSX.Element => {
|
||||
const [second, setSecond] = React.useState("00");
|
||||
const [minute, setMinute] = React.useState("00");
|
||||
const [isActive, setIsActive] = React.useState(false);
|
||||
const [counter, setCounter] = React.useState(0);
|
||||
const [statusColor, setStatusColor] = React.useState("");
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
const [statusColor, setStatusColor] = React.useState("locationYellowDot");
|
||||
const [statusColorAnimation, setStatusColorAnimation] = React.useState("ringringYellow");
|
||||
const toolTipContent = "Hosted runtime status.";
|
||||
React.useEffect(() => {
|
||||
let intervalId: NodeJS.Timeout;
|
||||
|
||||
@@ -68,15 +31,6 @@ export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Ele
|
||||
return () => clearInterval(intervalId);
|
||||
}, [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 = () => {
|
||||
setIsActive(false);
|
||||
setCounter(0);
|
||||
@@ -84,103 +38,35 @@ export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Ele
|
||||
setMinute("00");
|
||||
};
|
||||
|
||||
const memoryUsageInfo = useNotebook((state) => state.memoryUsageInfo);
|
||||
const totalGB = memoryUsageInfo ? memoryUsageInfo.totalKB / Notebook.memoryGuageToGB : 0;
|
||||
const usedGB = totalGB > 0 ? totalGB - memoryUsageInfo.freeKB / Notebook.memoryGuageToGB : 0;
|
||||
|
||||
if (
|
||||
connectionInfo &&
|
||||
(connectionInfo.status === ConnectionStatusType.Connect || connectionInfo.status === ConnectionStatusType.Reconnect)
|
||||
) {
|
||||
return (
|
||||
<ActionButton className="commandReactBtn" onClick={() => container.allocateContainer()}>
|
||||
<TooltipHost content={toolTipContent}>
|
||||
<Stack className="connectionStatusContainer" horizontal>
|
||||
<Icon iconName="ConnectVirtualMachine" className="connectIcon" />
|
||||
<span>{connectionInfo.status}</span>
|
||||
</Stack>
|
||||
</TooltipHost>
|
||||
</ActionButton>
|
||||
);
|
||||
const connectionInfo = useNotebook((state) => state.connectionInfo);
|
||||
if (!connectionInfo) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connecting && isActive === false) {
|
||||
stopTimer();
|
||||
setIsActive(true);
|
||||
setStatusColor("status connecting is-animating");
|
||||
setToolTipContent("Connecting to temporary workspace.");
|
||||
} else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connected && isActive === true) {
|
||||
stopTimer();
|
||||
setStatusColor("status connected is-animating");
|
||||
setToolTipContent("Connected to temporary workspace.");
|
||||
setStatusColor("locationGreenDot");
|
||||
setStatusColorAnimation("ringringGreen");
|
||||
} else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Failed && isActive === true) {
|
||||
stopTimer();
|
||||
setStatusColor("status failed is-animating");
|
||||
setToolTipContent("Click here to Reconnect to temporary workspace.");
|
||||
setStatusColor("locationRedDot");
|
||||
setStatusColorAnimation("ringringRed");
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<TooltipHost
|
||||
content={
|
||||
containerInfo?.status === ContainerStatusType.Active
|
||||
? `Connected to temporary workspace. This temporary workspace will get disconnected in ${Math.round(
|
||||
containerInfo.durationLeftInMinutes
|
||||
)} minutes.`
|
||||
: toolTipContent
|
||||
}
|
||||
>
|
||||
<ActionButton
|
||||
id={buttonId}
|
||||
className={connectionInfo.status === ConnectionStatusType.Failed ? "commandReactBtn" : "connectedReactBtn"}
|
||||
onClick={(e: React.MouseEvent<HTMLSpanElement>) =>
|
||||
connectionInfo.status === ConnectionStatusType.Failed ? container.allocateContainer() : e.preventDefault()
|
||||
}
|
||||
>
|
||||
<Stack className="connectionStatusContainer" horizontal>
|
||||
<i className={statusColor}></i>
|
||||
<span className={connectionInfo.status === ConnectionStatusType.Failed ? "connectionStatusFailed" : ""}>
|
||||
{connectionInfo.status}
|
||||
</span>
|
||||
{connectionInfo.status === ConnectionStatusType.Connecting && isActive && (
|
||||
<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 content={toolTipContent}>
|
||||
<Stack className="connectionStatusContainer" horizontal>
|
||||
<div className="ring-container">
|
||||
<div className={statusColorAnimation}></div>
|
||||
<Icon iconName="LocationDot" className={statusColor} />
|
||||
</div>
|
||||
<span className={connectionInfo.status === ConnectionStatusType.Failed ? "connectionStatusFailed" : ""}>
|
||||
{connectionInfo.status}
|
||||
</span>
|
||||
{connectionInfo.status === ConnectionStatusType.Connecting && isActive && (
|
||||
<ProgressIndicator description={minute + ":" + second} />
|
||||
)}
|
||||
</Stack>
|
||||
</TooltipHost>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -103,6 +103,7 @@ export class NotificationConsoleComponent extends React.Component<
|
||||
onClick={() => this.expandCollapseConsole()}
|
||||
onKeyDown={(event: React.KeyboardEvent<HTMLDivElement>) => this.onExpandCollapseKeyPress(event)}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
>
|
||||
<div className="statusBar">
|
||||
<span className="dataTypeIcons">
|
||||
@@ -162,7 +163,7 @@ export class NotificationConsoleComponent extends React.Component<
|
||||
onKeyDown={(event: React.KeyboardEvent<HTMLSpanElement>) => this.onClearNotificationsKeyPress(event)}
|
||||
tabIndex={0}
|
||||
>
|
||||
<img src={ClearIcon} alt="clear notifications image" />
|
||||
<img src={ClearIcon} alt="clear notifications icon" />
|
||||
Clear Notifications
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
|
||||
id="notificationConsoleHeader"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
<div
|
||||
@@ -150,7 +151,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
|
||||
tabIndex={0}
|
||||
>
|
||||
<img
|
||||
alt="clear notifications image"
|
||||
alt="clear notifications icon"
|
||||
src=""
|
||||
/>
|
||||
Clear Notifications
|
||||
@@ -173,6 +174,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
|
||||
id="notificationConsoleHeader"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
<div
|
||||
@@ -316,7 +318,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
|
||||
tabIndex={0}
|
||||
>
|
||||
<img
|
||||
alt="clear notifications image"
|
||||
alt="clear notifications icon"
|
||||
src=""
|
||||
/>
|
||||
Clear Notifications
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Link } from "@fluentui/react";
|
||||
import { CellId, CellType, ImmutableNotebook } from "@nteract/commutable";
|
||||
// Vendor modules
|
||||
import {
|
||||
@@ -15,15 +13,13 @@ import "@nteract/styles/editor-overrides.css";
|
||||
import "@nteract/styles/global-variables.css";
|
||||
import "codemirror/addon/hint/show-hint.css";
|
||||
import "codemirror/lib/codemirror.css";
|
||||
import { Notebook } from "Common/Constants";
|
||||
import { useDialog } from "Explorer/Controls/Dialog";
|
||||
import * as Immutable from "immutable";
|
||||
import * as React from "react";
|
||||
import { Provider } from "react-redux";
|
||||
import "react-table/react-table.css";
|
||||
import { AnyAction, Store } from "redux";
|
||||
import { NotebookClientV2 } from "../NotebookClientV2";
|
||||
import { NotebookContentProviderType, NotebookUtil } from "../NotebookUtil";
|
||||
import { NotebookUtil } from "../NotebookUtil";
|
||||
import * as NteractUtil from "../NTeractUtil";
|
||||
import * as CdbActions from "./actions";
|
||||
import { NotebookComponent } from "./NotebookComponent";
|
||||
@@ -34,19 +30,6 @@ export interface NotebookComponentBootstrapperOptions {
|
||||
contentRef: ContentRef;
|
||||
}
|
||||
|
||||
interface IWrapModel {
|
||||
name: string;
|
||||
path: string;
|
||||
last_modified: Date;
|
||||
created: string;
|
||||
content: unknown;
|
||||
format: string;
|
||||
mimetype: unknown;
|
||||
size: number;
|
||||
writeable: boolean;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export class NotebookComponentBootstrapper {
|
||||
public contentRef: ContentRef;
|
||||
protected renderExtraComponent: () => JSX.Element;
|
||||
@@ -58,7 +41,7 @@ export class NotebookComponentBootstrapper {
|
||||
this.contentRef = options.contentRef;
|
||||
}
|
||||
|
||||
protected static wrapModelIntoContent(name: string, path: string, content: unknown): IWrapModel {
|
||||
protected static wrapModelIntoContent(name: string, path: string, content: any) {
|
||||
return {
|
||||
name,
|
||||
path,
|
||||
@@ -66,7 +49,7 @@ export class NotebookComponentBootstrapper {
|
||||
created: "",
|
||||
content,
|
||||
format: "json",
|
||||
mimetype: undefined,
|
||||
mimetype: null as any,
|
||||
size: 0,
|
||||
writeable: false,
|
||||
type: "notebook",
|
||||
@@ -102,11 +85,7 @@ 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: any): void {
|
||||
this.getStore().dispatch(
|
||||
actions.fetchContentFulfilled({
|
||||
filepath: undefined,
|
||||
@@ -137,32 +116,11 @@ export class NotebookComponentBootstrapper {
|
||||
|
||||
/* Notebook operations. See nteract/packages/connected-components/src/notebook-menu/index.tsx */
|
||||
public notebookSave(): void {
|
||||
if (
|
||||
NotebookUtil.getContentProviderType(this.getNotebookPath()) ===
|
||||
NotebookContentProviderType.JupyterContentProviderType
|
||||
) {
|
||||
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,
|
||||
})
|
||||
);
|
||||
}
|
||||
this.getStore().dispatch(
|
||||
actions.save({
|
||||
contentRef: this.contentRef,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public notebookChangeKernel(kernelSpecName: string): void {
|
||||
@@ -312,6 +270,7 @@ export class NotebookComponentBootstrapper {
|
||||
public isContentDirty(): boolean {
|
||||
const content = selectors.content(this.getStore().getState(), { contentRef: this.contentRef });
|
||||
if (!content) {
|
||||
console.log("No error");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -369,19 +328,4 @@ export class NotebookComponentBootstrapper {
|
||||
protected getStore(): Store<AppState, AnyAction> {
|
||||
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,
|
||||
} from "@nteract/core";
|
||||
import { Channels, childOf, createMessage, JupyterMessage, message, ofMessageType } from "@nteract/messaging";
|
||||
import { defineConfigOption } from "@nteract/mythic-configuration";
|
||||
import { RecordOf } from "immutable";
|
||||
import { Action, AnyAction } from "redux";
|
||||
import { AnyAction } from "redux";
|
||||
import { ofType, StateObservable } from "redux-observable";
|
||||
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 {
|
||||
catchError,
|
||||
concatMap,
|
||||
@@ -42,7 +41,7 @@ import { logConsoleError, logConsoleInfo } from "../../../Utils/NotificationCons
|
||||
import { useDialog } from "../../Controls/Dialog";
|
||||
import * as FileSystemUtil from "../FileSystemUtil";
|
||||
import * as cdbActions from "../NotebookComponent/actions";
|
||||
import { NotebookContentProviderType, NotebookUtil } from "../NotebookUtil";
|
||||
import { NotebookUtil } from "../NotebookUtil";
|
||||
import * as CdbActions from "./actions";
|
||||
import * as TextFile from "./contents/file/text-file";
|
||||
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 = [
|
||||
addInitialCodeCellEpic,
|
||||
focusInitialCodeCellEpic,
|
||||
@@ -1014,5 +965,4 @@ export const allEpics = [
|
||||
traceNotebookInfoEpic,
|
||||
traceNotebookKernelEpic,
|
||||
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 { 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";
|
||||
|
||||
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
|
||||
// out how to safely filter out the ones we are overriding here.
|
||||
const filteredCoreEpics = [
|
||||
coreEpics.autoSaveCurrentContentEpic,
|
||||
coreEpics.executeCellEpic,
|
||||
coreEpics.executeFocusedCellEpic,
|
||||
coreEpics.executeCellAfterKernelLaunchEpic,
|
||||
|
||||
@@ -1,32 +1,20 @@
|
||||
/**
|
||||
* Notebook container related stuff
|
||||
*/
|
||||
import promiseRetry, { AbortError } from "p-retry";
|
||||
import { PhoenixClient } from "Phoenix/PhoenixClient";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import { ConnectionStatusType, HttpHeaders, HttpStatusCodes, Notebook } from "../../Common/Constants";
|
||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||
import * as Logger from "../../Common/Logger";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import { IPhoenixConnectionInfoResult, IProvisionData, IResponse } from "../../Contracts/DataModels";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
|
||||
import { createOrUpdate, destroy } from "../../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
|
||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||
import { useNotebook } from "./useNotebook";
|
||||
|
||||
export class NotebookContainerClient {
|
||||
private clearReconnectionAttemptMessage? = () => {};
|
||||
private isResettingWorkspace: boolean;
|
||||
private phoenixClient: PhoenixClient;
|
||||
private retryOptions: promiseRetry.Options;
|
||||
|
||||
constructor(private onConnectionLost: () => void) {
|
||||
this.phoenixClient = new PhoenixClient();
|
||||
this.retryOptions = {
|
||||
retries: Notebook.retryAttempts,
|
||||
maxTimeout: Notebook.retryAttemptDelayMs,
|
||||
minTimeout: Notebook.retryAttemptDelayMs,
|
||||
};
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
if (notebookServerInfo?.notebookServerEndpoint) {
|
||||
this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs);
|
||||
@@ -47,17 +35,14 @@ export class NotebookContainerClient {
|
||||
* Heartbeat: each ping schedules another ping
|
||||
*/
|
||||
private scheduleHeartbeat(delayMs: number): void {
|
||||
setTimeout(async () => {
|
||||
const memoryUsageInfo = await this.getMemoryUsage();
|
||||
useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo);
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
if (notebookServerInfo?.notebookServerEndpoint) {
|
||||
this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs);
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.getMemoryUsage()
|
||||
.then((memoryUsageInfo) => useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo))
|
||||
.finally(() => this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs));
|
||||
}, delayMs);
|
||||
}
|
||||
|
||||
public async getMemoryUsage(): Promise<DataModels.MemoryUsageInfo> {
|
||||
private async getMemoryUsage(): Promise<DataModels.MemoryUsageInfo> {
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
|
||||
const error = "No server endpoint detected";
|
||||
@@ -71,27 +56,6 @@ export class NotebookContainerClient {
|
||||
|
||||
const { notebookServerEndpoint, authToken } = this.getNotebookServerConfig();
|
||||
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`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
@@ -111,36 +75,31 @@ export class NotebookContainerClient {
|
||||
freeKB: memoryUsageInfo.free,
|
||||
};
|
||||
}
|
||||
} else if (response.status === HttpStatusCodes.NotFound) {
|
||||
throw new AbortError(response.statusText);
|
||||
}
|
||||
throw new Error(response.statusText);
|
||||
} else {
|
||||
return undefined;
|
||||
} 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 shouldExecuteMemoryCall(): boolean {
|
||||
return (
|
||||
useNotebook.getState().containerStatus?.status === Constants.ContainerStatusType.Active &&
|
||||
useNotebook.getState().connectionInfo?.status === ConnectionStatusType.Connected
|
||||
);
|
||||
}
|
||||
|
||||
public async resetWorkspace(): Promise<IResponse<IPhoenixConnectionInfoResult>> {
|
||||
public async resetWorkspace(): Promise<void> {
|
||||
this.isResettingWorkspace = true;
|
||||
let response: IResponse<IPhoenixConnectionInfoResult>;
|
||||
try {
|
||||
response = await this._resetWorkspace();
|
||||
await this._resetWorkspace();
|
||||
} catch (error) {
|
||||
Promise.reject(error);
|
||||
return response;
|
||||
}
|
||||
this.isResettingWorkspace = false;
|
||||
return response;
|
||||
}
|
||||
|
||||
private async _resetWorkspace(): Promise<IResponse<IPhoenixConnectionInfoResult>> {
|
||||
private async _resetWorkspace(): Promise<void> {
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
|
||||
const error = "No server endpoint detected";
|
||||
@@ -148,17 +107,15 @@ export class NotebookContainerClient {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
const { notebookServerEndpoint, authToken } = this.getNotebookServerConfig();
|
||||
try {
|
||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
||||
const provisionData: IProvisionData = {
|
||||
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
|
||||
};
|
||||
return await this.phoenixClient.resetContainer(provisionData);
|
||||
}
|
||||
return null;
|
||||
await fetch(`${notebookServerEndpoint}/api/shutdown`, {
|
||||
method: "POST",
|
||||
headers: { Authorization: authToken },
|
||||
});
|
||||
} catch (error) {
|
||||
Logger.logError(getErrorMessage(error), "NotebookContainerClient/resetWorkspace");
|
||||
throw error;
|
||||
await this.recreateNotebookWorkspaceAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,11 +129,22 @@ export class NotebookContainerClient {
|
||||
};
|
||||
}
|
||||
|
||||
private getHeaders(): HeadersInit {
|
||||
const authorizationHeader = getAuthorizationHeader();
|
||||
return {
|
||||
[authorizationHeader.header]: authorizationHeader.token,
|
||||
[HttpHeaders.contentType]: "application/json",
|
||||
};
|
||||
private async recreateNotebookWorkspaceAsync(): Promise<void> {
|
||||
const { databaseAccount } = userContext;
|
||||
if (!databaseAccount?.id) {
|
||||
throw new Error("DataExplorer not initialized");
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +212,6 @@ export default class NotebookManager {
|
||||
"Cancel",
|
||||
() => reject(new Error("Commit dialog canceled")),
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
label: "Commit message",
|
||||
autoAdjustHeight: true,
|
||||
|
||||
@@ -16,10 +16,9 @@ import "./NotebookReadOnlyRenderer.less";
|
||||
import SandboxOutputs from "./outputs/SandboxOutputs";
|
||||
|
||||
export interface NotebookRendererProps {
|
||||
contentRef: ContentRef;
|
||||
contentRef: any;
|
||||
hideInputs?: boolean;
|
||||
hidePrompts?: boolean;
|
||||
addTransform: (component: React.ComponentType & { MIMETYPE: string }) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -28,7 +27,7 @@ export interface NotebookRendererProps {
|
||||
class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
||||
componentDidMount() {
|
||||
if (!userContext.features.sandboxNotebookOutputs) {
|
||||
loadTransform(this.props as NotebookRendererProps);
|
||||
loadTransform(this.props as any);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +59,7 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
||||
<div className="NotebookReadOnlyRender">
|
||||
<Cells contentRef={this.props.contentRef}>
|
||||
{{
|
||||
code: ({ id, contentRef }: { id: string; contentRef: ContentRef }) => (
|
||||
code: ({ id, contentRef }: { id: any; contentRef: ContentRef }) => (
|
||||
<CodeCell id={id} contentRef={contentRef}>
|
||||
{{
|
||||
prompt: (props: { id: string; contentRef: string }) => this.renderPrompt(props.id, props.contentRef),
|
||||
@@ -74,14 +73,14 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
||||
}}
|
||||
</CodeCell>
|
||||
),
|
||||
markdown: ({ id, contentRef }: { id: string; contentRef: ContentRef }) => (
|
||||
markdown: ({ id, contentRef }: { id: any; contentRef: ContentRef }) => (
|
||||
<MarkdownCell id={id} contentRef={contentRef} cell_type="markdown">
|
||||
{{
|
||||
editor: {},
|
||||
}}
|
||||
</MarkdownCell>
|
||||
),
|
||||
raw: ({ id, contentRef }: { id: string; contentRef: ContentRef }) => (
|
||||
raw: ({ id, contentRef }: { id: any; contentRef: ContentRef }) => (
|
||||
<RawCell id={id} contentRef={contentRef} cell_type="raw">
|
||||
{{
|
||||
editor: {
|
||||
@@ -99,7 +98,6 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const makeMapDispatchToProps = (initialDispatch: Dispatch, initialProps: NotebookRendererProps) => {
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => {
|
||||
return {
|
||||
@@ -116,4 +114,4 @@ const makeMapDispatchToProps = (initialDispatch: Dispatch, initialProps: Noteboo
|
||||
return mapDispatchToProps;
|
||||
};
|
||||
|
||||
export default connect(undefined, makeMapDispatchToProps)(NotebookReadOnlyRenderer);
|
||||
export default connect(null, makeMapDispatchToProps)(NotebookReadOnlyRenderer);
|
||||
|
||||
@@ -5,17 +5,11 @@ import Html2Canvas from "html2canvas";
|
||||
import path from "path";
|
||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||
import * as StringUtils from "../../Utils/StringUtils";
|
||||
import * as InMemoryContentProviderUtils from "../Notebook/NotebookComponent/ContentProviders/InMemoryContentProviderUtils";
|
||||
import { SnapshotFragment } from "./NotebookComponent/types";
|
||||
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
|
||||
|
||||
// Must match rx-jupyter' FileType
|
||||
export type FileType = "directory" | "file" | "notebook";
|
||||
export enum NotebookContentProviderType {
|
||||
GitHubContentProviderType,
|
||||
InMemoryContentProviderType,
|
||||
JupyterContentProviderType,
|
||||
}
|
||||
// Utilities for notebooks
|
||||
export class NotebookUtil {
|
||||
public static UntrustedNotebookRunHint = "Please trust notebook first before running any code cells";
|
||||
@@ -132,18 +126,6 @@ export class NotebookUtil {
|
||||
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 {
|
||||
const contentInfo = GitHubUtils.fromContentUri(path);
|
||||
if (contentInfo) {
|
||||
|
||||
@@ -35,7 +35,6 @@ describe("auto start kernel", () => {
|
||||
connectionInfo: {
|
||||
authToken: "autToken",
|
||||
notebookServerEndpoint: "notebookServerEndpoint",
|
||||
forwardingId: "Id",
|
||||
},
|
||||
databaseAccountName: undefined,
|
||||
defaultExperience: undefined,
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { PhoenixClient } from "Phoenix/PhoenixClient";
|
||||
import create, { UseStore } from "zustand";
|
||||
import { AuthType } from "../../AuthType";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import { ConnectionStatusType } from "../../Common/Constants";
|
||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||
import * as Logger from "../../Common/Logger";
|
||||
import { configContext } from "../../ConfigContext";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import { ContainerConnectionInfo, ContainerInfo } from "../../Contracts/DataModels";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import { IPinnedRepo } from "../../Juno/JunoClient";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
@@ -33,13 +28,8 @@ interface NotebookState {
|
||||
myNotebooksContentRoot: NotebookContentItem;
|
||||
gitHubNotebooksContentRoot: NotebookContentItem;
|
||||
galleryContentRoot: NotebookContentItem;
|
||||
connectionInfo: ContainerConnectionInfo;
|
||||
connectionInfo: DataModels.ContainerConnectionInfo;
|
||||
notebookFolderName: string;
|
||||
isAllocating: boolean;
|
||||
isRefreshed: boolean;
|
||||
containerStatus: ContainerInfo;
|
||||
isPhoenixNotebooks: boolean;
|
||||
isPhoenixFeatures: boolean;
|
||||
setIsNotebookEnabled: (isNotebookEnabled: boolean) => void;
|
||||
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void;
|
||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void;
|
||||
@@ -56,14 +46,7 @@ interface NotebookState {
|
||||
deleteNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean) => void;
|
||||
initializeNotebooksTree: (notebookManager: NotebookManager) => Promise<void>;
|
||||
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]) => void;
|
||||
setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => void;
|
||||
setIsAllocating: (isAllocating: boolean) => void;
|
||||
resetContainerConnection: (connectionStatus: ContainerConnectionInfo) => void;
|
||||
setIsRefreshed: (isAllocating: boolean) => void;
|
||||
setContainerStatus: (containerStatus: ContainerInfo) => void;
|
||||
getPhoenixStatus: () => Promise<void>;
|
||||
setIsPhoenixNotebooks: (isPhoenixNotebooks: boolean) => void;
|
||||
setIsPhoenixFeatures: (isPhoenixFeatures: boolean) => void;
|
||||
setConnectionInfo: (connectionInfo: DataModels.ContainerConnectionInfo) => void;
|
||||
}
|
||||
|
||||
export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
@@ -72,7 +55,6 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
notebookServerInfo: {
|
||||
notebookServerEndpoint: undefined,
|
||||
authToken: undefined,
|
||||
forwardingId: undefined,
|
||||
},
|
||||
sparkClusterConnectionInfo: {
|
||||
userName: undefined,
|
||||
@@ -87,19 +69,8 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
myNotebooksContentRoot: undefined,
|
||||
gitHubNotebooksContentRoot: undefined,
|
||||
galleryContentRoot: undefined,
|
||||
connectionInfo: {
|
||||
status: ConnectionStatusType.Connect,
|
||||
},
|
||||
connectionInfo: undefined,
|
||||
notebookFolderName: undefined,
|
||||
isAllocating: false,
|
||||
isRefreshed: false,
|
||||
containerStatus: {
|
||||
status: undefined,
|
||||
durationLeftInMinutes: undefined,
|
||||
notebookServerInfo: undefined,
|
||||
},
|
||||
isPhoenixNotebooks: undefined,
|
||||
isPhoenixFeatures: undefined,
|
||||
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
|
||||
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
|
||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
||||
@@ -112,7 +83,6 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
setNotebookBasePath: (notebookBasePath: string) => set({ notebookBasePath }),
|
||||
setNotebookFolderName: (notebookFolderName: string) => set({ notebookFolderName }),
|
||||
refreshNotebooksEnabledStateForAccount: async (): Promise<void> => {
|
||||
await get().getPhoenixStatus();
|
||||
const { databaseAccount, authType } = userContext;
|
||||
if (
|
||||
authType === AuthType.EncryptedToken ||
|
||||
@@ -205,7 +175,7 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
||||
},
|
||||
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
|
||||
const notebookFolderName = get().isPhoenixNotebooks ? "Temporary Notebooks" : "My Notebooks";
|
||||
const notebookFolderName = userContext.features.phoenix === true ? "Temporary Notebooks" : "My Notebooks";
|
||||
set({ notebookFolderName });
|
||||
const myNotebooksContentRoot = {
|
||||
name: get().notebookFolderName,
|
||||
@@ -286,36 +256,5 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
set({ gitHubNotebooksContentRoot });
|
||||
}
|
||||
},
|
||||
setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => set({ connectionInfo }),
|
||||
setIsAllocating: (isAllocating: boolean) => set({ isAllocating }),
|
||||
resetContainerConnection: (connectionStatus: ContainerConnectionInfo): void => {
|
||||
useTabs.getState().closeAllNotebookTabs(true);
|
||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||
useNotebook.getState().setNotebookServerInfo(undefined);
|
||||
useNotebook.getState().setIsAllocating(false);
|
||||
useNotebook.getState().setContainerStatus({
|
||||
status: undefined,
|
||||
durationLeftInMinutes: undefined,
|
||||
notebookServerInfo: undefined,
|
||||
});
|
||||
},
|
||||
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 }),
|
||||
setConnectionInfo: (connectionInfo: DataModels.ContainerConnectionInfo) => set({ connectionInfo }),
|
||||
}));
|
||||
|
||||
@@ -4,8 +4,6 @@ import * as React from "react";
|
||||
import { useFullScreenURLs } from "../hooks/useFullScreenURLs";
|
||||
|
||||
export const OpenFullScreen: React.FunctionComponent = () => {
|
||||
const [isReadUrlCopy, setIsReadUrlCopy] = React.useState<boolean>(false);
|
||||
const [isReadWriteUrlCopy, setIsReadWriteUrlCopy] = React.useState<boolean>(false);
|
||||
const result = useFullScreenURLs();
|
||||
if (!result) {
|
||||
return <Spinner label="Generating URLs..." ariaLive="assertive" labelPosition="right" />;
|
||||
@@ -25,12 +23,10 @@ export const OpenFullScreen: React.FunctionComponent = () => {
|
||||
<TextField label="Read and Write" readOnly defaultValue={readWriteUrl} />
|
||||
<Stack horizontal tokens={{ childrenGap: 10 }}>
|
||||
<DefaultButton
|
||||
ariaLabel={isReadWriteUrlCopy ? "Copied url" : "Copy"}
|
||||
onClick={() => {
|
||||
copyToClipboard(readWriteUrl);
|
||||
setIsReadWriteUrlCopy(true);
|
||||
}}
|
||||
text={isReadWriteUrlCopy ? "Copied" : "Copy"}
|
||||
text="Copy"
|
||||
iconProps={{ iconName: "Copy" }}
|
||||
/>
|
||||
<PrimaryButton
|
||||
@@ -44,12 +40,10 @@ export const OpenFullScreen: React.FunctionComponent = () => {
|
||||
<TextField label="Read Only" readOnly defaultValue={readUrl} />
|
||||
<Stack horizontal tokens={{ childrenGap: 10 }}>
|
||||
<DefaultButton
|
||||
ariaLabel={isReadUrlCopy ? "Copied url" : "Copy"}
|
||||
onClick={() => {
|
||||
setIsReadUrlCopy(true);
|
||||
copyToClipboard(readUrl);
|
||||
}}
|
||||
text={isReadUrlCopy ? "Copied" : "Copy"}
|
||||
text="Copy"
|
||||
iconProps={{ iconName: "Copy" }}
|
||||
/>
|
||||
<PrimaryButton
|
||||
|
||||
@@ -13,21 +13,21 @@ import {
|
||||
Text,
|
||||
TooltipHost,
|
||||
} from "@fluentui/react";
|
||||
import * as Constants from "Common/Constants";
|
||||
import { createCollection } from "Common/dataAccess/createCollection";
|
||||
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
|
||||
import { configContext, Platform } from "ConfigContext";
|
||||
import * as DataModels from "Contracts/DataModels";
|
||||
import { SubscriptionType } from "Contracts/SubscriptionType";
|
||||
import { useSidePanel } from "hooks/useSidePanel";
|
||||
import React from "react";
|
||||
import { CollectionCreation } from "Shared/Constants";
|
||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "UserContext";
|
||||
import { getCollectionName } from "Utils/APITypeUtils";
|
||||
import { isCapabilityEnabled, isServerlessAccount } from "Utils/CapabilityUtils";
|
||||
import { getUpsellMessage } from "Utils/PricingUtils";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import { createCollection } from "../../Common/dataAccess/createCollection";
|
||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||
import { configContext, Platform } from "../../ConfigContext";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import { SubscriptionType } from "../../Contracts/SubscriptionType";
|
||||
import { useSidePanel } from "../../hooks/useSidePanel";
|
||||
import { CollectionCreation } from "../../Shared/Constants";
|
||||
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { getCollectionName } from "../../Utils/APITypeUtils";
|
||||
import { isCapabilityEnabled, isServerlessAccount } from "../../Utils/CapabilityUtils";
|
||||
import { getUpsellMessage } from "../../Utils/PricingUtils";
|
||||
import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent";
|
||||
import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput";
|
||||
import Explorer from "../Explorer";
|
||||
@@ -92,7 +92,6 @@ export interface AddCollectionPanelState {
|
||||
errorMessage: string;
|
||||
showErrorDetails: boolean;
|
||||
isExecuting: boolean;
|
||||
isThroughputCapExceeded: boolean;
|
||||
}
|
||||
|
||||
export class AddCollectionPanel extends React.Component<AddCollectionPanelProps, AddCollectionPanelState> {
|
||||
@@ -123,7 +122,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
errorMessage: "",
|
||||
showErrorDetails: false,
|
||||
isExecuting: false,
|
||||
isThroughputCapExceeded: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -175,7 +173,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
aria-checked={this.state.createNewDatabase}
|
||||
name="databaseType"
|
||||
type="radio"
|
||||
role="radio"
|
||||
id="databaseCreateNew"
|
||||
tabIndex={0}
|
||||
onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)}
|
||||
@@ -189,7 +186,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
aria-checked={!this.state.createNewDatabase}
|
||||
name="databaseType"
|
||||
type="radio"
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)}
|
||||
/>
|
||||
@@ -211,7 +207,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
size={40}
|
||||
className="panelTextField"
|
||||
aria-label="New database id"
|
||||
autoFocus
|
||||
tabIndex={0}
|
||||
value={this.state.newDatabaseId}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
@@ -251,9 +246,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
isSharded={this.state.isSharded}
|
||||
setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
|
||||
setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)}
|
||||
setIsThroughputCapExceeded={(isThroughputCapExceeded: boolean) =>
|
||||
this.setState({ isThroughputCapExceeded })
|
||||
}
|
||||
onCostAcknowledgeChange={(isAcknowledge: boolean) => (this.isCostAcknowledged = isAcknowledge)}
|
||||
/>
|
||||
)}
|
||||
@@ -279,7 +271,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
<Stack horizontal>
|
||||
<span className="mandatoryStar">* </span>
|
||||
<Text className="panelTextBold" variant="small">
|
||||
{`${getCollectionName()} id`}
|
||||
{`${getCollectionName()} ${userContext.apiType === "Mongo" ? "name" : "id"}`}
|
||||
</Text>
|
||||
<TooltipHost
|
||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||
@@ -325,7 +317,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
aria-label="Turn on indexing"
|
||||
aria-checked={this.state.enableIndexing}
|
||||
type="radio"
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
onChange={this.onTurnOnIndexing.bind(this)}
|
||||
/>
|
||||
@@ -337,7 +328,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
aria-label="Turn off indexing"
|
||||
aria-checked={!this.state.enableIndexing}
|
||||
type="radio"
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
onChange={this.onTurnOffIndexing.bind(this)}
|
||||
/>
|
||||
@@ -380,7 +370,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
aria-checked={!this.state.isSharded}
|
||||
name="unsharded"
|
||||
type="radio"
|
||||
role="radio"
|
||||
id="unshardedOption"
|
||||
tabIndex={0}
|
||||
onChange={this.onUnshardedRadioBtnChange.bind(this)}
|
||||
@@ -394,7 +383,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
aria-checked={this.state.isSharded}
|
||||
name="sharded"
|
||||
type="radio"
|
||||
role="radio"
|
||||
id="shardedOption"
|
||||
tabIndex={0}
|
||||
onChange={this.onShardedRadioBtnChange.bind(this)}
|
||||
@@ -485,9 +473,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
isSharded={this.state.isSharded}
|
||||
setThroughputValue={(throughput: number) => (this.collectionThroughput = throughput)}
|
||||
setIsAutoscale={(isAutoscale: boolean) => (this.isCollectionAutoscale = isAutoscale)}
|
||||
setIsThroughputCapExceeded={(isThroughputCapExceeded: boolean) =>
|
||||
this.setState({ isThroughputCapExceeded })
|
||||
}
|
||||
onCostAcknowledgeChange={(isAcknowledged: boolean) => {
|
||||
this.isCostAcknowledged = isAcknowledged;
|
||||
}}
|
||||
@@ -523,7 +508,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
: "Comma separated paths e.g. /firstName,/address/zipCode"
|
||||
}
|
||||
className="panelTextField"
|
||||
autoFocus
|
||||
value={uniqueKey}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const uniqueKeys = this.state.uniqueKeys.map((uniqueKey: string, j: number) => {
|
||||
@@ -582,7 +566,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
aria-checked={this.state.enableAnalyticalStore}
|
||||
name="analyticalStore"
|
||||
type="radio"
|
||||
role="radio"
|
||||
id="enableAnalyticalStoreBtn"
|
||||
tabIndex={0}
|
||||
onChange={this.onEnableAnalyticalStoreRadioBtnChange.bind(this)}
|
||||
@@ -597,7 +580,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
aria-checked={!this.state.enableAnalyticalStore}
|
||||
name="analyticalStore"
|
||||
type="radio"
|
||||
role="radio"
|
||||
id="disableAnalyticalStoreBtn"
|
||||
tabIndex={0}
|
||||
onChange={this.onDisableAnalyticalStoreRadioBtnChange.bind(this)}
|
||||
@@ -667,7 +649,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
|
||||
{userContext.apiType === "SQL" && (
|
||||
<Checkbox
|
||||
label="My partition key is larger than 101 bytes"
|
||||
label="My partition key is larger than 100 bytes"
|
||||
checked={this.state.useHashV2}
|
||||
styles={{
|
||||
text: { fontSize: 12 },
|
||||
@@ -684,7 +666,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
)}
|
||||
</div>
|
||||
|
||||
<PanelFooterComponent buttonLabel="OK" isButtonDisabled={this.state.isThroughputCapExceeded} />
|
||||
<PanelFooterComponent buttonLabel="OK" />
|
||||
|
||||
{this.state.isExecuting && <PanelLoadingScreen />}
|
||||
</form>
|
||||
@@ -887,6 +869,10 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isServerlessAccount()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (userContext.apiType) {
|
||||
case "SQL":
|
||||
case "Mongo":
|
||||
@@ -1003,7 +989,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
|
||||
const collectionId: string = this.state.collectionId.trim();
|
||||
let databaseId = this.state.createNewDatabase ? this.state.newDatabaseId.trim() : this.state.selectedDatabaseId;
|
||||
let partitionKeyString = this.state.isSharded ? this.state.partitionKey.trim() : undefined;
|
||||
let partitionKeyString = this.state.partitionKey.trim();
|
||||
|
||||
if (userContext.apiType === "Tables") {
|
||||
// Table require fixed Database: TablesDB, and fixed Partition Key: /'$pk'
|
||||
|
||||
@@ -52,7 +52,6 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
||||
);
|
||||
const [formErrors, setFormErrors] = useState<string>("");
|
||||
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
||||
const [isThroughputCapExceeded, setIsThroughputCapExceeded] = useState<boolean>(false);
|
||||
|
||||
const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier;
|
||||
|
||||
@@ -80,9 +79,7 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||
};
|
||||
TelemetryProcessor.trace(Action.CreateDatabase, ActionModifiers.Open, addDatabasePaneOpenMessage);
|
||||
if (buttonElement) {
|
||||
buttonElement.focus();
|
||||
}
|
||||
buttonElement.focus();
|
||||
}, []);
|
||||
|
||||
const onSubmit = () => {
|
||||
@@ -172,7 +169,6 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
||||
formError: formErrors,
|
||||
isExecuting,
|
||||
submitButtonText: "OK",
|
||||
isSubmitButtonDisabled: isThroughputCapExceeded,
|
||||
onSubmit,
|
||||
};
|
||||
|
||||
@@ -214,7 +210,6 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
||||
placeholder={databaseIdPlaceHolder}
|
||||
value={databaseId}
|
||||
onChange={handleonChangeDBId}
|
||||
autoFocus
|
||||
styles={getTextFieldStyles()}
|
||||
/>
|
||||
|
||||
@@ -243,7 +238,6 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
||||
isSharded={databaseCreateNewShared}
|
||||
setThroughputValue={(newThroughput: number) => (throughput = newThroughput)}
|
||||
setIsAutoscale={(isAutoscale: boolean) => (isAutoscaleSelected = isAutoscale)}
|
||||
setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)}
|
||||
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -4,7 +4,6 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
|
||||
<RightPaneForm
|
||||
formError=""
|
||||
isExecuting={false}
|
||||
isSubmitButtonDisabled={false}
|
||||
onSubmit={[Function]}
|
||||
submitButtonText="OK"
|
||||
>
|
||||
@@ -34,7 +33,6 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
|
||||
aria-label="Database id"
|
||||
aria-required="true"
|
||||
autoComplete="off"
|
||||
autoFocus={true}
|
||||
id="database-id"
|
||||
onChange={[Function]}
|
||||
pattern="[^/?#\\\\\\\\]*[^/?# \\\\\\\\]"
|
||||
@@ -93,7 +91,6 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
|
||||
isSharded={true}
|
||||
onCostAcknowledgeChange={[Function]}
|
||||
setIsAutoscale={[Function]}
|
||||
setIsThroughputCapExceeded={[Function]}
|
||||
setThroughputValue={[Function]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Checkbox, Dropdown, IDropdownOption, Link, Stack, Text, TextField } from "@fluentui/react";
|
||||
import * as Constants from "Common/Constants";
|
||||
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
|
||||
import { InfoTooltip } from "Common/Tooltip/InfoTooltip";
|
||||
import { useSidePanel } from "hooks/useSidePanel";
|
||||
import React, { FunctionComponent, useState } from "react";
|
||||
import * as SharedConstants from "Shared/Constants";
|
||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "UserContext";
|
||||
import { isServerlessAccount } from "Utils/CapabilityUtils";
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import * as SharedConstants from "../../../Shared/Constants";
|
||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import { isServerlessAccount } from "../../../Utils/CapabilityUtils";
|
||||
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
|
||||
import Explorer from "../../Explorer";
|
||||
import { CassandraAPIDataClient } from "../../Tables/TableDataClient";
|
||||
@@ -43,7 +43,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
||||
const [dedicateTableThroughput, setDedicateTableThroughput] = useState<boolean>(false);
|
||||
const [isExecuting, setIsExecuting] = useState<boolean>();
|
||||
const [formError, setFormError] = useState<string>("");
|
||||
const [isThroughputCapExceeded, setIsThroughputCapExceeded] = useState<boolean>(false);
|
||||
const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier;
|
||||
|
||||
const addCollectionPaneOpenMessage = {
|
||||
@@ -150,7 +149,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
||||
formError,
|
||||
isExecuting,
|
||||
submitButtonText: "OK",
|
||||
isSubmitButtonDisabled: isThroughputCapExceeded,
|
||||
onSubmit,
|
||||
};
|
||||
|
||||
@@ -171,7 +169,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
||||
aria-label="Create new keyspace"
|
||||
checked={keyspaceCreateNew}
|
||||
type="radio"
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
onChange={() => {
|
||||
setKeyspaceCreateNew(true);
|
||||
@@ -186,7 +183,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
||||
aria-label="Use existing keyspace"
|
||||
checked={!keyspaceCreateNew}
|
||||
type="radio"
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
onChange={() => {
|
||||
setKeyspaceCreateNew(false);
|
||||
@@ -210,7 +206,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
||||
value={newKeyspaceId}
|
||||
onChange={(e, newValue) => setNewKeyspaceId(newValue)}
|
||||
ariaLabel="Keyspace id"
|
||||
autoFocus
|
||||
/>
|
||||
|
||||
{!isServerlessAccount() && (
|
||||
@@ -264,7 +259,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
||||
isSharded
|
||||
setThroughputValue={(throughput: number) => (newKeySpaceThroughput = throughput)}
|
||||
setIsAutoscale={(isAutoscale: boolean) => (isNewKeySpaceAutoscale = isAutoscale)}
|
||||
setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)}
|
||||
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
||||
/>
|
||||
)}
|
||||
@@ -334,10 +328,9 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
||||
<ThroughputInput
|
||||
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !useDatabases.getState().isFirstResourceCreated()}
|
||||
isDatabase={false}
|
||||
isSharded
|
||||
isSharded={false}
|
||||
setThroughputValue={(throughput: number) => (tableThroughput = throughput)}
|
||||
setIsAutoscale={(isAutoscale: boolean) => (isTableAutoscale = isAutoscale)}
|
||||
setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)}
|
||||
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils
|
||||
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import { IPinnedRepo, JunoClient } from "../../../Juno/JunoClient";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||
import Explorer from "../../Explorer";
|
||||
@@ -75,8 +76,8 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
||||
selectedLocation.owner,
|
||||
selectedLocation.repo
|
||||
)} - ${selectedLocation.branch}`;
|
||||
} else if (selectedLocation.type === "MyNotebooks" && useNotebook.getState().isPhoenixNotebooks) {
|
||||
destination = useNotebook.getState().notebookFolderName;
|
||||
} else if (selectedLocation.type === "MyNotebooks" && userContext.features.phoenix) {
|
||||
destination = "My Notebooks Scratch";
|
||||
}
|
||||
|
||||
clearMessage = NotificationConsoleUtils.logConsoleProgress(`Copying ${name} to ${destination}`);
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Text, TextField } from "@fluentui/react";
|
||||
import { Areas } from "Common/Constants";
|
||||
import { deleteCollection } from "Common/dataAccess/deleteCollection";
|
||||
import DeleteFeedback from "Common/DeleteFeedback";
|
||||
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
|
||||
import { Collection } from "Contracts/ViewModels";
|
||||
import { useSidePanel } from "hooks/useSidePanel";
|
||||
import { useTabs } from "hooks/useTabs";
|
||||
import React, { FunctionComponent, useState } from "react";
|
||||
import { DefaultExperienceUtility } from "Shared/DefaultExperienceUtility";
|
||||
import { Action, ActionModifiers } from "Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "UserContext";
|
||||
import { getCollectionName } from "Utils/APITypeUtils";
|
||||
import * as NotificationConsoleUtils from "Utils/NotificationConsoleUtils";
|
||||
import { Areas } from "../../../Common/Constants";
|
||||
import { deleteCollection } from "../../../Common/dataAccess/deleteCollection";
|
||||
import DeleteFeedback from "../../../Common/DeleteFeedback";
|
||||
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||
import { Collection } from "../../../Contracts/ViewModels";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import { useTabs } from "../../../hooks/useTabs";
|
||||
import { DefaultExperienceUtility } from "../../../Shared/DefaultExperienceUtility";
|
||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import { getCollectionName } from "../../../Utils/APITypeUtils";
|
||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||
import { useDatabases } from "../../useDatabases";
|
||||
import { useSelectedNode } from "../../useSelectedNode";
|
||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||
@@ -38,7 +38,7 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
||||
const onSubmit = async (): Promise<void> => {
|
||||
const collection = useSelectedNode.getState().findSelectedCollection();
|
||||
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);
|
||||
NotificationConsoleUtils.logConsoleError(
|
||||
`Error while deleting ${collectionName} ${collection.id()}: ${errorMessage}`
|
||||
@@ -119,7 +119,6 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
||||
<Text variant="small">Confirm by typing the {collectionName.toLowerCase()} id</Text>
|
||||
<TextField
|
||||
id="confirmCollectionId"
|
||||
autoFocus
|
||||
value={inputCollectionName}
|
||||
styles={{ fieldGroup: { width: 300 } }}
|
||||
onChange={(event, newInput?: string) => {
|
||||
|
||||
@@ -41,7 +41,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
||||
</Text>
|
||||
<StyledTextFieldBase
|
||||
ariaLabel="Confirm by typing the container id"
|
||||
autoFocus={true}
|
||||
id="confirmCollectionId"
|
||||
onChange={[Function]}
|
||||
styles={
|
||||
@@ -55,7 +54,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
||||
>
|
||||
<TextFieldBase
|
||||
ariaLabel="Confirm by typing the container id"
|
||||
autoFocus={true}
|
||||
deferredValidationTime={200}
|
||||
id="confirmCollectionId"
|
||||
onChange={[Function]}
|
||||
@@ -349,7 +347,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
||||
<input
|
||||
aria-invalid={false}
|
||||
aria-label="Confirm by typing the container id"
|
||||
autoFocus={true}
|
||||
className="ms-TextField-field field-57"
|
||||
id="confirmCollectionId"
|
||||
onBlur={[Function]}
|
||||
@@ -369,21 +366,18 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
||||
</div>
|
||||
<PanelFooterComponent
|
||||
buttonLabel="OK"
|
||||
isButtonDisabled={false}
|
||||
>
|
||||
<div
|
||||
className="panelFooter"
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
ariaLabel="OK"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="OK"
|
||||
type="submit"
|
||||
>
|
||||
<PrimaryButton
|
||||
ariaLabel="OK"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="OK"
|
||||
theme={
|
||||
@@ -663,7 +657,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
ariaLabel="OK"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -945,7 +938,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
||||
>
|
||||
<DefaultButton
|
||||
ariaLabel="OK"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -1228,7 +1220,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
||||
<BaseButton
|
||||
ariaLabel="OK"
|
||||
baseClassName="ms-Button"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Text, TextField } from "@fluentui/react";
|
||||
import { useBoolean } from "@fluentui/react-hooks";
|
||||
import { Areas } from "Common/Constants";
|
||||
import { deleteDatabase } from "Common/dataAccess/deleteDatabase";
|
||||
import DeleteFeedback from "Common/DeleteFeedback";
|
||||
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
|
||||
import { Collection, Database } from "Contracts/ViewModels";
|
||||
import { useSidePanel } from "hooks/useSidePanel";
|
||||
import { useTabs } from "hooks/useTabs";
|
||||
import React, { FunctionComponent, useState } from "react";
|
||||
import { DefaultExperienceUtility } from "Shared/DefaultExperienceUtility";
|
||||
import { Action, ActionModifiers } from "Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "UserContext";
|
||||
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
||||
import { Areas } from "../../Common/Constants";
|
||||
import { deleteDatabase } from "../../Common/dataAccess/deleteDatabase";
|
||||
import DeleteFeedback from "../../Common/DeleteFeedback";
|
||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||
import { Collection, Database } from "../../Contracts/ViewModels";
|
||||
import { useSidePanel } from "../../hooks/useSidePanel";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||
import { useDatabases } from "../useDatabases";
|
||||
import { useSelectedNode } from "../useSelectedNode";
|
||||
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent";
|
||||
@@ -129,7 +129,6 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
||||
<Text variant="small">Confirm by typing the database id</Text>
|
||||
<TextField
|
||||
id="confirmDatabaseId"
|
||||
autoFocus
|
||||
styles={{ fieldGroup: { width: 300 } }}
|
||||
onChange={(event, newInput?: string) => {
|
||||
setDatabaseInput(newInput);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { IDropdownOption, IImageProps, Image, Stack, Text } from "@fluentui/react";
|
||||
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 { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
||||
@@ -25,16 +25,19 @@ interface UnwrappedExecuteSprocParam {
|
||||
export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPaneProps> = ({
|
||||
storedProcedure,
|
||||
}: 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 [numberOfParams, setNumberOfParams] = useState<number>(1);
|
||||
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 onPartitionKeyChange = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
|
||||
setSelectedKey(item);
|
||||
};
|
||||
|
||||
const validateUnwrappedParams = (): boolean => {
|
||||
const unwrappedParams: UnwrappedExecuteSprocParam[] = paramKeyValuesRef.current;
|
||||
const unwrappedParams: UnwrappedExecuteSprocParam[] = paramKeyValues;
|
||||
for (let i = 0; i < unwrappedParams.length; i++) {
|
||||
const { key: paramType, text: paramValue } = unwrappedParams[i];
|
||||
if (paramType === "custom" && (paramValue === "" || paramValue === undefined)) {
|
||||
@@ -50,9 +53,8 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
|
||||
};
|
||||
|
||||
const submit = (): void => {
|
||||
const wrappedSprocParams: UnwrappedExecuteSprocParam[] = paramKeyValuesRef.current;
|
||||
const partitionValue: string = partitionValueRef.current;
|
||||
const partitionKey: string = partitionKeyRef.current;
|
||||
const wrappedSprocParams: UnwrappedExecuteSprocParam[] = paramKeyValues;
|
||||
const { key: partitionKey } = selectedKey;
|
||||
if (partitionKey === "custom" && (partitionValue === "" || partitionValue === undefined)) {
|
||||
setInvalidParamError(partitionValue);
|
||||
return;
|
||||
@@ -76,21 +78,37 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
|
||||
};
|
||||
|
||||
const deleteParamAtIndex = (indexToRemove: number): void => {
|
||||
paramKeyValuesRef.current.splice(indexToRemove, 1);
|
||||
setNumberOfParams(numberOfParams - 1);
|
||||
const cloneParamKeyValue = [...paramKeyValues];
|
||||
cloneParamKeyValue.splice(indexToRemove, 1);
|
||||
setParamKeyValues(cloneParamKeyValue);
|
||||
};
|
||||
|
||||
const addNewParamAtIndex = (indexToAdd: number): void => {
|
||||
paramKeyValuesRef.current.splice(indexToAdd, 0, { key: "string", text: "" });
|
||||
setNumberOfParams(numberOfParams + 1);
|
||||
const cloneParamKeyValue = [...paramKeyValues];
|
||||
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 => {
|
||||
paramKeyValuesRef.current.push({
|
||||
key: "string",
|
||||
text: "",
|
||||
});
|
||||
setNumberOfParams(numberOfParams + 1);
|
||||
const cloneParamKeyValue = [...paramKeyValues];
|
||||
cloneParamKeyValue.splice(cloneParamKeyValue.length, 0, { key: "string", text: "" });
|
||||
setParamKeyValues(cloneParamKeyValue);
|
||||
};
|
||||
|
||||
const props: RightPaneFormProps = {
|
||||
@@ -100,52 +118,46 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
|
||||
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 (
|
||||
<RightPaneForm {...props}>
|
||||
<div className="panelMainContent">
|
||||
<InputParameter
|
||||
dropdownLabel="Key"
|
||||
inputParameterTitle="Partition key value"
|
||||
inputLabel="Value"
|
||||
isAddRemoveVisible={false}
|
||||
onParamValueChange={(_event, newInput?: string) => (partitionValueRef.current = newInput)}
|
||||
onParamKeyChange={(_event: React.FormEvent<HTMLDivElement>, item: IDropdownOption) =>
|
||||
(partitionKeyRef.current = item.key.toString())
|
||||
}
|
||||
paramValue={partitionValueRef.current}
|
||||
selectedKey={partitionKeyRef.current}
|
||||
/>
|
||||
{getInputParameterComponent()}
|
||||
<Stack horizontal onClick={() => addNewParamAtLastIndex()} tabIndex={0}>
|
||||
<Image {...imageProps} src={AddPropertyIcon} alt="Add param" />
|
||||
<Text className="addNewParamStyle">Add New Param</Text>
|
||||
</Stack>
|
||||
<div className="panelFormWrapper">
|
||||
<div className="panelMainContent">
|
||||
<InputParameter
|
||||
dropdownLabel="Key"
|
||||
inputParameterTitle="Partition key value"
|
||||
inputLabel="Value"
|
||||
isAddRemoveVisible={false}
|
||||
onParamValueChange={(_event, newInput?: string) => {
|
||||
setPartitionValue(newInput);
|
||||
}}
|
||||
onParamKeyChange={onPartitionKeyChange}
|
||||
paramValue={partitionValue}
|
||||
selectedKey={selectedKey.key}
|
||||
/>
|
||||
{paramKeyValues.map((paramKeyValue, index) => (
|
||||
<InputParameter
|
||||
key={paramKeyValue && paramKeyValue.text + index}
|
||||
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>
|
||||
</RightPaneForm>
|
||||
);
|
||||
|
||||
@@ -55,7 +55,7 @@ export const InputParameter: FunctionComponent<InputParameterProps> = ({
|
||||
<Stack horizontal>
|
||||
<Dropdown
|
||||
label={dropdownLabel && dropdownLabel}
|
||||
defaultSelectedKey={selectedKey}
|
||||
selectedKey={selectedKey}
|
||||
onChange={onParamKeyChange}
|
||||
options={options}
|
||||
styles={dropdownStyles}
|
||||
@@ -64,31 +64,16 @@ export const InputParameter: FunctionComponent<InputParameterProps> = ({
|
||||
<TextField
|
||||
label={inputLabel && inputLabel}
|
||||
id="confirmCollectionId"
|
||||
defaultValue={paramValue}
|
||||
value={paramValue}
|
||||
onChange={onParamValueChange}
|
||||
tabIndex={0}
|
||||
/>
|
||||
{isAddRemoveVisible && (
|
||||
<>
|
||||
<div tabIndex={0}>
|
||||
<Image
|
||||
{...imageProps}
|
||||
src={EntityCancelIcon}
|
||||
alt="Delete param"
|
||||
id="deleteparam"
|
||||
role="button"
|
||||
onClick={onDeleteParamKeyPress}
|
||||
/>
|
||||
<div tabIndex={0} onClick={onDeleteParamKeyPress} role="button" onKeyPress={onDeleteParamKeyPress}>
|
||||
<Image {...imageProps} src={EntityCancelIcon} alt="Delete param" id="deleteparam" />
|
||||
</div>
|
||||
<div tabIndex={0}>
|
||||
<Image
|
||||
{...imageProps}
|
||||
src={AddPropertyIcon}
|
||||
alt="Add param"
|
||||
id="addparam"
|
||||
role="button"
|
||||
onClick={onAddNewParamKeyPress}
|
||||
/>
|
||||
<div tabIndex={0} onClick={onAddNewParamKeyPress} role="button" onKeyPress={onAddNewParamKeyPress}>
|
||||
<Image {...imageProps} src={AddPropertyIcon} alt="Add param" id="addparam" />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -35,9 +35,6 @@ interface IGitHubReposPanelState {
|
||||
}
|
||||
export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IGitHubReposPanelState> {
|
||||
private static readonly PageSize = 30;
|
||||
private static readonly MasterBranchName = "master";
|
||||
private static readonly MainBranchName = "main";
|
||||
|
||||
private isAddedRepo = false;
|
||||
private gitHubClient: GitHubClient;
|
||||
private junoClient: JunoClient;
|
||||
@@ -119,8 +116,6 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
|
||||
if (response.status !== HttpStatusCodes.OK) {
|
||||
throw new Error(`Received HTTP ${response.status} when saving pinned repos`);
|
||||
}
|
||||
|
||||
this.props.explorer.notebookManager?.refreshPinnedRepos();
|
||||
} catch (error) {
|
||||
handleError(error, "GitHubReposPane/submit", "Failed to save pinned repos");
|
||||
}
|
||||
@@ -212,14 +207,6 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
|
||||
if (response.data) {
|
||||
branchesProps.branches = branchesProps.branches.concat(response.data);
|
||||
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) {
|
||||
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);
|
||||
if (existingRepo) {
|
||||
existingRepo.branches = item.branches;
|
||||
this.setState({
|
||||
gitHubReposState: {
|
||||
...this.state.gitHubReposState,
|
||||
reposListProps: {
|
||||
...this.state.gitHubReposState.reposListProps,
|
||||
pinnedReposProps: {
|
||||
repos: this.pinnedReposProps.repos,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
this.pinnedReposProps.repos = [...this.pinnedReposProps.repos, item];
|
||||
}
|
||||
@@ -398,7 +374,6 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
|
||||
lastPageInfo: undefined,
|
||||
hasMore: true,
|
||||
isLoading: true,
|
||||
defaultBranchName: undefined,
|
||||
loadMore: (): Promise<void> => this.loadMoreBranches(item.repo),
|
||||
};
|
||||
this.loadMoreBranches(item.repo);
|
||||
|
||||
@@ -23,13 +23,7 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
|
||||
"isTabsContentExpanded": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"phoenixClient": PhoenixClient {
|
||||
"retryOptions": Object {
|
||||
"maxTimeout": 5000,
|
||||
"minTimeout": 5000,
|
||||
"retries": 3,
|
||||
},
|
||||
},
|
||||
"phoenixClient": PhoenixClient {},
|
||||
"provideFeedbackEmail": [Function],
|
||||
"queriesClient": QueriesClient {
|
||||
"container": [Circular],
|
||||
|
||||
@@ -92,7 +92,6 @@ export const LoadQueryPane: FunctionComponent = (): JSX.Element => {
|
||||
id="confirmCollectionId"
|
||||
label="Select a query document"
|
||||
value={selectedFileName}
|
||||
autoFocus
|
||||
readOnly
|
||||
styles={{ fieldGroup: { width: 300 } }}
|
||||
/>
|
||||
|
||||
@@ -17,7 +17,6 @@ exports[`Load Query Pane should render Default properly 1`] = `
|
||||
horizontal={true}
|
||||
>
|
||||
<StyledTextFieldBase
|
||||
autoFocus={true}
|
||||
id="confirmCollectionId"
|
||||
label="Select a query document"
|
||||
readOnly={true}
|
||||
|
||||
@@ -3,20 +3,12 @@ import React from "react";
|
||||
|
||||
export interface PanelFooterProps {
|
||||
buttonLabel: string;
|
||||
isButtonDisabled?: boolean;
|
||||
}
|
||||
|
||||
export const PanelFooterComponent: React.FunctionComponent<PanelFooterProps> = ({
|
||||
buttonLabel,
|
||||
isButtonDisabled,
|
||||
}: PanelFooterProps): JSX.Element => (
|
||||
<div className="panelFooter">
|
||||
<PrimaryButton
|
||||
type="submit"
|
||||
id="sidePanelOkButton"
|
||||
text={buttonLabel}
|
||||
ariaLabel={buttonLabel}
|
||||
disabled={!!isButtonDisabled}
|
||||
/>
|
||||
<PrimaryButton type="submit" id="sidePanelOkButton" text={buttonLabel} ariaLabel={buttonLabel} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -48,9 +48,15 @@ export const PanelInfoErrorComponent: React.FunctionComponent<PanelInfoErrorProp
|
||||
)}
|
||||
</Text>
|
||||
{showErrorDetails && (
|
||||
<a className="paneErrorLink" role="link" onClick={expandConsole}>
|
||||
<span
|
||||
className="paneErrorLink moreOption"
|
||||
role="link"
|
||||
onClick={expandConsole}
|
||||
onKeyPress={expandConsole}
|
||||
tabIndex={0}
|
||||
>
|
||||
More details
|
||||
</a>
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</Stack>
|
||||
|
||||
@@ -3,6 +3,6 @@ import LoadingIndicator_3Squares from "../../../images/LoadingIndicator_3Squares
|
||||
|
||||
export const PanelLoadingScreen: React.FunctionComponent = () => (
|
||||
<div className="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer">
|
||||
<img className="dataExplorerLoader" src={LoadingIndicator_3Squares} />
|
||||
<img className="dataExplorerLoader" src={LoadingIndicator_3Squares} alt="loading indicator" />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,6 @@ export interface RightPaneFormProps {
|
||||
onSubmit: () => void;
|
||||
submitButtonText: string;
|
||||
isSubmitButtonHidden?: boolean;
|
||||
isSubmitButtonDisabled?: boolean;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
@@ -19,7 +18,6 @@ export const RightPaneForm: FunctionComponent<RightPaneFormProps> = ({
|
||||
onSubmit,
|
||||
submitButtonText,
|
||||
isSubmitButtonHidden = false,
|
||||
isSubmitButtonDisabled = false,
|
||||
children,
|
||||
}: RightPaneFormProps) => {
|
||||
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
@@ -32,9 +30,7 @@ export const RightPaneForm: FunctionComponent<RightPaneFormProps> = ({
|
||||
<form className="panelFormWrapper" onSubmit={handleOnSubmit}>
|
||||
{formError && <PanelInfoErrorComponent messageType="error" message={formError} showErrorDetails={true} />}
|
||||
{children}
|
||||
{!isSubmitButtonHidden && (
|
||||
<PanelFooterComponent buttonLabel={submitButtonText} isButtonDisabled={isSubmitButtonDisabled} />
|
||||
)}
|
||||
{!isSubmitButtonHidden && <PanelFooterComponent buttonLabel={submitButtonText} />}
|
||||
</form>
|
||||
{isExecuting && <PanelLoadingScreen />}
|
||||
</>
|
||||
|
||||
@@ -14,21 +14,18 @@ exports[`Right Pane Form should render Default properly 1`] = `
|
||||
>
|
||||
<PanelFooterComponent
|
||||
buttonLabel="Load"
|
||||
isButtonDisabled={false}
|
||||
>
|
||||
<div
|
||||
className="panelFooter"
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
ariaLabel="Load"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="Load"
|
||||
type="submit"
|
||||
>
|
||||
<PrimaryButton
|
||||
ariaLabel="Load"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="Load"
|
||||
theme={
|
||||
@@ -308,7 +305,6 @@ exports[`Right Pane Form should render Default properly 1`] = `
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
ariaLabel="Load"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -590,7 +586,6 @@ exports[`Right Pane Form should render Default properly 1`] = `
|
||||
>
|
||||
<DefaultButton
|
||||
ariaLabel="Load"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -873,7 +868,6 @@ exports[`Right Pane Form should render Default properly 1`] = `
|
||||
<BaseButton
|
||||
ariaLabel="Load"
|
||||
baseClassName="ms-Button"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
|
||||
@@ -146,7 +146,6 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({ explorer
|
||||
<TextField
|
||||
id="saveQueryInput"
|
||||
label="Name"
|
||||
autoFocus
|
||||
styles={{ fieldGroup: { width: 300 } }}
|
||||
onChange={(event, newInput?: string) => {
|
||||
setQueryName(newInput);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Checkbox, ChoiceGroup, IChoiceGroupOption, SpinButton } from "@fluentui/react";
|
||||
import * as Constants from "Common/Constants";
|
||||
import { InfoTooltip } from "Common/Tooltip/InfoTooltip";
|
||||
import { configContext } from "ConfigContext";
|
||||
import { useSidePanel } from "hooks/useSidePanel";
|
||||
import React, { FunctionComponent, MouseEvent, useState } from "react";
|
||||
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||
import * as StringUtility from "Shared/StringUtility";
|
||||
import { userContext } from "UserContext";
|
||||
import { logConsoleInfo } from "Utils/NotificationConsoleUtils";
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
|
||||
import { configContext } from "../../../ConfigContext";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import { LocalStorageUtility, StorageKey } from "../../../Shared/StorageUtility";
|
||||
import * as StringUtility from "../../../Shared/StringUtility";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import { logConsoleInfo } from "../../../Utils/NotificationConsoleUtils";
|
||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||
|
||||
export const SettingsPane: FunctionComponent = () => {
|
||||
@@ -113,50 +113,20 @@ export const SettingsPane: FunctionComponent = () => {
|
||||
const handleOnPageOptionChange = (ev: React.FormEvent<HTMLInputElement>, option: IChoiceGroupOption): void => {
|
||||
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 (
|
||||
<RightPaneForm {...genericPaneProps}>
|
||||
<div className="paneMainContent">
|
||||
{shouldShowQueryPageOptions && (
|
||||
<div className="settingsSection">
|
||||
<div className="settingsSectionPart">
|
||||
<fieldset>
|
||||
<legend id="pageOptions" className="settingsSectionLabel legendLabel">
|
||||
Page Options
|
||||
</legend>
|
||||
<div className="settingsSectionPart pageOptionsPart">
|
||||
<div className="settingsSectionLabel">
|
||||
Page options
|
||||
<InfoTooltip>
|
||||
Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as many
|
||||
query results per page.
|
||||
</InfoTooltip>
|
||||
<ChoiceGroup
|
||||
ariaLabelledBy="pageOptions"
|
||||
selectedKey={pageOption}
|
||||
options={pageOptionList}
|
||||
styles={choiceButtonStyles}
|
||||
onChange={handleOnPageOptionChange}
|
||||
/>
|
||||
</fieldset>
|
||||
</div>
|
||||
<ChoiceGroup selectedKey={pageOption} options={pageOptionList} onChange={handleOnPageOptionChange} />
|
||||
</div>
|
||||
<div className="tabs settingsSectionPart">
|
||||
{isCustomPageOptionSelected() && (
|
||||
|
||||
@@ -14,59 +14,32 @@ exports[`Settings Pane should render Default properly 1`] = `
|
||||
className="settingsSection"
|
||||
>
|
||||
<div
|
||||
className="settingsSectionPart"
|
||||
className="settingsSectionPart pageOptionsPart"
|
||||
>
|
||||
<fieldset>
|
||||
<legend
|
||||
className="settingsSectionLabel legendLabel"
|
||||
id="pageOptions"
|
||||
>
|
||||
Page Options
|
||||
</legend>
|
||||
<div
|
||||
className="settingsSectionLabel"
|
||||
>
|
||||
Page options
|
||||
<InfoTooltip>
|
||||
Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as many query results per page.
|
||||
</InfoTooltip>
|
||||
<StyledChoiceGroup
|
||||
ariaLabelledBy="pageOptions"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"key": "custom",
|
||||
"text": "Custom",
|
||||
},
|
||||
Object {
|
||||
"key": "unlimited",
|
||||
"text": "Unlimited",
|
||||
},
|
||||
]
|
||||
}
|
||||
selectedKey="custom"
|
||||
styles={
|
||||
</div>
|
||||
<StyledChoiceGroup
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"flexContainer": Array [
|
||||
Object {
|
||||
"selectors": Object {
|
||||
".ms-ChoiceField": Object {
|
||||
"marginTop": 0,
|
||||
},
|
||||
".ms-ChoiceField-wrapper label": Object {
|
||||
"fontSize": 12,
|
||||
"paddingTop": 0,
|
||||
},
|
||||
".ms-ChoiceFieldGroup root-133": Object {
|
||||
"clear": "both",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
"root": Object {
|
||||
"clear": "both",
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
</fieldset>
|
||||
"key": "custom",
|
||||
"text": "Custom",
|
||||
},
|
||||
Object {
|
||||
"key": "unlimited",
|
||||
"text": "Unlimited",
|
||||
},
|
||||
]
|
||||
}
|
||||
selectedKey="custom"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="tabs settingsSectionPart"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { TextField } from "@fluentui/react";
|
||||
import * as ViewModels from "Contracts/ViewModels";
|
||||
import { useTabs } from "hooks/useTabs";
|
||||
import React, { FormEvent, FunctionComponent, useState } from "react";
|
||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "Utils/NotificationConsoleUtils";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { useTabs } from "../../../hooks/useTabs";
|
||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
|
||||
import * as FileSystemUtil from "../../Notebook/FileSystemUtil";
|
||||
import { NotebookContentItem } from "../../Notebook/NotebookContentItem";
|
||||
import NotebookV2Tab from "../../Tabs/NotebookV2Tab";
|
||||
@@ -97,7 +97,6 @@ export const StringInputPane: FunctionComponent<StringInputPanelProps> = ({
|
||||
label={inputLabel}
|
||||
name="collectionIdConfirmation"
|
||||
value={stringInput}
|
||||
autoFocus
|
||||
required
|
||||
onChange={(event: FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) =>
|
||||
setStringInput(newValue)
|
||||
|
||||
@@ -13,13 +13,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
||||
"isTabsContentExpanded": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"phoenixClient": PhoenixClient {
|
||||
"retryOptions": Object {
|
||||
"maxTimeout": 5000,
|
||||
"minTimeout": 5000,
|
||||
"retries": 3,
|
||||
},
|
||||
},
|
||||
"phoenixClient": PhoenixClient {},
|
||||
"provideFeedbackEmail": [Function],
|
||||
"queriesClient": QueriesClient {
|
||||
"container": [Circular],
|
||||
@@ -62,7 +56,6 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
||||
>
|
||||
<StyledTextFieldBase
|
||||
aria-label="Enter new directory name"
|
||||
autoFocus={true}
|
||||
label="Enter new directory name"
|
||||
name="collectionIdConfirmation"
|
||||
onChange={[Function]}
|
||||
@@ -71,7 +64,6 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
||||
>
|
||||
<TextFieldBase
|
||||
aria-label="Enter new directory name"
|
||||
autoFocus={true}
|
||||
deferredValidationTime={200}
|
||||
label="Enter new directory name"
|
||||
name="collectionIdConfirmation"
|
||||
@@ -661,7 +653,6 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
||||
<input
|
||||
aria-invalid={false}
|
||||
aria-labelledby="TextFieldLabel2"
|
||||
autoFocus={true}
|
||||
className="ms-TextField-field field-56"
|
||||
id="TextField0"
|
||||
name="collectionIdConfirmation"
|
||||
@@ -681,21 +672,18 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
||||
</div>
|
||||
<PanelFooterComponent
|
||||
buttonLabel="Create"
|
||||
isButtonDisabled={false}
|
||||
>
|
||||
<div
|
||||
className="panelFooter"
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
ariaLabel="Create"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="Create"
|
||||
type="submit"
|
||||
>
|
||||
<PrimaryButton
|
||||
ariaLabel="Create"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="Create"
|
||||
theme={
|
||||
@@ -975,7 +963,6 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
ariaLabel="Create"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -1257,7 +1244,6 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
||||
>
|
||||
<DefaultButton
|
||||
ariaLabel="Create"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -1540,7 +1526,6 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
||||
<BaseButton
|
||||
ariaLabel="Create"
|
||||
baseClassName="ms-Button"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
|
||||
@@ -1262,21 +1262,18 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
||||
</div>
|
||||
<PanelFooterComponent
|
||||
buttonLabel="OK"
|
||||
isButtonDisabled={false}
|
||||
>
|
||||
<div
|
||||
className="panelFooter"
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
ariaLabel="OK"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="OK"
|
||||
type="submit"
|
||||
>
|
||||
<PrimaryButton
|
||||
ariaLabel="OK"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="OK"
|
||||
theme={
|
||||
@@ -1556,7 +1553,6 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
ariaLabel="OK"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -1838,7 +1834,6 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
||||
>
|
||||
<DefaultButton
|
||||
ariaLabel="OK"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -2121,7 +2116,6 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
||||
<BaseButton
|
||||
ariaLabel="OK"
|
||||
baseClassName="ms-Button"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
|
||||
@@ -24,7 +24,6 @@ const {
|
||||
Ascii,
|
||||
Bigint,
|
||||
Blob,
|
||||
Date: DateType,
|
||||
Decimal,
|
||||
Float,
|
||||
Int,
|
||||
@@ -34,7 +33,6 @@ const {
|
||||
Inet,
|
||||
Smallint,
|
||||
Tinyint,
|
||||
Timestamp,
|
||||
} = TableConstants.CassandraType;
|
||||
export const cassandraOptions = [
|
||||
{ key: Text, text: Text },
|
||||
@@ -42,7 +40,6 @@ export const cassandraOptions = [
|
||||
{ key: Bigint, text: Bigint },
|
||||
{ key: Blob, text: Blob },
|
||||
{ key: Boolean, text: Boolean },
|
||||
{ key: DateType, text: DateType },
|
||||
{ key: Decimal, text: Decimal },
|
||||
{ key: Double, text: Double },
|
||||
{ key: Float, text: Float },
|
||||
@@ -53,7 +50,6 @@ export const cassandraOptions = [
|
||||
{ key: Inet, text: Inet },
|
||||
{ key: Smallint, text: Smallint },
|
||||
{ key: Tinyint, text: Tinyint },
|
||||
{ key: Timestamp, text: Timestamp },
|
||||
];
|
||||
|
||||
export const imageProps: IImageProps = {
|
||||
|
||||
@@ -356,21 +356,18 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
|
||||
</div>
|
||||
<PanelFooterComponent
|
||||
buttonLabel="Add Entity"
|
||||
isButtonDisabled={false}
|
||||
>
|
||||
<div
|
||||
className="panelFooter"
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
ariaLabel="Add Entity"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="Add Entity"
|
||||
type="submit"
|
||||
>
|
||||
<PrimaryButton
|
||||
ariaLabel="Add Entity"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="Add Entity"
|
||||
theme={
|
||||
@@ -650,7 +647,6 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
ariaLabel="Add Entity"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -932,7 +928,6 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
|
||||
>
|
||||
<DefaultButton
|
||||
ariaLabel="Add Entity"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -1215,7 +1210,6 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
|
||||
<BaseButton
|
||||
ariaLabel="Add Entity"
|
||||
baseClassName="ms-Button"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
|
||||
@@ -357,21 +357,18 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
|
||||
</div>
|
||||
<PanelFooterComponent
|
||||
buttonLabel="Update"
|
||||
isButtonDisabled={false}
|
||||
>
|
||||
<div
|
||||
className="panelFooter"
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
ariaLabel="Update"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="Update"
|
||||
type="submit"
|
||||
>
|
||||
<PrimaryButton
|
||||
ariaLabel="Update"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="Update"
|
||||
theme={
|
||||
@@ -651,7 +648,6 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
ariaLabel="Update"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -933,7 +929,6 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
|
||||
>
|
||||
<DefaultButton
|
||||
ariaLabel="Update"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -1216,7 +1211,6 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
|
||||
<BaseButton
|
||||
ariaLabel="Update"
|
||||
baseClassName="ms-Button"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Upload } from "Common/Upload/Upload";
|
||||
import { useSidePanel } from "hooks/useSidePanel";
|
||||
import React, { ChangeEvent, FunctionComponent, useState } from "react";
|
||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "Utils/NotificationConsoleUtils";
|
||||
import { Upload } from "../../../Common/Upload/Upload";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
|
||||
import { NotebookContentItem } from "../../Notebook/NotebookContentItem";
|
||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { DetailsList, DetailsListLayoutMode, IColumn, SelectionMode } from "@fluentui/react";
|
||||
import { Upload } from "Common/Upload/Upload";
|
||||
import { UploadDetailsRecord } from "Contracts/ViewModels";
|
||||
import React, { ChangeEvent, FunctionComponent, useState } from "react";
|
||||
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
||||
import { Upload } from "../../../Common/Upload/Upload";
|
||||
import { UploadDetailsRecord } from "../../../Contracts/ViewModels";
|
||||
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
||||
import { getErrorMessage } from "../../Tables/Utilities";
|
||||
import { useSelectedNode } from "../../useSelectedNode";
|
||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||
|
||||
@@ -368,7 +368,6 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
||||
</Text>
|
||||
<StyledTextFieldBase
|
||||
ariaLabel="Confirm by typing the database id"
|
||||
autoFocus={true}
|
||||
id="confirmDatabaseId"
|
||||
onChange={[Function]}
|
||||
styles={
|
||||
@@ -381,7 +380,6 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
||||
>
|
||||
<TextFieldBase
|
||||
ariaLabel="Confirm by typing the database id"
|
||||
autoFocus={true}
|
||||
deferredValidationTime={200}
|
||||
id="confirmDatabaseId"
|
||||
onChange={[Function]}
|
||||
@@ -674,7 +672,6 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
||||
<input
|
||||
aria-invalid={false}
|
||||
aria-label="Confirm by typing the database id"
|
||||
autoFocus={true}
|
||||
className="ms-TextField-field field-60"
|
||||
id="confirmDatabaseId"
|
||||
onBlur={[Function]}
|
||||
@@ -1041,21 +1038,18 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
||||
</div>
|
||||
<PanelFooterComponent
|
||||
buttonLabel="OK"
|
||||
isButtonDisabled={false}
|
||||
>
|
||||
<div
|
||||
className="panelFooter"
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
ariaLabel="OK"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="OK"
|
||||
type="submit"
|
||||
>
|
||||
<PrimaryButton
|
||||
ariaLabel="OK"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="OK"
|
||||
theme={
|
||||
@@ -1335,7 +1329,6 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
ariaLabel="OK"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -1617,7 +1610,6 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
||||
>
|
||||
<DefaultButton
|
||||
ariaLabel="OK"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
@@ -1900,7 +1892,6 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
||||
<BaseButton
|
||||
ariaLabel="OK"
|
||||
baseClassName="ms-Button"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
primary={true}
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
margin: auto;
|
||||
padding-left: 21px;
|
||||
padding-right: 16px;
|
||||
max-width: 1168px;
|
||||
max-width: 1168px;;
|
||||
|
||||
> * {
|
||||
>* {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
> .title {
|
||||
>.title {
|
||||
position: relative; // To attach FeaturePanelLauncher as absolute
|
||||
color: @BaseHigh;
|
||||
font-size: 48px;
|
||||
@@ -27,7 +27,7 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
> .subtitle {
|
||||
>.subtitle {
|
||||
color: @BaseHigh;
|
||||
font-size: 18px;
|
||||
padding-left: 0px;
|
||||
@@ -41,7 +41,7 @@
|
||||
cursor: pointer;
|
||||
margin: 40px auto;
|
||||
|
||||
> .mainButton {
|
||||
>.mainButton {
|
||||
min-width: 124px;
|
||||
max-width: 296px;
|
||||
padding: 32px 16px;
|
||||
@@ -52,7 +52,7 @@
|
||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
|
||||
border-radius: 4px;
|
||||
|
||||
> .legendContainer {
|
||||
>.legendContainer {
|
||||
margin-left: 16px;
|
||||
text-align: left;
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
> :nth-child(n + 2) {
|
||||
>:nth-child(n+2) {
|
||||
margin-left: 32px;
|
||||
}
|
||||
}
|
||||
@@ -83,7 +83,7 @@
|
||||
min-width: 124px;
|
||||
max-width: 296px;
|
||||
|
||||
> .title {
|
||||
>.title {
|
||||
font-size: 18px;
|
||||
font-family: @SemiboldFont;
|
||||
color: @BaseDark;
|
||||
@@ -91,7 +91,7 @@
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
> ul {
|
||||
>ul {
|
||||
list-style: none;
|
||||
padding-left: 0px;
|
||||
margin-bottom: 0px;
|
||||
@@ -101,14 +101,15 @@
|
||||
.flex-display();
|
||||
align-items: flex-start;
|
||||
|
||||
> img {
|
||||
>img {
|
||||
margin-right: @DefaultSpace;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.oneLineContent {
|
||||
margin-top: 4px;
|
||||
margin-top: 6px;
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.twoLineContent {
|
||||
@@ -133,31 +134,23 @@
|
||||
.flex-display();
|
||||
.flex-direction();
|
||||
|
||||
> .title {
|
||||
color: @BaseDark;
|
||||
padding: 0px;
|
||||
font-size: 12px;
|
||||
}
|
||||
> .description {
|
||||
color: @BaseDark;
|
||||
}
|
||||
.title {
|
||||
color: @BaseDark;
|
||||
padding: 0px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.description {
|
||||
color: @BaseDark;
|
||||
}
|
||||
|
||||
&:not(:hover):not(:focus) {
|
||||
background-color: @BaseLow;
|
||||
}
|
||||
&:not(:hover):not(:focus) {
|
||||
background-color: @BaseLow;
|
||||
}
|
||||
}
|
||||
|
||||
&.commonTasks {
|
||||
li {
|
||||
cursor: pointer;
|
||||
.commonTaskList {
|
||||
width: 100%;
|
||||
> img {
|
||||
margin-right: @DefaultSpace;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,12 +168,12 @@
|
||||
}
|
||||
|
||||
&:focus {
|
||||
.focus();
|
||||
.focus();
|
||||
}
|
||||
|
||||
&:active {
|
||||
.active();
|
||||
.active();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,13 +57,13 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
this.subscriptions = [];
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
public componentWillUnmount() {
|
||||
while (this.subscriptions.length) {
|
||||
this.subscriptions.pop().dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
public componentDidMount() {
|
||||
this.subscriptions.push(
|
||||
{
|
||||
dispose: useNotebook.subscribe(
|
||||
@@ -84,7 +84,9 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
const mainItems = this.createMainItems();
|
||||
const commonTaskItems = this.createCommonTaskItems();
|
||||
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 onClearRecent = this.clearMostRecent;
|
||||
@@ -126,13 +128,13 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
<div className="title">Common Tasks</div>
|
||||
<ul>
|
||||
{commonTaskItems.map((item) => (
|
||||
<li
|
||||
className="focusable"
|
||||
key={`${item.title}${item.description}`}
|
||||
onClick={item.onClick}
|
||||
onKeyPress={(event: React.KeyboardEvent) => this.onSplashScreenItemKeyPress(event, item.onClick)}
|
||||
>
|
||||
<div className="commonTaskList" role="button" tabIndex={0}>
|
||||
<li className="focusable" key={`${item.title}${item.description}`}>
|
||||
<div
|
||||
onClick={item.onClick}
|
||||
onKeyPress={(event: React.KeyboardEvent) => this.onSplashScreenItemKeyPress(event, item.onClick)}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
>
|
||||
<img src={item.iconSrc} alt="" />
|
||||
<span className="oneLineContent" title={item.info}>
|
||||
{item.title}
|
||||
@@ -163,22 +165,23 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
<div className="title">Tips</div>
|
||||
<ul>
|
||||
{tipsItems.map((item) => (
|
||||
<li
|
||||
className="tipContainer focusable"
|
||||
key={`${item.title}${item.description}`}
|
||||
onClick={item.onClick}
|
||||
onKeyPress={(event: React.KeyboardEvent) => this.onSplashScreenItemKeyPress(event, item.onClick)}
|
||||
tabIndex={0}
|
||||
role="link"
|
||||
>
|
||||
<div className="title" title={item.info}>
|
||||
{item.title}
|
||||
<li className="tipContainer focusable" key={`${item.title}${item.description}`}>
|
||||
<div
|
||||
onClick={item.onClick}
|
||||
onKeyPress={(event: React.KeyboardEvent) => this.onSplashScreenItemKeyPress(event, item.onClick)}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
className="tipsWrapper"
|
||||
>
|
||||
<div className="title" title={item.info}>
|
||||
{item.title}
|
||||
</div>
|
||||
<div className="description">{item.description}</div>
|
||||
</div>
|
||||
<div className="description">{item.description}</div>
|
||||
</li>
|
||||
))}
|
||||
<li>
|
||||
<a role="link" href={SplashScreen.seeMoreItemUrl} rel="noreferrer" target="_blank" tabIndex={0}>
|
||||
<a href={SplashScreen.seeMoreItemUrl} rel="noreferrer" target="_blank" tabIndex={0}>
|
||||
{SplashScreen.seeMoreItemTitle}
|
||||
</a>
|
||||
</li>
|
||||
@@ -221,7 +224,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
});
|
||||
}
|
||||
|
||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
||||
if (useNotebook.getState().isNotebookEnabled && !userContext.features.notebooksTemporarilyDown) {
|
||||
heroes.push({
|
||||
iconSrc: NewNotebookIcon,
|
||||
title: "New Notebook",
|
||||
@@ -305,24 +308,23 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
iconSrc: AddDatabaseIcon,
|
||||
title: "New " + getDatabaseName(),
|
||||
description: undefined,
|
||||
onClick: async () => {
|
||||
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} />
|
||||
);
|
||||
},
|
||||
onClick: () => this.openAddDatabasePanel(),
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
return {
|
||||
iconSrc: NotebookIcon,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user