mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-07 19:46:53 +00:00
Compare commits
1 Commits
users/artr
...
fix_400%_z
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6043260d8 |
108
.eslintignore
108
.eslintignore
@@ -71,6 +71,7 @@ src/Explorer/DataSamples/ContainerSampleGenerator.test.ts
|
||||
src/Explorer/DataSamples/ContainerSampleGenerator.ts
|
||||
src/Explorer/DataSamples/DataSamplesUtil.test.ts
|
||||
src/Explorer/DataSamples/DataSamplesUtil.ts
|
||||
src/Explorer/Explorer.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/ArraysByKeyCache.test.ts
|
||||
src/Explorer/Graph/GraphExplorerComponent/ArraysByKeyCache.ts
|
||||
src/Explorer/Graph/GraphExplorerComponent/D3ForceGraph.test.ts
|
||||
@@ -82,6 +83,11 @@ src/Explorer/Graph/GraphExplorerComponent/GremlinClient.test.ts
|
||||
src/Explorer/Graph/GraphExplorerComponent/GremlinClient.ts
|
||||
src/Explorer/Graph/GraphExplorerComponent/GremlinSimpleClient.test.ts
|
||||
src/Explorer/Graph/GraphExplorerComponent/GremlinSimpleClient.ts
|
||||
# src/Explorer/Graph/GraphStyleComponent/GraphStyle.test.ts
|
||||
# src/Explorer/Graph/GraphStyleComponent/GraphStyleComponent.ts
|
||||
|
||||
src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts
|
||||
src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.ts
|
||||
src/Explorer/Menus/ContextMenu.ts
|
||||
src/Explorer/MostRecentActivity/MostRecentActivity.ts
|
||||
src/Explorer/Notebook/NotebookClientV2.ts
|
||||
@@ -99,10 +105,17 @@ src/Explorer/Notebook/NotebookContentClient.ts
|
||||
src/Explorer/Notebook/NotebookContentItem.ts
|
||||
src/Explorer/Notebook/NotebookUtil.ts
|
||||
src/Explorer/OpenActionsStubs.ts
|
||||
src/Explorer/Panes/AddDatabasePane.ts
|
||||
src/Explorer/Panes/AddDatabasePane.test.ts
|
||||
src/Explorer/Panes/BrowseQueriesPane.ts
|
||||
src/Explorer/Panes/RenewAdHocAccessPane.ts
|
||||
src/Explorer/Panes/SetupNotebooksPane.ts
|
||||
src/Explorer/Panes/SwitchDirectoryPane.ts
|
||||
src/Explorer/Panes/Tables/Validators/EntityPropertyNameValidator.ts
|
||||
src/Explorer/Panes/Tables/Validators/EntityPropertyValidationCommon.ts
|
||||
src/Explorer/Panes/Tables/Validators/EntityPropertyValueValidator.ts
|
||||
src/Explorer/SplashScreen/SplashScreen.test.ts
|
||||
src/Explorer/Tables/Constants.ts
|
||||
src/Explorer/Tables/DataTable/CacheBase.ts
|
||||
src/Explorer/Tables/DataTable/DataTableBindingManager.ts
|
||||
src/Explorer/Tables/DataTable/DataTableBuilder.ts
|
||||
@@ -128,13 +141,17 @@ src/Explorer/Tabs/DocumentsTab.test.ts
|
||||
src/Explorer/Tabs/DocumentsTab.ts
|
||||
src/Explorer/Tabs/GraphTab.ts
|
||||
src/Explorer/Tabs/MongoDocumentsTab.ts
|
||||
# src/Explorer/Tabs/MongoQueryTab.ts
|
||||
# src/Explorer/Tabs/MongoShellTab.ts
|
||||
src/Explorer/Tabs/NotebookV2Tab.ts
|
||||
src/Explorer/Tabs/ScriptTabBase.ts
|
||||
# src/Explorer/Tabs/StoredProcedureTab.ts
|
||||
src/Explorer/Tabs/TabComponents.ts
|
||||
src/Explorer/Tabs/TabsBase.ts
|
||||
src/Explorer/Tabs/TriggerTab.ts
|
||||
src/Explorer/Tabs/UserDefinedFunctionTab.ts
|
||||
src/Explorer/Tree/AccessibleVerticalList.ts
|
||||
src/Explorer/Tree/Collection.test.ts
|
||||
src/Explorer/Tree/Collection.ts
|
||||
src/Explorer/Tree/ConflictId.ts
|
||||
src/Explorer/Tree/DocumentId.ts
|
||||
@@ -143,32 +160,96 @@ src/Explorer/Tree/ResourceTokenCollection.ts
|
||||
src/Explorer/Tree/StoredProcedure.ts
|
||||
src/Explorer/Tree/TreeComponents.ts
|
||||
src/Explorer/Tree/Trigger.ts
|
||||
src/Explorer/Tree/UserDefinedFunction.ts
|
||||
src/Explorer/WaitsForTemplateViewModel.ts
|
||||
src/GitHub/GitHubClient.test.ts
|
||||
src/GitHub/GitHubClient.ts
|
||||
src/GitHub/GitHubConnector.ts
|
||||
src/GitHub/GitHubContentProvider.test.ts
|
||||
src/GitHub/GitHubContentProvider.ts
|
||||
src/GitHub/GitHubOAuthService.ts
|
||||
src/HostedExplorer.ts
|
||||
src/Index.ts
|
||||
src/Juno/JunoClient.test.ts
|
||||
src/Juno/JunoClient.ts
|
||||
src/Main.ts
|
||||
src/NotebookWorkspaceManager/NotebookWorkspaceManager.ts
|
||||
src/NotebookWorkspaceManager/NotebookWorkspaceResourceProviderMockClients.ts
|
||||
src/Platform/Emulator/DataAccessUtility.ts
|
||||
src/Platform/Emulator/ExplorerFactory.ts
|
||||
src/Platform/Emulator/Main.ts
|
||||
src/Platform/Emulator/NotificationsClient.ts
|
||||
src/Platform/Hosted/ArmResourceUtils.ts
|
||||
src/Platform/Hosted/Authorization.ts
|
||||
src/Platform/Hosted/DataAccessUtility.ts
|
||||
src/Platform/Hosted/ExplorerFactory.ts
|
||||
src/Platform/Hosted/Helpers/ConnectionStringParser.test.ts
|
||||
src/Platform/Hosted/Main.ts
|
||||
src/Platform/Hosted/Maint.test.ts
|
||||
src/Platform/Hosted/NotificationsClient.ts
|
||||
src/Platform/Portal/DataAccessUtility.ts
|
||||
src/Platform/Portal/ExplorerFactory.ts
|
||||
src/Platform/Portal/Main.ts
|
||||
src/Platform/Portal/NotificationsClient.ts
|
||||
src/PlatformType.ts
|
||||
src/ReactDevTools.ts
|
||||
src/ResourceProvider/IResourceProviderClient.test.ts
|
||||
src/ResourceProvider/IResourceProviderClient.ts
|
||||
src/ResourceProvider/ResourceProviderClient.ts
|
||||
src/ResourceProvider/ResourceProviderClientFactory.ts
|
||||
src/Shared/Constants.ts
|
||||
src/Shared/DefaultExperienceUtility.test.ts
|
||||
src/Shared/DefaultExperienceUtility.ts
|
||||
src/Shared/ExplorerSettings.ts
|
||||
src/Shared/PriceEstimateCalculator.ts
|
||||
src/Shared/StorageUtility.test.ts
|
||||
src/Shared/StorageUtility.ts
|
||||
src/Shared/appInsights.ts
|
||||
src/SparkClusterManager/ArcadiaResourceManager.ts
|
||||
src/SparkClusterManager/SparkClusterManager.ts
|
||||
src/Terminal/JupyterLabAppFactory.ts
|
||||
src/Terminal/NotebookAppContracts.d.ts
|
||||
src/Terminal/index.ts
|
||||
src/TokenProviders/PortalTokenProvider.ts
|
||||
src/TokenProviders/TokenProviderFactory.ts
|
||||
src/Utils/PricingUtils.test.ts
|
||||
src/Utils/QueryUtils.test.ts
|
||||
src/applyExplorerBindings.ts
|
||||
src/global.d.ts
|
||||
src/setupTests.ts
|
||||
src/Explorer/Controls/AccessibleElement/AccessibleElement.tsx
|
||||
src/Explorer/Controls/Accordion/AccordionComponent.tsx
|
||||
src/Explorer/Controls/AccountSwitch/AccountSwitchComponent.test.tsx
|
||||
src/Explorer/Controls/AccountSwitch/AccountSwitchComponent.tsx
|
||||
src/Explorer/Controls/AccountSwitch/AccountSwitchComponentAdapter.tsx
|
||||
src/Explorer/Controls/Arcadia/ArcadiaMenuPicker.tsx
|
||||
src/Explorer/Controls/CollapsiblePanel/CollapsiblePanel.tsx
|
||||
src/Explorer/Controls/CommandButton/CommandButtonComponent.tsx
|
||||
src/Explorer/Controls/DialogReactComponent/DialogComponent.tsx
|
||||
src/Explorer/Controls/DialogReactComponent/DialogComponentAdapter.tsx
|
||||
src/Explorer/Controls/Directory/DefaultDirectoryDropdownComponent.test.tsx
|
||||
src/Explorer/Controls/Directory/DefaultDirectoryDropdownComponent.tsx
|
||||
src/Explorer/Controls/Directory/DirectoryComponentAdapter.tsx
|
||||
src/Explorer/Controls/Directory/DirectoryListComponent.test.tsx
|
||||
src/Explorer/Controls/Directory/DirectoryListComponent.tsx
|
||||
src/Explorer/Controls/Editor/EditorReact.tsx
|
||||
src/Explorer/Controls/InputTypeahead/InputTypeaheadComponent.tsx
|
||||
src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx
|
||||
src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx
|
||||
src/Explorer/Controls/NotebookViewer/NotebookMetadataComponent.tsx
|
||||
src/NotebookViewer/NotebookViewer.tsx
|
||||
src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx
|
||||
src/Explorer/Controls/QueriesGridReactComponent/QueriesGridComponent.tsx
|
||||
src/Explorer/Controls/QueriesGridReactComponent/QueriesGridComponentAdapter.tsx
|
||||
src/Explorer/Controls/ResizeSensorReactComponent/ResizeSensorComponent.tsx
|
||||
src/Explorer/Controls/Spark/ClusterSettingsComponent.tsx
|
||||
src/Explorer/Controls/Spark/ClusterSettingsComponentAdapter.tsx
|
||||
src/Explorer/Controls/Tabs/TabComponent.tsx
|
||||
src/Explorer/Controls/TreeComponent/TreeComponent.test.tsx
|
||||
src/Explorer/Controls/TreeComponent/TreeComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/EditorNeighborsComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/EditorNodePropertiesComponent.test.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/EditorNodePropertiesComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/GraphVizComponent.tsx
|
||||
@@ -176,19 +257,46 @@ 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/QueryContainerComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNeighborsComponent.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.tsx
|
||||
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx
|
||||
src/Explorer/Menus/CommandBar/CommandBarUtil.tsx
|
||||
src/Explorer/Menus/NotificationConsole/NotificationConsoleComponent.test.tsx
|
||||
src/Explorer/Notebook/NotebookComponent/NotebookComponent.tsx
|
||||
src/Explorer/Notebook/NotebookComponent/NotebookComponentAdapter.tsx
|
||||
src/Explorer/Notebook/NotebookComponent/NotebookComponentBootstrapper.tsx
|
||||
src/Explorer/Notebook/NotebookComponent/VirtualCommandBarComponent.tsx
|
||||
src/Explorer/Notebook/NotebookComponent/contents/file/index.tsx
|
||||
src/Explorer/Notebook/NotebookComponent/contents/file/text-file.tsx
|
||||
src/Explorer/Notebook/NotebookComponent/contents/index.tsx
|
||||
src/Explorer/Notebook/NotebookRenderer/AzureTheme.tsx
|
||||
src/Explorer/Notebook/NotebookRenderer/NotebookReadOnlyRenderer.tsx
|
||||
src/Explorer/Notebook/NotebookRenderer/NotebookRenderer.tsx
|
||||
src/Explorer/Notebook/NotebookRenderer/Prompt.tsx
|
||||
src/Explorer/Notebook/NotebookRenderer/PromptContent.tsx
|
||||
src/Explorer/Notebook/NotebookRenderer/StatusBar.test.tsx
|
||||
src/Explorer/Notebook/NotebookRenderer/StatusBar.tsx
|
||||
src/Explorer/Notebook/NotebookRenderer/Toolbar.tsx
|
||||
src/Explorer/Notebook/NotebookRenderer/decorators/CellCreator.tsx
|
||||
src/Explorer/Notebook/NotebookRenderer/decorators/CellLabeler.tsx
|
||||
src/Explorer/Notebook/NotebookRenderer/decorators/HoverableCell.tsx
|
||||
src/Explorer/Notebook/NotebookRenderer/decorators/draggable/index.tsx
|
||||
src/Explorer/Notebook/NotebookRenderer/decorators/hijack-scroll/index.tsx
|
||||
src/Explorer/Notebook/NotebookRenderer/decorators/kbd-shortcuts/index.tsx
|
||||
src/Explorer/Notebook/temp/inputs/connected-editors/codemirror.tsx
|
||||
src/Explorer/Notebook/temp/inputs/editor.tsx
|
||||
src/Explorer/Notebook/temp/markdown-cell.tsx
|
||||
src/Explorer/Notebook/temp/source.tsx
|
||||
src/Explorer/Notebook/temp/syntax-highlighter/index.tsx
|
||||
src/Explorer/SplashScreen/SplashScreen.tsx
|
||||
src/Explorer/Tabs/GalleryTab.tsx
|
||||
src/Explorer/Tabs/NotebookViewerTab.tsx
|
||||
src/Explorer/Tabs/TerminalTab.tsx
|
||||
src/Explorer/Tree/ResourceTreeAdapter.tsx
|
||||
src/Explorer/Tree/ResourceTreeAdapterForResourceToken.tsx
|
||||
src/GalleryViewer/Cards/GalleryCardComponent.tsx
|
||||
src/GalleryViewer/GalleryViewer.tsx
|
||||
src/GalleryViewer/GalleryViewerComponent.tsx
|
||||
__mocks__/monaco-editor.ts
|
||||
src/Explorer/Tree/ResourceTreeAdapterForResourceToken.test.tsx
|
||||
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com"
|
||||
"JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com",
|
||||
"enableSchemaAnalyzer": true
|
||||
}
|
||||
@@ -94,7 +94,7 @@ export class Flights {
|
||||
public static readonly MongoIndexEditor = "mongoindexeditor";
|
||||
public static readonly MongoIndexing = "mongoindexing";
|
||||
public static readonly AutoscaleTest = "autoscaletest";
|
||||
public static readonly PartitionKeyTest = "partitionkeytest";
|
||||
public static readonly SchemaAnalyzer = "schemaanalyzer";
|
||||
}
|
||||
|
||||
export class AfecFeatures {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as ExplorerSettings from "../Shared/ExplorerSettings";
|
||||
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
|
||||
import * as HeadersUtility from "./HeadersUtility";
|
||||
import { ExplorerSettings } from "../Shared/ExplorerSettings";
|
||||
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
|
||||
|
||||
describe("Headers Utility", () => {
|
||||
describe("shouldEnableCrossPartitionKeyForResourceWithPartitionKey()", () => {
|
||||
|
||||
@@ -6,7 +6,6 @@ import * as DataModels from "../Contracts/DataModels";
|
||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||
import { Collection } from "../Contracts/ViewModels";
|
||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||
import { hasFlag } from "../Platform/Hosted/extractFeatures";
|
||||
import { userContext } from "../UserContext";
|
||||
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
|
||||
@@ -142,8 +141,7 @@ export function readDocument(
|
||||
: "",
|
||||
};
|
||||
|
||||
const endpoint = getFeatureEndpointOrDefault("readDocument");
|
||||
|
||||
const endpoint = getEndpoint();
|
||||
return window
|
||||
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
||||
method: "GET",
|
||||
@@ -344,10 +342,6 @@ export function getEndpoint(): string {
|
||||
return url;
|
||||
}
|
||||
|
||||
function getFeatureEndpointOrDefault(feature: string): string {
|
||||
return (hasFlag("mongoProxyFeatures", feature)) ? userContext.features.mongoProxyEndpoint : getEndpoint();
|
||||
}
|
||||
|
||||
// TODO: This function throws most of the time except on Forbidden which is a bit strange
|
||||
// It causes problems for TypeScript understanding the types
|
||||
async function errorHandling(response: Response, action: string, params: unknown): Promise<void> {
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface ConfigContext {
|
||||
hostedExplorerURL: string;
|
||||
armAPIVersion?: string;
|
||||
allowedJunoOrigins: string[];
|
||||
enableSchemaAnalyzer: boolean;
|
||||
msalRedirectURI?: string;
|
||||
}
|
||||
|
||||
@@ -62,6 +63,7 @@ let configContext: Readonly<ConfigContext> = {
|
||||
"https://tools-staging.cosmos.azure.com",
|
||||
"https://localhost",
|
||||
],
|
||||
enableSchemaAnalyzer: false,
|
||||
};
|
||||
|
||||
export function resetConfigContext(): void {
|
||||
|
||||
@@ -9,7 +9,6 @@ export interface DatabaseAccount {
|
||||
|
||||
export interface DatabaseAccountExtendedProperties {
|
||||
documentEndpoint?: string;
|
||||
disableLocalAuth?: boolean;
|
||||
tableEndpoint?: string;
|
||||
gremlinEndpoint?: string;
|
||||
cassandraEndpoint?: string;
|
||||
|
||||
@@ -16,7 +16,6 @@ import { userContext } from "../UserContext";
|
||||
import { getCollectionName, getDatabaseName } from "../Utils/APITypeUtils";
|
||||
import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent";
|
||||
import Explorer from "./Explorer";
|
||||
import { useNotebook } from "./Notebook/useNotebook";
|
||||
import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane";
|
||||
import { DeleteDatabaseConfirmationPanel } from "./Panes/DeleteDatabaseConfirmationPanel";
|
||||
import StoredProcedure from "./Tree/StoredProcedure";
|
||||
@@ -50,10 +49,7 @@ export const createDatabaseContextMenu = (container: Explorer, databaseId: strin
|
||||
onClick: () =>
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
"Delete " + getDatabaseName(),
|
||||
<DeleteDatabaseConfirmationPanel refreshDatabases={() => container.refreshAllDatabases()} />
|
||||
),
|
||||
.openSidePanel("Delete " + getDatabaseName(), <DeleteDatabaseConfirmationPanel explorer={container} />),
|
||||
label: `Delete ${getDatabaseName()}`,
|
||||
styleClass: "deleteDatabaseMenuItem",
|
||||
});
|
||||
@@ -85,13 +81,13 @@ export const createCollectionContextMenuButton = (
|
||||
iconSrc: HostedTerminalIcon,
|
||||
onClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||
if (useNotebook.getState().isShellEnabled) {
|
||||
if (container.isShellEnabled()) {
|
||||
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
||||
} else {
|
||||
selectedCollection && selectedCollection.onNewMongoShellClick();
|
||||
}
|
||||
},
|
||||
label: useNotebook.getState().isShellEnabled ? "Open Mongo Shell" : "New Shell",
|
||||
label: container.isShellEnabled() ? "Open Mongo Shell" : "New Shell",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -109,7 +105,7 @@ export const createCollectionContextMenuButton = (
|
||||
iconSrc: AddUdfIcon,
|
||||
onClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection);
|
||||
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection, undefined);
|
||||
},
|
||||
label: "New UDF",
|
||||
});
|
||||
@@ -129,10 +125,7 @@ export const createCollectionContextMenuButton = (
|
||||
onClick: () =>
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
"Delete " + getCollectionName(),
|
||||
<DeleteCollectionConfirmationPane refreshDatabases={() => container.refreshAllDatabases()} />
|
||||
),
|
||||
.openSidePanel("Delete " + getCollectionName(), <DeleteCollectionConfirmationPane explorer={container} />),
|
||||
label: `Delete ${getCollectionName()}`,
|
||||
styleClass: "deleteCollectionMenuItem",
|
||||
});
|
||||
|
||||
@@ -8,9 +8,7 @@ import TriangleDownIcon from "../../../../images/Triangle-down.svg";
|
||||
import TriangleRightIcon from "../../../../images/Triangle-right.svg";
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
|
||||
export interface AccordionComponentProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
export interface AccordionComponentProps {}
|
||||
|
||||
export class AccordionComponent extends React.Component<AccordionComponentProps> {
|
||||
public render(): JSX.Element {
|
||||
@@ -80,7 +78,7 @@ export class AccordionItemComponent extends React.Component<AccordionItemCompone
|
||||
);
|
||||
}
|
||||
|
||||
private onHeaderClick = (): void => {
|
||||
private onHeaderClick = (_event: React.MouseEvent<HTMLDivElement>): void => {
|
||||
this.setState({ isExpanded: !this.state.isExpanded });
|
||||
};
|
||||
|
||||
|
||||
@@ -121,7 +121,8 @@ export class CommandButtonComponent extends React.Component<CommandButtonCompone
|
||||
if (!this.dropdownElt || !this.expandButtonElt) {
|
||||
return;
|
||||
}
|
||||
$(this.dropdownElt).offset({ left: $(this.expandButtonElt).offset().left });
|
||||
|
||||
const dropdownElt = $(this.dropdownElt).offset({ left: $(this.expandButtonElt).offset().left });
|
||||
}
|
||||
|
||||
private onKeyPress(event: React.KeyboardEvent): boolean {
|
||||
|
||||
@@ -56,7 +56,7 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
||||
this.editor = editor;
|
||||
const queryEditorModel = this.editor.getModel();
|
||||
if (!this.props.isReadOnly && this.props.onContentChanged) {
|
||||
queryEditorModel.onDidChangeContent(() => {
|
||||
queryEditorModel.onDidChangeContent((e: monaco.editor.IModelContentChangedEvent) => {
|
||||
const queryEditorModel = this.editor.getModel();
|
||||
this.props.onContentChanged(queryEditorModel.getValue());
|
||||
});
|
||||
|
||||
@@ -56,7 +56,7 @@ export class GitHubReposComponent extends React.Component<GitHubReposComponentPr
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>{content}</div>
|
||||
<div className={"paneMainContent"}>{content}</div>
|
||||
{!this.props.showAuthorizeAccess && (
|
||||
<>
|
||||
<div className={"paneFooter"} style={ContentFooterStyle}>
|
||||
|
||||
@@ -1,90 +1,154 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import { NotebookTerminalComponent, NotebookTerminalComponentProps } from "./NotebookTerminalComponent";
|
||||
import { NotebookTerminalComponent } from "./NotebookTerminalComponent";
|
||||
|
||||
const testAccount: DataModels.DatabaseAccount = {
|
||||
id: "id",
|
||||
kind: "kind",
|
||||
location: "location",
|
||||
name: "name",
|
||||
properties: {
|
||||
documentEndpoint: "https://testDocumentEndpoint.azure.com/",
|
||||
},
|
||||
type: "type",
|
||||
const createTestDatabaseAccount = (): DataModels.DatabaseAccount => {
|
||||
return {
|
||||
id: "testId",
|
||||
kind: "testKind",
|
||||
location: "testLocation",
|
||||
name: "testName",
|
||||
properties: {
|
||||
cassandraEndpoint: null,
|
||||
documentEndpoint: "https://testDocumentEndpoint.azure.com/",
|
||||
gremlinEndpoint: null,
|
||||
tableEndpoint: null,
|
||||
},
|
||||
type: "testType",
|
||||
};
|
||||
};
|
||||
|
||||
const testMongo32Account: DataModels.DatabaseAccount = {
|
||||
...testAccount,
|
||||
const createTestMongo32DatabaseAccount = (): DataModels.DatabaseAccount => {
|
||||
return {
|
||||
id: "testId",
|
||||
kind: "testKind",
|
||||
location: "testLocation",
|
||||
name: "testName",
|
||||
properties: {
|
||||
cassandraEndpoint: null,
|
||||
documentEndpoint: "https://testDocumentEndpoint.azure.com/",
|
||||
gremlinEndpoint: null,
|
||||
tableEndpoint: null,
|
||||
},
|
||||
type: "testType",
|
||||
};
|
||||
};
|
||||
|
||||
const testMongo36Account: DataModels.DatabaseAccount = {
|
||||
...testAccount,
|
||||
properties: {
|
||||
mongoEndpoint: "https://testMongoEndpoint.azure.com/",
|
||||
},
|
||||
const createTestMongo36DatabaseAccount = (): DataModels.DatabaseAccount => {
|
||||
return {
|
||||
id: "testId",
|
||||
kind: "testKind",
|
||||
location: "testLocation",
|
||||
name: "testName",
|
||||
properties: {
|
||||
cassandraEndpoint: null,
|
||||
documentEndpoint: "https://testDocumentEndpoint.azure.com/",
|
||||
gremlinEndpoint: null,
|
||||
tableEndpoint: null,
|
||||
mongoEndpoint: "https://testMongoEndpoint.azure.com/",
|
||||
},
|
||||
type: "testType",
|
||||
};
|
||||
};
|
||||
|
||||
const testCassandraAccount: DataModels.DatabaseAccount = {
|
||||
...testAccount,
|
||||
properties: {
|
||||
cassandraEndpoint: "https://testCassandraEndpoint.azure.com/",
|
||||
},
|
||||
const createTestCassandraDatabaseAccount = (): DataModels.DatabaseAccount => {
|
||||
return {
|
||||
id: "testId",
|
||||
kind: "testKind",
|
||||
location: "testLocation",
|
||||
name: "testName",
|
||||
properties: {
|
||||
cassandraEndpoint: "https://testCassandraEndpoint.azure.com/",
|
||||
documentEndpoint: null,
|
||||
gremlinEndpoint: null,
|
||||
tableEndpoint: null,
|
||||
},
|
||||
type: "testType",
|
||||
};
|
||||
};
|
||||
|
||||
const testNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||
authToken: "authToken",
|
||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com",
|
||||
const createTerminal = (): NotebookTerminalComponent => {
|
||||
return new NotebookTerminalComponent({
|
||||
notebookServerInfo: {
|
||||
authToken: "testAuthToken",
|
||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/",
|
||||
},
|
||||
databaseAccount: createTestDatabaseAccount(),
|
||||
});
|
||||
};
|
||||
|
||||
const testMongoNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||
authToken: "authToken",
|
||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo",
|
||||
const createMongo32Terminal = (): NotebookTerminalComponent => {
|
||||
return new NotebookTerminalComponent({
|
||||
notebookServerInfo: {
|
||||
authToken: "testAuthToken",
|
||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo",
|
||||
},
|
||||
databaseAccount: createTestMongo32DatabaseAccount(),
|
||||
});
|
||||
};
|
||||
|
||||
const testCassandraNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
||||
authToken: "authToken",
|
||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/cassandra",
|
||||
const createMongo36Terminal = (): NotebookTerminalComponent => {
|
||||
return new NotebookTerminalComponent({
|
||||
notebookServerInfo: {
|
||||
authToken: "testAuthToken",
|
||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo",
|
||||
},
|
||||
databaseAccount: createTestMongo36DatabaseAccount(),
|
||||
});
|
||||
};
|
||||
|
||||
const createCassandraTerminal = (): NotebookTerminalComponent => {
|
||||
return new NotebookTerminalComponent({
|
||||
notebookServerInfo: {
|
||||
authToken: "testAuthToken",
|
||||
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/cassandra",
|
||||
},
|
||||
databaseAccount: createTestCassandraDatabaseAccount(),
|
||||
});
|
||||
};
|
||||
|
||||
describe("NotebookTerminalComponent", () => {
|
||||
it("renders terminal", () => {
|
||||
const props: NotebookTerminalComponentProps = {
|
||||
databaseAccount: testAccount,
|
||||
notebookServerInfo: testNotebookServerInfo,
|
||||
};
|
||||
it("getTerminalParams: Test for terminal", () => {
|
||||
const terminal: NotebookTerminalComponent = createTerminal();
|
||||
const params: Map<string, string> = terminal.getTerminalParams();
|
||||
|
||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(params).toEqual(
|
||||
new Map<string, string>([["terminal", "true"]])
|
||||
);
|
||||
});
|
||||
|
||||
it("renders mongo 3.2 shell", () => {
|
||||
const props: NotebookTerminalComponentProps = {
|
||||
databaseAccount: testMongo32Account,
|
||||
notebookServerInfo: testMongoNotebookServerInfo,
|
||||
};
|
||||
it("getTerminalParams: Test for Mongo 3.2 terminal", () => {
|
||||
const terminal: NotebookTerminalComponent = createMongo32Terminal();
|
||||
const params: Map<string, string> = terminal.getTerminalParams();
|
||||
|
||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(params).toEqual(
|
||||
new Map<string, string>([
|
||||
["terminal", "true"],
|
||||
["terminalEndpoint", new URL(terminal.props.databaseAccount.properties.documentEndpoint).host],
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it("renders mongo 3.6 shell", () => {
|
||||
const props: NotebookTerminalComponentProps = {
|
||||
databaseAccount: testMongo36Account,
|
||||
notebookServerInfo: testMongoNotebookServerInfo,
|
||||
};
|
||||
it("getTerminalParams: Test for Mongo 3.6 terminal", () => {
|
||||
const terminal: NotebookTerminalComponent = createMongo36Terminal();
|
||||
const params: Map<string, string> = terminal.getTerminalParams();
|
||||
|
||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(params).toEqual(
|
||||
new Map<string, string>([
|
||||
["terminal", "true"],
|
||||
["terminalEndpoint", new URL(terminal.props.databaseAccount.properties.mongoEndpoint).host],
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it("renders cassandra shell", () => {
|
||||
const props: NotebookTerminalComponentProps = {
|
||||
databaseAccount: testCassandraAccount,
|
||||
notebookServerInfo: testCassandraNotebookServerInfo,
|
||||
};
|
||||
it("getTerminalParams: Test for Cassandra terminal", () => {
|
||||
const terminal: NotebookTerminalComponent = createCassandraTerminal();
|
||||
const params: Map<string, string> = terminal.getTerminalParams();
|
||||
|
||||
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(params).toEqual(
|
||||
new Map<string, string>([
|
||||
["terminal", "true"],
|
||||
["terminalEndpoint", new URL(terminal.props.databaseAccount.properties.cassandraEndpoint).host],
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
* Wrapper around Notebook server terminal
|
||||
*/
|
||||
|
||||
import postRobot from "post-robot";
|
||||
import * as React from "react";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import { TerminalProps } from "../../../Terminal/TerminalProps";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import * as StringUtils from "../../../Utils/StringUtils";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import { TerminalQueryParams } from "../../../Common/Constants";
|
||||
import { handleError } from "../../../Common/ErrorHandlingUtils";
|
||||
|
||||
export interface NotebookTerminalComponentProps {
|
||||
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
||||
@@ -15,69 +15,79 @@ export interface NotebookTerminalComponentProps {
|
||||
}
|
||||
|
||||
export class NotebookTerminalComponent extends React.Component<NotebookTerminalComponentProps> {
|
||||
private terminalWindow: Window;
|
||||
|
||||
constructor(props: NotebookTerminalComponentProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.sendPropsToTerminalFrame();
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<div className="notebookTerminalContainer">
|
||||
<iframe
|
||||
title="Terminal to Notebook Server"
|
||||
onLoad={(event) => this.handleFrameLoad(event)}
|
||||
src="terminal.html"
|
||||
src={NotebookTerminalComponent.createNotebookAppSrc(this.props.notebookServerInfo, this.getTerminalParams())}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleFrameLoad(event: React.SyntheticEvent<HTMLIFrameElement, Event>): void {
|
||||
this.terminalWindow = (event.target as HTMLIFrameElement).contentWindow;
|
||||
this.sendPropsToTerminalFrame();
|
||||
}
|
||||
public getTerminalParams(): Map<string, string> {
|
||||
let params: Map<string, string> = new Map<string, string>();
|
||||
params.set(TerminalQueryParams.Terminal, "true");
|
||||
|
||||
sendPropsToTerminalFrame(): void {
|
||||
if (!this.terminalWindow) {
|
||||
return;
|
||||
const terminalEndpoint: string = this.tryGetTerminalEndpoint();
|
||||
if (terminalEndpoint) {
|
||||
params.set(TerminalQueryParams.TerminalEndpoint, terminalEndpoint);
|
||||
}
|
||||
|
||||
const props: TerminalProps = {
|
||||
terminalEndpoint: this.tryGetTerminalEndpoint(),
|
||||
notebookServerEndpoint: this.props.notebookServerInfo?.notebookServerEndpoint,
|
||||
authToken: this.props.notebookServerInfo?.authToken,
|
||||
subscriptionId: userContext.subscriptionId,
|
||||
apiType: userContext.apiType,
|
||||
authType: userContext.authType,
|
||||
databaseAccount: userContext.databaseAccount,
|
||||
};
|
||||
|
||||
postRobot.send(this.terminalWindow, "props", props, {
|
||||
domain: window.location.origin,
|
||||
});
|
||||
return params;
|
||||
}
|
||||
|
||||
public tryGetTerminalEndpoint(): string | undefined {
|
||||
let terminalEndpoint: string | undefined;
|
||||
public tryGetTerminalEndpoint(): string | null {
|
||||
let terminalEndpoint: string | null;
|
||||
|
||||
const notebookServerEndpoint = this.props.notebookServerInfo?.notebookServerEndpoint;
|
||||
const notebookServerEndpoint: string = this.props.notebookServerInfo.notebookServerEndpoint;
|
||||
if (StringUtils.endsWith(notebookServerEndpoint, "mongo")) {
|
||||
// mongoEndpoint is only available for Mongo 3.6 and higher, fallback to documentEndpoint otherwise
|
||||
terminalEndpoint =
|
||||
this.props.databaseAccount?.properties.mongoEndpoint || this.props.databaseAccount?.properties.documentEndpoint;
|
||||
let mongoShellEndpoint: string = this.props.databaseAccount.properties.mongoEndpoint;
|
||||
if (!mongoShellEndpoint) {
|
||||
// mongoEndpoint is only available for Mongo 3.6 and higher.
|
||||
// Fallback to documentEndpoint otherwise.
|
||||
mongoShellEndpoint = this.props.databaseAccount.properties.documentEndpoint;
|
||||
}
|
||||
terminalEndpoint = mongoShellEndpoint;
|
||||
} else if (StringUtils.endsWith(notebookServerEndpoint, "cassandra")) {
|
||||
terminalEndpoint = this.props.databaseAccount?.properties.cassandraEndpoint;
|
||||
terminalEndpoint = this.props.databaseAccount.properties.cassandraEndpoint;
|
||||
}
|
||||
|
||||
if (terminalEndpoint) {
|
||||
return new URL(terminalEndpoint).host;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
public static createNotebookAppSrc(
|
||||
serverInfo: DataModels.NotebookWorkspaceConnectionInfo,
|
||||
params: Map<string, string>
|
||||
): string {
|
||||
if (!serverInfo.notebookServerEndpoint) {
|
||||
handleError(
|
||||
"Notebook server endpoint not defined. Terminal will fail to connect to jupyter server.",
|
||||
"NotebookTerminalComponent/createNotebookAppSrc"
|
||||
);
|
||||
return "";
|
||||
}
|
||||
|
||||
params.set(TerminalQueryParams.Server, serverInfo.notebookServerEndpoint);
|
||||
if (serverInfo.authToken && serverInfo.authToken.length > 0) {
|
||||
params.set(TerminalQueryParams.Token, serverInfo.authToken);
|
||||
}
|
||||
|
||||
params.set(TerminalQueryParams.SubscriptionId, userContext.subscriptionId);
|
||||
|
||||
let result: string = "terminal.html?";
|
||||
for (let key of params.keys()) {
|
||||
result += `${key}=${encodeURIComponent(params.get(key))}&`;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`NotebookTerminalComponent renders cassandra shell 1`] = `
|
||||
<div
|
||||
className="notebookTerminalContainer"
|
||||
>
|
||||
<iframe
|
||||
onLoad={[Function]}
|
||||
src="terminal.html"
|
||||
title="Terminal to Notebook Server"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`NotebookTerminalComponent renders mongo 3.2 shell 1`] = `
|
||||
<div
|
||||
className="notebookTerminalContainer"
|
||||
>
|
||||
<iframe
|
||||
onLoad={[Function]}
|
||||
src="terminal.html"
|
||||
title="Terminal to Notebook Server"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`NotebookTerminalComponent renders mongo 3.6 shell 1`] = `
|
||||
<div
|
||||
className="notebookTerminalContainer"
|
||||
>
|
||||
<iframe
|
||||
onLoad={[Function]}
|
||||
src="terminal.html"
|
||||
title="Terminal to Notebook Server"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`NotebookTerminalComponent renders terminal 1`] = `
|
||||
<div
|
||||
className="notebookTerminalContainer"
|
||||
>
|
||||
<iframe
|
||||
onLoad={[Function]}
|
||||
src="terminal.html"
|
||||
title="Terminal to Notebook Server"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
@@ -30,7 +30,7 @@ import * as DataModels from "../../../Contracts/DataModels";
|
||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
|
||||
const title = "Open Saved Queries";
|
||||
const title: string = "Open Saved Queries";
|
||||
|
||||
export interface QueriesGridComponentProps {
|
||||
queriesClient: QueriesClient;
|
||||
@@ -196,9 +196,9 @@ export class QueriesGridComponent extends React.Component<QueriesGridComponentPr
|
||||
{
|
||||
key: "Action",
|
||||
name: "Action",
|
||||
fieldName: undefined,
|
||||
fieldName: null,
|
||||
minWidth: 70,
|
||||
onRender: (query: Query) => {
|
||||
onRender: (query: Query, index: number, column: IColumn) => {
|
||||
const buttonProps: IButtonProps = {
|
||||
iconProps: {
|
||||
iconName: "More",
|
||||
@@ -214,15 +214,19 @@ export class QueriesGridComponent extends React.Component<QueriesGridComponentPr
|
||||
{
|
||||
key: "Open",
|
||||
text: "Open query",
|
||||
onClick: () => {
|
||||
onClick: (event: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>, menuItem: any) => {
|
||||
this.props.onQuerySelect(query);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "Delete",
|
||||
text: "Delete query",
|
||||
onClick: async () => {
|
||||
onClick: async (
|
||||
event: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
|
||||
menuItem: any
|
||||
) => {
|
||||
if (window.confirm("Are you sure you want to delete this query?")) {
|
||||
const container = window.dataExplorer;
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteSavedQuery, {
|
||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||
paneTitle: title,
|
||||
|
||||
@@ -16,6 +16,7 @@ import { trace, traceFailure, traceStart, traceSuccess } from "../../../Shared/T
|
||||
import { userContext } from "../../../UserContext";
|
||||
import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types";
|
||||
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
||||
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
|
||||
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
|
||||
@@ -109,6 +110,7 @@ export interface SettingsComponentState {
|
||||
|
||||
initialNotification: DataModels.Notification;
|
||||
selectedTab: SettingsV2TabTypes;
|
||||
offerLoaded: boolean;
|
||||
}
|
||||
|
||||
export class SettingsComponent extends React.Component<SettingsComponentProps, SettingsComponentState> {
|
||||
@@ -193,6 +195,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
|
||||
initialNotification: undefined,
|
||||
selectedTab: SettingsV2TabTypes.ScaleTab,
|
||||
offerLoaded: !!this.offer,
|
||||
};
|
||||
|
||||
this.saveSettingsButton = {
|
||||
@@ -214,6 +217,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
if (this.isCollectionSettingsTab) {
|
||||
this.refreshIndexTransformationProgress();
|
||||
this.loadMongoIndexes();
|
||||
this.loadCollectionOffer();
|
||||
}
|
||||
|
||||
this.setAutoPilotStates();
|
||||
@@ -368,6 +372,34 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
});
|
||||
};
|
||||
|
||||
private async loadCollectionOffer() {
|
||||
try {
|
||||
this.props.settingsTab.isExecuting(true);
|
||||
await this.collection.loadOffer();
|
||||
this.props.settingsTab.tabTitle(this.collection.offer() ? "Settings" : "Scale & Settings");
|
||||
this.setState({ offerLoaded: true });
|
||||
} catch (error) {
|
||||
this.props.settingsTab.isExecutionError(true);
|
||||
const errorMessage = getErrorMessage(error);
|
||||
traceFailure(
|
||||
Action.Tab,
|
||||
{
|
||||
databaseName: this.collection.databaseId,
|
||||
collectionName: this.collection.id(),
|
||||
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: this.props.settingsTab.tabTitle,
|
||||
error: errorMessage,
|
||||
errorStack: getErrorStack(error),
|
||||
},
|
||||
this.props.settingsTab.onLoadStartKey
|
||||
);
|
||||
logConsoleError(`Error while fetching container settings for container ${this.collection.id()}: ${errorMessage}`);
|
||||
} finally {
|
||||
this.props.settingsTab.isExecuting(false);
|
||||
}
|
||||
}
|
||||
|
||||
private getMongoIndexesToSave = (): MongoIndex[] => {
|
||||
let finalIndexes: MongoIndex[] = [];
|
||||
this.state.currentMongoIndexes?.map((mongoIndex: MongoIndex, index: number) => {
|
||||
@@ -905,6 +937,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.state.offerLoaded) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const subSettingsComponentProps: SubSettingsComponentProps = {
|
||||
collection: this.collection,
|
||||
isAnalyticalStorageEnabled: this.isAnalyticalStorageEnabled,
|
||||
|
||||
@@ -30,8 +30,17 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"container": Explorer {
|
||||
"_isInitializingNotebooks": false,
|
||||
"_resetNotebookWorkspace": [Function],
|
||||
"isAccountReady": [Function],
|
||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||
"isNotebookEnabled": [Function],
|
||||
"isNotebooksEnabledForAccount": [Function],
|
||||
"isSchemaEnabled": [Function],
|
||||
"isShellEnabled": [Function],
|
||||
"isSynapseLinkUpdating": [Function],
|
||||
"isTabsContentExpanded": [Function],
|
||||
"memoryUsageInfo": [Function],
|
||||
"notebookBasePath": [Function],
|
||||
"notebookServerInfo": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"provideFeedbackEmail": [Function],
|
||||
@@ -39,24 +48,21 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"container": [Circular],
|
||||
},
|
||||
"refreshNotebookList": [Function],
|
||||
"resourceTokenCollection": [Function],
|
||||
"resourceTree": ResourceTreeAdapter {
|
||||
"container": [Circular],
|
||||
"copyNotebook": [Function],
|
||||
"gitHubOAuthService": GitHubOAuthService {
|
||||
"junoClient": JunoClient {
|
||||
"cachedPinnedRepos": [Function],
|
||||
},
|
||||
"token": [Function],
|
||||
},
|
||||
"junoClient": JunoClient {
|
||||
"cachedPinnedRepos": [Function],
|
||||
},
|
||||
"parameters": [Function],
|
||||
},
|
||||
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
|
||||
"container": [Circular],
|
||||
"parameters": [Function],
|
||||
},
|
||||
"sparkClusterConnectionInfo": [Function],
|
||||
"tabsManager": TabsManager {
|
||||
"activeTab": [Function],
|
||||
"openedTabs": [Function],
|
||||
},
|
||||
},
|
||||
"databaseId": "test",
|
||||
"defaultTtl": [Function],
|
||||
@@ -110,8 +116,17 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"container": Explorer {
|
||||
"_isInitializingNotebooks": false,
|
||||
"_resetNotebookWorkspace": [Function],
|
||||
"isAccountReady": [Function],
|
||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||
"isNotebookEnabled": [Function],
|
||||
"isNotebooksEnabledForAccount": [Function],
|
||||
"isSchemaEnabled": [Function],
|
||||
"isShellEnabled": [Function],
|
||||
"isSynapseLinkUpdating": [Function],
|
||||
"isTabsContentExpanded": [Function],
|
||||
"memoryUsageInfo": [Function],
|
||||
"notebookBasePath": [Function],
|
||||
"notebookServerInfo": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"provideFeedbackEmail": [Function],
|
||||
@@ -119,24 +134,21 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"container": [Circular],
|
||||
},
|
||||
"refreshNotebookList": [Function],
|
||||
"resourceTokenCollection": [Function],
|
||||
"resourceTree": ResourceTreeAdapter {
|
||||
"container": [Circular],
|
||||
"copyNotebook": [Function],
|
||||
"gitHubOAuthService": GitHubOAuthService {
|
||||
"junoClient": JunoClient {
|
||||
"cachedPinnedRepos": [Function],
|
||||
},
|
||||
"token": [Function],
|
||||
},
|
||||
"junoClient": JunoClient {
|
||||
"cachedPinnedRepos": [Function],
|
||||
},
|
||||
"parameters": [Function],
|
||||
},
|
||||
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
|
||||
"container": [Circular],
|
||||
"parameters": [Function],
|
||||
},
|
||||
"sparkClusterConnectionInfo": [Function],
|
||||
"tabsManager": TabsManager {
|
||||
"activeTab": [Function],
|
||||
"openedTabs": [Function],
|
||||
},
|
||||
},
|
||||
"databaseId": "test",
|
||||
"defaultTtl": [Function],
|
||||
|
||||
@@ -58,7 +58,7 @@ export class TabComponent extends React.Component<TabComponentProps> {
|
||||
as="span"
|
||||
className={className}
|
||||
role="presentation"
|
||||
onActivated={() => this.setActiveTab(index)}
|
||||
onActivated={(e) => this.setActiveTab(index)}
|
||||
aria-label={`Select tab: ${tab.title}`}
|
||||
>
|
||||
{tab.title}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,12 +4,12 @@
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import AddPropertyIcon from "../../../../images/Add-property.svg";
|
||||
import DeleteIcon from "../../../../images/delete.svg";
|
||||
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
|
||||
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
|
||||
import { EditedEdges, GraphNewEdgeData, NeighborVertexBasicInfo, PossibleVertex } from "./GraphExplorer";
|
||||
import { NeighborVertexBasicInfo, EditedEdges, GraphNewEdgeData, PossibleVertex } from "./GraphExplorer";
|
||||
import * as GraphUtil from "./GraphUtil";
|
||||
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
|
||||
import DeleteIcon from "../../../../images/delete.svg";
|
||||
import AddPropertyIcon from "../../../../images/Add-property.svg";
|
||||
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
|
||||
|
||||
export interface EditorNeighborsComponentProps {
|
||||
isSource: boolean;
|
||||
@@ -83,11 +83,11 @@ export class EditorNeighborsComponent extends React.Component<EditorNeighborsCom
|
||||
}
|
||||
|
||||
private removeCurrentNeighborEdge(index: number): void {
|
||||
const sources = this.props.editedNeighbors.currentNeighbors;
|
||||
const id = sources[index].edgeId;
|
||||
let sources = this.props.editedNeighbors.currentNeighbors;
|
||||
let id = sources[index].edgeId;
|
||||
sources.splice(index, 1);
|
||||
|
||||
const droppedIds = this.props.editedNeighbors.droppedIds;
|
||||
let droppedIds = this.props.editedNeighbors.droppedIds;
|
||||
droppedIds.push(id);
|
||||
this.onUpdateEdges();
|
||||
}
|
||||
@@ -215,7 +215,7 @@ export class EditorNeighborsComponent extends React.Component<EditorNeighborsCom
|
||||
</td>
|
||||
<td className="actionCol">
|
||||
<span className="rightPaneTrashIcon rightPaneBtns">
|
||||
<img src={DeleteIcon} alt="Delete" onClick={() => this.removeAddedEdgeToNeighbor(index)} />
|
||||
<img src={DeleteIcon} alt="Delete" onClick={(e) => this.removeAddedEdgeToNeighbor(index)} />
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { shallow } from "enzyme";
|
||||
import { GraphHighlightedNodeData, EditedProperties } from "./GraphExplorer";
|
||||
|
||||
import { EditorNodePropertiesComponent, EditorNodePropertiesComponentProps } from "./EditorNodePropertiesComponent";
|
||||
|
||||
describe("<EditorNodePropertiesComponent />", () => {
|
||||
// Tests that: single value prop is rendered with a textbox and a delete button
|
||||
// multi-value prop only a delete button (cannot be edited)
|
||||
const onUpdateProperties = jest.fn();
|
||||
it("renders component", () => {
|
||||
const props: EditorNodePropertiesComponentProps = {
|
||||
editedProperties: {
|
||||
@@ -23,6 +24,7 @@ describe("<EditorNodePropertiesComponent />", () => {
|
||||
{ value: true, type: "boolean" },
|
||||
{ value: false, type: "boolean" },
|
||||
{ value: undefined, type: "null" },
|
||||
{ value: null, type: "null" },
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -39,13 +41,14 @@ describe("<EditorNodePropertiesComponent />", () => {
|
||||
{ value: true, type: "boolean" },
|
||||
{ value: false, type: "boolean" },
|
||||
{ value: undefined, type: "null" },
|
||||
{ value: null, type: "null" },
|
||||
],
|
||||
},
|
||||
],
|
||||
addedProperties: [],
|
||||
droppedKeys: [],
|
||||
},
|
||||
onUpdateProperties,
|
||||
onUpdateProperties: (editedProperties: EditedProperties): void => {},
|
||||
};
|
||||
const wrapper = shallow(<EditorNodePropertiesComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
@@ -78,7 +81,7 @@ describe("<EditorNodePropertiesComponent />", () => {
|
||||
addedProperties: [],
|
||||
droppedKeys: [],
|
||||
},
|
||||
onUpdateProperties,
|
||||
onUpdateProperties: (editedProperties: EditedProperties): void => {},
|
||||
};
|
||||
const wrapper = shallow(<EditorNodePropertiesComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import AddIcon from "../../../../images/Add-property.svg";
|
||||
import DeleteIcon from "../../../../images/delete.svg";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
|
||||
import { EditedProperties } from "./GraphExplorer";
|
||||
import DeleteIcon from "../../../../images/delete.svg";
|
||||
import AddIcon from "../../../../images/Add-property.svg";
|
||||
import { ReadOnlyNodePropertiesComponent } from "./ReadOnlyNodePropertiesComponent";
|
||||
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
|
||||
|
||||
export interface EditorNodePropertiesComponentProps {
|
||||
editedProperties: EditedProperties;
|
||||
@@ -48,7 +48,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
|
||||
const editedProperties = this.props.editedProperties;
|
||||
// search for it
|
||||
for (let i = 0; i < editedProperties.existingProperties.length; i++) {
|
||||
const ip = editedProperties.existingProperties[i];
|
||||
let ip = editedProperties.existingProperties[i];
|
||||
if (ip.key === key) {
|
||||
editedProperties.existingProperties.splice(i, 1);
|
||||
editedProperties.droppedKeys.push(key);
|
||||
@@ -60,7 +60,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
|
||||
|
||||
private removeAddedProperty(index: number): void {
|
||||
const editedProperties = this.props.editedProperties;
|
||||
const ap = editedProperties.addedProperties;
|
||||
let ap = editedProperties.addedProperties;
|
||||
ap.splice(index, 1);
|
||||
|
||||
this.props.onUpdateProperties(editedProperties);
|
||||
@@ -68,7 +68,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
|
||||
|
||||
private addProperty(): void {
|
||||
const editedProperties = this.props.editedProperties;
|
||||
const ap = editedProperties.addedProperties;
|
||||
let ap = editedProperties.addedProperties;
|
||||
ap.push({ key: "", values: [{ value: "", type: EditorNodePropertiesComponent.DEFAULT_PROPERTY_TYPE }] });
|
||||
this.props.onUpdateProperties(editedProperties);
|
||||
}
|
||||
@@ -126,7 +126,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
|
||||
onChange={(e) => {
|
||||
singleValue.type = e.target.value as ViewModels.InputPropertyValueTypeString;
|
||||
if (singleValue.type === "null") {
|
||||
singleValue.value = undefined;
|
||||
singleValue.value = null;
|
||||
}
|
||||
this.props.onUpdateProperties(this.props.editedProperties);
|
||||
}}
|
||||
@@ -144,7 +144,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
|
||||
className="rightPaneTrashIcon rightPaneBtns"
|
||||
as="span"
|
||||
aria-label="Delete property"
|
||||
onActivated={() => this.removeExistingProperty(key)}
|
||||
onActivated={(e) => this.removeExistingProperty(key)}
|
||||
>
|
||||
<img src={DeleteIcon} alt="Delete" />
|
||||
</AccessibleElement>
|
||||
@@ -166,7 +166,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
|
||||
className="rightPaneTrashIcon rightPaneBtns"
|
||||
as="span"
|
||||
aria-label="Remove existing property"
|
||||
onActivated={() => this.removeExistingProperty(nodeProp.key)}
|
||||
onActivated={(e) => this.removeExistingProperty(nodeProp.key)}
|
||||
>
|
||||
<img src={DeleteIcon} alt="Delete" />
|
||||
</AccessibleElement>
|
||||
@@ -206,7 +206,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
|
||||
onChange={(e) => {
|
||||
firstValue.value = e.target.value;
|
||||
if (firstValue.type === "null") {
|
||||
firstValue.value = undefined;
|
||||
firstValue.value = null;
|
||||
}
|
||||
this.props.onUpdateProperties(this.props.editedProperties);
|
||||
}}
|
||||
@@ -235,7 +235,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
|
||||
className="rightPaneTrashIcon rightPaneBtns"
|
||||
as="span"
|
||||
aria-label="Remove property"
|
||||
onActivated={() => this.removeAddedProperty(index)}
|
||||
onActivated={(e) => this.removeAddedProperty(index)}
|
||||
>
|
||||
<img src={DeleteIcon} alt="Delete" />
|
||||
</AccessibleElement>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
import CloseIcon from "../../../../images/close-black.svg";
|
||||
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
|
||||
import CloseIcon from "../../../../images/close-black.svg";
|
||||
|
||||
export interface QueryContainerComponentProps {
|
||||
initialQuery: string;
|
||||
@@ -82,7 +82,7 @@ export class QueryContainerComponent extends React.Component<
|
||||
<button
|
||||
type="button"
|
||||
className="filterbtnstyle queryButton"
|
||||
onClick={() => this.props.onExecuteClick(this.state.query)}
|
||||
onClick={(e) => this.props.onExecuteClick(this.state.query)}
|
||||
disabled={this.props.isLoading || !QueryContainerComponent.isQueryValid(this.state.query)}
|
||||
>
|
||||
Execute Gremlin Query
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
|
||||
import { GraphHighlightedNodeData, NeighborVertexBasicInfo } from "./GraphExplorer";
|
||||
import * as GraphUtil from "./GraphUtil";
|
||||
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
|
||||
|
||||
export interface ReadOnlyNeighborsComponentProps {
|
||||
node: GraphHighlightedNodeData;
|
||||
@@ -48,7 +48,7 @@ export class ReadOnlyNeighborsComponent extends React.Component<ReadOnlyNeighbor
|
||||
className="clickableLink"
|
||||
as="a"
|
||||
aria-label={_neighbor.name}
|
||||
onActivated={() => this.props.selectNode(_neighbor.id)}
|
||||
onActivated={(e) => this.props.selectNode(_neighbor.id)}
|
||||
title={GraphUtil.getNeighborTitle(_neighbor)}
|
||||
>
|
||||
{_neighbor.name}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { GraphHighlightedNodeData } from "./GraphExplorer";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
|
||||
export interface ReadOnlyNodePropertiesComponentProps {
|
||||
node: GraphHighlightedNodeData;
|
||||
|
||||
@@ -37,7 +37,7 @@ exports[`<EditorNodePropertiesComponent /> renders component 1`] = `
|
||||
</td>
|
||||
<td
|
||||
className="valueCol"
|
||||
title="efgh, 1234, true, false, undefined"
|
||||
title="efgh, 1234, true, false, undefined, null"
|
||||
>
|
||||
<div
|
||||
className="propertyValue"
|
||||
@@ -69,6 +69,12 @@ exports[`<EditorNodePropertiesComponent /> renders component 1`] = `
|
||||
>
|
||||
undefined
|
||||
</div>
|
||||
<div
|
||||
className="propertyValue isNull"
|
||||
key="null"
|
||||
>
|
||||
null
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
@@ -172,6 +178,12 @@ exports[`<EditorNodePropertiesComponent /> renders component 1`] = `
|
||||
>
|
||||
undefined
|
||||
</div>
|
||||
<div
|
||||
className="propertyValue isNull"
|
||||
key="null"
|
||||
>
|
||||
null
|
||||
</div>
|
||||
</td>
|
||||
<td />
|
||||
<td
|
||||
|
||||
@@ -8,7 +8,6 @@ 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 { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
import Explorer from "../../Explorer";
|
||||
import { useSelectedNode } from "../../useSelectedNode";
|
||||
@@ -54,8 +53,8 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
||||
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
|
||||
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
|
||||
|
||||
if (useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2) {
|
||||
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker"));
|
||||
if (container.tabsManager.activeTab()?.tabKind === ViewModels.CollectionTabKind.NotebookV2) {
|
||||
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker", container.memoryUsageInfo));
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -6,8 +6,6 @@ import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
|
||||
import { updateUserContext } from "../../../UserContext";
|
||||
import Explorer from "../../Explorer";
|
||||
import NotebookManager from "../../Notebook/NotebookManager";
|
||||
import { useNotebook } from "../../Notebook/useNotebook";
|
||||
import { useDatabases } from "../../useDatabases";
|
||||
import { useSelectedNode } from "../../useSelectedNode";
|
||||
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
|
||||
|
||||
@@ -29,6 +27,9 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
},
|
||||
} as DatabaseAccount,
|
||||
});
|
||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
||||
});
|
||||
|
||||
it("Account is not serverless - button should be visible", () => {
|
||||
@@ -69,19 +70,18 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
},
|
||||
} as DatabaseAccount,
|
||||
});
|
||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
updateUserContext({
|
||||
portalEnv: "prod",
|
||||
});
|
||||
useNotebook.getState().setIsNotebookEnabled(false);
|
||||
useNotebook.getState().setIsNotebooksEnabledForAccount(false);
|
||||
});
|
||||
|
||||
it("Notebooks is already enabled - button should be hidden", () => {
|
||||
useNotebook.getState().setIsNotebookEnabled(true);
|
||||
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
|
||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
|
||||
@@ -89,6 +89,8 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
});
|
||||
|
||||
it("Account is running on one of the national clouds - button should be hidden", () => {
|
||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
||||
updateUserContext({
|
||||
portalEnv: "mooncake",
|
||||
});
|
||||
@@ -99,7 +101,8 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
});
|
||||
|
||||
it("Notebooks is not enabled but is available - button should be shown and enabled", () => {
|
||||
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
|
||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
|
||||
@@ -109,6 +112,9 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
});
|
||||
|
||||
it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => {
|
||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
|
||||
expect(enableNotebookBtn).toBeDefined();
|
||||
@@ -132,25 +138,24 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
},
|
||||
} as DatabaseAccount,
|
||||
});
|
||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||
mockExplorer.isShellEnabled = ko.observable(true);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
updateUserContext({
|
||||
apiType: "SQL",
|
||||
});
|
||||
useNotebook.getState().setIsShellEnabled(false);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
updateUserContext({
|
||||
apiType: "Mongo",
|
||||
});
|
||||
useNotebook.getState().setIsShellEnabled(true);
|
||||
});
|
||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
||||
|
||||
afterEach(() => {
|
||||
useNotebook.getState().setIsNotebookEnabled(false);
|
||||
useNotebook.getState().setIsNotebooksEnabledForAccount(false);
|
||||
mockExplorer.isShellEnabled = ko.observable(true);
|
||||
});
|
||||
|
||||
it("Mongo Api not available - button should be hidden", () => {
|
||||
@@ -179,7 +184,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
});
|
||||
|
||||
it("Notebooks is not enabled and is available - button should be hidden", () => {
|
||||
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||
@@ -187,7 +192,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
});
|
||||
|
||||
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
|
||||
useNotebook.getState().setIsNotebookEnabled(true);
|
||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||
@@ -197,8 +202,8 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
});
|
||||
|
||||
it("Notebooks is enabled and is available - button should be shown and enabled", () => {
|
||||
useNotebook.getState().setIsNotebookEnabled(true);
|
||||
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
|
||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||
@@ -208,9 +213,9 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
});
|
||||
|
||||
it("Notebooks is enabled and is available, terminal is unavailable due to ipRules - button should be hidden", () => {
|
||||
useNotebook.getState().setIsNotebookEnabled(true);
|
||||
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
|
||||
useNotebook.getState().setIsShellEnabled(false);
|
||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
||||
mockExplorer.isShellEnabled = ko.observable(false);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
|
||||
@@ -231,6 +236,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
},
|
||||
} as DatabaseAccount,
|
||||
});
|
||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -241,11 +247,8 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
},
|
||||
} as DatabaseAccount,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
useNotebook.getState().setIsNotebookEnabled(false);
|
||||
useNotebook.getState().setIsNotebooksEnabledForAccount(false);
|
||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
||||
});
|
||||
|
||||
it("Cassandra Api not available - button should be hidden", () => {
|
||||
@@ -256,6 +259,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
},
|
||||
} as DatabaseAccount,
|
||||
});
|
||||
console.log(mockExplorer);
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
||||
expect(openCassandraShellBtn).toBeUndefined();
|
||||
@@ -278,7 +282,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
});
|
||||
|
||||
it("Notebooks is not enabled and is available - button should be shown and enabled", () => {
|
||||
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
||||
@@ -286,7 +290,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
});
|
||||
|
||||
it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => {
|
||||
useNotebook.getState().setIsNotebookEnabled(true);
|
||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
||||
@@ -296,8 +300,8 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
});
|
||||
|
||||
it("Notebooks is enabled and is available - button should be shown and enabled", () => {
|
||||
useNotebook.getState().setIsNotebookEnabled(true);
|
||||
useNotebook.getState().setIsNotebooksEnabledForAccount(true);
|
||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
||||
@@ -322,17 +326,23 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
} as DatabaseAccount,
|
||||
});
|
||||
|
||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
||||
|
||||
mockExplorer.notebookManager = new NotebookManager();
|
||||
mockExplorer.notebookManager.gitHubOAuthService = new GitHubOAuthService(undefined);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
useNotebook.getState().setIsNotebookEnabled(false);
|
||||
});
|
||||
|
||||
it("Notebooks is enabled and GitHubOAuthService is not logged in - connect to github button should be visible", () => {
|
||||
useNotebook.getState().setIsNotebookEnabled(true);
|
||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel);
|
||||
@@ -340,7 +350,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
});
|
||||
|
||||
it("Notebooks is enabled and GitHubOAuthService is logged in - manage github settings button should be visible", () => {
|
||||
useNotebook.getState().setIsNotebookEnabled(true);
|
||||
mockExplorer.isNotebookEnabled = ko.observable(true);
|
||||
mockExplorer.notebookManager.gitHubOAuthService.isLoggedIn = jest.fn().mockReturnValue(true);
|
||||
|
||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||
@@ -366,11 +376,10 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
describe("Resource token", () => {
|
||||
const mockCollection = { id: ko.observable("test") } as CollectionBase;
|
||||
useSelectedNode.getState().setSelectedNode(mockCollection);
|
||||
useDatabases.setState({ resourceTokenCollection: mockCollection });
|
||||
const selectedNodeState = useSelectedNode.getState();
|
||||
|
||||
beforeAll(() => {
|
||||
mockExplorer = {} as Explorer;
|
||||
mockExplorer.resourceTokenCollection = ko.observable(mockCollection);
|
||||
|
||||
updateUserContext({
|
||||
authType: AuthType.ResourceToken,
|
||||
|
||||
@@ -22,22 +22,15 @@ import * as Constants from "../../../Common/Constants";
|
||||
import { configContext, Platform } from "../../../ConfigContext";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
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";
|
||||
import { useNotebook } from "../../Notebook/useNotebook";
|
||||
import { OpenFullScreen } from "../../OpenFullScreen";
|
||||
import { AddDatabasePanel } from "../../Panes/AddDatabasePanel/AddDatabasePanel";
|
||||
import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPane";
|
||||
import { GitHubReposPanel } from "../../Panes/GitHubReposPanel/GitHubReposPanel";
|
||||
import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane";
|
||||
import { SettingsPane } from "../../Panes/SettingsPane/SettingsPane";
|
||||
import { SetupNoteBooksPanel } from "../../Panes/SetupNotebooksPanel/SetupNotebooksPanel";
|
||||
import { useDatabases } from "../../useDatabases";
|
||||
import { SelectedNodeState } from "../../useSelectedNode";
|
||||
|
||||
let counter = 0;
|
||||
@@ -69,7 +62,7 @@ export function createStaticCommandBarButtons(
|
||||
|
||||
buttons.push(createDivider());
|
||||
|
||||
if (useNotebook.getState().isNotebookEnabled) {
|
||||
if (container.isNotebookEnabled()) {
|
||||
const newNotebookButton = createNewNotebookButton(container);
|
||||
newNotebookButton.children = [createNewNotebookButton(container), createuploadNotebookButton(container)];
|
||||
buttons.push(newNotebookButton);
|
||||
@@ -83,7 +76,7 @@ export function createStaticCommandBarButtons(
|
||||
buttons.push(createNotebookWorkspaceResetButton(container));
|
||||
if (
|
||||
(userContext.apiType === "Mongo" &&
|
||||
useNotebook.getState().isShellEnabled &&
|
||||
container.isShellEnabled() &&
|
||||
selectedNodeState.isDatabaseNodeOrNoneSelected()) ||
|
||||
userContext.apiType === "Cassandra"
|
||||
) {
|
||||
@@ -145,13 +138,13 @@ export function createContextCommandBarButtons(
|
||||
const buttons: CommandButtonComponentProps[] = [];
|
||||
|
||||
if (!selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") {
|
||||
const label = useNotebook.getState().isShellEnabled ? "Open Mongo Shell" : "New Shell";
|
||||
const label = container.isShellEnabled() ? "Open Mongo Shell" : "New Shell";
|
||||
const newMongoShellBtn: CommandButtonComponentProps = {
|
||||
iconSrc: HostedTerminalIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||
if (useNotebook.getState().isShellEnabled) {
|
||||
if (container.isShellEnabled()) {
|
||||
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
||||
} else {
|
||||
selectedCollection && selectedCollection.onNewMongoShellClick();
|
||||
@@ -276,7 +269,7 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
|
||||
onCommandClick: () => container.openEnableSynapseLinkDialog(),
|
||||
commandButtonLabel: label,
|
||||
hasPopup: false,
|
||||
disabled: useNotebook.getState().isSynapseLinkUpdating,
|
||||
disabled: container.isSynapseLinkUpdating(),
|
||||
ariaLabel: label,
|
||||
};
|
||||
}
|
||||
@@ -286,8 +279,9 @@ function createNewDatabase(container: Explorer): CommandButtonComponentProps {
|
||||
return {
|
||||
iconSrc: AddDatabaseIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () =>
|
||||
useSidePanel.getState().openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={container} />),
|
||||
onCommandClick: () => {
|
||||
container.openAddDatabasePane();
|
||||
},
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: true,
|
||||
@@ -419,8 +413,7 @@ function createOpenQueryButton(container: Explorer): CommandButtonComponentProps
|
||||
return {
|
||||
iconSrc: BrowseQueriesIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () =>
|
||||
useSidePanel.getState().openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={container} />),
|
||||
onCommandClick: () => container.openBrowseQueriesPanel(),
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
hasPopup: true,
|
||||
@@ -453,18 +446,12 @@ function createEnableNotebooksButton(container: Explorer): CommandButtonComponen
|
||||
return {
|
||||
iconSrc: EnableNotebooksIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () =>
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
label,
|
||||
<SetupNoteBooksPanel explorer={container} panelTitle={label} panelDescription={description} />
|
||||
),
|
||||
onCommandClick: () => container.openSetupNotebooksPanel(label, description),
|
||||
commandButtonLabel: label,
|
||||
hasPopup: false,
|
||||
disabled: !useNotebook.getState().isNotebooksEnabledForAccount,
|
||||
disabled: !container.isNotebooksEnabledForAccount(),
|
||||
ariaLabel: label,
|
||||
tooltipText: useNotebook.getState().isNotebooksEnabledForAccount ? "" : tooltip,
|
||||
tooltipText: container.isNotebooksEnabledForAccount() ? "" : tooltip,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -488,21 +475,15 @@ function createOpenMongoTerminalButton(container: Explorer): CommandButtonCompon
|
||||
const title = "Set up workspace";
|
||||
const description =
|
||||
"Looks like you have not created a workspace for this account. To proceed and start using features including mongo shell and notebook, we will need to create a default workspace in this account.";
|
||||
const disableButton =
|
||||
!useNotebook.getState().isNotebooksEnabledForAccount && !useNotebook.getState().isNotebookEnabled;
|
||||
const disableButton = !container.isNotebooksEnabledForAccount() && !container.isNotebookEnabled();
|
||||
return {
|
||||
iconSrc: HostedTerminalIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () => {
|
||||
if (useNotebook.getState().isNotebookEnabled) {
|
||||
if (container.isNotebookEnabled()) {
|
||||
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
||||
} else {
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
title,
|
||||
<SetupNoteBooksPanel explorer={container} panelTitle={title} panelDescription={description} />
|
||||
);
|
||||
container.openSetupNotebooksPanel(title, description);
|
||||
}
|
||||
},
|
||||
commandButtonLabel: label,
|
||||
@@ -520,21 +501,15 @@ function createOpenCassandraTerminalButton(container: Explorer): CommandButtonCo
|
||||
const title = "Set up workspace";
|
||||
const description =
|
||||
"Looks like you have not created a workspace for this account. To proceed and start using features including cassandra shell and notebook, we will need to create a default workspace in this account.";
|
||||
const disableButton =
|
||||
!useNotebook.getState().isNotebooksEnabledForAccount && !useNotebook.getState().isNotebookEnabled;
|
||||
const disableButton = !container.isNotebooksEnabledForAccount() && !container.isNotebookEnabled();
|
||||
return {
|
||||
iconSrc: HostedTerminalIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () => {
|
||||
if (useNotebook.getState().isNotebookEnabled) {
|
||||
if (container.isNotebookEnabled()) {
|
||||
container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra);
|
||||
} else {
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
title,
|
||||
<SetupNoteBooksPanel explorer={container} panelTitle={title} panelDescription={description} />
|
||||
);
|
||||
container.openSetupNotebooksPanel(title, description);
|
||||
}
|
||||
},
|
||||
commandButtonLabel: label,
|
||||
@@ -561,21 +536,10 @@ function createNotebookWorkspaceResetButton(container: Explorer): CommandButtonC
|
||||
function createManageGitHubAccountButton(container: Explorer): CommandButtonComponentProps {
|
||||
const connectedToGitHub: boolean = container.notebookManager?.gitHubOAuthService.isLoggedIn();
|
||||
const label = connectedToGitHub ? "Manage GitHub settings" : "Connect to GitHub";
|
||||
const junoClient = new JunoClient();
|
||||
return {
|
||||
iconSrc: GitHubIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () =>
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
label,
|
||||
<GitHubReposPanel
|
||||
explorer={container}
|
||||
gitHubClientProp={container.notebookManager.gitHubClient}
|
||||
junoClientProp={junoClient}
|
||||
/>
|
||||
),
|
||||
onCommandClick: () => container.openGitHubReposPanel(label),
|
||||
commandButtonLabel: label,
|
||||
hasPopup: false,
|
||||
disabled: false,
|
||||
@@ -590,12 +554,12 @@ function createStaticCommandBarButtonsForResourceToken(
|
||||
const newSqlQueryBtn = createNewSQLQueryButton(selectedNodeState);
|
||||
const openQueryBtn = createOpenQueryButton(container);
|
||||
|
||||
const resourceTokenCollection: ViewModels.CollectionBase = useDatabases.getState().resourceTokenCollection;
|
||||
const isResourceTokenCollectionNodeSelected: boolean =
|
||||
resourceTokenCollection?.id() === selectedNodeState.selectedNode?.id();
|
||||
container.resourceTokenCollection() &&
|
||||
container.resourceTokenCollection().id() === selectedNodeState.selectedNode?.id();
|
||||
newSqlQueryBtn.disabled = !isResourceTokenCollectionNodeSelected;
|
||||
newSqlQueryBtn.onCommandClick = () => {
|
||||
const resourceTokenCollection: ViewModels.CollectionBase = useDatabases.getState().resourceTokenCollection;
|
||||
const resourceTokenCollection: ViewModels.CollectionBase = container.resourceTokenCollection();
|
||||
resourceTokenCollection && resourceTokenCollection.onNewQueryClick(resourceTokenCollection, undefined);
|
||||
};
|
||||
|
||||
|
||||
@@ -6,14 +6,16 @@ import {
|
||||
IDropdownOption,
|
||||
IDropdownStyles,
|
||||
} from "@fluentui/react";
|
||||
import { Observable } from "knockout";
|
||||
import * as React from "react";
|
||||
import _ from "underscore";
|
||||
import ChevronDownIcon from "../../../../images/Chevron_down.svg";
|
||||
import { StyleConstants } from "../../../Common/Constants";
|
||||
import { MemoryUsageInfo } from "../../../Contracts/DataModels";
|
||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
import { MemoryTracker } from "./MemoryTrackerComponent";
|
||||
import { MemoryTrackerComponent } from "./MemoryTrackerComponent";
|
||||
|
||||
/**
|
||||
* Convert our NavbarButtonConfig to UI Fabric buttons
|
||||
@@ -183,9 +185,12 @@ export const createDivider = (key: string): ICommandBarItemProps => {
|
||||
};
|
||||
};
|
||||
|
||||
export const createMemoryTracker = (key: string): ICommandBarItemProps => {
|
||||
export const createMemoryTracker = (
|
||||
key: string,
|
||||
memoryUsageInfo: Observable<MemoryUsageInfo>
|
||||
): ICommandBarItemProps => {
|
||||
return {
|
||||
key,
|
||||
onRender: () => <MemoryTracker />,
|
||||
onRender: () => <MemoryTrackerComponent memoryUsageInfo={memoryUsageInfo} />,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,29 +1,48 @@
|
||||
import { ProgressIndicator, Spinner, SpinnerSize, Stack } from "@fluentui/react";
|
||||
import { Observable, Subscription } from "knockout";
|
||||
import * as React from "react";
|
||||
import { useNotebook } from "../../Notebook/useNotebook";
|
||||
import { MemoryUsageInfo } from "../../../Contracts/DataModels";
|
||||
|
||||
interface MemoryTrackerProps {
|
||||
memoryUsageInfo: Observable<MemoryUsageInfo>;
|
||||
}
|
||||
|
||||
export class MemoryTrackerComponent extends React.Component<MemoryTrackerProps> {
|
||||
private memoryUsageInfoSubscription: Subscription;
|
||||
|
||||
public componentDidMount(): void {
|
||||
this.memoryUsageInfoSubscription = this.props.memoryUsageInfo.subscribe(() => {
|
||||
this.forceUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
this.memoryUsageInfoSubscription && this.memoryUsageInfoSubscription.dispose();
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
const memoryUsageInfo: MemoryUsageInfo = this.props.memoryUsageInfo();
|
||||
if (!memoryUsageInfo) {
|
||||
return (
|
||||
<Stack className="memoryTrackerContainer" horizontal>
|
||||
<span>Memory</span>
|
||||
<Spinner size={SpinnerSize.medium} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
const totalGB = memoryUsageInfo.totalKB / 1048576;
|
||||
const usedGB = totalGB - memoryUsageInfo.freeKB / 1048576;
|
||||
|
||||
export const MemoryTracker: React.FC = (): JSX.Element => {
|
||||
const memoryUsageInfo = useNotebook((state) => state.memoryUsageInfo);
|
||||
if (!memoryUsageInfo) {
|
||||
return (
|
||||
<Stack className="memoryTrackerContainer" horizontal>
|
||||
<span>Memory</span>
|
||||
<Spinner size={SpinnerSize.medium} />
|
||||
<ProgressIndicator
|
||||
className={usedGB / totalGB > 0.8 ? "lowMemory" : ""}
|
||||
description={usedGB.toFixed(1) + " of " + totalGB.toFixed(1) + " GB"}
|
||||
percentComplete={usedGB / totalGB}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
const totalGB = memoryUsageInfo.totalKB / 1048576;
|
||||
const usedGB = totalGB - memoryUsageInfo.freeKB / 1048576;
|
||||
|
||||
return (
|
||||
<Stack className="memoryTrackerContainer" horizontal>
|
||||
<span>Memory</span>
|
||||
<ProgressIndicator
|
||||
className={usedGB / totalGB > 0.8 ? "lowMemory" : ""}
|
||||
description={usedGB.toFixed(1) + " of " + totalGB.toFixed(1) + " GB"}
|
||||
percentComplete={usedGB / totalGB}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AppState, ContentRef, selectors } from "@nteract/core";
|
||||
import * as React from "react";
|
||||
import { AppState, ContentRef, selectors } from "@nteract/core";
|
||||
import { connect } from "react-redux";
|
||||
import * as NteractUtil from "../NTeractUtil";
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { AppState, ContentRef, selectors } from "@nteract/core";
|
||||
import * as React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import styled from "styled-components";
|
||||
|
||||
import NotebookRenderer from "../../../NotebookRenderer/NotebookRenderer";
|
||||
import * as TextFile from "./text-file";
|
||||
|
||||
@@ -31,14 +32,14 @@ interface FileProps {
|
||||
|
||||
export class File extends React.PureComponent<FileProps> {
|
||||
getChoice = () => {
|
||||
let choice;
|
||||
let choice = null;
|
||||
|
||||
// notebooks don't report a mimetype so we'll use the content.type
|
||||
if (this.props.type === "notebook") {
|
||||
choice = <NotebookRenderer contentRef={this.props.contentRef} />;
|
||||
} else if (this.props.type === "dummy") {
|
||||
choice = undefined;
|
||||
} else if (this.props.mimetype === undefined || !TextFile.handles(this.props.mimetype)) {
|
||||
choice = null;
|
||||
} else if (this.props.mimetype == null || !TextFile.handles(this.props.mimetype)) {
|
||||
// This should not happen as we intercept mimetype upstream, but just in case
|
||||
choice = (
|
||||
<PaddedContainer>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import * as StringUtils from "../../../../../Utils/StringUtils";
|
||||
import { actions, AppState, ContentRef, selectors } from "@nteract/core";
|
||||
import { IMonacoProps as MonacoEditorProps } from "@nteract/monaco-editor";
|
||||
import * as React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Dispatch } from "redux";
|
||||
import styled from "styled-components";
|
||||
import * as StringUtils from "../../../../../Utils/StringUtils";
|
||||
|
||||
const EditorContainer = styled.div`
|
||||
position: absolute;
|
||||
@@ -37,7 +37,7 @@ interface TextFileState {
|
||||
class EditorPlaceholder extends React.PureComponent<MonacoEditorProps> {
|
||||
render(): JSX.Element {
|
||||
// TODO: Show a little blocky placeholder
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ function makeMapStateToTextFileProps(
|
||||
|
||||
return {
|
||||
contentRef,
|
||||
mimetype: content.mimetype !== undefined ? content.mimetype : "text/plain",
|
||||
mimetype: content.mimetype != null ? content.mimetype : "text/plain",
|
||||
text,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -34,7 +34,6 @@ import {
|
||||
import { webSocket } from "rxjs/webSocket";
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import { Areas } from "../../../Common/Constants";
|
||||
import { useTabs } from "../../../hooks/useTabs";
|
||||
import { Action as TelemetryAction, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { logConsoleError, logConsoleInfo } from "../../../Utils/NotificationConsoleUtils";
|
||||
@@ -777,11 +776,9 @@ const closeUnsupportedMimetypesEpic = (
|
||||
if (explorer && !TextFile.handles(mimetype)) {
|
||||
const filepath = action.payload.filepath;
|
||||
// Close tab and show error message
|
||||
useTabs
|
||||
.getState()
|
||||
.closeTabsByComparator(
|
||||
(tab: any) => (tab as any).notebookPath && FileSystemUtil.isPathEqual((tab as any).notebookPath(), filepath)
|
||||
);
|
||||
explorer.tabsManager.closeTabsByComparator(
|
||||
(tab: any) => (tab as any).notebookPath && FileSystemUtil.isPathEqual((tab as any).notebookPath(), filepath)
|
||||
);
|
||||
const msg = `${filepath} cannot be rendered. Please download the file, in order to view it outside of Data Explorer.`;
|
||||
explorer.showOkModalDialog("File cannot be rendered", msg);
|
||||
logConsoleError(msg);
|
||||
@@ -807,11 +804,9 @@ const closeContentFailedToFetchEpic = (
|
||||
if (explorer) {
|
||||
const filepath = action.payload.filepath;
|
||||
// Close tab and show error message
|
||||
useTabs
|
||||
.getState()
|
||||
.closeTabsByComparator(
|
||||
(tab: any) => (tab as any).notebookPath && FileSystemUtil.isPathEqual((tab as any).notebookPath(), filepath)
|
||||
);
|
||||
explorer.tabsManager.closeTabsByComparator(
|
||||
(tab: any) => (tab as any).notebookPath && FileSystemUtil.isPathEqual((tab as any).notebookPath(), filepath)
|
||||
);
|
||||
const msg = `Failed to load file: ${filepath}.`;
|
||||
explorer.showOkModalDialog("Failure to load", msg);
|
||||
logConsoleError(msg);
|
||||
|
||||
@@ -8,26 +8,25 @@ import * as DataModels from "../../Contracts/DataModels";
|
||||
import { userContext } from "../../UserContext";
|
||||
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;
|
||||
|
||||
constructor(private onConnectionLost: () => void) {
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
if (notebookServerInfo?.notebookServerEndpoint) {
|
||||
constructor(
|
||||
private notebookServerInfo: ko.Observable<DataModels.NotebookWorkspaceConnectionInfo>,
|
||||
private onConnectionLost: () => void,
|
||||
private onMemoryUsageInfoUpdate: (update: DataModels.MemoryUsageInfo) => void
|
||||
) {
|
||||
if (notebookServerInfo() && notebookServerInfo().notebookServerEndpoint) {
|
||||
this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs);
|
||||
} else {
|
||||
const unsub = useNotebook.subscribe(
|
||||
(newServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => {
|
||||
if (newServerInfo?.notebookServerEndpoint) {
|
||||
this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs);
|
||||
}
|
||||
unsub();
|
||||
},
|
||||
(state) => state.notebookServerInfo
|
||||
);
|
||||
const subscription = notebookServerInfo.subscribe((newServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => {
|
||||
if (newServerInfo && newServerInfo.notebookServerEndpoint) {
|
||||
this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs);
|
||||
}
|
||||
subscription.dispose();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,14 +36,13 @@ export class NotebookContainerClient {
|
||||
private scheduleHeartbeat(delayMs: number): void {
|
||||
setTimeout(() => {
|
||||
this.getMemoryUsage()
|
||||
.then((memoryUsageInfo) => useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo))
|
||||
.then((memoryUsageInfo) => this.onMemoryUsageInfoUpdate(memoryUsageInfo))
|
||||
.finally(() => this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs));
|
||||
}, delayMs);
|
||||
}
|
||||
|
||||
private async getMemoryUsage(): Promise<DataModels.MemoryUsageInfo> {
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
|
||||
if (!this.notebookServerInfo() || !this.notebookServerInfo().notebookServerEndpoint) {
|
||||
const error = "No server endpoint detected";
|
||||
Logger.logError(error, "NotebookContainerClient/getMemoryUsage");
|
||||
return Promise.reject(error);
|
||||
@@ -100,8 +98,7 @@ export class NotebookContainerClient {
|
||||
}
|
||||
|
||||
private async _resetWorkspace(): Promise<void> {
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
|
||||
if (!this.notebookServerInfo() || !this.notebookServerInfo().notebookServerEndpoint) {
|
||||
const error = "No server endpoint detected";
|
||||
Logger.logError(error, "NotebookContainerClient/resetWorkspace");
|
||||
return Promise.reject(error);
|
||||
@@ -120,11 +117,15 @@ export class NotebookContainerClient {
|
||||
}
|
||||
|
||||
private getNotebookServerConfig(): { notebookServerEndpoint: string; authToken: string } {
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
const authToken: string = notebookServerInfo.authToken ? `Token ${notebookServerInfo.authToken}` : undefined;
|
||||
let authToken: string,
|
||||
notebookServerEndpoint = this.notebookServerInfo().notebookServerEndpoint,
|
||||
token = this.notebookServerInfo().authToken;
|
||||
if (token) {
|
||||
authToken = `Token ${token}`;
|
||||
}
|
||||
|
||||
return {
|
||||
notebookServerEndpoint: notebookServerInfo.notebookServerEndpoint,
|
||||
notebookServerEndpoint,
|
||||
authToken,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { stringifyNotebook } from "@nteract/commutable";
|
||||
import { FileType, IContent, IContentProvider, IEmptyContent, ServerConfig } from "@nteract/core";
|
||||
import { AjaxResponse } from "rxjs/ajax";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as StringUtils from "../../Utils/StringUtils";
|
||||
import * as FileSystemUtil from "./FileSystemUtil";
|
||||
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
|
||||
import { NotebookUtil } from "./NotebookUtil";
|
||||
import { useNotebook } from "./useNotebook";
|
||||
|
||||
export class NotebookContentClient {
|
||||
constructor(private contentProvider: IContentProvider) {}
|
||||
constructor(
|
||||
private notebookServerInfo: ko.Observable<DataModels.NotebookWorkspaceConnectionInfo>,
|
||||
public notebookBasePath: ko.Observable<string>,
|
||||
private contentProvider: IContentProvider
|
||||
) {}
|
||||
|
||||
/**
|
||||
* This updates the item and points all the children's parent to this item
|
||||
@@ -267,10 +271,9 @@ export class NotebookContentClient {
|
||||
}
|
||||
|
||||
private getServerConfig(): ServerConfig {
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
return {
|
||||
endpoint: notebookServerInfo.notebookServerEndpoint,
|
||||
token: notebookServerInfo.authToken,
|
||||
endpoint: this.notebookServerInfo().notebookServerEndpoint,
|
||||
token: this.notebookServerInfo().authToken,
|
||||
crossDomain: true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
|
||||
import { ImmutableNotebook } from "@nteract/commutable";
|
||||
import type { IContentProvider } from "@nteract/core";
|
||||
import ko from "knockout";
|
||||
import React from "react";
|
||||
import { contents } from "rx-jupyter";
|
||||
import { Areas, HttpStatusCodes } from "../../Common/Constants";
|
||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||
import * as Logger from "../../Common/Logger";
|
||||
import { MemoryUsageInfo } from "../../Contracts/DataModels";
|
||||
import { GitHubClient } from "../../GitHub/GitHubClient";
|
||||
import { GitHubContentProvider } from "../../GitHub/GitHubContentProvider";
|
||||
import { GitHubOAuthService } from "../../GitHub/GitHubOAuthService";
|
||||
@@ -20,7 +22,6 @@ import { userContext } from "../../UserContext";
|
||||
import { getFullName } from "../../Utils/UserUtils";
|
||||
import Explorer from "../Explorer";
|
||||
import { CopyNotebookPane } from "../Panes/CopyNotebookPane/CopyNotebookPane";
|
||||
import { GitHubReposPanel } from "../Panes/GitHubReposPanel/GitHubReposPanel";
|
||||
import { PublishNotebookPane } from "../Panes/PublishNotebookPane/PublishNotebookPane";
|
||||
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
|
||||
import { InMemoryContentProvider } from "./NotebookComponent/ContentProviders/InMemoryContentProvider";
|
||||
@@ -36,6 +37,7 @@ export type { NotebookPaneContent };
|
||||
|
||||
export interface NotebookManagerOptions {
|
||||
container: Explorer;
|
||||
notebookBasePath: ko.Observable<string>;
|
||||
resourceTree: ResourceTreeAdapter;
|
||||
refreshCommandBarButtons: () => void;
|
||||
refreshNotebookList: () => void;
|
||||
@@ -79,28 +81,23 @@ export default class NotebookManager {
|
||||
contents.JupyterContentProvider
|
||||
);
|
||||
|
||||
this.notebookClient = new NotebookContainerClient(() =>
|
||||
this.params.container.initNotebooks(userContext?.databaseAccount)
|
||||
this.notebookClient = new NotebookContainerClient(
|
||||
this.params.container.notebookServerInfo,
|
||||
() => this.params.container.initNotebooks(userContext?.databaseAccount),
|
||||
(update: MemoryUsageInfo) => this.params.container.memoryUsageInfo(update)
|
||||
);
|
||||
|
||||
this.notebookContentClient = new NotebookContentClient(this.notebookContentProvider);
|
||||
this.notebookContentClient = new NotebookContentClient(
|
||||
this.params.container.notebookServerInfo,
|
||||
this.params.notebookBasePath,
|
||||
this.notebookContentProvider
|
||||
);
|
||||
|
||||
this.gitHubOAuthService.getTokenObservable().subscribe((token) => {
|
||||
this.gitHubClient.setToken(token?.access_token);
|
||||
if (this?.gitHubOAuthService.isLoggedIn()) {
|
||||
useSidePanel.getState().closeSidePanel();
|
||||
setTimeout(() => {
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
"Manage GitHub settings",
|
||||
<GitHubReposPanel
|
||||
explorer={this.params.container}
|
||||
gitHubClientProp={this.params.container.notebookManager.gitHubClient}
|
||||
junoClientProp={this.junoClient}
|
||||
/>
|
||||
);
|
||||
}, 200);
|
||||
this.params.container.openGitHubReposPanel("Manager GitHub settings", this.junoClient);
|
||||
}
|
||||
|
||||
this.params.refreshCommandBarButtons();
|
||||
@@ -173,17 +170,7 @@ export default class NotebookManager {
|
||||
undefined,
|
||||
"Cosmos DB cannot access your Github account anymore. Please connect to GitHub again.",
|
||||
"Connect to GitHub",
|
||||
() =>
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
"Connect to GitHub",
|
||||
<GitHubReposPanel
|
||||
explorer={this.params.container}
|
||||
gitHubClientProp={this.params.container.notebookManager.gitHubClient}
|
||||
junoClientProp={this.junoClient}
|
||||
/>
|
||||
),
|
||||
() => this.params.container.openGitHubReposPanel("Connect to GitHub"),
|
||||
"Cancel",
|
||||
undefined
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@ import "./Prompt.less";
|
||||
|
||||
export const promptContent = (props: PassedPromptProps): JSX.Element => {
|
||||
if (props.status === "busy") {
|
||||
const stopButtonText = "Stop cell execution";
|
||||
const stopButtonText: string = "Stop cell execution";
|
||||
return (
|
||||
<div
|
||||
style={{ position: "sticky", width: "100%", maxHeight: "100%", left: 0, top: 0, zIndex: 300 }}
|
||||
@@ -23,7 +23,7 @@ export const promptContent = (props: PassedPromptProps): JSX.Element => {
|
||||
</div>
|
||||
);
|
||||
} else if (props.isHovered) {
|
||||
const playButtonText = "Run cell";
|
||||
const playButtonText: string = "Run cell";
|
||||
return (
|
||||
<IconButton
|
||||
className="runCellButton"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
import { StatusBar } from "./StatusBar";
|
||||
|
||||
describe("StatusBar", () => {
|
||||
@@ -27,8 +28,8 @@ describe("StatusBar", () => {
|
||||
kernelSpecDisplayName: "javascript",
|
||||
kernelStatus: "kernelStatus",
|
||||
},
|
||||
undefined,
|
||||
undefined
|
||||
null,
|
||||
null
|
||||
);
|
||||
expect(shouldUpdate).toBe(true);
|
||||
});
|
||||
@@ -46,8 +47,8 @@ describe("StatusBar", () => {
|
||||
kernelSpecDisplayName: "python3",
|
||||
kernelStatus: "kernelStatus",
|
||||
},
|
||||
undefined,
|
||||
undefined
|
||||
null,
|
||||
null
|
||||
);
|
||||
expect(shouldUpdate).toBe(true);
|
||||
});
|
||||
|
||||
@@ -2,7 +2,6 @@ import { AppState, ContentRef, selectors } from "@nteract/core";
|
||||
import distanceInWordsToNow from "date-fns/distance_in_words_to_now";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import styled from "styled-components";
|
||||
import { StyleConstants } from "../../../Common/Constants";
|
||||
|
||||
interface Props {
|
||||
@@ -13,6 +12,8 @@ interface Props {
|
||||
|
||||
const NOT_CONNECTED = "not connected";
|
||||
|
||||
import styled from "styled-components";
|
||||
|
||||
export const LeftStatus = styled.div`
|
||||
float: left;
|
||||
display: block;
|
||||
@@ -79,7 +80,7 @@ interface InitialProps {
|
||||
contentRef: ContentRef;
|
||||
}
|
||||
|
||||
const makeMapStateToProps = (_initialState: AppState, initialProps: InitialProps): ((state: AppState) => Props) => {
|
||||
const makeMapStateToProps = (initialState: AppState, initialProps: InitialProps): ((state: AppState) => Props) => {
|
||||
const { contentRef } = initialProps;
|
||||
|
||||
const mapStateToProps = (state: AppState) => {
|
||||
@@ -89,26 +90,26 @@ const makeMapStateToProps = (_initialState: AppState, initialProps: InitialProps
|
||||
return {
|
||||
kernelStatus: NOT_CONNECTED,
|
||||
kernelSpecDisplayName: "no kernel",
|
||||
lastSaved: undefined,
|
||||
lastSaved: null,
|
||||
};
|
||||
}
|
||||
|
||||
const kernelRef = content.model.kernelRef;
|
||||
let kernel;
|
||||
let kernel = null;
|
||||
if (kernelRef) {
|
||||
kernel = selectors.kernel(state, { kernelRef });
|
||||
}
|
||||
|
||||
const lastSaved = content && content.lastSaved ? content.lastSaved : undefined;
|
||||
const lastSaved = content && content.lastSaved ? content.lastSaved : null;
|
||||
|
||||
const kernelStatus = kernel?.status || NOT_CONNECTED;
|
||||
const kernelStatus = kernel != null && kernel.status != null ? kernel.status : NOT_CONNECTED;
|
||||
|
||||
// TODO: We need kernels associated to the kernelspec they came from
|
||||
// so we can pluck off the display_name and provide it here
|
||||
let kernelSpecDisplayName = " ";
|
||||
if (kernelStatus === NOT_CONNECTED) {
|
||||
kernelSpecDisplayName = "no kernel";
|
||||
} else if (kernel?.kernelSpecName) {
|
||||
} else if (kernel != null && kernel.kernelSpecName != null) {
|
||||
kernelSpecDisplayName = kernel.kernelSpecName;
|
||||
} else if (content && content.type === "notebook") {
|
||||
kernelSpecDisplayName = selectors.notebook.displayName(content.model) || " ";
|
||||
|
||||
@@ -27,7 +27,7 @@ interface DispatchProps {
|
||||
moveCell: (destinationId: CellId, above: boolean) => void;
|
||||
clearOutputs: () => void;
|
||||
deleteCell: () => void;
|
||||
traceNotebookTelemetry: (action: Action, actionModifier?: string, data?: string) => void;
|
||||
traceNotebookTelemetry: (action: Action, actionModifier?: string, data?: any) => void;
|
||||
takeNotebookSnapshot: (payload: SnapshotRequest) => void;
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ const mapDispatchToProps = (
|
||||
dispatch(actions.moveCell({ id, contentRef, destinationId, above })),
|
||||
clearOutputs: () => dispatch(actions.clearOutputs({ id, contentRef })),
|
||||
deleteCell: () => dispatch(actions.deleteCell({ id, contentRef })),
|
||||
traceNotebookTelemetry: (action: Action, actionModifier?: string, data?: string) =>
|
||||
traceNotebookTelemetry: (action: Action, actionModifier?: string, data?: any) =>
|
||||
dispatch(cdbActions.traceNotebookTelemetry({ action, actionModifier, data })),
|
||||
takeNotebookSnapshot: (request: SnapshotRequest) => dispatch(cdbActions.takeNotebookSnapshot(request)),
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { ContentRef } from "@nteract/core";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Dispatch } from "redux";
|
||||
|
||||
import { ContentRef } from "@nteract/core";
|
||||
import * as actions from "../../NotebookComponent/actions";
|
||||
|
||||
interface ComponentProps {
|
||||
@@ -28,7 +29,10 @@ class HoverableCell extends React.Component<ComponentProps & DispatchProps> {
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch, { id }: { id: string }): DispatchProps => ({
|
||||
const mapDispatchToProps = (
|
||||
dispatch: Dispatch,
|
||||
{ id, contentRef }: { id: string; contentRef: ContentRef }
|
||||
): DispatchProps => ({
|
||||
hover: () => dispatch(actions.setHoveredCell({ cellId: id })),
|
||||
unHover: () => dispatch(actions.setHoveredCell({ cellId: undefined })),
|
||||
});
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
import create, { UseStore } from "zustand";
|
||||
import { AuthType } from "../../AuthType";
|
||||
import * as Constants 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 { userContext } from "../../UserContext";
|
||||
import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
|
||||
|
||||
interface NotebookState {
|
||||
isNotebookEnabled: boolean;
|
||||
isNotebooksEnabledForAccount: boolean;
|
||||
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
||||
sparkClusterConnectionInfo: DataModels.SparkClusterConnectionInfo;
|
||||
isSynapseLinkUpdating: boolean;
|
||||
memoryUsageInfo: DataModels.MemoryUsageInfo;
|
||||
isShellEnabled: boolean;
|
||||
notebookBasePath: string;
|
||||
isInitializingNotebooks: boolean;
|
||||
setIsNotebookEnabled: (isNotebookEnabled: boolean) => void;
|
||||
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void;
|
||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void;
|
||||
setSparkClusterConnectionInfo: (sparkClusterConnectionInfo: DataModels.SparkClusterConnectionInfo) => void;
|
||||
setIsSynapseLinkUpdating: (isSynapseLinkUpdating: boolean) => void;
|
||||
setMemoryUsageInfo: (memoryUsageInfo: DataModels.MemoryUsageInfo) => void;
|
||||
setIsShellEnabled: (isShellEnabled: boolean) => void;
|
||||
setNotebookBasePath: (notebookBasePath: string) => void;
|
||||
refreshNotebooksEnabledStateForAccount: () => Promise<void>;
|
||||
}
|
||||
|
||||
export const useNotebook: UseStore<NotebookState> = create((set) => ({
|
||||
isNotebookEnabled: false,
|
||||
isNotebooksEnabledForAccount: false,
|
||||
notebookServerInfo: {
|
||||
notebookServerEndpoint: undefined,
|
||||
authToken: undefined,
|
||||
},
|
||||
sparkClusterConnectionInfo: {
|
||||
userName: undefined,
|
||||
password: undefined,
|
||||
endpoints: [],
|
||||
},
|
||||
isSynapseLinkUpdating: false,
|
||||
memoryUsageInfo: undefined,
|
||||
isShellEnabled: false,
|
||||
notebookBasePath: Constants.Notebook.defaultBasePath,
|
||||
isInitializingNotebooks: false,
|
||||
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
|
||||
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
|
||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
||||
set({ notebookServerInfo }),
|
||||
setSparkClusterConnectionInfo: (sparkClusterConnectionInfo: DataModels.SparkClusterConnectionInfo) =>
|
||||
set({ sparkClusterConnectionInfo }),
|
||||
setIsSynapseLinkUpdating: (isSynapseLinkUpdating: boolean) => set({ isSynapseLinkUpdating }),
|
||||
setMemoryUsageInfo: (memoryUsageInfo: DataModels.MemoryUsageInfo) => set({ memoryUsageInfo }),
|
||||
setIsShellEnabled: (isShellEnabled: boolean) => set({ isShellEnabled }),
|
||||
setNotebookBasePath: (notebookBasePath: string) => set({ notebookBasePath }),
|
||||
refreshNotebooksEnabledStateForAccount: async (): Promise<void> => {
|
||||
const { databaseAccount, authType } = userContext;
|
||||
if (
|
||||
authType === AuthType.EncryptedToken ||
|
||||
authType === AuthType.ResourceToken ||
|
||||
authType === AuthType.MasterKey
|
||||
) {
|
||||
set({ isNotebooksEnabledForAccount: false });
|
||||
return;
|
||||
}
|
||||
|
||||
const firstWriteLocation =
|
||||
databaseAccount?.properties?.writeLocations &&
|
||||
databaseAccount?.properties?.writeLocations[0]?.locationName.toLowerCase();
|
||||
const disallowedLocationsUri = `${configContext.BACKEND_ENDPOINT}/api/disallowedLocations`;
|
||||
const authorizationHeader = getAuthorizationHeader();
|
||||
try {
|
||||
const response = await fetch(disallowedLocationsUri, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
resourceTypes: [Constants.ArmResourceTypes.notebookWorkspaces],
|
||||
}),
|
||||
headers: {
|
||||
[authorizationHeader.header]: authorizationHeader.token,
|
||||
[Constants.HttpHeaders.contentType]: "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch disallowed locations");
|
||||
}
|
||||
|
||||
const disallowedLocations: string[] = await response.json();
|
||||
if (!disallowedLocations) {
|
||||
Logger.logInfo("No disallowed locations found", "Explorer/isNotebooksEnabledForAccount");
|
||||
set({ isNotebooksEnabledForAccount: true });
|
||||
return;
|
||||
}
|
||||
|
||||
// firstWriteLocation should not be disallowed
|
||||
const isAccountInAllowedLocation = firstWriteLocation && disallowedLocations.indexOf(firstWriteLocation) === -1;
|
||||
set({ isNotebooksEnabledForAccount: isAccountInAllowedLocation });
|
||||
} catch (error) {
|
||||
Logger.logError(getErrorMessage(error), "Explorer/isNotebooksEnabledForAccount");
|
||||
set({ isNotebooksEnabledForAccount: false });
|
||||
}
|
||||
},
|
||||
}));
|
||||
@@ -113,11 +113,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
collectionId: "",
|
||||
enableIndexing: true,
|
||||
isSharded: userContext.apiType !== "Tables",
|
||||
partitionKey:
|
||||
(userContext.features.partitionKeyDefault && userContext.apiType === "SQL") ||
|
||||
(userContext.features.partitionKeyDefault && userContext.apiType === "Mongo")
|
||||
? "/id"
|
||||
: "",
|
||||
partitionKey: "",
|
||||
enableDedicatedThroughput: false,
|
||||
createMongoWildCardIndex: isCapabilityEnabled("EnableMongo"),
|
||||
useHashV2: false,
|
||||
@@ -417,10 +413,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
</TooltipHost>
|
||||
</Stack>
|
||||
|
||||
<Text variant="small" aria-label="pkDescription">
|
||||
{this.getPartitionKeySubtext()}
|
||||
</Text>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
id="addCollection-partitionKeyValue"
|
||||
@@ -815,17 +807,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
return tooltipText;
|
||||
}
|
||||
|
||||
private getPartitionKeySubtext(): string {
|
||||
if (
|
||||
userContext.features.partitionKeyDefault &&
|
||||
(userContext.apiType === "SQL" || userContext.apiType === "Mongo")
|
||||
) {
|
||||
const subtext = "For small workloads, the item ID is a suitable choice for the partition key.";
|
||||
return subtext;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private getAnalyticalStorageTooltipContent(): JSX.Element {
|
||||
return (
|
||||
<Text variant="small">
|
||||
|
||||
@@ -4,7 +4,6 @@ import { logError } from "../../../Common/Logger";
|
||||
import { Query } from "../../../Contracts/DataModels";
|
||||
import { Collection } from "../../../Contracts/ViewModels";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import { useTabs } from "../../../hooks/useTabs";
|
||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import { trace } from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "../../../UserContext";
|
||||
@@ -37,7 +36,7 @@ export const BrowseQueriesPane: FunctionComponent<BrowseQueriesPaneProps> = ({
|
||||
selectedCollection.onNewQueryClick(selectedCollection, undefined, savedQuery.query);
|
||||
}
|
||||
|
||||
const queryTab = useTabs.getState().activeTab as NewQueryTab;
|
||||
const queryTab = explorer && (explorer.tabsManager.activeTab() as NewQueryTab);
|
||||
queryTab.tabTitle(savedQuery.queryName);
|
||||
queryTab.tabPath(`${selectedCollection.databaseId}>${selectedCollection.id()}>${savedQuery.queryName}`);
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||
import Explorer from "../../Explorer";
|
||||
import { NotebookContentItem, NotebookContentItemType } from "../../Notebook/NotebookContentItem";
|
||||
import { useNotebook } from "../../Notebook/useNotebook";
|
||||
import { ResourceTreeAdapter } from "../../Tree/ResourceTreeAdapter";
|
||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||
import { CopyNotebookPaneComponent, CopyNotebookPaneProps } from "./CopyNotebookPaneComponent";
|
||||
@@ -102,7 +101,7 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
||||
case "MyNotebooks":
|
||||
parent = {
|
||||
name: ResourceTreeAdapter.MyNotebooksTitle,
|
||||
path: useNotebook.getState().notebookBasePath,
|
||||
path: container.getNotebookBasePath(),
|
||||
type: NotebookContentItemType.Directory,
|
||||
};
|
||||
break;
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Collection, Database } from "../../../Contracts/ViewModels";
|
||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { updateUserContext } from "../../../UserContext";
|
||||
import Explorer from "../../Explorer";
|
||||
import { useDatabases } from "../../useDatabases";
|
||||
import { useSelectedNode } from "../../useSelectedNode";
|
||||
import { DeleteCollectionConfirmationPane } from "./DeleteCollectionConfirmationPane";
|
||||
@@ -52,7 +53,10 @@ describe("Delete Collection Confirmation Pane", () => {
|
||||
|
||||
describe("shouldRecordFeedback()", () => {
|
||||
it("should return true if last collection and database does not have shared throughput else false", () => {
|
||||
const wrapper = shallow(<DeleteCollectionConfirmationPane refreshDatabases={() => undefined} />);
|
||||
const fakeExplorer = new Explorer();
|
||||
fakeExplorer.refreshAllDatabases = () => undefined;
|
||||
|
||||
const wrapper = shallow(<DeleteCollectionConfirmationPane explorer={fakeExplorer} />);
|
||||
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(false);
|
||||
|
||||
const database = { id: ko.observable("testDB") } as Database;
|
||||
@@ -61,11 +65,11 @@ describe("Delete Collection Confirmation Pane", () => {
|
||||
database.isDatabaseShared = ko.computed(() => false);
|
||||
useDatabases.getState().addDatabases([database]);
|
||||
useSelectedNode.getState().setSelectedNode(database);
|
||||
wrapper.setProps({});
|
||||
wrapper.setProps({ explorer: fakeExplorer });
|
||||
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(true);
|
||||
|
||||
database.isDatabaseShared = ko.computed(() => true);
|
||||
wrapper.setProps({});
|
||||
wrapper.setProps({ explorer: fakeExplorer });
|
||||
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -73,6 +77,8 @@ describe("Delete Collection Confirmation Pane", () => {
|
||||
describe("submit()", () => {
|
||||
const selectedCollectionId = "testCol";
|
||||
const databaseId = "testDatabase";
|
||||
const fakeExplorer = {} as Explorer;
|
||||
fakeExplorer.refreshAllDatabases = () => undefined;
|
||||
const database = { id: ko.observable(databaseId) } as Database;
|
||||
const collection = {
|
||||
id: ko.observable(selectedCollectionId),
|
||||
@@ -109,7 +115,7 @@ describe("Delete Collection Confirmation Pane", () => {
|
||||
});
|
||||
|
||||
it("should call delete collection", () => {
|
||||
const wrapper = mount(<DeleteCollectionConfirmationPane refreshDatabases={() => undefined} />);
|
||||
const wrapper = mount(<DeleteCollectionConfirmationPane explorer={fakeExplorer} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
expect(wrapper.exists("#confirmCollectionId")).toBe(true);
|
||||
@@ -126,7 +132,7 @@ describe("Delete Collection Confirmation Pane", () => {
|
||||
});
|
||||
|
||||
it("should record feedback", async () => {
|
||||
const wrapper = mount(<DeleteCollectionConfirmationPane refreshDatabases={() => undefined} />);
|
||||
const wrapper = mount(<DeleteCollectionConfirmationPane explorer={fakeExplorer} />);
|
||||
expect(wrapper.exists("#confirmCollectionId")).toBe(true);
|
||||
wrapper
|
||||
.find("#confirmCollectionId")
|
||||
|
||||
@@ -6,23 +6,23 @@ 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 Explorer from "../../Explorer";
|
||||
import { useDatabases } from "../../useDatabases";
|
||||
import { useSelectedNode } from "../../useSelectedNode";
|
||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||
|
||||
export interface DeleteCollectionConfirmationPaneProps {
|
||||
refreshDatabases: () => Promise<void>;
|
||||
explorer: Explorer;
|
||||
}
|
||||
|
||||
export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectionConfirmationPaneProps> = ({
|
||||
refreshDatabases,
|
||||
explorer,
|
||||
}: DeleteCollectionConfirmationPaneProps) => {
|
||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||
const [deleteCollectionFeedback, setDeleteCollectionFeedback] = useState<string>("");
|
||||
@@ -31,7 +31,8 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
||||
const [isExecuting, setIsExecuting] = useState(false);
|
||||
|
||||
const shouldRecordFeedback = (): boolean =>
|
||||
useDatabases.getState().isLastCollection() && !useDatabases.getState().findSelectedDatabase()?.isDatabaseShared();
|
||||
useDatabases.getState().isLastCollection() &&
|
||||
!useSelectedNode.getState().findSelectedDatabase()?.isDatabaseShared();
|
||||
|
||||
const collectionName = getCollectionName().toLocaleLowerCase();
|
||||
const paneTitle = "Delete " + collectionName;
|
||||
@@ -62,12 +63,10 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
||||
|
||||
setIsExecuting(false);
|
||||
useSelectedNode.getState().setSelectedNode(collection.database);
|
||||
useTabs
|
||||
.getState()
|
||||
.closeTabsByComparator(
|
||||
(tab) => tab.node?.id() === collection.id() && (tab.node as Collection).databaseId === collection.databaseId
|
||||
);
|
||||
refreshDatabases();
|
||||
explorer.tabsManager?.closeTabsByComparator(
|
||||
(tab) => tab.node?.id() === collection.id() && (tab.node as Collection).databaseId === collection.databaseId
|
||||
);
|
||||
explorer.refreshAllDatabases();
|
||||
|
||||
TelemetryProcessor.traceSuccess(Action.DeleteCollection, paneInfo, startKey);
|
||||
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
|
||||
exports[`Delete Collection Confirmation Pane submit() should call delete collection 1`] = `
|
||||
<DeleteCollectionConfirmationPane
|
||||
refreshDatabases={[Function]}
|
||||
explorer={
|
||||
Object {
|
||||
"refreshAllDatabases": [Function],
|
||||
}
|
||||
}
|
||||
>
|
||||
<RightPaneForm
|
||||
formError=""
|
||||
|
||||
@@ -10,12 +10,15 @@ import { Collection, Database } from "../../Contracts/ViewModels";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { updateUserContext } from "../../UserContext";
|
||||
import Explorer from "../Explorer";
|
||||
import { TabsManager } from "../Tabs/TabsManager";
|
||||
import { useDatabases } from "../useDatabases";
|
||||
import { useSelectedNode } from "../useSelectedNode";
|
||||
import { DeleteDatabaseConfirmationPanel } from "./DeleteDatabaseConfirmationPanel";
|
||||
|
||||
describe("Delete Database Confirmation Pane", () => {
|
||||
const selectedDatabaseId = "testDatabase";
|
||||
let fakeExplorer: Explorer;
|
||||
let database: Database;
|
||||
|
||||
beforeAll(() => {
|
||||
@@ -34,6 +37,10 @@ describe("Delete Database Confirmation Pane", () => {
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fakeExplorer = {} as Explorer;
|
||||
fakeExplorer.refreshAllDatabases = () => undefined;
|
||||
fakeExplorer.tabsManager = new TabsManager();
|
||||
|
||||
database = {} as Database;
|
||||
database.collections = ko.observableArray<Collection>([{ id: ko.observable("testCollection") } as Collection]);
|
||||
database.id = ko.observable<string>(selectedDatabaseId);
|
||||
@@ -49,17 +56,17 @@ describe("Delete Database Confirmation Pane", () => {
|
||||
});
|
||||
|
||||
it("shouldRecordFeedback() should return true if last non empty database or is last database that has shared throughput", () => {
|
||||
const wrapper = shallow(<DeleteDatabaseConfirmationPanel refreshDatabases={() => undefined} />);
|
||||
const wrapper = shallow(<DeleteDatabaseConfirmationPanel explorer={fakeExplorer} />);
|
||||
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(true);
|
||||
|
||||
useDatabases.getState().addDatabases([database]);
|
||||
wrapper.setProps({});
|
||||
wrapper.setProps({ explorer: fakeExplorer });
|
||||
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(false);
|
||||
useDatabases.getState().clearDatabases();
|
||||
});
|
||||
|
||||
it("Should call delete database", () => {
|
||||
const wrapper = mount(<DeleteDatabaseConfirmationPanel refreshDatabases={() => undefined} />);
|
||||
const wrapper = mount(<DeleteDatabaseConfirmationPanel explorer={fakeExplorer} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
|
||||
|
||||
@@ -74,7 +81,7 @@ describe("Delete Database Confirmation Pane", () => {
|
||||
});
|
||||
|
||||
it("should record feedback", async () => {
|
||||
const wrapper = mount(<DeleteDatabaseConfirmationPanel refreshDatabases={() => undefined} />);
|
||||
const wrapper = mount(<DeleteDatabaseConfirmationPanel explorer={fakeExplorer} />);
|
||||
expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
|
||||
wrapper
|
||||
.find("#confirmDatabaseId")
|
||||
|
||||
@@ -7,23 +7,23 @@ 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 Explorer from "../Explorer";
|
||||
import { useDatabases } from "../useDatabases";
|
||||
import { useSelectedNode } from "../useSelectedNode";
|
||||
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent";
|
||||
import { RightPaneForm, RightPaneFormProps } from "./RightPaneForm/RightPaneForm";
|
||||
|
||||
interface DeleteDatabaseConfirmationPanelProps {
|
||||
refreshDatabases: () => Promise<void>;
|
||||
explorer: Explorer;
|
||||
}
|
||||
|
||||
export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseConfirmationPanelProps> = ({
|
||||
refreshDatabases,
|
||||
explorer,
|
||||
}: DeleteDatabaseConfirmationPanelProps): JSX.Element => {
|
||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||
const isLastNonEmptyDatabase = useDatabases((state) => state.isLastNonEmptyDatabase);
|
||||
@@ -32,7 +32,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
||||
const [formError, setFormError] = useState<string>("");
|
||||
const [databaseInput, setDatabaseInput] = useState<string>("");
|
||||
const [databaseFeedbackInput, setDatabaseFeedbackInput] = useState<string>("");
|
||||
const selectedDatabase: Database = useDatabases.getState().findSelectedDatabase();
|
||||
const selectedDatabase: Database = useSelectedNode.getState().findSelectedDatabase();
|
||||
|
||||
const submit = async (): Promise<void> => {
|
||||
if (selectedDatabase?.id() && databaseInput !== selectedDatabase.id()) {
|
||||
@@ -52,18 +52,15 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
||||
try {
|
||||
await deleteDatabase(selectedDatabase.id());
|
||||
closeSidePanel();
|
||||
refreshDatabases();
|
||||
useTabs.getState().closeTabsByComparator((tab) => tab.node?.id() === selectedDatabase.id());
|
||||
explorer.refreshAllDatabases();
|
||||
explorer.tabsManager.closeTabsByComparator((tab) => tab.node?.id() === selectedDatabase.id());
|
||||
useSelectedNode.getState().setSelectedNode(undefined);
|
||||
selectedDatabase
|
||||
.collections()
|
||||
.forEach((collection: Collection) =>
|
||||
useTabs
|
||||
.getState()
|
||||
.closeTabsByComparator(
|
||||
(tab) =>
|
||||
tab.node?.id() === collection.id() && (tab.node as Collection).databaseId === collection.databaseId
|
||||
)
|
||||
explorer.tabsManager.closeTabsByComparator(
|
||||
(tab) => tab.node?.id() === collection.id() && (tab.node as Collection).databaseId === collection.databaseId
|
||||
)
|
||||
);
|
||||
TelemetryProcessor.traceSuccess(
|
||||
Action.DeleteDatabase,
|
||||
|
||||
@@ -120,7 +120,6 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
|
||||
handleError(error, "GitHubReposPane/submit", "Failed to save pinned repos");
|
||||
}
|
||||
}
|
||||
useSidePanel.getState().closeSidePanel();
|
||||
}
|
||||
|
||||
public resetData(): void {
|
||||
@@ -145,18 +144,11 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
|
||||
|
||||
private setup(forceShowConnectToGitHub = false): void {
|
||||
forceShowConnectToGitHub || !this.props.explorer.notebookManager?.gitHubOAuthService.isLoggedIn()
|
||||
? this.setupForConnectToGitHub(forceShowConnectToGitHub)
|
||||
? this.setupForConnectToGitHub()
|
||||
: this.setupForManageRepos();
|
||||
}
|
||||
|
||||
private setupForConnectToGitHub(forceShowConnectToGitHub: boolean): void {
|
||||
if (forceShowConnectToGitHub) {
|
||||
const newState = { ...this.state.gitHubReposState };
|
||||
newState.showAuthorizeAccess = forceShowConnectToGitHub;
|
||||
this.setState({
|
||||
gitHubReposState: newState,
|
||||
});
|
||||
}
|
||||
private setupForConnectToGitHub(): void {
|
||||
this.setState({
|
||||
isExecuting: false,
|
||||
});
|
||||
@@ -376,28 +368,46 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
|
||||
isLoading: true,
|
||||
loadMore: (): Promise<void> => this.loadMoreBranches(item.repo),
|
||||
};
|
||||
this.setState({
|
||||
gitHubReposState: {
|
||||
...this.state.gitHubReposState,
|
||||
reposListProps: {
|
||||
...this.state.gitHubReposState.reposListProps,
|
||||
branchesProps: {
|
||||
...this.state.gitHubReposState.reposListProps.branchesProps,
|
||||
[GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)]: this.branchesProps[item.key],
|
||||
},
|
||||
pinnedReposProps: {
|
||||
repos: this.pinnedReposProps.repos,
|
||||
},
|
||||
unpinnedReposProps: {
|
||||
...this.state.gitHubReposState.reposListProps.unpinnedReposProps,
|
||||
repos: this.unpinnedReposProps.repos,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
this.loadMoreBranches(item.repo);
|
||||
} else {
|
||||
if (this.isAddedRepo === false) {
|
||||
this.setState({
|
||||
gitHubReposState: {
|
||||
...this.state.gitHubReposState,
|
||||
reposListProps: {
|
||||
...this.state.gitHubReposState.reposListProps,
|
||||
pinnedReposProps: {
|
||||
repos: this.pinnedReposProps.repos,
|
||||
},
|
||||
unpinnedReposProps: {
|
||||
...this.state.gitHubReposState.reposListProps.unpinnedReposProps,
|
||||
repos: this.unpinnedReposProps.repos,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
gitHubReposState: {
|
||||
...this.state.gitHubReposState,
|
||||
reposListProps: {
|
||||
...this.state.gitHubReposState.reposListProps,
|
||||
branchesProps: {
|
||||
...this.branchesProps,
|
||||
},
|
||||
pinnedReposProps: {
|
||||
repos: this.pinnedReposProps.repos,
|
||||
},
|
||||
unpinnedReposProps: {
|
||||
...this.state.gitHubReposState.reposListProps.unpinnedReposProps,
|
||||
repos: this.unpinnedReposProps.repos,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
this.isAddedRepo = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,17 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
|
||||
"container": Explorer {
|
||||
"_isInitializingNotebooks": false,
|
||||
"_resetNotebookWorkspace": [Function],
|
||||
"isAccountReady": [Function],
|
||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||
"isNotebookEnabled": [Function],
|
||||
"isNotebooksEnabledForAccount": [Function],
|
||||
"isSchemaEnabled": [Function],
|
||||
"isShellEnabled": [Function],
|
||||
"isSynapseLinkUpdating": [Function],
|
||||
"isTabsContentExpanded": [Function],
|
||||
"memoryUsageInfo": [Function],
|
||||
"notebookBasePath": [Function],
|
||||
"notebookServerInfo": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"provideFeedbackEmail": [Function],
|
||||
@@ -28,24 +37,21 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
|
||||
"container": [Circular],
|
||||
},
|
||||
"refreshNotebookList": [Function],
|
||||
"resourceTokenCollection": [Function],
|
||||
"resourceTree": ResourceTreeAdapter {
|
||||
"container": [Circular],
|
||||
"copyNotebook": [Function],
|
||||
"gitHubOAuthService": GitHubOAuthService {
|
||||
"junoClient": JunoClient {
|
||||
"cachedPinnedRepos": [Function],
|
||||
},
|
||||
"token": [Function],
|
||||
},
|
||||
"junoClient": JunoClient {
|
||||
"cachedPinnedRepos": [Function],
|
||||
},
|
||||
"parameters": [Function],
|
||||
},
|
||||
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
|
||||
"container": [Circular],
|
||||
"parameters": [Function],
|
||||
},
|
||||
"sparkClusterConnectionInfo": [Function],
|
||||
"tabsManager": TabsManager {
|
||||
"activeTab": [Function],
|
||||
"openedTabs": [Function],
|
||||
},
|
||||
},
|
||||
"getRepo": [Function],
|
||||
"pinRepo": [Function],
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
padding: 0 34px;
|
||||
margin: 20px 0;
|
||||
overflow: auto;
|
||||
min-height: 30px;
|
||||
|
||||
& > :not(.collapsibleSection) {
|
||||
margin-bottom: @DefaultSpace;
|
||||
|
||||
@@ -4,8 +4,8 @@ import { useNotificationConsole } from "../../hooks/useNotificationConsole";
|
||||
import { useSidePanel } from "../../hooks/useSidePanel";
|
||||
|
||||
export interface PanelContainerProps {
|
||||
headerText?: string;
|
||||
panelContent?: JSX.Element;
|
||||
headerText: string;
|
||||
panelContent: JSX.Element;
|
||||
isConsoleExpanded: boolean;
|
||||
isOpen: boolean;
|
||||
panelWidth?: string;
|
||||
@@ -66,8 +66,8 @@ export class PanelContainerComponent extends React.Component<PanelContainerProps
|
||||
);
|
||||
}
|
||||
|
||||
private onDissmiss = (ev?: KeyboardEvent | React.SyntheticEvent<HTMLElement>): void => {
|
||||
if (ev && (ev.target as HTMLElement).id === "notificationConsoleHeader") {
|
||||
private onDissmiss = (ev?: React.SyntheticEvent<HTMLElement>): void => {
|
||||
if ((ev.target as HTMLElement).id === "notificationConsoleHeader") {
|
||||
ev.preventDefault();
|
||||
} else {
|
||||
useSidePanel.getState().closeSidePanel();
|
||||
|
||||
@@ -5,7 +5,6 @@ import { Areas, SavedQueries } from "../../../Common/Constants";
|
||||
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||
import { Query } from "../../../Contracts/DataModels";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import { useTabs } from "../../../hooks/useTabs";
|
||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
||||
@@ -35,7 +34,7 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({ explorer
|
||||
logConsoleError("Failed to save query: account not setup to save queries");
|
||||
}
|
||||
|
||||
const queryTab = useTabs.getState().activeTab as NewQueryTab;
|
||||
const queryTab = explorer && (explorer.tabsManager.activeTab() as NewQueryTab);
|
||||
const query: string = queryTab && queryTab.iTabAccessor.onSaveClickEvent();
|
||||
|
||||
if (!queryName || queryName.length === 0) {
|
||||
|
||||
@@ -63,7 +63,7 @@ export const SetupNoteBooksPanel: FunctionComponent<SetupNoteBooksPanelProps> =
|
||||
userContext.databaseAccount.name,
|
||||
"default"
|
||||
);
|
||||
explorer.refreshExplorer();
|
||||
explorer.isAccountReady.valueHasMutated(); // re-trigger init notebooks
|
||||
|
||||
closeSidePanel();
|
||||
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { TextField } from "@fluentui/react";
|
||||
import React, { FormEvent, FunctionComponent, useState } from "react";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { useTabs } from "../../../hooks/useTabs";
|
||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
|
||||
import Explorer from "../../Explorer";
|
||||
import * as FileSystemUtil from "../../Notebook/FileSystemUtil";
|
||||
import { NotebookContentItem } from "../../Notebook/NotebookContentItem";
|
||||
import NotebookV2Tab from "../../Tabs/NotebookV2Tab";
|
||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||
|
||||
export interface StringInputPanelProps {
|
||||
explorer: Explorer;
|
||||
closePanel: () => void;
|
||||
errorMessage: string;
|
||||
inProgressMessage: string;
|
||||
@@ -22,6 +23,7 @@ export interface StringInputPanelProps {
|
||||
}
|
||||
|
||||
export const StringInputPane: FunctionComponent<StringInputPanelProps> = ({
|
||||
explorer: container,
|
||||
closePanel,
|
||||
errorMessage,
|
||||
inProgressMessage,
|
||||
@@ -53,12 +55,10 @@ export const StringInputPane: FunctionComponent<StringInputPanelProps> = ({
|
||||
logConsoleInfo(`${successMessage}: ${stringInput}`);
|
||||
const originalPath = notebookFile.path;
|
||||
|
||||
const notebookTabs = useTabs
|
||||
.getState()
|
||||
.getTabs(
|
||||
ViewModels.CollectionTabKind.NotebookV2,
|
||||
(tab: NotebookV2Tab) => tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), originalPath)
|
||||
);
|
||||
const notebookTabs = container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.NotebookV2,
|
||||
(tab: NotebookV2Tab) => tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), originalPath)
|
||||
);
|
||||
notebookTabs.forEach((tab) => {
|
||||
tab.tabTitle(newNotebookFile.name);
|
||||
tab.tabPath(newNotebookFile.path);
|
||||
|
||||
@@ -9,8 +9,17 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
||||
Explorer {
|
||||
"_isInitializingNotebooks": false,
|
||||
"_resetNotebookWorkspace": [Function],
|
||||
"isAccountReady": [Function],
|
||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||
"isNotebookEnabled": [Function],
|
||||
"isNotebooksEnabledForAccount": [Function],
|
||||
"isSchemaEnabled": [Function],
|
||||
"isShellEnabled": [Function],
|
||||
"isSynapseLinkUpdating": [Function],
|
||||
"isTabsContentExpanded": [Function],
|
||||
"memoryUsageInfo": [Function],
|
||||
"notebookBasePath": [Function],
|
||||
"notebookServerInfo": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"provideFeedbackEmail": [Function],
|
||||
@@ -18,24 +27,21 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
||||
"container": [Circular],
|
||||
},
|
||||
"refreshNotebookList": [Function],
|
||||
"resourceTokenCollection": [Function],
|
||||
"resourceTree": ResourceTreeAdapter {
|
||||
"container": [Circular],
|
||||
"copyNotebook": [Function],
|
||||
"gitHubOAuthService": GitHubOAuthService {
|
||||
"junoClient": JunoClient {
|
||||
"cachedPinnedRepos": [Function],
|
||||
},
|
||||
"token": [Function],
|
||||
},
|
||||
"junoClient": JunoClient {
|
||||
"cachedPinnedRepos": [Function],
|
||||
},
|
||||
"parameters": [Function],
|
||||
},
|
||||
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
|
||||
"container": [Circular],
|
||||
"parameters": [Function],
|
||||
},
|
||||
"sparkClusterConnectionInfo": [Function],
|
||||
"tabsManager": TabsManager {
|
||||
"activeTab": [Function],
|
||||
"openedTabs": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
inProgressMessage="Creating directory "
|
||||
|
||||
@@ -2,7 +2,15 @@
|
||||
|
||||
exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
||||
<DeleteDatabaseConfirmationPanel
|
||||
refreshDatabases={[Function]}
|
||||
explorer={
|
||||
Object {
|
||||
"refreshAllDatabases": [Function],
|
||||
"tabsManager": TabsManager {
|
||||
"activeTab": [Function],
|
||||
"openedTabs": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<RightPaneForm
|
||||
formError=""
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import * as ko from "knockout";
|
||||
import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil";
|
||||
import Explorer from "../Explorer";
|
||||
import { TabsManager } from "../Tabs/TabsManager";
|
||||
import { SplashScreen } from "./SplashScreen";
|
||||
jest.mock("../Explorer");
|
||||
|
||||
const createExplorer = () => {
|
||||
const mock = new Explorer();
|
||||
mock.isNotebookEnabled = ko.observable(false);
|
||||
mock.tabsManager = new TabsManager();
|
||||
return mock as jest.Mocked<Explorer>;
|
||||
};
|
||||
|
||||
|
||||
@@ -16,16 +16,12 @@ import CollectionIcon from "../../../images/tree-collection.svg";
|
||||
import { AuthType } from "../../AuthType";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { useSidePanel } from "../../hooks/useSidePanel";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { getCollectionName, getDatabaseName } from "../../Utils/APITypeUtils";
|
||||
import { FeaturePanelLauncher } from "../Controls/FeaturePanel/FeaturePanelLauncher";
|
||||
import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil";
|
||||
import Explorer from "../Explorer";
|
||||
import * as MostRecentActivity from "../MostRecentActivity/MostRecentActivity";
|
||||
import { useNotebook } from "../Notebook/useNotebook";
|
||||
import { AddDatabasePanel } from "../Panes/AddDatabasePanel/AddDatabasePanel";
|
||||
import { BrowseQueriesPane } from "../Panes/BrowseQueriesPane/BrowseQueriesPane";
|
||||
import { useDatabases } from "../useDatabases";
|
||||
import { useSelectedNode } from "../useSelectedNode";
|
||||
|
||||
@@ -65,13 +61,8 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
|
||||
public componentDidMount() {
|
||||
this.subscriptions.push(
|
||||
{
|
||||
dispose: useNotebook.subscribe(
|
||||
() => this.setState({}),
|
||||
(state) => state.isNotebookEnabled
|
||||
),
|
||||
},
|
||||
{ dispose: useSelectedNode.subscribe(() => this.setState({})) }
|
||||
{ dispose: useSelectedNode.subscribe(() => this.setState({})) },
|
||||
this.container.isNotebookEnabled.subscribe(() => this.setState({}))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -176,7 +167,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
</li>
|
||||
))}
|
||||
<li>
|
||||
<a role="link" href={SplashScreen.seeMoreItemUrl} rel="noreferrer" target="_blank" tabIndex={0}>
|
||||
<a role="link" href={SplashScreen.seeMoreItemUrl} target="_blank" tabIndex={0}>
|
||||
{SplashScreen.seeMoreItemTitle}
|
||||
</a>
|
||||
</li>
|
||||
@@ -219,7 +210,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
});
|
||||
}
|
||||
|
||||
if (useNotebook.getState().isNotebookEnabled) {
|
||||
if (this.container.isNotebookEnabled()) {
|
||||
heroes.push({
|
||||
iconSrc: NewNotebookIcon,
|
||||
title: "New Notebook",
|
||||
@@ -244,20 +235,20 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
iconSrc: NewQueryIcon,
|
||||
onClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, undefined);
|
||||
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null);
|
||||
},
|
||||
title: "New SQL Query",
|
||||
description: undefined,
|
||||
description: null,
|
||||
});
|
||||
} else if (userContext.apiType === "Mongo") {
|
||||
items.push({
|
||||
iconSrc: NewQueryIcon,
|
||||
onClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||
selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, undefined);
|
||||
selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, null);
|
||||
},
|
||||
title: "New Query",
|
||||
description: undefined,
|
||||
description: null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -265,11 +256,8 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
items.push({
|
||||
iconSrc: OpenQueryIcon,
|
||||
title: "Open Query",
|
||||
description: undefined,
|
||||
onClick: () =>
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={this.container} />),
|
||||
description: null,
|
||||
onClick: () => this.container.openBrowseQueriesPanel(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -277,22 +265,22 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
items.push({
|
||||
iconSrc: NewStoredProcedureIcon,
|
||||
title: "New Stored Procedure",
|
||||
description: undefined,
|
||||
description: null,
|
||||
onClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, undefined);
|
||||
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, null);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/* Scale & Settings */
|
||||
const isShared = useDatabases.getState().findSelectedDatabase()?.isDatabaseShared();
|
||||
const isShared = useSelectedNode.getState().findSelectedDatabase()?.isDatabaseShared();
|
||||
|
||||
const label = isShared ? "Settings" : "Scale & Settings";
|
||||
items.push({
|
||||
iconSrc: ScaleAndSettingsIcon,
|
||||
title: label,
|
||||
description: undefined,
|
||||
description: null,
|
||||
onClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||
selectedCollection && selectedCollection.onSettingsClick();
|
||||
@@ -302,11 +290,8 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
items.push({
|
||||
iconSrc: AddDatabaseIcon,
|
||||
title: "New " + getDatabaseName(),
|
||||
description: undefined,
|
||||
onClick: () =>
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={this.container} />),
|
||||
description: null,
|
||||
onClick: () => this.container.openAddDatabasePane(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -357,19 +342,19 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
private createTipsItems(): SplashScreenItem[] {
|
||||
return [
|
||||
{
|
||||
iconSrc: undefined,
|
||||
iconSrc: null,
|
||||
title: "Data Modeling",
|
||||
description: "Learn more about modeling",
|
||||
onClick: () => window.open(SplashScreen.dataModelingUrl),
|
||||
},
|
||||
{
|
||||
iconSrc: undefined,
|
||||
iconSrc: null,
|
||||
title: "Cost & Throughput Calculation",
|
||||
description: "Learn more about cost calculation",
|
||||
onClick: () => window.open(SplashScreen.throughputEstimatorUrl),
|
||||
},
|
||||
{
|
||||
iconSrc: undefined,
|
||||
iconSrc: null,
|
||||
title: "Configure automatic failover",
|
||||
description: "Learn more about Cosmos DB high-availability",
|
||||
onClick: () => window.open(SplashScreen.failoverUrl),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const TableType = {
|
||||
export var TableType = {
|
||||
String: "String",
|
||||
Boolean: "Boolean",
|
||||
Binary: "Binary",
|
||||
@@ -9,7 +9,7 @@ export const TableType = {
|
||||
Int64: "Int64",
|
||||
};
|
||||
|
||||
export const CassandraType = {
|
||||
export var CassandraType = {
|
||||
Ascii: "Ascii",
|
||||
Bigint: "Bigint",
|
||||
Blob: "Blob",
|
||||
@@ -27,12 +27,12 @@ export const CassandraType = {
|
||||
Tinyint: "Tinyint",
|
||||
};
|
||||
|
||||
export const ClauseRule = {
|
||||
export var ClauseRule = {
|
||||
And: "And",
|
||||
Or: "Or",
|
||||
};
|
||||
|
||||
export const Operator = {
|
||||
export var Operator = {
|
||||
EqualTo: "==",
|
||||
GreaterThan: ">",
|
||||
GreaterThanOrEqualTo: ">=",
|
||||
@@ -42,7 +42,7 @@ export const Operator = {
|
||||
Equal: "=",
|
||||
};
|
||||
|
||||
export const ODataOperator = {
|
||||
export var ODataOperator = {
|
||||
EqualTo: "eq",
|
||||
GreaterThan: "gt",
|
||||
GreaterThanOrEqualTo: "ge",
|
||||
@@ -51,7 +51,7 @@ export const ODataOperator = {
|
||||
NotEqualTo: "ne",
|
||||
};
|
||||
|
||||
export const timeOptions = {
|
||||
export var timeOptions = {
|
||||
lastHour: "Last hour",
|
||||
last24Hours: "Last 24 hours",
|
||||
last7Days: "Last 7 days",
|
||||
@@ -62,7 +62,7 @@ export const timeOptions = {
|
||||
custom: "Custom...",
|
||||
};
|
||||
|
||||
export const htmlSelectors = {
|
||||
export var htmlSelectors = {
|
||||
dataTableSelector: "#storageTable",
|
||||
dataTableAllRowsSelector: "#storageTable tbody tr",
|
||||
dataTableHeadRowSelector: ".dataTable thead tr",
|
||||
@@ -84,9 +84,9 @@ export const htmlSelectors = {
|
||||
selectAllDropdownSelector: "#select-all-dropdown",
|
||||
};
|
||||
|
||||
export const defaultHeader = " ";
|
||||
export var defaultHeader = " ";
|
||||
|
||||
export const EntityKeyNames = {
|
||||
export var EntityKeyNames = {
|
||||
PartitionKey: "PartitionKey",
|
||||
RowKey: "RowKey",
|
||||
Timestamp: "Timestamp",
|
||||
@@ -94,7 +94,7 @@ export const EntityKeyNames = {
|
||||
Etag: "etag",
|
||||
};
|
||||
|
||||
export const htmlAttributeNames = {
|
||||
export var htmlAttributeNames = {
|
||||
dataTableNameAttr: "name_attr",
|
||||
dataTableContentTypeAttr: "contentType_attr",
|
||||
dataTableSnapshotAttr: "snapshot_attr",
|
||||
@@ -103,14 +103,14 @@ export const htmlAttributeNames = {
|
||||
dataTableHeaderIndex: "data-column-index",
|
||||
};
|
||||
|
||||
export const cssColors = {
|
||||
export var cssColors = {
|
||||
commonControlsButtonActive: "#B4C7DC" /* A darker shade of [{common-controls-button-hover-background}] */,
|
||||
};
|
||||
|
||||
export const clauseGroupColors = ["#ffe1ff", "#fffacd", "#f0ffff", "#ffefd5", "#f0fff0"];
|
||||
export const transparentColor = "transparent";
|
||||
export var clauseGroupColors = ["#ffe1ff", "#fffacd", "#f0ffff", "#ffefd5", "#f0fff0"];
|
||||
export var transparentColor = "transparent";
|
||||
|
||||
export const keyCodes = {
|
||||
export var keyCodes = {
|
||||
RightClick: 3,
|
||||
Enter: 13,
|
||||
Esc: 27,
|
||||
@@ -163,7 +163,7 @@ export const keyCodes = {
|
||||
Dash: 189,
|
||||
};
|
||||
|
||||
export const InputType = {
|
||||
export var InputType = {
|
||||
Text: "text",
|
||||
// Chrome doesn't support datetime, instead, datetime-local is supported.
|
||||
DateTime: "datetime-local",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// Added return type optional undefined because passing undefined from test cases.
|
||||
export function getQuotedCqlIdentifier(identifier: string | undefined): string | undefined {
|
||||
export function getQuotedCqlIdentifier(identifier: string): string {
|
||||
let result = identifier;
|
||||
if (!identifier) {
|
||||
return result;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from "react";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import type { TabOptions } from "../../../Contracts/ViewModels";
|
||||
import { useTabs } from "../../../hooks/useTabs";
|
||||
import Explorer from "../../Explorer";
|
||||
import TabsBase from "../TabsBase";
|
||||
import MongoShellTabComponent, { IMongoShellTabAccessor, IMongoShellTabComponentProps } from "./MongoShellTabComponent";
|
||||
@@ -34,7 +33,7 @@ export class NewMongoShellTab extends TabsBase {
|
||||
}
|
||||
|
||||
public onTabClick(): void {
|
||||
useTabs.getState().activateTab(this);
|
||||
this.manager?.activateTab(this);
|
||||
this.iMongoShellTabAccessor.onTabClickEvent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "../../UserContext";
|
||||
import Explorer from "../Explorer";
|
||||
import { NotebookClientV2 } from "../Notebook/NotebookClientV2";
|
||||
import { useNotebook } from "../Notebook/useNotebook";
|
||||
import TabsBase from "./TabsBase";
|
||||
|
||||
export interface NotebookTabBaseOptions extends ViewModels.TabOptions {
|
||||
@@ -29,7 +28,7 @@ export default class NotebookTabBase extends TabsBase {
|
||||
|
||||
if (!NotebookTabBase.clientManager) {
|
||||
NotebookTabBase.clientManager = new NotebookClientV2({
|
||||
connectionInfo: useNotebook.getState().notebookServerInfo,
|
||||
connectionInfo: this.container.notebookServerInfo(),
|
||||
databaseAccountName: userContext?.databaseAccount?.name,
|
||||
defaultExperience: userContext.apiType,
|
||||
contentProvider: this.container.notebookManager?.notebookContentProvider,
|
||||
|
||||
@@ -23,7 +23,6 @@ import * as CdbActions from "../Notebook/NotebookComponent/actions";
|
||||
import { NotebookComponentAdapter } from "../Notebook/NotebookComponent/NotebookComponentAdapter";
|
||||
import { CdbAppState, SnapshotRequest } from "../Notebook/NotebookComponent/types";
|
||||
import { NotebookContentItem } from "../Notebook/NotebookContentItem";
|
||||
import { useNotebook } from "../Notebook/useNotebook";
|
||||
import NotebookTabBase, { NotebookTabBaseOptions } from "./NotebookTabBase";
|
||||
|
||||
export interface NotebookTabOptions extends NotebookTabBaseOptions {
|
||||
@@ -40,13 +39,10 @@ export default class NotebookTabV2 extends NotebookTabBase {
|
||||
|
||||
this.container = options.container;
|
||||
this.notebookPath = ko.observable(options.notebookContentItem.path);
|
||||
useNotebook.subscribe(
|
||||
() => logConsoleInfo("New notebook server info received."),
|
||||
(state) => state.notebookServerInfo
|
||||
);
|
||||
this.container.notebookServerInfo.subscribe(() => logConsoleInfo("New notebook server info received."));
|
||||
this.notebookComponentAdapter = new NotebookComponentAdapter({
|
||||
contentItem: options.notebookContentItem,
|
||||
notebooksBasePath: useNotebook.getState().notebookBasePath,
|
||||
notebooksBasePath: this.container.getNotebookBasePath(),
|
||||
notebookClient: NotebookTabBase.clientManager,
|
||||
onUpdateKernelInfo: this.onKernelUpdate,
|
||||
});
|
||||
@@ -363,8 +359,8 @@ export default class NotebookTabV2 extends NotebookTabBase {
|
||||
};
|
||||
|
||||
private async configureServiceEndpoints(kernelName: string) {
|
||||
const notebookConnectionInfo = useNotebook.getState().notebookServerInfo;
|
||||
const sparkClusterConnectionInfo = useNotebook.getState().sparkClusterConnectionInfo;
|
||||
const notebookConnectionInfo = this.container && this.container.notebookServerInfo();
|
||||
const sparkClusterConnectionInfo = this.container && this.container.sparkClusterConnectionInfo();
|
||||
await NotebookConfigurationUtils.configureServiceEndpoints(
|
||||
this.notebookPath(),
|
||||
notebookConnectionInfo,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from "react";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import type { QueryTabOptions } from "../../../Contracts/ViewModels";
|
||||
import { useTabs } from "../../../hooks/useTabs";
|
||||
import Explorer from "../../Explorer";
|
||||
import { IQueryTabComponentProps, ITabAccessor } from "../../Tabs/QueryTab/QueryTabComponent";
|
||||
import TabsBase from "../TabsBase";
|
||||
@@ -41,12 +40,12 @@ export class NewQueryTab extends TabsBase {
|
||||
}
|
||||
|
||||
public onTabClick(): void {
|
||||
useTabs.getState().activateTab(this);
|
||||
this.manager?.activateTab(this);
|
||||
this.iTabAccessor.onTabClickEvent();
|
||||
}
|
||||
|
||||
public onCloseTabButtonClick(): void {
|
||||
useTabs.getState().closeTab(this);
|
||||
this.manager?.closeTab(this);
|
||||
if (this.iTabAccessor) {
|
||||
this.iTabAccessor.onCloseClickEvent(true);
|
||||
}
|
||||
|
||||
@@ -22,16 +22,14 @@ import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { useNotificationConsole } from "../../../hooks/useNotificationConsole";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import * as QueryUtils from "../../../Utils/QueryUtils";
|
||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
import { EditorReact } from "../../Controls/Editor/EditorReact";
|
||||
import Explorer from "../../Explorer";
|
||||
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
|
||||
import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPane";
|
||||
import { SaveQueryPane } from "../../Panes/SaveQueryPane/SaveQueryPane";
|
||||
import TabsBase from "../TabsBase";
|
||||
import { TabsManager } from "../TabsManager";
|
||||
import "./QueryTabComponent.less";
|
||||
|
||||
enum ToggleState {
|
||||
@@ -67,6 +65,7 @@ export interface IQueryTabComponentProps {
|
||||
partitionKey: DataModels.PartitionKey;
|
||||
container: Explorer;
|
||||
activeTab?: TabsBase;
|
||||
tabManager?: TabsManager;
|
||||
onTabAccessor: (instance: ITabAccessor) => void;
|
||||
isPreferredApiMongoDB?: boolean;
|
||||
monacoEditorSetting?: string;
|
||||
@@ -392,13 +391,13 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
||||
};
|
||||
|
||||
public onSaveQueryClick = (): void => {
|
||||
useSidePanel.getState().openSidePanel("Save Query", <SaveQueryPane explorer={this.props.collection.container} />);
|
||||
this.props.collection && this.props.collection.container && this.props.collection.container.openSaveQueryPanel();
|
||||
};
|
||||
|
||||
public onSavedQueriesClick = (): void => {
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={this.props.collection.container} />);
|
||||
this.props.collection &&
|
||||
this.props.collection.container &&
|
||||
this.props.collection.container.openBrowseQueriesPanel();
|
||||
};
|
||||
|
||||
public async onFetchNextPageClick(): Promise<void> {
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from "react";
|
||||
import { ExecuteSprocResult } from "../../../Common/dataAccess/executeStoredProcedure";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { useTabs } from "../../../hooks/useTabs";
|
||||
import Explorer from "../../Explorer";
|
||||
import StoredProcedure from "../../Tree/StoredProcedure";
|
||||
import ScriptTabBase from "../ScriptTabBase";
|
||||
@@ -52,12 +51,12 @@ export class NewStoredProcedureTab extends ScriptTabBase {
|
||||
}
|
||||
|
||||
public onTabClick(): void {
|
||||
useTabs.getState().activateTab(this);
|
||||
this.manager?.activateTab(this);
|
||||
this.iStoreProcAccessor.onTabClickEvent();
|
||||
}
|
||||
|
||||
public onCloseTabButtonClick(): void {
|
||||
useTabs.getState().closeTab(this);
|
||||
this.manager?.closeTab(this);
|
||||
}
|
||||
|
||||
public onExecuteSprocsResult(result: ExecuteSprocResult): void {
|
||||
|
||||
@@ -10,7 +10,6 @@ import { ExecuteSprocResult } from "../../../Common/dataAccess/executeStoredProc
|
||||
import { updateStoredProcedure } from "../../../Common/dataAccess/updateStoredProcedure";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { useNotificationConsole } from "../../../hooks/useNotificationConsole";
|
||||
import { useTabs } from "../../../hooks/useTabs";
|
||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
import { EditorReact } from "../../Controls/Editor/EditorReact";
|
||||
import Explorer from "../../Explorer";
|
||||
@@ -145,7 +144,7 @@ export default class StoredProcedureTabComponent extends React.Component<
|
||||
}
|
||||
|
||||
public onTabClick(): void {
|
||||
if (useTabs.getState().openedTabs.length > 0) {
|
||||
if (this.props.container.tabsManager.openedTabs().length > 0) {
|
||||
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||
}
|
||||
}
|
||||
@@ -397,8 +396,10 @@ export default class StoredProcedureTabComponent extends React.Component<
|
||||
editorModel && editorModel.setValue(createdResource.body as string);
|
||||
this.props.scriptTabBaseInstance.editorContent.setBaseline(createdResource.body as string);
|
||||
this.node = this.collection.createStoredProcedureNode(createdResource);
|
||||
this.props.scriptTabBaseInstance.node = this.node;
|
||||
useTabs.getState().updateTab(this.props.scriptTabBaseInstance);
|
||||
this.props.container.tabsManager.openedTabs()[
|
||||
this.props.container.tabsManager.openedTabs().length - 1
|
||||
].node = this.node;
|
||||
|
||||
this.props.scriptTabBaseInstance.editorState(ViewModels.ScriptEditorState.exisitingNoEdits);
|
||||
|
||||
this.setState({
|
||||
|
||||
@@ -3,32 +3,28 @@ import React, { useEffect, useRef, useState } from "react";
|
||||
import loadingIcon from "../../../images/circular_loader_black_16x16.gif";
|
||||
import errorIcon from "../../../images/close-black.svg";
|
||||
import { useObservable } from "../../hooks/useObservable";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import TabsBase from "./TabsBase";
|
||||
|
||||
type Tab = TabsBase | (TabsBase & { render: () => JSX.Element });
|
||||
|
||||
export const Tabs = (): JSX.Element => {
|
||||
const { openedTabs, activeTab } = useTabs();
|
||||
return (
|
||||
<div className="tabsManagerContainer">
|
||||
<div id="content" className="flexContainer hideOverflows">
|
||||
<div className="nav-tabs-margin">
|
||||
<ul className="nav nav-tabs level navTabHeight" id="navTabs" role="tablist">
|
||||
{openedTabs.map((tab) => (
|
||||
<TabNav key={tab.tabId} tab={tab} active={activeTab === tab} />
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="tabPanesContainer">
|
||||
{openedTabs.map((tab) => (
|
||||
<TabPane key={tab.tabId} tab={tab} active={activeTab === tab} />
|
||||
export const Tabs = ({ tabs, activeTab }: { tabs: readonly Tab[]; activeTab: Tab }): JSX.Element => (
|
||||
<div className="tabsManagerContainer">
|
||||
<div id="content" className="flexContainer hideOverflows">
|
||||
<div className="nav-tabs-margin">
|
||||
<ul className="nav nav-tabs level navTabHeight" id="navTabs" role="tablist">
|
||||
{tabs.map((tab) => (
|
||||
<TabNav key={tab.tabId} tab={tab} active={activeTab === tab} />
|
||||
))}
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="tabPanesContainer">
|
||||
{tabs.map((tab) => (
|
||||
<TabPane key={tab.tabId} tab={tab} active={activeTab === tab} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</div>
|
||||
);
|
||||
|
||||
function TabNav({ tab, active }: { tab: Tab; active: boolean }) {
|
||||
const [hovering, setHovering] = useState(false);
|
||||
|
||||
@@ -4,7 +4,6 @@ import * as ThemeUtility from "../../Common/ThemeUtility";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { useNotificationConsole } from "../../hooks/useNotificationConsole";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||
@@ -12,6 +11,7 @@ import Explorer from "../Explorer";
|
||||
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
|
||||
import { useSelectedNode } from "../useSelectedNode";
|
||||
import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel";
|
||||
import { TabsManager } from "./TabsManager";
|
||||
// TODO: Use specific actions for logging telemetry data
|
||||
export default class TabsBase extends WaitsForTemplateViewModel {
|
||||
private static id = 0;
|
||||
@@ -28,6 +28,7 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
||||
public isExecutionError = ko.observable(false);
|
||||
public isExecuting = ko.observable(false);
|
||||
public pendingNotification?: ko.Observable<DataModels.Notification>;
|
||||
public manager?: TabsManager;
|
||||
protected _theme: string;
|
||||
public onLoadStartKey: number;
|
||||
|
||||
@@ -59,7 +60,7 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
||||
}
|
||||
|
||||
public onCloseTabButtonClick(): void {
|
||||
useTabs.getState().closeTab(this);
|
||||
this.manager?.closeTab(this);
|
||||
TelemetryProcessor.trace(Action.Tab, ActionModifiers.Close, {
|
||||
tabName: this.constructor.name,
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
@@ -69,7 +70,7 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
||||
}
|
||||
|
||||
public onTabClick(): void {
|
||||
useTabs.getState().activateTab(this);
|
||||
this.manager?.activateTab(this);
|
||||
}
|
||||
|
||||
protected updateSelectedNode(): void {
|
||||
@@ -104,7 +105,7 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
||||
|
||||
/** @deprecated this is no longer observable, bind to comparisons with manager.activeTab() instead */
|
||||
public isActive() {
|
||||
return this === useTabs.getState().activeTab;
|
||||
return this === this.manager?.activeTab();
|
||||
}
|
||||
|
||||
public onActivate(): void {
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
import * as ko from "knockout";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import { updateUserContext } from "../../UserContext";
|
||||
import { container } from "../Controls/Settings/TestUtils";
|
||||
import Explorer from "../Explorer";
|
||||
import DocumentId from "../Tree/DocumentId";
|
||||
import { container } from "./../Controls/Settings/TestUtils";
|
||||
import DocumentsTab from "./DocumentsTab";
|
||||
import { NewQueryTab } from "./QueryTab/QueryTab";
|
||||
import { TabsManager } from "./TabsManager";
|
||||
|
||||
describe("useTabs tests", () => {
|
||||
describe("Tabs manager tests", () => {
|
||||
let tabsManager: TabsManager;
|
||||
let explorer: Explorer;
|
||||
let database: ViewModels.Database;
|
||||
let collection: ViewModels.Collection;
|
||||
let queryTab: NewQueryTab;
|
||||
let documentsTab: DocumentsTab;
|
||||
|
||||
beforeEach(() => {
|
||||
explorer = new Explorer();
|
||||
updateUserContext({
|
||||
databaseAccount: {
|
||||
id: "test",
|
||||
@@ -26,6 +30,7 @@ describe("useTabs tests", () => {
|
||||
});
|
||||
|
||||
database = {
|
||||
container: explorer,
|
||||
id: ko.observable<string>("test"),
|
||||
isDatabaseShared: () => false,
|
||||
} as ViewModels.Database;
|
||||
@@ -33,6 +38,7 @@ describe("useTabs tests", () => {
|
||||
database.selectedSubnodeKind = ko.observable<ViewModels.CollectionTabKind>();
|
||||
|
||||
collection = {
|
||||
container: explorer,
|
||||
databaseId: "test",
|
||||
id: ko.observable<string>("test"),
|
||||
} as ViewModels.Collection;
|
||||
@@ -70,70 +76,63 @@ describe("useTabs tests", () => {
|
||||
documentsTab.tabId = "2";
|
||||
});
|
||||
|
||||
beforeEach(() => useTabs.setState({ openedTabs: [], activeTab: undefined }));
|
||||
beforeEach(() => (tabsManager = new TabsManager()));
|
||||
|
||||
it("open new tabs", () => {
|
||||
const { activateNewTab } = useTabs.getState();
|
||||
activateNewTab(queryTab);
|
||||
let tabsState = useTabs.getState();
|
||||
expect(tabsState.openedTabs.length).toBe(1);
|
||||
expect(tabsState.openedTabs[0]).toEqual(queryTab);
|
||||
expect(tabsState.activeTab).toEqual(queryTab);
|
||||
tabsManager.activateNewTab(queryTab);
|
||||
expect(tabsManager.openedTabs().length).toBe(1);
|
||||
expect(tabsManager.openedTabs()[0]).toEqual(queryTab);
|
||||
expect(tabsManager.activeTab()).toEqual(queryTab);
|
||||
expect(queryTab.isActive()).toBe(true);
|
||||
|
||||
activateNewTab(documentsTab);
|
||||
tabsState = useTabs.getState();
|
||||
expect(tabsState.openedTabs.length).toBe(2);
|
||||
expect(tabsState.openedTabs[1]).toEqual(documentsTab);
|
||||
expect(tabsState.activeTab).toEqual(documentsTab);
|
||||
tabsManager.activateNewTab(documentsTab);
|
||||
expect(tabsManager.openedTabs().length).toBe(2);
|
||||
expect(tabsManager.openedTabs()[1]).toEqual(documentsTab);
|
||||
expect(tabsManager.activeTab()).toEqual(documentsTab);
|
||||
expect(queryTab.isActive()).toBe(false);
|
||||
expect(documentsTab.isActive()).toBe(true);
|
||||
});
|
||||
|
||||
it("open existing tabs", () => {
|
||||
const { activateNewTab, activateTab } = useTabs.getState();
|
||||
activateNewTab(queryTab);
|
||||
activateNewTab(documentsTab);
|
||||
activateTab(queryTab);
|
||||
|
||||
const { openedTabs, activeTab } = useTabs.getState();
|
||||
expect(openedTabs.length).toBe(2);
|
||||
expect(activeTab).toEqual(queryTab);
|
||||
tabsManager.activateNewTab(queryTab);
|
||||
tabsManager.activateNewTab(documentsTab);
|
||||
tabsManager.activateTab(queryTab);
|
||||
expect(tabsManager.openedTabs().length).toBe(2);
|
||||
expect(tabsManager.activeTab()).toEqual(queryTab);
|
||||
expect(queryTab.isActive()).toBe(true);
|
||||
expect(documentsTab.isActive()).toBe(false);
|
||||
});
|
||||
|
||||
it("get tabs", () => {
|
||||
const { activateNewTab, getTabs } = useTabs.getState();
|
||||
activateNewTab(queryTab);
|
||||
activateNewTab(documentsTab);
|
||||
tabsManager.activateNewTab(queryTab);
|
||||
tabsManager.activateNewTab(documentsTab);
|
||||
|
||||
const queryTabs = getTabs(ViewModels.CollectionTabKind.Query);
|
||||
const queryTabs = tabsManager.getTabs(ViewModels.CollectionTabKind.Query);
|
||||
expect(queryTabs.length).toBe(1);
|
||||
expect(queryTabs[0]).toEqual(queryTab);
|
||||
|
||||
const documentsTabs = getTabs(ViewModels.CollectionTabKind.Documents, (tab) => tab.tabId === documentsTab.tabId);
|
||||
const documentsTabs = tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.Documents,
|
||||
(tab) => tab.tabId === documentsTab.tabId
|
||||
);
|
||||
expect(documentsTabs.length).toBe(1);
|
||||
expect(documentsTabs[0]).toEqual(documentsTab);
|
||||
});
|
||||
|
||||
it("close tabs", () => {
|
||||
const { activateNewTab, closeTab, closeTabsByComparator } = useTabs.getState();
|
||||
activateNewTab(queryTab);
|
||||
activateNewTab(documentsTab);
|
||||
closeTab(documentsTab);
|
||||
tabsManager.activateNewTab(queryTab);
|
||||
tabsManager.activateNewTab(documentsTab);
|
||||
|
||||
let tabsState = useTabs.getState();
|
||||
expect(tabsState.openedTabs.length).toBe(1);
|
||||
expect(tabsState.openedTabs[0]).toEqual(queryTab);
|
||||
expect(tabsState.activeTab).toEqual(queryTab);
|
||||
tabsManager.closeTab(documentsTab);
|
||||
expect(tabsManager.openedTabs().length).toBe(1);
|
||||
expect(tabsManager.openedTabs()[0]).toEqual(queryTab);
|
||||
expect(tabsManager.activeTab()).toEqual(queryTab);
|
||||
expect(queryTab.isActive()).toBe(true);
|
||||
expect(documentsTab.isActive()).toBe(false);
|
||||
|
||||
closeTabsByComparator((tab) => tab.tabId === queryTab.tabId);
|
||||
tabsState = useTabs.getState();
|
||||
expect(tabsState.openedTabs.length).toBe(0);
|
||||
expect(tabsState.activeTab).toEqual(undefined);
|
||||
tabsManager.closeTabsByComparator((tab) => tab.tabId === queryTab.tabId);
|
||||
expect(tabsManager.openedTabs().length).toBe(0);
|
||||
expect(tabsManager.activeTab()).toEqual(undefined);
|
||||
expect(queryTab.isActive()).toBe(false);
|
||||
});
|
||||
});
|
||||
54
src/Explorer/Tabs/TabsManager.ts
Normal file
54
src/Explorer/Tabs/TabsManager.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import * as ko from "knockout";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import TabsBase from "./TabsBase";
|
||||
|
||||
export class TabsManager {
|
||||
public openedTabs = ko.observableArray<TabsBase>([]);
|
||||
public activeTab = ko.observable<TabsBase>();
|
||||
|
||||
public activateNewTab(tab: TabsBase): void {
|
||||
this.openedTabs.push(tab);
|
||||
this.activateTab(tab);
|
||||
}
|
||||
|
||||
public activateTab(tab: TabsBase): void {
|
||||
if (this.openedTabs().includes(tab)) {
|
||||
tab.manager = this;
|
||||
this.activeTab(tab);
|
||||
tab.onActivate();
|
||||
}
|
||||
}
|
||||
|
||||
public getTabs(tabKind: ViewModels.CollectionTabKind, comparator?: (tab: TabsBase) => boolean): TabsBase[] {
|
||||
return this.openedTabs().filter((tab) => tab.tabKind === tabKind && (!comparator || comparator(tab)));
|
||||
}
|
||||
|
||||
public refreshActiveTab(comparator: (tab: TabsBase) => boolean): void {
|
||||
// ensures that the tab selects/highlights the right node based on resource tree expand/collapse state
|
||||
this.activeTab() && comparator(this.activeTab()) && this.activeTab().onActivate();
|
||||
}
|
||||
|
||||
public closeTabsByComparator(comparator: (tab: TabsBase) => boolean): void {
|
||||
this.openedTabs()
|
||||
.filter(comparator)
|
||||
.forEach((tab) => tab.onCloseTabButtonClick());
|
||||
}
|
||||
|
||||
public closeTab(tab: TabsBase): void {
|
||||
const tabIndex = this.openedTabs().indexOf(tab);
|
||||
if (tabIndex !== -1) {
|
||||
this.openedTabs.remove(tab);
|
||||
tab.manager = undefined;
|
||||
|
||||
if (this.openedTabs().length === 0) {
|
||||
this.activeTab(undefined);
|
||||
}
|
||||
|
||||
if (tab === this.activeTab()) {
|
||||
const tabToTheRight = this.openedTabs()[tabIndex];
|
||||
const lastOpenTab = this.openedTabs()[this.openedTabs().length - 1];
|
||||
this.activateTab(tabToTheRight ?? lastOpenTab);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import { userContext } from "../../UserContext";
|
||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||
import { NotebookTerminalComponent } from "../Controls/Notebook/NotebookTerminalComponent";
|
||||
import Explorer from "../Explorer";
|
||||
import { useNotebook } from "../Notebook/useNotebook";
|
||||
import TabsBase from "./TabsBase";
|
||||
|
||||
export interface TerminalTabOptions extends ViewModels.TabOptions {
|
||||
@@ -55,8 +54,8 @@ export default class TerminalTab extends TabsBase {
|
||||
this.notebookTerminalComponentAdapter.parameters = ko.computed<boolean>(() => {
|
||||
if (
|
||||
this.isTemplateReady() &&
|
||||
useNotebook.getState().isNotebookEnabled &&
|
||||
useNotebook.getState().notebookServerInfo?.notebookServerEndpoint
|
||||
this.container.isNotebookEnabled() &&
|
||||
this.container.notebookServerInfo().notebookServerEndpoint
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -96,7 +95,7 @@ export default class TerminalTab extends TabsBase {
|
||||
throw new Error(`Terminal kind: ${options.kind} not supported`);
|
||||
}
|
||||
|
||||
const info: DataModels.NotebookWorkspaceConnectionInfo = useNotebook.getState().notebookServerInfo;
|
||||
const info: DataModels.NotebookWorkspaceConnectionInfo = options.container.notebookServerInfo();
|
||||
return {
|
||||
authToken: info.authToken,
|
||||
notebookServerEndpoint: `${info.notebookServerEndpoint.replace(/\/+$/, "")}/${endpointSuffix}`,
|
||||
|
||||
@@ -4,12 +4,18 @@ import Collection from "./Collection";
|
||||
jest.mock("monaco-editor");
|
||||
|
||||
describe("Collection", () => {
|
||||
const generateCollection = (container: Explorer, databaseId: string, data: DataModels.Collection): Collection =>
|
||||
new Collection(container, databaseId, data);
|
||||
function generateCollection(
|
||||
container: Explorer,
|
||||
databaseId: string,
|
||||
data: DataModels.Collection,
|
||||
offer: DataModels.Offer
|
||||
): Collection {
|
||||
return new Collection(container, databaseId, data);
|
||||
}
|
||||
|
||||
const generateMockCollectionsDataModelWithPartitionKey = (
|
||||
function generateMockCollectionsDataModelWithPartitionKey(
|
||||
partitionKey: DataModels.PartitionKey
|
||||
): DataModels.Collection => {
|
||||
): DataModels.Collection {
|
||||
return {
|
||||
defaultTtl: 1,
|
||||
indexingPolicy: {} as DataModels.IndexingPolicy,
|
||||
@@ -20,12 +26,13 @@ describe("Collection", () => {
|
||||
_ts: 1,
|
||||
id: "",
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const generateMockCollectionWithDataModel = (data: DataModels.Collection): Collection => {
|
||||
function generateMockCollectionWithDataModel(data: DataModels.Collection): Collection {
|
||||
const mockContainer = {} as Explorer;
|
||||
return generateCollection(mockContainer, "abc", data);
|
||||
};
|
||||
|
||||
return generateCollection(mockContainer, "abc", data, {} as DataModels.Offer);
|
||||
}
|
||||
|
||||
describe("Partition key path parsing", () => {
|
||||
let collection: Collection;
|
||||
@@ -81,7 +88,7 @@ describe("Collection", () => {
|
||||
kind: "Hash",
|
||||
});
|
||||
collection = generateMockCollectionWithDataModel(collectionsDataModel);
|
||||
expect(collection.partitionKeyPropertyHeader).toBeNull();
|
||||
expect(collection.partitionKeyPropertyHeader).toBeNull;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,7 +15,6 @@ import { fetchPortalNotifications } from "../../Common/PortalNotifications";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { UploadDetailsRecord } from "../../Contracts/ViewModels";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "../../UserContext";
|
||||
@@ -240,11 +239,9 @@ export default class Collection implements ViewModels.Collection {
|
||||
this.expandCollection();
|
||||
}
|
||||
useCommandBar.getState().setContextButtons([]);
|
||||
useTabs
|
||||
.getState()
|
||||
.refreshActiveTab(
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
);
|
||||
this.container.tabsManager.refreshActiveTab(
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
);
|
||||
}
|
||||
|
||||
public collapseCollection() {
|
||||
@@ -291,16 +288,14 @@ export default class Collection implements ViewModels.Collection {
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
|
||||
const documentsTabs: DocumentsTab[] = useTabs
|
||||
.getState()
|
||||
.getTabs(
|
||||
ViewModels.CollectionTabKind.Documents,
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
) as DocumentsTab[];
|
||||
const documentsTabs: DocumentsTab[] = this.container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.Documents,
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
) as DocumentsTab[];
|
||||
let documentsTab: DocumentsTab = documentsTabs && documentsTabs[0];
|
||||
|
||||
if (documentsTab) {
|
||||
useTabs.getState().activateTab(documentsTab);
|
||||
this.container.tabsManager.activateTab(documentsTab);
|
||||
} else {
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||
databaseName: this.databaseId,
|
||||
@@ -322,7 +317,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
onLoadStartKey: startKey,
|
||||
});
|
||||
|
||||
useTabs.getState().activateNewTab(documentsTab);
|
||||
this.container.tabsManager.activateNewTab(documentsTab);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,16 +333,14 @@ export default class Collection implements ViewModels.Collection {
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
|
||||
const conflictsTabs: ConflictsTab[] = useTabs
|
||||
.getState()
|
||||
.getTabs(
|
||||
ViewModels.CollectionTabKind.Conflicts,
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
) as ConflictsTab[];
|
||||
const conflictsTabs: ConflictsTab[] = this.container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.Conflicts,
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
) as ConflictsTab[];
|
||||
let conflictsTab: ConflictsTab = conflictsTabs && conflictsTabs[0];
|
||||
|
||||
if (conflictsTab) {
|
||||
useTabs.getState().activateTab(conflictsTab);
|
||||
this.container.tabsManager.activateTab(conflictsTab);
|
||||
} else {
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||
databaseName: this.databaseId,
|
||||
@@ -369,7 +362,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
onLoadStartKey: startKey,
|
||||
});
|
||||
|
||||
useTabs.getState().activateNewTab(conflictsTab);
|
||||
this.container.tabsManager.activateNewTab(conflictsTab);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,16 +384,14 @@ export default class Collection implements ViewModels.Collection {
|
||||
});
|
||||
}
|
||||
|
||||
const queryTablesTabs: QueryTablesTab[] = useTabs
|
||||
.getState()
|
||||
.getTabs(
|
||||
ViewModels.CollectionTabKind.QueryTables,
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
) as QueryTablesTab[];
|
||||
const queryTablesTabs: QueryTablesTab[] = this.container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.QueryTables,
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
) as QueryTablesTab[];
|
||||
let queryTablesTab: QueryTablesTab = queryTablesTabs && queryTablesTabs[0];
|
||||
|
||||
if (queryTablesTab) {
|
||||
useTabs.getState().activateTab(queryTablesTab);
|
||||
this.container.tabsManager.activateTab(queryTablesTab);
|
||||
} else {
|
||||
this.documentIds([]);
|
||||
let title = `Entities`;
|
||||
@@ -424,7 +415,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
onLoadStartKey: startKey,
|
||||
});
|
||||
|
||||
useTabs.getState().activateNewTab(queryTablesTab);
|
||||
this.container.tabsManager.activateNewTab(queryTablesTab);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,16 +431,14 @@ export default class Collection implements ViewModels.Collection {
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
|
||||
const graphTabs: GraphTab[] = useTabs
|
||||
.getState()
|
||||
.getTabs(
|
||||
ViewModels.CollectionTabKind.Graph,
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
) as GraphTab[];
|
||||
const graphTabs: GraphTab[] = this.container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.Graph,
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
) as GraphTab[];
|
||||
let graphTab: GraphTab = graphTabs && graphTabs[0];
|
||||
|
||||
if (graphTab) {
|
||||
useTabs.getState().activateTab(graphTab);
|
||||
this.container.tabsManager.activateTab(graphTab);
|
||||
} else {
|
||||
this.documentIds([]);
|
||||
const title = "Graph";
|
||||
@@ -477,7 +466,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
onLoadStartKey: startKey,
|
||||
});
|
||||
|
||||
useTabs.getState().activateNewTab(graphTab);
|
||||
this.container.tabsManager.activateNewTab(graphTab);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -493,16 +482,14 @@ export default class Collection implements ViewModels.Collection {
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
|
||||
const mongoDocumentsTabs: MongoDocumentsTab[] = useTabs
|
||||
.getState()
|
||||
.getTabs(
|
||||
ViewModels.CollectionTabKind.Documents,
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
) as MongoDocumentsTab[];
|
||||
const mongoDocumentsTabs: MongoDocumentsTab[] = this.container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.Documents,
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
) as MongoDocumentsTab[];
|
||||
let mongoDocumentsTab: MongoDocumentsTab = mongoDocumentsTabs && mongoDocumentsTabs[0];
|
||||
|
||||
if (mongoDocumentsTab) {
|
||||
useTabs.getState().activateTab(mongoDocumentsTab);
|
||||
this.container.tabsManager.activateTab(mongoDocumentsTab);
|
||||
} else {
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||
databaseName: this.databaseId,
|
||||
@@ -523,7 +510,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
node: this,
|
||||
onLoadStartKey: startKey,
|
||||
});
|
||||
useTabs.getState().activateNewTab(mongoDocumentsTab);
|
||||
this.container.tabsManager.activateNewTab(mongoDocumentsTab);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -538,13 +525,13 @@ export default class Collection implements ViewModels.Collection {
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
|
||||
for (const tab of useTabs.getState().openedTabs) {
|
||||
for (const tab of this.container.tabsManager.openedTabs()) {
|
||||
if (
|
||||
tab instanceof SchemaAnalyzerTab &&
|
||||
tab.collection?.databaseId === this.databaseId &&
|
||||
tab.collection?.id() === this.id()
|
||||
) {
|
||||
return useTabs.getState().activateTab(tab);
|
||||
return this.container.tabsManager.activateTab(tab);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -555,7 +542,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
tabTitle: "Schema",
|
||||
});
|
||||
this.documentIds([]);
|
||||
useTabs.getState().activateNewTab(
|
||||
this.container.tabsManager.activateNewTab(
|
||||
new SchemaAnalyzerTab({
|
||||
account: userContext.databaseAccount,
|
||||
masterKey: userContext.masterKey || "",
|
||||
@@ -571,7 +558,6 @@ export default class Collection implements ViewModels.Collection {
|
||||
};
|
||||
|
||||
public onSettingsClick = async (): Promise<void> => {
|
||||
await this.loadOffer();
|
||||
useSelectedNode.getState().setSelectedNode(this);
|
||||
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Settings);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
@@ -584,9 +570,12 @@ export default class Collection implements ViewModels.Collection {
|
||||
});
|
||||
|
||||
const tabTitle = !this.offer() ? "Settings" : "Scale & Settings";
|
||||
const matchingTabs = useTabs.getState().getTabs(ViewModels.CollectionTabKind.CollectionSettingsV2, (tab) => {
|
||||
return tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id();
|
||||
});
|
||||
const matchingTabs = this.container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.CollectionSettingsV2,
|
||||
(tab) => {
|
||||
return tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id();
|
||||
}
|
||||
);
|
||||
|
||||
const traceStartData = {
|
||||
databaseName: this.databaseId,
|
||||
@@ -618,15 +607,15 @@ export default class Collection implements ViewModels.Collection {
|
||||
settingsTabOptions.onLoadStartKey = startKey;
|
||||
settingsTabOptions.tabKind = ViewModels.CollectionTabKind.CollectionSettingsV2;
|
||||
settingsTabV2 = new CollectionSettingsTabV2(settingsTabOptions);
|
||||
useTabs.getState().activateNewTab(settingsTabV2);
|
||||
this.container.tabsManager.activateNewTab(settingsTabV2);
|
||||
} else {
|
||||
useTabs.getState().activateTab(settingsTabV2);
|
||||
this.container.tabsManager.activateTab(settingsTabV2);
|
||||
}
|
||||
};
|
||||
|
||||
public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) {
|
||||
const collection: ViewModels.Collection = source.collection || source;
|
||||
const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Query).length + 1;
|
||||
const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Query).length + 1;
|
||||
const title = "Query " + id;
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||
databaseName: this.databaseId,
|
||||
@@ -636,7 +625,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
tabTitle: title,
|
||||
});
|
||||
|
||||
useTabs.getState().activateNewTab(
|
||||
this.container.tabsManager.activateNewTab(
|
||||
new NewQueryTab(
|
||||
{
|
||||
tabKind: ViewModels.CollectionTabKind.Query,
|
||||
@@ -655,7 +644,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
|
||||
public onNewMongoQueryClick(source: any, event: MouseEvent, queryText?: string) {
|
||||
const collection: ViewModels.Collection = source.collection || source;
|
||||
const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Query).length + 1;
|
||||
const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Query).length + 1;
|
||||
|
||||
const title = "Query " + id;
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||
@@ -682,11 +671,11 @@ export default class Collection implements ViewModels.Collection {
|
||||
}
|
||||
);
|
||||
|
||||
useTabs.getState().activateNewTab(newMongoQueryTab);
|
||||
this.container.tabsManager.activateNewTab(newMongoQueryTab);
|
||||
}
|
||||
|
||||
public onNewGraphClick() {
|
||||
const id: number = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Graph).length + 1;
|
||||
const id: number = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Graph).length + 1;
|
||||
const title: string = "Graph Query " + id;
|
||||
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||
@@ -712,11 +701,13 @@ export default class Collection implements ViewModels.Collection {
|
||||
onLoadStartKey: startKey,
|
||||
});
|
||||
|
||||
useTabs.getState().activateNewTab(graphTab);
|
||||
this.container.tabsManager.activateNewTab(graphTab);
|
||||
}
|
||||
|
||||
public onNewMongoShellClick() {
|
||||
const mongoShellTabs = useTabs.getState().getTabs(ViewModels.CollectionTabKind.MongoShell) as NewMongoShellTab[];
|
||||
const mongoShellTabs = this.container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.MongoShell
|
||||
) as NewMongoShellTab[];
|
||||
|
||||
let index = 1;
|
||||
if (mongoShellTabs.length > 0) {
|
||||
@@ -737,15 +728,15 @@ export default class Collection implements ViewModels.Collection {
|
||||
}
|
||||
);
|
||||
|
||||
useTabs.getState().activateNewTab(mongoShellTab);
|
||||
this.container.tabsManager.activateNewTab(mongoShellTab);
|
||||
}
|
||||
|
||||
public onNewStoredProcedureClick(source: ViewModels.Collection, event: MouseEvent) {
|
||||
StoredProcedure.create(source, event);
|
||||
}
|
||||
|
||||
public onNewUserDefinedFunctionClick(source: ViewModels.Collection) {
|
||||
UserDefinedFunction.create(source);
|
||||
public onNewUserDefinedFunctionClick(source: ViewModels.Collection, event: MouseEvent) {
|
||||
UserDefinedFunction.create(source, event);
|
||||
}
|
||||
|
||||
public onNewTriggerClick(source: ViewModels.Collection, event: MouseEvent) {
|
||||
@@ -795,11 +786,9 @@ export default class Collection implements ViewModels.Collection {
|
||||
} else {
|
||||
this.expandStoredProcedures();
|
||||
}
|
||||
useTabs
|
||||
.getState()
|
||||
.refreshActiveTab(
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
);
|
||||
this.container.tabsManager.refreshActiveTab(
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
);
|
||||
}
|
||||
|
||||
public expandStoredProcedures() {
|
||||
@@ -856,11 +845,9 @@ export default class Collection implements ViewModels.Collection {
|
||||
} else {
|
||||
this.expandUserDefinedFunctions();
|
||||
}
|
||||
useTabs
|
||||
.getState()
|
||||
.refreshActiveTab(
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
);
|
||||
this.container.tabsManager.refreshActiveTab(
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
);
|
||||
}
|
||||
|
||||
public expandUserDefinedFunctions() {
|
||||
@@ -917,11 +904,9 @@ export default class Collection implements ViewModels.Collection {
|
||||
} else {
|
||||
this.expandTriggers();
|
||||
}
|
||||
useTabs
|
||||
.getState()
|
||||
.refreshActiveTab(
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
);
|
||||
this.container.tabsManager.refreshActiveTab(
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
);
|
||||
}
|
||||
|
||||
public expandTriggers() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as ko from "knockout";
|
||||
import { HttpStatusCodes } from "../../Common/Constants";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import { JunoClient } from "../../Juno/JunoClient";
|
||||
import { Features } from "../../Platform/Hosted/extractFeatures";
|
||||
import { updateUserContext, userContext } from "../../UserContext";
|
||||
import Explorer from "../Explorer";
|
||||
import Database from "./Database";
|
||||
@@ -35,6 +35,7 @@ describe("Add Schema", () => {
|
||||
collection.analyticalStorageTtl = undefined;
|
||||
const database = new Database(createMockContainer(), collection);
|
||||
database.container = createMockContainer();
|
||||
database.container.isSchemaEnabled = ko.computed<boolean>(() => false);
|
||||
|
||||
database.junoClient = new JunoClient();
|
||||
database.junoClient.requestSchema = jest.fn();
|
||||
@@ -51,11 +52,7 @@ describe("Add Schema", () => {
|
||||
|
||||
const database = new Database(createMockContainer(), collection);
|
||||
database.container = createMockContainer();
|
||||
updateUserContext({
|
||||
features: {
|
||||
enableSchema: true,
|
||||
} as Features,
|
||||
});
|
||||
database.container.isSchemaEnabled = ko.computed<boolean>(() => true);
|
||||
|
||||
database.junoClient = new JunoClient();
|
||||
database.junoClient.requestSchema = jest.fn();
|
||||
|
||||
@@ -11,7 +11,6 @@ import { fetchPortalNotifications } from "../../Common/PortalNotifications";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { useSidePanel } from "../../hooks/useSidePanel";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import { IJunoResponse, JunoClient } from "../../Juno/JunoClient";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
@@ -68,7 +67,7 @@ export default class Database implements ViewModels.Database {
|
||||
|
||||
const pendingNotificationsPromise: Promise<DataModels.Notification> = this.getPendingThroughputSplitNotification();
|
||||
const tabKind = ViewModels.CollectionTabKind.DatabaseSettingsV2;
|
||||
const matchingTabs = useTabs.getState().getTabs(tabKind, (tab) => tab.node?.id() === this.id());
|
||||
const matchingTabs = this.container.tabsManager.getTabs(tabKind, (tab) => tab.node?.id() === this.id());
|
||||
let settingsTab = matchingTabs?.[0] as DatabaseSettingsTabV2;
|
||||
|
||||
if (!settingsTab) {
|
||||
@@ -92,7 +91,7 @@ export default class Database implements ViewModels.Database {
|
||||
};
|
||||
settingsTab = new DatabaseSettingsTabV2(tabOptions);
|
||||
settingsTab.pendingNotification(pendingNotification);
|
||||
useTabs.getState().activateNewTab(settingsTab);
|
||||
this.container.tabsManager.activateNewTab(settingsTab);
|
||||
},
|
||||
(error) => {
|
||||
const errorMessage = getErrorMessage(error);
|
||||
@@ -117,11 +116,11 @@ export default class Database implements ViewModels.Database {
|
||||
pendingNotificationsPromise.then(
|
||||
(pendingNotification: DataModels.Notification) => {
|
||||
settingsTab.pendingNotification(pendingNotification);
|
||||
useTabs.getState().activateTab(settingsTab);
|
||||
this.container.tabsManager.activateTab(settingsTab);
|
||||
},
|
||||
() => {
|
||||
settingsTab.pendingNotification(undefined);
|
||||
useTabs.getState().activateTab(settingsTab);
|
||||
this.container.tabsManager.activateTab(settingsTab);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -313,7 +312,7 @@ export default class Database implements ViewModels.Database {
|
||||
let checkForSchema: NodeJS.Timeout;
|
||||
interval = interval || 5000;
|
||||
|
||||
if (collection.analyticalStorageTtl !== undefined && userContext.features.enableSchema) {
|
||||
if (collection.analyticalStorageTtl !== undefined && this.container.isSchemaEnabled()) {
|
||||
collection.requestSchema = () => {
|
||||
this.junoClient.requestSchema({
|
||||
id: undefined,
|
||||
|
||||
@@ -2,7 +2,6 @@ import * as ko from "knockout";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "../../UserContext";
|
||||
@@ -78,7 +77,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
||||
|
||||
public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) {
|
||||
const collection: ViewModels.Collection = source.collection || source;
|
||||
const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Query).length + 1;
|
||||
const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Query).length + 1;
|
||||
const title = "Query " + id;
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||
databaseName: this.databaseId,
|
||||
@@ -88,7 +87,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
||||
tabTitle: title,
|
||||
});
|
||||
|
||||
useTabs.getState().activateNewTab(
|
||||
this.container.tabsManager.activateNewTab(
|
||||
new NewQueryTab(
|
||||
{
|
||||
tabKind: ViewModels.CollectionTabKind.Query,
|
||||
@@ -116,18 +115,16 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
|
||||
const documentsTabs: DocumentsTab[] = useTabs
|
||||
.getState()
|
||||
.getTabs(
|
||||
ViewModels.CollectionTabKind.Documents,
|
||||
(tab: TabsBase) =>
|
||||
tab.collection?.id() === this.id() &&
|
||||
(tab.collection as ViewModels.CollectionBase).databaseId === this.databaseId
|
||||
) as DocumentsTab[];
|
||||
const documentsTabs: DocumentsTab[] = this.container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.Documents,
|
||||
(tab: TabsBase) =>
|
||||
tab.collection?.id() === this.id() &&
|
||||
(tab.collection as ViewModels.CollectionBase).databaseId === this.databaseId
|
||||
) as DocumentsTab[];
|
||||
let documentsTab: DocumentsTab = documentsTabs && documentsTabs[0];
|
||||
|
||||
if (documentsTab) {
|
||||
useTabs.getState().activateTab(documentsTab);
|
||||
this.container.tabsManager.activateTab(documentsTab);
|
||||
} else {
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||
databaseName: this.databaseId,
|
||||
@@ -149,7 +146,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
||||
onLoadStartKey: startKey,
|
||||
});
|
||||
|
||||
useTabs.getState().activateNewTab(documentsTab);
|
||||
this.container.tabsManager.activateNewTab(documentsTab);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,34 +1,28 @@
|
||||
import * as ko from "knockout";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import TabsBase from "../Tabs/TabsBase";
|
||||
import { useSelectedNode } from "../useSelectedNode";
|
||||
|
||||
describe("useSelectedNode", () => {
|
||||
describe("useSelectedNode.getState()", () => {
|
||||
const mockTab = {
|
||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||
} as TabsBase;
|
||||
|
||||
// TODO isDataNodeSelected needs a better design and refactor, but for now, we protect some of the code paths
|
||||
describe("isDataNodeSelected", () => {
|
||||
afterEach(() => {
|
||||
useSelectedNode.getState().setSelectedNode(undefined);
|
||||
useTabs.setState({ activeTab: undefined });
|
||||
});
|
||||
afterEach(() => useSelectedNode.getState().setSelectedNode(undefined));
|
||||
it("it should not select if no selected node", () => {
|
||||
useTabs.setState({ activeTab: mockTab });
|
||||
const isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected("foo", "bar", undefined);
|
||||
const isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected(mockTab, "foo", "bar", undefined);
|
||||
expect(isDataNodeSelected).toBeFalsy();
|
||||
});
|
||||
|
||||
it("it should not select incorrect subnodekinds", () => {
|
||||
useTabs.setState({ activeTab: mockTab });
|
||||
useSelectedNode.getState().setSelectedNode({
|
||||
nodeKind: "nodeKind",
|
||||
rid: "rid",
|
||||
id: ko.observable<string>("id"),
|
||||
});
|
||||
const isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected("foo", "bar", undefined);
|
||||
const isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected(mockTab, "foo", "bar", undefined);
|
||||
expect(isDataNodeSelected).toBeFalsy();
|
||||
});
|
||||
|
||||
@@ -38,12 +32,11 @@ describe("useSelectedNode", () => {
|
||||
rid: "rid",
|
||||
id: ko.observable<string>("id"),
|
||||
});
|
||||
const isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected("foo", "bar", undefined);
|
||||
const isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected(undefined, "foo", "bar", undefined);
|
||||
expect(isDataNodeSelected).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should select if correct database node regardless of subnodekinds", () => {
|
||||
useTabs.setState({ activeTab: mockTab });
|
||||
const subNodeKind = ViewModels.CollectionTabKind.Documents;
|
||||
useSelectedNode.getState().setSelectedNode({
|
||||
nodeKind: "Database",
|
||||
@@ -53,7 +46,7 @@ describe("useSelectedNode", () => {
|
||||
} as ViewModels.TreeNode);
|
||||
const isDataNodeSelected = useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected("dbid", undefined, [ViewModels.CollectionTabKind.Documents]);
|
||||
.isDataNodeSelected(mockTab, "dbid", undefined, [ViewModels.CollectionTabKind.Documents]);
|
||||
expect(isDataNodeSelected).toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -62,7 +55,6 @@ describe("useSelectedNode", () => {
|
||||
let activeTab = {
|
||||
tabKind: subNodeKind,
|
||||
} as TabsBase;
|
||||
useTabs.setState({ activeTab });
|
||||
useSelectedNode.getState().setSelectedNode({
|
||||
nodeKind: "Collection",
|
||||
rid: "collrid",
|
||||
@@ -70,14 +62,15 @@ describe("useSelectedNode", () => {
|
||||
id: ko.observable<string>("collid"),
|
||||
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind),
|
||||
} as ViewModels.TreeNode);
|
||||
let isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected("dbid", "collid", [subNodeKind]);
|
||||
let isDataNodeSelected = useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected(activeTab, "dbid", "collid", [subNodeKind]);
|
||||
expect(isDataNodeSelected).toBeTruthy();
|
||||
|
||||
subNodeKind = ViewModels.CollectionTabKind.Graph;
|
||||
activeTab = {
|
||||
tabKind: subNodeKind,
|
||||
} as TabsBase;
|
||||
useTabs.setState({ activeTab });
|
||||
useSelectedNode.getState().setSelectedNode({
|
||||
nodeKind: "Collection",
|
||||
rid: "collrid",
|
||||
@@ -85,7 +78,7 @@ describe("useSelectedNode", () => {
|
||||
id: ko.observable<string>("collid"),
|
||||
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind),
|
||||
} as ViewModels.TreeNode);
|
||||
isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected("dbid", "collid", [subNodeKind]);
|
||||
isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected(activeTab, "dbid", "collid", [subNodeKind]);
|
||||
expect(isDataNodeSelected).toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -100,10 +93,9 @@ describe("useSelectedNode", () => {
|
||||
const activeTab = {
|
||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||
} as TabsBase;
|
||||
useTabs.setState({ activeTab });
|
||||
const isDataNodeSelected = useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected("dbid", "collid", [ViewModels.CollectionTabKind.Settings]);
|
||||
.isDataNodeSelected(activeTab, "dbid", "collid", [ViewModels.CollectionTabKind.Settings]);
|
||||
expect(isDataNodeSelected).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,10 +16,7 @@ import { Areas } from "../../Common/Constants";
|
||||
import { isPublicInternetAccessAllowed } from "../../Common/DatabaseAccountUtility";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { GitHubOAuthService } from "../../GitHub/GitHubOAuthService";
|
||||
import { useSidePanel } from "../../hooks/useSidePanel";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import { IPinnedRepo, JunoClient } from "../../Juno/JunoClient";
|
||||
import { IPinnedRepo } from "../../Juno/JunoClient";
|
||||
import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
|
||||
import { Action, ActionModifiers, Source } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
@@ -34,8 +31,6 @@ import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
|
||||
import { mostRecentActivity } from "../MostRecentActivity/MostRecentActivity";
|
||||
import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem";
|
||||
import { NotebookUtil } from "../Notebook/NotebookUtil";
|
||||
import { useNotebook } from "../Notebook/useNotebook";
|
||||
import { GitHubReposPanel } from "../Panes/GitHubReposPanel/GitHubReposPanel";
|
||||
import TabsBase from "../Tabs/TabsBase";
|
||||
import { useDatabases } from "../useDatabases";
|
||||
import { useSelectedNode } from "../useSelectedNode";
|
||||
@@ -56,26 +51,16 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
public galleryContentRoot: NotebookContentItem;
|
||||
public myNotebooksContentRoot: NotebookContentItem;
|
||||
public gitHubNotebooksContentRoot: NotebookContentItem;
|
||||
public junoClient: JunoClient;
|
||||
public gitHubOAuthService: GitHubOAuthService;
|
||||
|
||||
public constructor(private container: Explorer) {
|
||||
this.parameters = ko.observable(Date.now());
|
||||
|
||||
useSelectedNode.subscribe(() => this.triggerRender());
|
||||
useTabs.subscribe(
|
||||
() => this.triggerRender(),
|
||||
(state) => state.activeTab
|
||||
);
|
||||
useNotebook.subscribe(
|
||||
() => this.triggerRender(),
|
||||
(state) => state.isNotebookEnabled
|
||||
);
|
||||
this.container.tabsManager.activeTab.subscribe((newValue: TabsBase) => this.triggerRender());
|
||||
this.container.isNotebookEnabled.subscribe((newValue) => this.triggerRender());
|
||||
|
||||
useDatabases.subscribe(() => this.triggerRender());
|
||||
this.triggerRender();
|
||||
this.junoClient = new JunoClient();
|
||||
this.gitHubOAuthService = new GitHubOAuthService(this.junoClient);
|
||||
}
|
||||
|
||||
private traceMyNotebookTreeInfo() {
|
||||
@@ -106,7 +91,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
const dataRootNode = this.buildDataTree();
|
||||
const notebooksRootNode = this.buildNotebooksTrees();
|
||||
|
||||
if (useNotebook.getState().isNotebookEnabled) {
|
||||
if (this.container.isNotebookEnabled()) {
|
||||
return (
|
||||
<>
|
||||
<AccordionComponent>
|
||||
@@ -137,12 +122,12 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
|
||||
this.myNotebooksContentRoot = {
|
||||
name: ResourceTreeAdapter.MyNotebooksTitle,
|
||||
path: useNotebook.getState().notebookBasePath,
|
||||
path: this.container.getNotebookBasePath(),
|
||||
type: NotebookContentItemType.Directory,
|
||||
};
|
||||
|
||||
// Only if notebook server is available we can refresh
|
||||
if (useNotebook.getState().notebookServerInfo?.notebookServerEndpoint) {
|
||||
if (this.container.notebookServerInfo().notebookServerEndpoint) {
|
||||
refreshTasks.push(
|
||||
this.container.refreshContentItem(this.myNotebooksContentRoot).then(() => {
|
||||
this.triggerRender();
|
||||
@@ -199,7 +184,8 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
isExpanded: false,
|
||||
className: "databaseHeader",
|
||||
children: [],
|
||||
isSelected: () => useSelectedNode.getState().isDataNodeSelected(database.id()),
|
||||
isSelected: () =>
|
||||
useSelectedNode.getState().isDataNodeSelected(this.container.tabsManager.activeTab(), database.id()),
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createDatabaseContextMenu(this.container, database.id()),
|
||||
onClick: async (isExpanded) => {
|
||||
// Rewritten version of expandCollapseDatabase():
|
||||
@@ -214,7 +200,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
databaseNode.isLoading = false;
|
||||
useSelectedNode.getState().setSelectedNode(database);
|
||||
useCommandBar.getState().setContextButtons([]);
|
||||
useTabs.getState().refreshActiveTab((tab: TabsBase) => tab.collection?.databaseId === database.id());
|
||||
this.container.tabsManager.refreshActiveTab((tab: TabsBase) => tab.collection?.databaseId === database.id());
|
||||
},
|
||||
onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(database),
|
||||
};
|
||||
@@ -225,7 +211,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
isSelected: () =>
|
||||
useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected(database.id(), undefined, [ViewModels.CollectionTabKind.DatabaseSettings]),
|
||||
.isDataNodeSelected(this.container.tabsManager.activeTab(), database.id(), undefined, [
|
||||
ViewModels.CollectionTabKind.DatabaseSettings,
|
||||
]),
|
||||
onClick: database.onSettingsClick.bind(database),
|
||||
});
|
||||
}
|
||||
@@ -273,25 +261,23 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
isSelected: () =>
|
||||
useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected(collection.databaseId, collection.id(), [
|
||||
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
|
||||
ViewModels.CollectionTabKind.Documents,
|
||||
ViewModels.CollectionTabKind.Graph,
|
||||
]),
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(this.container, collection),
|
||||
});
|
||||
|
||||
if (
|
||||
useNotebook.getState().isNotebookEnabled &&
|
||||
userContext.apiType === "Mongo" &&
|
||||
isPublicInternetAccessAllowed()
|
||||
) {
|
||||
if (this.container.isNotebookEnabled() && userContext.apiType === "Mongo" && isPublicInternetAccessAllowed()) {
|
||||
children.push({
|
||||
label: "Schema (Preview)",
|
||||
onClick: collection.onSchemaAnalyzerClick.bind(collection),
|
||||
isSelected: () =>
|
||||
useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.SchemaAnalyzer]),
|
||||
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
|
||||
ViewModels.CollectionTabKind.SchemaAnalyzer,
|
||||
]),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -302,7 +288,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
isSelected: () =>
|
||||
useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Settings]),
|
||||
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
|
||||
ViewModels.CollectionTabKind.Settings,
|
||||
]),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -330,7 +318,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
isSelected: () =>
|
||||
useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Conflicts]),
|
||||
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
|
||||
ViewModels.CollectionTabKind.Conflicts,
|
||||
]),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -345,12 +335,10 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
// Rewritten version of expandCollapseCollection
|
||||
useSelectedNode.getState().setSelectedNode(collection);
|
||||
useCommandBar.getState().setContextButtons([]);
|
||||
useTabs
|
||||
.getState()
|
||||
.refreshActiveTab(
|
||||
(tab: TabsBase) =>
|
||||
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||
);
|
||||
this.container.tabsManager.refreshActiveTab(
|
||||
(tab: TabsBase) =>
|
||||
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||
);
|
||||
},
|
||||
onExpanded: () => {
|
||||
if (ResourceTreeAdapter.showScriptNodes(this.container)) {
|
||||
@@ -359,7 +347,10 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
collection.loadTriggers();
|
||||
}
|
||||
},
|
||||
isSelected: () => useSelectedNode.getState().isDataNodeSelected(collection.databaseId, collection.id()),
|
||||
isSelected: () =>
|
||||
useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id()),
|
||||
onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(collection),
|
||||
};
|
||||
}
|
||||
@@ -373,19 +364,17 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
isSelected: () =>
|
||||
useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected(collection.databaseId, collection.id(), [
|
||||
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
|
||||
ViewModels.CollectionTabKind.StoredProcedures,
|
||||
]),
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createStoreProcedureContextMenuItems(this.container, sp),
|
||||
})),
|
||||
onClick: () => {
|
||||
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.StoredProcedures);
|
||||
useTabs
|
||||
.getState()
|
||||
.refreshActiveTab(
|
||||
(tab: TabsBase) =>
|
||||
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||
);
|
||||
this.container.tabsManager.refreshActiveTab(
|
||||
(tab: TabsBase) =>
|
||||
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -399,7 +388,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
isSelected: () =>
|
||||
useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected(collection.databaseId, collection.id(), [
|
||||
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
|
||||
ViewModels.CollectionTabKind.UserDefinedFunctions,
|
||||
]),
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createUserDefinedFunctionContextMenuItems(
|
||||
@@ -409,12 +398,10 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
})),
|
||||
onClick: () => {
|
||||
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.UserDefinedFunctions);
|
||||
useTabs
|
||||
.getState()
|
||||
.refreshActiveTab(
|
||||
(tab: TabsBase) =>
|
||||
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||
);
|
||||
this.container.tabsManager.refreshActiveTab(
|
||||
(tab: TabsBase) =>
|
||||
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -428,17 +415,17 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
isSelected: () =>
|
||||
useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Triggers]),
|
||||
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
|
||||
ViewModels.CollectionTabKind.Triggers,
|
||||
]),
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createTriggerContextMenuItems(this.container, trigger),
|
||||
})),
|
||||
onClick: () => {
|
||||
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Triggers);
|
||||
useTabs
|
||||
.getState()
|
||||
.refreshActiveTab(
|
||||
(tab: TabsBase) =>
|
||||
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||
);
|
||||
this.container.tabsManager.refreshActiveTab(
|
||||
(tab: TabsBase) =>
|
||||
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -457,7 +444,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
children: this.getSchemaNodes(collection.schema.fields),
|
||||
onClick: () => {
|
||||
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Schema);
|
||||
useTabs.getState().refreshActiveTab((tab: TabsBase) => tab.collection && tab.collection.rid === collection.rid);
|
||||
this.container.tabsManager.refreshActiveTab(
|
||||
(tab: TabsBase) => tab.collection && tab.collection.rid === collection.rid
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -587,7 +576,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
className: "notebookHeader galleryHeader",
|
||||
onClick: () => this.container.openGallery(),
|
||||
isSelected: () => {
|
||||
const activeTab = useTabs.getState().activeTab;
|
||||
const activeTab = this.container.tabsManager.activeTab();
|
||||
return activeTab && activeTab.tabKind === ViewModels.CollectionTabKind.Gallery;
|
||||
},
|
||||
};
|
||||
@@ -631,17 +620,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
gitHubNotebooksTree.contextMenu = [
|
||||
{
|
||||
label: "Manage GitHub settings",
|
||||
onClick: () =>
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
"Manage GitHub settings",
|
||||
<GitHubReposPanel
|
||||
explorer={this.container}
|
||||
gitHubClientProp={this.container.notebookManager.gitHubClient}
|
||||
junoClientProp={this.junoClient}
|
||||
/>
|
||||
),
|
||||
onClick: () => this.container.openGitHubReposPanel("Manage GitHub settings"),
|
||||
},
|
||||
{
|
||||
label: "Disconnect from GitHub",
|
||||
@@ -691,7 +670,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
className: "notebookHeader",
|
||||
onClick: () => onFileClick(item),
|
||||
isSelected: () => {
|
||||
const activeTab = useTabs.getState().activeTab;
|
||||
const activeTab = this.container.tabsManager.activeTab();
|
||||
return (
|
||||
activeTab &&
|
||||
activeTab.tabKind === ViewModels.CollectionTabKind.NotebookV2 &&
|
||||
@@ -846,7 +825,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
}
|
||||
},
|
||||
isSelected: () => {
|
||||
const activeTab = useTabs.getState().activeTab;
|
||||
const activeTab = this.container.tabsManager.activeTab();
|
||||
return (
|
||||
activeTab &&
|
||||
activeTab.tabKind === ViewModels.CollectionTabKind.NotebookV2 &&
|
||||
|
||||
@@ -1,27 +1,37 @@
|
||||
import { shallow } from "enzyme";
|
||||
import * as ko from "knockout";
|
||||
import React from "react";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { TreeComponent, TreeComponentProps, TreeNode } from "../Controls/TreeComponent/TreeComponent";
|
||||
import Explorer from "../Explorer";
|
||||
import { useDatabases } from "../useDatabases";
|
||||
import ResourceTokenCollection from "./ResourceTokenCollection";
|
||||
import { ResourceTreeAdapterForResourceToken } from "./ResourceTreeAdapterForResourceToken";
|
||||
|
||||
describe("Resource tree for resource token", () => {
|
||||
const mockContainer = {} as Explorer;
|
||||
const resourceTree = new ResourceTreeAdapterForResourceToken(mockContainer);
|
||||
const mockCollection = {
|
||||
_rid: "fakeRid",
|
||||
_self: "fakeSelf",
|
||||
id: "fakeId",
|
||||
} as DataModels.Collection;
|
||||
const createMockContainer = (): Explorer => {
|
||||
let mockContainer = {} as Explorer;
|
||||
mockContainer.resourceTokenCollection = createMockCollection(mockContainer);
|
||||
|
||||
return mockContainer;
|
||||
};
|
||||
|
||||
const createMockCollection = (container: Explorer): ko.Observable<ViewModels.CollectionBase> => {
|
||||
let mockCollection = {} as DataModels.Collection;
|
||||
mockCollection._rid = "fakeRid";
|
||||
mockCollection._self = "fakeSelf";
|
||||
mockCollection.id = "fakeId";
|
||||
|
||||
const mockResourceTokenCollection: ViewModels.CollectionBase = new ResourceTokenCollection(
|
||||
mockContainer,
|
||||
container,
|
||||
"fakeDatabaseId",
|
||||
mockCollection
|
||||
);
|
||||
useDatabases.setState({ resourceTokenCollection: mockResourceTokenCollection });
|
||||
return ko.observable<ViewModels.CollectionBase>(mockResourceTokenCollection);
|
||||
};
|
||||
|
||||
describe("Resource tree for resource token", () => {
|
||||
const mockContainer: Explorer = createMockContainer();
|
||||
const resourceTree = new ResourceTreeAdapterForResourceToken(mockContainer);
|
||||
|
||||
it("should render", () => {
|
||||
const rootNode: TreeNode = resourceTree.buildCollectionNode();
|
||||
|
||||
@@ -3,14 +3,12 @@ import * as React from "react";
|
||||
import CollectionIcon from "../../../images/tree-collection.svg";
|
||||
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { TreeComponent, TreeNode } from "../Controls/TreeComponent/TreeComponent";
|
||||
import Explorer from "../Explorer";
|
||||
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
|
||||
import { mostRecentActivity } from "../MostRecentActivity/MostRecentActivity";
|
||||
import { NotebookContentItem } from "../Notebook/NotebookContentItem";
|
||||
import { useDatabases } from "../useDatabases";
|
||||
import { useSelectedNode } from "../useSelectedNode";
|
||||
|
||||
export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
|
||||
@@ -20,15 +18,9 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
|
||||
public constructor(private container: Explorer) {
|
||||
this.parameters = ko.observable(Date.now());
|
||||
|
||||
useDatabases.subscribe(
|
||||
() => this.triggerRender(),
|
||||
(state) => state.resourceTokenCollection
|
||||
);
|
||||
this.container.resourceTokenCollection.subscribe(() => this.triggerRender());
|
||||
useSelectedNode.subscribe(() => this.triggerRender());
|
||||
useTabs.subscribe(
|
||||
() => this.triggerRender(),
|
||||
(state) => state.activeTab
|
||||
);
|
||||
this.container.tabsManager && this.container.tabsManager.activeTab.subscribe(() => this.triggerRender());
|
||||
|
||||
this.triggerRender();
|
||||
}
|
||||
@@ -39,7 +31,7 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
|
||||
}
|
||||
|
||||
public buildCollectionNode(): TreeNode {
|
||||
const collection: ViewModels.CollectionBase = useDatabases.getState().resourceTokenCollection;
|
||||
const collection: ViewModels.CollectionBase = this.container.resourceTokenCollection();
|
||||
if (!collection) {
|
||||
return {
|
||||
label: undefined,
|
||||
@@ -59,7 +51,9 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
|
||||
isSelected: () =>
|
||||
useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Documents]),
|
||||
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
|
||||
ViewModels.CollectionTabKind.Documents,
|
||||
]),
|
||||
});
|
||||
|
||||
const collectionNode: TreeNode = {
|
||||
@@ -72,13 +66,14 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
|
||||
// Rewritten version of expandCollapseCollection
|
||||
useSelectedNode.getState().setSelectedNode(collection);
|
||||
useCommandBar.getState().setContextButtons([]);
|
||||
useTabs
|
||||
.getState()
|
||||
.refreshActiveTab(
|
||||
(tab) => tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||
);
|
||||
this.container.tabsManager.refreshActiveTab(
|
||||
(tab) => tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||
);
|
||||
},
|
||||
isSelected: () => useSelectedNode.getState().isDataNodeSelected(collection.databaseId, collection.id()),
|
||||
isSelected: () =>
|
||||
useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id()),
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -4,7 +4,6 @@ import * as Constants from "../../Common/Constants";
|
||||
import { deleteStoredProcedure } from "../../Common/dataAccess/deleteStoredProcedure";
|
||||
import { executeStoredProcedure } from "../../Common/dataAccess/executeStoredProcedure";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "../../UserContext";
|
||||
@@ -63,7 +62,7 @@ export default class StoredProcedure {
|
||||
}
|
||||
|
||||
public static create(source: ViewModels.Collection, event: MouseEvent) {
|
||||
const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.StoredProcedures).length + 1;
|
||||
const id = source.container.tabsManager.getTabs(ViewModels.CollectionTabKind.StoredProcedures).length + 1;
|
||||
const storedProcedure = <StoredProcedureDefinition>{
|
||||
id: "",
|
||||
body: sampleStoredProcedureBody,
|
||||
@@ -85,7 +84,7 @@ export default class StoredProcedure {
|
||||
}
|
||||
);
|
||||
|
||||
useTabs.getState().activateNewTab(storedProcedureTab);
|
||||
source.container.tabsManager.activateNewTab(storedProcedureTab);
|
||||
}
|
||||
|
||||
public select() {
|
||||
@@ -100,16 +99,14 @@ export default class StoredProcedure {
|
||||
public open = () => {
|
||||
this.select();
|
||||
|
||||
const storedProcedureTabs: NewStoredProcedureTab[] = useTabs
|
||||
.getState()
|
||||
.getTabs(
|
||||
ViewModels.CollectionTabKind.StoredProcedures,
|
||||
(tab: TabsBase) => tab.node && tab.node.rid === this.rid
|
||||
) as NewStoredProcedureTab[];
|
||||
const storedProcedureTabs: NewStoredProcedureTab[] = this.container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.StoredProcedures,
|
||||
(tab: TabsBase) => tab.node && tab.node.rid === this.rid
|
||||
) as NewStoredProcedureTab[];
|
||||
let storedProcedureTab: NewStoredProcedureTab = storedProcedureTabs && storedProcedureTabs[0];
|
||||
|
||||
if (storedProcedureTab) {
|
||||
useTabs.getState().activateTab(storedProcedureTab);
|
||||
this.container.tabsManager.activateTab(storedProcedureTab);
|
||||
} else {
|
||||
const storedProcedureData = <StoredProcedureDefinition>{
|
||||
_rid: this.rid,
|
||||
@@ -134,7 +131,7 @@ export default class StoredProcedure {
|
||||
}
|
||||
);
|
||||
|
||||
useTabs.getState().activateNewTab(storedProcedureTab);
|
||||
this.container.tabsManager.activateNewTab(storedProcedureTab);
|
||||
}
|
||||
};
|
||||
public delete() {
|
||||
@@ -144,7 +141,7 @@ export default class StoredProcedure {
|
||||
|
||||
deleteStoredProcedure(this.collection.databaseId, this.collection.id(), this.id()).then(
|
||||
() => {
|
||||
useTabs.getState().closeTabsByComparator((tab: TabsBase) => tab.node && tab.node.rid === this.rid);
|
||||
this.container.tabsManager.closeTabsByComparator((tab: TabsBase) => tab.node && tab.node.rid === this.rid);
|
||||
this.collection.children.remove(this);
|
||||
},
|
||||
(reason) => {}
|
||||
@@ -152,12 +149,10 @@ export default class StoredProcedure {
|
||||
}
|
||||
|
||||
public execute(params: string[], partitionKeyValue?: string): void {
|
||||
const sprocTabs: NewStoredProcedureTab[] = useTabs
|
||||
.getState()
|
||||
.getTabs(
|
||||
ViewModels.CollectionTabKind.StoredProcedures,
|
||||
(tab: TabsBase) => tab.node && tab.node.rid === this.rid
|
||||
) as NewStoredProcedureTab[];
|
||||
const sprocTabs: NewStoredProcedureTab[] = this.container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.StoredProcedures,
|
||||
(tab: TabsBase) => tab.node && tab.node.rid === this.rid
|
||||
) as NewStoredProcedureTab[];
|
||||
const sprocTab: NewStoredProcedureTab = sprocTabs && sprocTabs.length > 0 && sprocTabs[0];
|
||||
sprocTab.isExecuting(true);
|
||||
this.container &&
|
||||
|
||||
@@ -3,7 +3,6 @@ import * as ko from "knockout";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import { deleteTrigger } from "../../Common/dataAccess/deleteTrigger";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import Explorer from "../Explorer";
|
||||
@@ -43,7 +42,7 @@ export default class Trigger {
|
||||
}
|
||||
|
||||
public static create(source: ViewModels.Collection, event: MouseEvent) {
|
||||
const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Triggers).length + 1;
|
||||
const id = source.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Triggers).length + 1;
|
||||
const trigger = <StoredProcedureDefinition>{
|
||||
id: "",
|
||||
body: "function trigger(){}",
|
||||
@@ -61,19 +60,20 @@ export default class Trigger {
|
||||
node: source,
|
||||
});
|
||||
|
||||
useTabs.getState().activateNewTab(triggerTab);
|
||||
source.container.tabsManager.activateNewTab(triggerTab);
|
||||
}
|
||||
|
||||
public open = () => {
|
||||
this.select();
|
||||
|
||||
const triggerTabs: TriggerTab[] = useTabs
|
||||
.getState()
|
||||
.getTabs(ViewModels.CollectionTabKind.Triggers, (tab) => tab.node && tab.node.rid === this.rid) as TriggerTab[];
|
||||
const triggerTabs: TriggerTab[] = this.container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.Triggers,
|
||||
(tab) => tab.node && tab.node.rid === this.rid
|
||||
) as TriggerTab[];
|
||||
let triggerTab: TriggerTab = triggerTabs && triggerTabs[0];
|
||||
|
||||
if (triggerTab) {
|
||||
useTabs.getState().activateTab(triggerTab);
|
||||
this.container.tabsManager.activateTab(triggerTab);
|
||||
} else {
|
||||
const triggerData = <StoredProcedureDefinition>{
|
||||
_rid: this.rid,
|
||||
@@ -94,7 +94,7 @@ export default class Trigger {
|
||||
node: this,
|
||||
});
|
||||
|
||||
useTabs.getState().activateNewTab(triggerTab);
|
||||
this.container.tabsManager.activateNewTab(triggerTab);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -105,7 +105,7 @@ export default class Trigger {
|
||||
|
||||
deleteTrigger(this.collection.databaseId, this.collection.id(), this.id()).then(
|
||||
() => {
|
||||
useTabs.getState().closeTabsByComparator((tab) => tab.node && tab.node.rid === this.rid);
|
||||
this.container.tabsManager.closeTabsByComparator((tab) => tab.node && tab.node.rid === this.rid);
|
||||
this.collection.children.remove(this);
|
||||
},
|
||||
(reason) => {}
|
||||
|
||||
@@ -3,7 +3,6 @@ import * as ko from "knockout";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import { deleteUserDefinedFunction } from "../../Common/dataAccess/deleteUserDefinedFunction";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import Explorer from "../Explorer";
|
||||
@@ -30,8 +29,8 @@ export default class UserDefinedFunction {
|
||||
this.body = ko.observable(data.body as string);
|
||||
}
|
||||
|
||||
public static create(source: ViewModels.Collection) {
|
||||
const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.UserDefinedFunctions).length + 1;
|
||||
public static create(source: ViewModels.Collection, event: MouseEvent) {
|
||||
const id = source.container.tabsManager.getTabs(ViewModels.CollectionTabKind.UserDefinedFunctions).length + 1;
|
||||
const userDefinedFunction = {
|
||||
id: "",
|
||||
body: "function userDefinedFunction(){}",
|
||||
@@ -47,22 +46,20 @@ export default class UserDefinedFunction {
|
||||
node: source,
|
||||
});
|
||||
|
||||
useTabs.getState().activateNewTab(userDefinedFunctionTab);
|
||||
source.container.tabsManager.activateNewTab(userDefinedFunctionTab);
|
||||
}
|
||||
|
||||
public open = () => {
|
||||
this.select();
|
||||
|
||||
const userDefinedFunctionTabs: UserDefinedFunctionTab[] = useTabs
|
||||
.getState()
|
||||
.getTabs(
|
||||
ViewModels.CollectionTabKind.UserDefinedFunctions,
|
||||
(tab) => tab.node?.rid === this.rid
|
||||
) as UserDefinedFunctionTab[];
|
||||
const userDefinedFunctionTabs: UserDefinedFunctionTab[] = this.container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.UserDefinedFunctions,
|
||||
(tab) => tab.node?.rid === this.rid
|
||||
) as UserDefinedFunctionTab[];
|
||||
let userDefinedFunctionTab: UserDefinedFunctionTab = userDefinedFunctionTabs && userDefinedFunctionTabs[0];
|
||||
|
||||
if (userDefinedFunctionTab) {
|
||||
useTabs.getState().activateTab(userDefinedFunctionTab);
|
||||
this.container.tabsManager.activateTab(userDefinedFunctionTab);
|
||||
} else {
|
||||
const userDefinedFunctionData = {
|
||||
_rid: this.rid,
|
||||
@@ -81,7 +78,7 @@ export default class UserDefinedFunction {
|
||||
node: this,
|
||||
});
|
||||
|
||||
useTabs.getState().activateNewTab(userDefinedFunctionTab);
|
||||
this.container.tabsManager.activateNewTab(userDefinedFunctionTab);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -101,12 +98,10 @@ export default class UserDefinedFunction {
|
||||
|
||||
deleteUserDefinedFunction(this.collection.databaseId, this.collection.id(), this.id()).then(
|
||||
() => {
|
||||
useTabs.getState().closeTabsByComparator((tab) => tab.node && tab.node.rid === this.rid);
|
||||
this.container.tabsManager.closeTabsByComparator((tab) => tab.node && tab.node.rid === this.rid);
|
||||
this.collection.children.remove(this);
|
||||
},
|
||||
() => {
|
||||
/**/
|
||||
}
|
||||
(reason) => {}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,9 @@ import _ from "underscore";
|
||||
import create, { UseStore } from "zustand";
|
||||
import * as Constants from "../Common/Constants";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
import { useSelectedNode } from "./useSelectedNode";
|
||||
|
||||
interface DatabasesState {
|
||||
databases: ViewModels.Database[];
|
||||
resourceTokenCollection: ViewModels.CollectionBase;
|
||||
updateDatabase: (database: ViewModels.Database) => void;
|
||||
addDatabases: (databases: ViewModels.Database[]) => void;
|
||||
deleteDatabase: (database: ViewModels.Database) => void;
|
||||
@@ -18,12 +16,10 @@ interface DatabasesState {
|
||||
isLastCollection: () => boolean;
|
||||
loadDatabaseOffers: () => Promise<void>;
|
||||
isFirstResourceCreated: () => boolean;
|
||||
findSelectedDatabase: () => ViewModels.Database;
|
||||
}
|
||||
|
||||
export const useDatabases: UseStore<DatabasesState> = create((set, get) => ({
|
||||
databases: [],
|
||||
resourceTokenCollection: undefined,
|
||||
updateDatabase: (updatedDatabase: ViewModels.Database) =>
|
||||
set((state) => {
|
||||
const updatedDatabases = state.databases.map((database: ViewModels.Database) => {
|
||||
@@ -114,19 +110,4 @@ export const useDatabases: UseStore<DatabasesState> = create((set, get) => ({
|
||||
return false;
|
||||
});
|
||||
},
|
||||
findSelectedDatabase: (): ViewModels.Database => {
|
||||
const selectedNode = useSelectedNode.getState().selectedNode;
|
||||
if (!selectedNode) {
|
||||
return undefined;
|
||||
}
|
||||
if (selectedNode.nodeKind === "Database") {
|
||||
return _.find(get().databases, (database: ViewModels.Database) => database.id() === selectedNode.id());
|
||||
}
|
||||
|
||||
if (selectedNode.nodeKind === "Collection") {
|
||||
return selectedNode.database;
|
||||
}
|
||||
|
||||
return selectedNode.collection?.database;
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import _ from "underscore";
|
||||
import create, { UseStore } from "zustand";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
import { useTabs } from "../hooks/useTabs";
|
||||
import TabsBase from "./Tabs/TabsBase";
|
||||
import { useDatabases } from "./useDatabases";
|
||||
|
||||
export interface SelectedNodeState {
|
||||
selectedNode: ViewModels.TreeNode;
|
||||
setSelectedNode: (node: ViewModels.TreeNode) => void;
|
||||
isDatabaseNodeOrNoneSelected: () => boolean;
|
||||
findSelectedDatabase: () => ViewModels.Database;
|
||||
findSelectedCollection: () => ViewModels.Collection;
|
||||
isDataNodeSelected: (
|
||||
activeTab: TabsBase,
|
||||
databaseId: string,
|
||||
collectionId?: string,
|
||||
subnodeKinds?: ViewModels.CollectionTabKind[]
|
||||
@@ -21,11 +25,30 @@ export const useSelectedNode: UseStore<SelectedNodeState> = create((set, get) =>
|
||||
const selectedNode = get().selectedNode;
|
||||
return !selectedNode || selectedNode.nodeKind === "Database";
|
||||
},
|
||||
findSelectedDatabase: (): ViewModels.Database => {
|
||||
const selectedNode = get().selectedNode;
|
||||
if (!selectedNode) {
|
||||
return undefined;
|
||||
}
|
||||
if (selectedNode.nodeKind === "Database") {
|
||||
return _.find(
|
||||
useDatabases.getState().databases,
|
||||
(database: ViewModels.Database) => database.id() === selectedNode.id()
|
||||
);
|
||||
}
|
||||
|
||||
if (selectedNode.nodeKind === "Collection") {
|
||||
return selectedNode.database;
|
||||
}
|
||||
|
||||
return selectedNode.collection?.database;
|
||||
},
|
||||
findSelectedCollection: (): ViewModels.Collection => {
|
||||
const selectedNode = get().selectedNode;
|
||||
return (selectedNode.nodeKind === "Collection" ? selectedNode : selectedNode.collection) as ViewModels.Collection;
|
||||
},
|
||||
isDataNodeSelected: (
|
||||
activeTab: TabsBase,
|
||||
databaseId: string,
|
||||
collectionId?: string,
|
||||
subnodeKinds?: ViewModels.CollectionTabKind[]
|
||||
@@ -47,7 +70,6 @@ export const useSelectedNode: UseStore<SelectedNodeState> = create((set, get) =>
|
||||
return true;
|
||||
}
|
||||
|
||||
const activeTab = useTabs.getState().activeTab;
|
||||
const selectedSubnodeKind = collectionId
|
||||
? (selectedNode as ViewModels.Collection).selectedSubnodeKind()
|
||||
: (selectedNode as ViewModels.Database).selectedSubnodeKind();
|
||||
|
||||
@@ -42,10 +42,10 @@ const onInit = async () => {
|
||||
practices, and how to get started with Azure Cosmos DB.
|
||||
</Text>
|
||||
<Text styles={{ root: { marginTop: 10 } }} block>
|
||||
If {`you'd`} like to run or edit the notebook in your own Azure Cosmos DB account,{" "}
|
||||
If you'd like to run or edit the notebook in your own Azure Cosmos DB account,{" "}
|
||||
<Link href={dataExplorerUrl}>sign in</Link> and select an account with{" "}
|
||||
<Link href={enableNotebooksUrl}>notebooks enabled</Link>. From there, you can download the sample to your
|
||||
account. If you {`don't`} have an account yet, you can{" "}
|
||||
account. If you don't have an account yet, you can{" "}
|
||||
<Link href={createAccountUrl}>create one from the Azure portal</Link>.
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@ window.addEventListener("load", async () => {
|
||||
if (openerWindow) {
|
||||
const params = new URLSearchParams(document.location.search);
|
||||
await postRobot.send(
|
||||
openerWindow,
|
||||
window,
|
||||
GitHubConnectorMsgType,
|
||||
{
|
||||
state: params.get("state"),
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { IContent } from "@nteract/core";
|
||||
import { fixture } from "@nteract/fixtures";
|
||||
import { HttpStatusCodes } from "../Common/Constants";
|
||||
import * as GitHubUtils from "../Utils/GitHubUtils";
|
||||
import { GitHubClient, IGitHubCommit, IGitHubFile } from "./GitHubClient";
|
||||
import { GitHubContentProvider } from "./GitHubContentProvider";
|
||||
import * as GitHubUtils from "../Utils/GitHubUtils";
|
||||
|
||||
const gitHubClient = new GitHubClient(() => {
|
||||
/**/
|
||||
});
|
||||
const gitHubClient = new GitHubClient(() => {});
|
||||
const gitHubContentProvider = new GitHubContentProvider({
|
||||
gitHubClient,
|
||||
promptForCommitMsg: () => Promise.resolve("commit msg"),
|
||||
@@ -48,7 +46,7 @@ const sampleNotebookModel: IContent<"notebook"> = {
|
||||
created: "",
|
||||
last_modified: "date",
|
||||
mimetype: "application/x-ipynb+json",
|
||||
content: sampleFile.content ? JSON.parse(sampleFile.content) : undefined,
|
||||
content: sampleFile.content ? JSON.parse(sampleFile.content) : null,
|
||||
format: "json",
|
||||
};
|
||||
|
||||
@@ -56,7 +54,7 @@ describe("GitHubContentProvider remove", () => {
|
||||
it("errors on invalid path", async () => {
|
||||
spyOn(GitHubClient.prototype, "getContentsAsync");
|
||||
|
||||
const response = await gitHubContentProvider.remove(undefined, "invalid path").toPromise();
|
||||
const response = await gitHubContentProvider.remove(null, "invalid path").toPromise();
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBe(GitHubContentProvider.SelfErrorCode);
|
||||
expect(gitHubClient.getContentsAsync).not.toBeCalled();
|
||||
@@ -65,7 +63,7 @@ describe("GitHubContentProvider remove", () => {
|
||||
it("errors on failed read", async () => {
|
||||
spyOn(GitHubClient.prototype, "getContentsAsync").and.returnValue(Promise.resolve({ status: 888 }));
|
||||
|
||||
const response = await gitHubContentProvider.remove(undefined, sampleGitHubUri).toPromise();
|
||||
const response = await gitHubContentProvider.remove(null, sampleGitHubUri).toPromise();
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBe(888);
|
||||
expect(gitHubClient.getContentsAsync).toBeCalled();
|
||||
@@ -77,7 +75,7 @@ describe("GitHubContentProvider remove", () => {
|
||||
);
|
||||
spyOn(GitHubClient.prototype, "deleteFileAsync").and.returnValue(Promise.resolve({ status: 888 }));
|
||||
|
||||
const response = await gitHubContentProvider.remove(undefined, sampleGitHubUri).toPromise();
|
||||
const response = await gitHubContentProvider.remove(null, sampleGitHubUri).toPromise();
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBe(888);
|
||||
expect(gitHubClient.getContentsAsync).toBeCalled();
|
||||
@@ -92,7 +90,7 @@ describe("GitHubContentProvider remove", () => {
|
||||
Promise.resolve({ status: HttpStatusCodes.OK, data: gitHubCommit })
|
||||
);
|
||||
|
||||
const response = await gitHubContentProvider.remove(undefined, sampleGitHubUri).toPromise();
|
||||
const response = await gitHubContentProvider.remove(null, sampleGitHubUri).toPromise();
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBe(HttpStatusCodes.NoContent);
|
||||
expect(gitHubClient.deleteFileAsync).toBeCalled();
|
||||
@@ -104,7 +102,7 @@ describe("GitHubContentProvider get", () => {
|
||||
it("errors on invalid path", async () => {
|
||||
spyOn(GitHubClient.prototype, "getContentsAsync");
|
||||
|
||||
const response = await gitHubContentProvider.get(undefined, "invalid path", undefined).toPromise();
|
||||
const response = await gitHubContentProvider.get(null, "invalid path", null).toPromise();
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBe(GitHubContentProvider.SelfErrorCode);
|
||||
expect(gitHubClient.getContentsAsync).not.toBeCalled();
|
||||
@@ -113,7 +111,7 @@ describe("GitHubContentProvider get", () => {
|
||||
it("errors on failed read", async () => {
|
||||
spyOn(GitHubClient.prototype, "getContentsAsync").and.returnValue(Promise.resolve({ status: 888 }));
|
||||
|
||||
const response = await gitHubContentProvider.get(undefined, sampleGitHubUri, undefined).toPromise();
|
||||
const response = await gitHubContentProvider.get(null, sampleGitHubUri, null).toPromise();
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBe(888);
|
||||
expect(gitHubClient.getContentsAsync).toBeCalled();
|
||||
@@ -124,7 +122,7 @@ describe("GitHubContentProvider get", () => {
|
||||
Promise.resolve({ status: HttpStatusCodes.OK, data: sampleFile })
|
||||
);
|
||||
|
||||
const response = await gitHubContentProvider.get(undefined, sampleGitHubUri, {}).toPromise();
|
||||
const response = await gitHubContentProvider.get(null, sampleGitHubUri, {}).toPromise();
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||
expect(gitHubClient.getContentsAsync).toBeCalled();
|
||||
@@ -136,7 +134,7 @@ describe("GitHubContentProvider update", () => {
|
||||
it("errors on invalid path", async () => {
|
||||
spyOn(GitHubClient.prototype, "getContentsAsync");
|
||||
|
||||
const response = await gitHubContentProvider.update(undefined, "invalid path", undefined).toPromise();
|
||||
const response = await gitHubContentProvider.update(null, "invalid path", null).toPromise();
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBe(GitHubContentProvider.SelfErrorCode);
|
||||
expect(gitHubClient.getContentsAsync).not.toBeCalled();
|
||||
@@ -145,7 +143,7 @@ describe("GitHubContentProvider update", () => {
|
||||
it("errors on failed read", async () => {
|
||||
spyOn(GitHubClient.prototype, "getContentsAsync").and.returnValue(Promise.resolve({ status: 888 }));
|
||||
|
||||
const response = await gitHubContentProvider.update(undefined, sampleGitHubUri, undefined).toPromise();
|
||||
const response = await gitHubContentProvider.update(null, sampleGitHubUri, null).toPromise();
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBe(888);
|
||||
expect(gitHubClient.getContentsAsync).toBeCalled();
|
||||
@@ -157,7 +155,7 @@ describe("GitHubContentProvider update", () => {
|
||||
);
|
||||
spyOn(GitHubClient.prototype, "renameFileAsync").and.returnValue(Promise.resolve({ status: 888 }));
|
||||
|
||||
const response = await gitHubContentProvider.update(undefined, sampleGitHubUri, sampleNotebookModel).toPromise();
|
||||
const response = await gitHubContentProvider.update(null, sampleGitHubUri, sampleNotebookModel).toPromise();
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBe(888);
|
||||
expect(gitHubClient.getContentsAsync).toBeCalled();
|
||||
@@ -172,7 +170,7 @@ describe("GitHubContentProvider update", () => {
|
||||
Promise.resolve({ status: HttpStatusCodes.OK, data: gitHubCommit })
|
||||
);
|
||||
|
||||
const response = await gitHubContentProvider.update(undefined, sampleGitHubUri, sampleNotebookModel).toPromise();
|
||||
const response = await gitHubContentProvider.update(null, sampleGitHubUri, sampleNotebookModel).toPromise();
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||
expect(gitHubClient.getContentsAsync).toBeCalled();
|
||||
@@ -188,7 +186,7 @@ describe("GitHubContentProvider create", () => {
|
||||
it("errors on invalid path", async () => {
|
||||
spyOn(GitHubClient.prototype, "createOrUpdateFileAsync");
|
||||
|
||||
const response = await gitHubContentProvider.create(undefined, "invalid path", sampleNotebookModel).toPromise();
|
||||
const response = await gitHubContentProvider.create(null, "invalid path", sampleNotebookModel).toPromise();
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBe(GitHubContentProvider.SelfErrorCode);
|
||||
expect(gitHubClient.createOrUpdateFileAsync).not.toBeCalled();
|
||||
@@ -197,7 +195,7 @@ describe("GitHubContentProvider create", () => {
|
||||
it("errors on failed create", async () => {
|
||||
spyOn(GitHubClient.prototype, "createOrUpdateFileAsync").and.returnValue(Promise.resolve({ status: 888 }));
|
||||
|
||||
const response = await gitHubContentProvider.create(undefined, sampleGitHubUri, sampleNotebookModel).toPromise();
|
||||
const response = await gitHubContentProvider.create(null, sampleGitHubUri, sampleNotebookModel).toPromise();
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBe(888);
|
||||
expect(gitHubClient.createOrUpdateFileAsync).toBeCalled();
|
||||
@@ -208,7 +206,7 @@ describe("GitHubContentProvider create", () => {
|
||||
Promise.resolve({ status: HttpStatusCodes.Created, data: gitHubCommit })
|
||||
);
|
||||
|
||||
const response = await gitHubContentProvider.create(undefined, sampleGitHubUri, sampleNotebookModel).toPromise();
|
||||
const response = await gitHubContentProvider.create(null, sampleGitHubUri, sampleNotebookModel).toPromise();
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBe(HttpStatusCodes.Created);
|
||||
expect(gitHubClient.createOrUpdateFileAsync).toBeCalled();
|
||||
@@ -223,7 +221,7 @@ describe("GitHubContentProvider save", () => {
|
||||
it("errors on invalid path", async () => {
|
||||
spyOn(GitHubClient.prototype, "getContentsAsync");
|
||||
|
||||
const response = await gitHubContentProvider.save(undefined, "invalid path", undefined).toPromise();
|
||||
const response = await gitHubContentProvider.save(null, "invalid path", null).toPromise();
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBe(GitHubContentProvider.SelfErrorCode);
|
||||
expect(gitHubClient.getContentsAsync).not.toBeCalled();
|
||||
@@ -232,7 +230,7 @@ describe("GitHubContentProvider save", () => {
|
||||
it("errors on failed read", async () => {
|
||||
spyOn(GitHubClient.prototype, "getContentsAsync").and.returnValue(Promise.resolve({ status: 888 }));
|
||||
|
||||
const response = await gitHubContentProvider.save(undefined, sampleGitHubUri, undefined).toPromise();
|
||||
const response = await gitHubContentProvider.save(null, sampleGitHubUri, null).toPromise();
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBe(888);
|
||||
expect(gitHubClient.getContentsAsync).toBeCalled();
|
||||
@@ -244,7 +242,7 @@ describe("GitHubContentProvider save", () => {
|
||||
);
|
||||
spyOn(GitHubClient.prototype, "createOrUpdateFileAsync").and.returnValue(Promise.resolve({ status: 888 }));
|
||||
|
||||
const response = await gitHubContentProvider.save(undefined, sampleGitHubUri, sampleNotebookModel).toPromise();
|
||||
const response = await gitHubContentProvider.save(null, sampleGitHubUri, sampleNotebookModel).toPromise();
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBe(888);
|
||||
expect(gitHubClient.getContentsAsync).toBeCalled();
|
||||
@@ -259,7 +257,7 @@ describe("GitHubContentProvider save", () => {
|
||||
Promise.resolve({ status: HttpStatusCodes.OK, data: gitHubCommit })
|
||||
);
|
||||
|
||||
const response = await gitHubContentProvider.save(undefined, sampleGitHubUri, sampleNotebookModel).toPromise();
|
||||
const response = await gitHubContentProvider.save(null, sampleGitHubUri, sampleNotebookModel).toPromise();
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||
expect(gitHubClient.getContentsAsync).toBeCalled();
|
||||
@@ -273,7 +271,7 @@ describe("GitHubContentProvider save", () => {
|
||||
|
||||
describe("GitHubContentProvider listCheckpoints", () => {
|
||||
it("errors for everything", async () => {
|
||||
const response = await gitHubContentProvider.listCheckpoints().toPromise();
|
||||
const response = await gitHubContentProvider.listCheckpoints(null, null).toPromise();
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBe(GitHubContentProvider.SelfErrorCode);
|
||||
});
|
||||
@@ -281,7 +279,7 @@ describe("GitHubContentProvider listCheckpoints", () => {
|
||||
|
||||
describe("GitHubContentProvider createCheckpoint", () => {
|
||||
it("errors for everything", async () => {
|
||||
const response = await gitHubContentProvider.createCheckpoint().toPromise();
|
||||
const response = await gitHubContentProvider.createCheckpoint(null, null).toPromise();
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBe(GitHubContentProvider.SelfErrorCode);
|
||||
});
|
||||
@@ -289,7 +287,7 @@ describe("GitHubContentProvider createCheckpoint", () => {
|
||||
|
||||
describe("GitHubContentProvider deleteCheckpoint", () => {
|
||||
it("errors for everything", async () => {
|
||||
const response = await gitHubContentProvider.deleteCheckpoint().toPromise();
|
||||
const response = await gitHubContentProvider.deleteCheckpoint(null, null, null).toPromise();
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBe(GitHubContentProvider.SelfErrorCode);
|
||||
});
|
||||
@@ -297,7 +295,7 @@ describe("GitHubContentProvider deleteCheckpoint", () => {
|
||||
|
||||
describe("GitHubContentProvider restoreFromCheckpoint", () => {
|
||||
it("errors for everything", async () => {
|
||||
const response = await gitHubContentProvider.restoreFromCheckpoint().toPromise();
|
||||
const response = await gitHubContentProvider.restoreFromCheckpoint(null, null, null).toPromise();
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBe(GitHubContentProvider.SelfErrorCode);
|
||||
});
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { makeNotebookRecord, Notebook, stringifyNotebook, toJS } from "@nteract/commutable";
|
||||
import { Notebook, stringifyNotebook, makeNotebookRecord, toJS } from "@nteract/commutable";
|
||||
import { FileType, IContent, IContentProvider, IEmptyContent, IGetParams, ServerConfig } from "@nteract/core";
|
||||
import { from, Observable, of } from "rxjs";
|
||||
import { AjaxResponse } from "rxjs/ajax";
|
||||
import { HttpStatusCodes } from "../Common/Constants";
|
||||
import { getErrorMessage } from "../Common/ErrorHandlingUtils";
|
||||
import * as Logger from "../Common/Logger";
|
||||
import * as UrlUtility from "../Common/UrlUtility";
|
||||
import { NotebookUtil } from "../Explorer/Notebook/NotebookUtil";
|
||||
import * as Base64Utils from "../Utils/Base64Utils";
|
||||
import * as GitHubUtils from "../Utils/GitHubUtils";
|
||||
import { HttpStatusCodes } from "../Common/Constants";
|
||||
import * as Logger from "../Common/Logger";
|
||||
import { NotebookUtil } from "../Explorer/Notebook/NotebookUtil";
|
||||
import { GitHubClient, IGitHubFile, IGitHubResponse } from "./GitHubClient";
|
||||
import * as GitHubUtils from "../Utils/GitHubUtils";
|
||||
import * as UrlUtility from "../Common/UrlUtility";
|
||||
import { getErrorMessage } from "../Common/ErrorHandlingUtils";
|
||||
|
||||
export interface GitHubContentProviderParams {
|
||||
gitHubClient: GitHubClient;
|
||||
@@ -267,25 +267,25 @@ export class GitHubContentProvider implements IContentProvider {
|
||||
);
|
||||
}
|
||||
|
||||
public listCheckpoints(): Observable<AjaxResponse> {
|
||||
public listCheckpoints(_: ServerConfig, path: string): Observable<AjaxResponse> {
|
||||
const error = new GitHubContentProviderError("Not implemented");
|
||||
Logger.logError(error.message, "GitHubContentProvider/listCheckpoints", error.errno);
|
||||
return of(this.createErrorAjaxResponse(error));
|
||||
}
|
||||
|
||||
public createCheckpoint(): Observable<AjaxResponse> {
|
||||
public createCheckpoint(_: ServerConfig, path: string): Observable<AjaxResponse> {
|
||||
const error = new GitHubContentProviderError("Not implemented");
|
||||
Logger.logError(error.message, "GitHubContentProvider/createCheckpoint", error.errno);
|
||||
return of(this.createErrorAjaxResponse(error));
|
||||
}
|
||||
|
||||
public deleteCheckpoint(): Observable<AjaxResponse> {
|
||||
public deleteCheckpoint(_: ServerConfig, path: string, checkpointID: string): Observable<AjaxResponse> {
|
||||
const error = new GitHubContentProviderError("Not implemented");
|
||||
Logger.logError(error.message, "GitHubContentProvider/deleteCheckpoint", error.errno);
|
||||
return of(this.createErrorAjaxResponse(error));
|
||||
}
|
||||
|
||||
public restoreFromCheckpoint(): Observable<AjaxResponse> {
|
||||
public restoreFromCheckpoint(_: ServerConfig, path: string, checkpointID: string): Observable<AjaxResponse> {
|
||||
const error = new GitHubContentProviderError("Not implemented");
|
||||
Logger.logError(error.message, "GitHubContentProvider/restoreFromCheckpoint", error.errno);
|
||||
return of(this.createErrorAjaxResponse(error));
|
||||
|
||||
13
src/Main.tsx
13
src/Main.tsx
@@ -34,6 +34,7 @@ import "./Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.less";
|
||||
import "./Explorer/Controls/JsonEditor/JsonEditorComponent.less";
|
||||
import "./Explorer/Controls/Notebook/NotebookTerminalComponent.less";
|
||||
import "./Explorer/Controls/TreeComponent/treeComponent.less";
|
||||
import { ExplorerParams } from "./Explorer/Explorer";
|
||||
import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less";
|
||||
import "./Explorer/Menus/CommandBar/CommandBarComponent.less";
|
||||
import { CommandBar } from "./Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
||||
@@ -55,10 +56,14 @@ initializeIcons();
|
||||
|
||||
const App: React.FunctionComponent = () => {
|
||||
const [isLeftPaneExpanded, setIsLeftPaneExpanded] = useState<boolean>(true);
|
||||
const openedTabs = useTabs((state) => state.openedTabs);
|
||||
const { tabs, activeTab, tabsManager } = useTabs();
|
||||
|
||||
const explorerParams: ExplorerParams = {
|
||||
tabsManager,
|
||||
};
|
||||
|
||||
const config = useConfig();
|
||||
const explorer = useKnockoutExplorer(config?.platform);
|
||||
const explorer = useKnockoutExplorer(config?.platform, explorerParams);
|
||||
|
||||
const toggleLeftPaneExpanded = () => {
|
||||
setIsLeftPaneExpanded(!isLeftPaneExpanded);
|
||||
@@ -95,8 +100,8 @@ const App: React.FunctionComponent = () => {
|
||||
</div>
|
||||
</div>
|
||||
{/* Collections Tree - End */}
|
||||
{openedTabs.length === 0 && <SplashScreen explorer={explorer} />}
|
||||
<Tabs />
|
||||
{tabs.length === 0 && <SplashScreen explorer={explorer} />}
|
||||
<Tabs tabs={tabs} activeTab={activeTab} />
|
||||
</div>
|
||||
{/* Collections Tree and Tabs - End */}
|
||||
<div
|
||||
|
||||
@@ -26,7 +26,7 @@ export const SwitchAccount: FunctionComponent<Props> = ({
|
||||
data: account,
|
||||
}))}
|
||||
onChange={(_, option) => {
|
||||
setSelectedAccountName(String(option?.key));
|
||||
setSelectedAccountName(String(option.key));
|
||||
dismissMenu();
|
||||
}}
|
||||
defaultSelectedKey={selectedAccount?.name}
|
||||
|
||||
@@ -26,7 +26,7 @@ export const SwitchSubscription: FunctionComponent<Props> = ({
|
||||
};
|
||||
})}
|
||||
onChange={(_, option) => {
|
||||
setSelectedSubscriptionId(String(option?.key));
|
||||
setSelectedSubscriptionId(String(option.key));
|
||||
}}
|
||||
defaultSelectedKey={selectedSubscription?.subscriptionId}
|
||||
placeholder={subscriptions && subscriptions.length === 0 ? "No Subscriptions Found" : "Select a Subscription"}
|
||||
|
||||
@@ -2,8 +2,8 @@ import * as DataModels from "../../../Contracts/DataModels";
|
||||
import { parseConnectionString } from "./ConnectionStringParser";
|
||||
|
||||
describe("ConnectionStringParser", () => {
|
||||
const mockAccountName = "Test";
|
||||
const mockMasterKey = "some-key";
|
||||
const mockAccountName: string = "Test";
|
||||
const mockMasterKey: string = "some-key";
|
||||
|
||||
it("should parse a valid sql account connection string", () => {
|
||||
const metadata = parseConnectionString(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user